寫在前面
作爲FPGA的基礎知識教程怎麼能少得了這個簡單的實際應用七段數碼管顯示,本篇博文算得上是對以往這個話題的一個總結吧!
注:數碼管本身是七段,但是加上小數點之後就是八段了!
正文
七段數碼管原理
七段顯示器是FPGA設計人員通常用來向用戶顯示信息的指示器。在VHDL和Verilog中可以輕鬆完成將二進制文件轉換爲兼容七段顯示器的代碼。有許多應用程序可能需要使用一個或多個八段顯示器,例如:
- 鬧鐘
- 秒錶
- 按鈕計數指示器
- 電壓測量(從模擬到數字轉換器)
等等!
如下是七段數碼管的示意圖:我們將七段分別標記爲A、B、C、D、E、F、G,還有一個小數點DP。
如果用reg型變量來存七段數碼管的位值,定義reg型變量:
reg [6:0] seg;
其中G對應seg[6],F對應seg[5],一直到A對應seg[0],小數點用專門的dp來表示。
七段數碼管譯碼錶
那麼要想顯示0到F,對應的譯碼錶爲:
parameter NUM0 = 7'h3f,//40,
NUM1 = 7'h06,//79,
NUM2 = 7'h5b,//24,
NUM3 = 7'h4f,//30,
NUM4 = 7'h66,//19,
NUM5 = 7'h6d,//12,
NUM6 = 7'h7d,//02,
NUM7 = 7'h07,//78,
NUM8 = 7'h7f,//20,
NUM9 = 7'h6f,//10,
NUMA = 7'h77,//08,
NUMB = 7'h7c,//03,
NUMC = 7'h39,//46,
NUMD = 7'h5e,//21,
NUME = 7'h79,//06,
NUMF = 7'h71,//0e;
注意,前面部分爲共陰極時的譯碼錶,後面註釋掉的爲共陽極的譯碼錶!
所謂的共陽極,如下原理圖所示:
晶體管的集電極連接電源,片選低電平有效,片選選中時,晶體管導通,數碼管公共端連接高電平,因此段選低電平,對應段發亮。
單個七段數碼管顯示verilog設計
以共陰極爲例,對單個數碼管顯示的Verilog設計爲:
module Binary_To_7Segment
(
input i_Clk,
input [3:0] i_Binary_Num,
input i_dp, //小數點輸入
output o_Segment_A,
output o_Segment_B,
output o_Segment_C,
output o_Segment_D,
output o_Segment_E,
output o_Segment_F,
output o_Segment_G,
output o_dp
);
reg [6:0] r_Hex_Encoding = 7'h00;
// Purpose: Creates a case statement for all possible input binary numbers.
// Drives r_Hex_Encoding appropriately for each input combination.
always @(posedge i_Clk)
begin
case (i_Binary_Num)
4'b0000 : r_Hex_Encoding <= 7'h3f;
4'b0001 : r_Hex_Encoding <= 7'h06;
4'b0010 : r_Hex_Encoding <= 7'h5b;
4'b0011 : r_Hex_Encoding <= 7'h4f;
4'b0100 : r_Hex_Encoding <= 7'h66;
4'b0101 : r_Hex_Encoding <= 7'h6d;
4'b0110 : r_Hex_Encoding <= 7'h7d;
4'b0111 : r_Hex_Encoding <= 7'h07;
4'b1000 : r_Hex_Encoding <= 7'h7f;
4'b1001 : r_Hex_Encoding <= 7'h6f;
4'b1010 : r_Hex_Encoding <= 7'h77;
4'b1011 : r_Hex_Encoding <= 7'h7c;
4'b1100 : r_Hex_Encoding <= 7'h39;
4'b1101 : r_Hex_Encoding <= 7'h5e;
4'b1110 : r_Hex_Encoding <= 7'h79;
4'b1111 : r_Hex_Encoding <= 7'h71;
endcase
end // always @ (posedge i_Clk)
// r_Hex_Encoding[7] is unused
assign o_Segment_A = r_Hex_Encoding[0];
assign o_Segment_B = r_Hex_Encoding[1];
assign o_Segment_C = r_Hex_Encoding[2];
assign o_Segment_D = r_Hex_Encoding[3];
assign o_Segment_E = r_Hex_Encoding[4];
assign o_Segment_F = r_Hex_Encoding[5];
assign o_Segment_G = r_Hex_Encoding[6];
assign o_dp = i_dp;
endmodule // Binary_To_7Segment
多個數碼管動態掃描顯示
用一句話來描述多個數碼管動態掃描顯示,那就是,只要你閃的夠快,你就看不出我滅過!
動態掃描顯示就是如此,數碼管的動態顯示是對每個數碼管採用分時複用的方式輪流點亮每個數碼管,在同一時間只會點亮一個數碼管。
分時複用的掃描顯示利用了人眼的視覺暫留特性,如果公共端的控制信號刷新速度足夠快,人眼就不會區分出LED的閃爍,認爲4個數碼管是同時點亮。
如下圖是多個數碼管連接的原理圖:
片選控制信號的刷新速度必須足夠快才能避免閃爍感,但也不能太快,以免影響數碼管的開關切換,最佳的工作頻率爲1000Hz左右。如果FPGA的時鐘爲50MHz,那麼至少跑5*10^4
個週期,也即50000個週期刷新一次纔行,我們知道2^16=65536,2^15=32768
。
我們選擇計數器位寬爲16試試!
注:效果可以實際上板子測試決定,由於現在開發板不在手上,暫時並未驗證怎麼樣!
代碼中採用的是18位2進制數,高2位控制片選,低16位計數滿一次,進位一次,高兩位的變化時00——01——10——11——00——…,分別選中數碼管的其中之一,使用case語句即可完成這個功能。
下面代碼中的hex0、hex1、hex2、hex3是輸入信號,用來控制數碼管顯示的數字,dp_in用來控制小數點的亮滅。
`timescale 1ns / 1ps
module scan_led_hex_disp(
input clk,
input reset,
input [3:0] hex0, //第一個數碼管顯示的數字
input [3:0] hex1,
input [3:0] hex2,
input [3:0] hex3,
input [3:0] dp_in, //小數點控制
output reg [3:0] an, //片選
output reg [7:0] sseg //段選
);
localparam N = 18; //使用低16位對50Mhz的時鐘進行分頻(50MHZ/2^16)
reg [N-1:0] regN; //高兩位作爲控制信號,低16位爲計數器,對時鐘進行分頻
reg [3:0] hex_in; //段選控制信號
reg dp;
always@(posedge clk, posedge reset)
begin
if(reset)
regN <= 0;
else
regN <= regN + 1;
end
always@ *
begin
case(regN[N-1:N-2])
2'b00:begin
an = 4'b1110; //選中第1個數碼管
hex_in = hex0; //數碼管顯示的數字由hex_in控制,顯示hex0輸入的數字;
dp = dp_in[0]; //控制該數碼管的小數點的亮滅
end
2'b01:begin
an = 4'b1101; //選中第二個數碼管
hex_in = hex1;
dp = dp_in[1];
end
2'b10:begin
an = 4'b1011;
hex_in = hex2;
dp = dp_in[2];
end
default:begin
an = 4'b0111;
hex_in = hex3;
dp = dp_in[3];
end
endcase
end
always@ *
begin
case(hex_in)
4'h0: sseg[6:0] = 7'b0000001; //共陽極數碼管
4'h1: sseg[6:0] = 7'b1001111;
4'h2: sseg[6:0] = 7'b0010010;
4'h3: sseg[6:0] = 7'b0000110;
4'h4: sseg[6:0] = 7'b1001100;
4'h5: sseg[6:0] = 7'b0100100;
4'h6: sseg[6:0] = 7'b0100000;
4'h7: sseg[6:0] = 7'b0001111;
4'h8: sseg[6:0] = 7'b0000010;
4'h9: sseg[6:0] = 7'b0000100;
4'ha: sseg[6:0] = 7'b0001000;
4'hb: sseg[6:0] = 7'b1100000;
4'hc: sseg[6:0] = 7'b0110001;
4'hd: sseg[6:0] = 7'b1000010;
4'he: sseg[6:0] = 7'b0110000;
default: sseg[6:0] = 7'b0111000;
endcase
sseg[7] = dp;
end
endmodule
根據上一小節,單個數碼管顯示的Verilog設計,最後一段譯碼模塊可以使用時序邏輯,也就是代碼可以改爲:
`timescale 1ns / 1ps
module scan_led_hex_disp(
input clk,
input reset,
input [3:0] hex0, //第一個數碼管顯示的數字
input [3:0] hex1,
input [3:0] hex2,
input [3:0] hex3,
input [3:0] dp_in, //小數點控制
output reg [3:0] an, //片選
output reg [7:0] sseg //段選
);
localparam N = 18; //使用低16位對50Mhz的時鐘進行分頻(50MHZ/2^16)
reg [N-1:0] regN; //高兩位作爲控制信號,低16位爲計數器,對時鐘進行分頻
reg [3:0] hex_in; //段選控制信號
reg dp;
always@(posedge clk, posedge reset)
begin
if(reset)
regN <= 0;
else
regN <= regN + 1;
end
always@ *
begin
case(regN[N-1:N-2])
2'b00:begin
an = 4'b1110; //選中第1個數碼管
hex_in = hex0; //數碼管顯示的數字由hex_in控制,顯示hex0輸入的數字;
dp = dp_in[0]; //控制該數碼管的小數點的亮滅
end
2'b01:begin
an = 4'b1101; //選中第二個數碼管
hex_in = hex1;
dp = dp_in[1];
end
2'b10:begin
an = 4'b1011;
hex_in = hex2;
dp = dp_in[2];
end
default:begin
an = 4'b0111;
hex_in = hex3;
dp = dp_in[3];
end
endcase
end
always@(posedge clk)
begin
case(hex_in)
4'h0: sseg[6:0] = 7'b0000001; //共陽極數碼管
4'h1: sseg[6:0] = 7'b1001111;
4'h2: sseg[6:0] = 7'b0010010;
4'h3: sseg[6:0] = 7'b0000110;
4'h4: sseg[6:0] = 7'b1001100;
4'h5: sseg[6:0] = 7'b0100100;
4'h6: sseg[6:0] = 7'b0100000;
4'h7: sseg[6:0] = 7'b0001111;
4'h8: sseg[6:0] = 7'b0000010;
4'h9: sseg[6:0] = 7'b0000100;
4'ha: sseg[6:0] = 7'b0001000;
4'hb: sseg[6:0] = 7'b1100000;
4'hc: sseg[6:0] = 7'b0110001;
4'hd: sseg[6:0] = 7'b1000010;
4'he: sseg[6:0] = 7'b0110000;
default: sseg[6:0] = 7'b0111000;
endcase
sseg[7] = dp;
end
endmodule
注:由於代碼都是根據過去的博客以及參考資料移植,導致命名方式沒有統一,請注意;還有多個數碼管顯示時,數碼管是共陽極的數碼管,而單個數碼管顯示那一節,也就是上一節,使用的數碼管是共陰極爲例,因此,數碼管譯碼部分數值不同,請注意!
參考資料
交個朋友
-
個人微信公衆號:FPGA LAB
-
知乎:李銳博恩