設計規劃
設計一個8位數碼管靜態顯示:采用共陽極(低電平點亮)8段數碼管,控制八位數碼管讓其以00000000、11111111、22222222一直到FFFFFFFF循環顯示。每個字符顯示0.5s。
硬件資源
數碼管
常見數碼管分為七段或八段,八段是多一個小數點。也可分為共陰極或共陽極,觀察下面的原理圖,共陰極數碼管對應端口為高電平時對應二極管點亮,共陽極則相反。
段式數碼管工作方式有兩種:靜態顯示和動態顯示。靜態顯示是指將8個數碼管的段選信號連接在一起,就可以顯示相同的數字。每個數碼管的段選必須接一個8位數據線來顯示字形,顯示字形可一直保持,直到送入新字形碼為止。如果每個數碼管都接8位段選數據線,那么8個數碼管就需要64根數據線。(例如8個數碼管都顯示0,那么每個數碼管都要接一個8位的段選數據線,且控制段選信號為1100_0000)這樣占用的I/O接口太多。如下圖所示,我們將8個數碼管的段選信號連接在一起,由位選信號去控制,每一個數碼管上都有一個位選信號。那么在同一時刻8個數碼管顯示的字符都一樣了。(例如8個數碼管都顯示0,那么8位位選信號為1111_1111,這樣才能控制8個共陽數碼管都能點亮。段選信號是連接在一起的,當它為1100_0000時,8個數碼管都顯示0)
即使這樣控制數碼管仍然需要使用16個I/O口資源。如果想要節省I/O口,可以通過74HC595芯片(位移緩存器)來實現。
74HC595芯片
使用一個串行輸入口就可以并行輸出八個輸入的串行數據。但是一片芯片只能并行輸出8位數據,但是8個數碼管需要16位數據線,因此需要級聯兩片74HC595芯片進行輸出:將Q7S引腳接入下一片的DS引腳,這樣我們最少使用3個I/O口就可以控制多片芯片了。
10號引腳是主復位,低電平有效將移位寄存器的數據清零,通常接到Vcc防止數據清零。SHCP為移位寄存器時鐘輸入,上升沿時將輸入的串行數據(DS端輸入)移入移位寄存器中。如果一次輸入的數據超過8bit,后面的數據會通過Q7S端口傳到下一級芯片的DS端口。74HC595內部有一個8位 存儲寄存器 ,由STCP(存儲寄存器時鐘)控制,STCP上升沿時移位寄存器的數據會進入數據存儲寄存器中,令第13引腳為低即可讓存儲寄存器中的數據進行輸出。
總結一下使用步驟:
- 首先把要傳輸的數據通過引腳DS輸入到74HC595中。
- 產生SHCP時鐘,將DS上的數據串行移入移位寄存器。
- 產生STCP時鐘,將移位寄存器里的數據送入存儲寄存器。
- 將13引腳置為低電平,存儲寄存器的數據會在Q0-Q7并行輸出,同時并行輸出的數據會被鎖存起來。
AC620 開發板上的數碼管采用串行移位寄存器芯片將串行數據轉化為 16 位并行數據后進行驅動。Cyclone IV E 通過 3 根數據線,連接到兩片級聯的串行移位器芯片 74HC595 上,再由 74HC595 將每次 16 位串行的數據轉化為 16 位并行的數據,分別用以驅動8 段 8 位數碼管的段選和位選。(用戶手冊是7段,但是有小數點,這里統一稱為8段)
我們采用的是低電平點亮的共陽極數碼管。首先看位選信號,SEL[0]對應開發板最右側的數碼管,以此類推。位選信號為1111_1111才能點亮數碼管。再看段選信號,以顯示0為例,需要將abcdef點亮,按照左邊高位右邊低位的順序,dp、g、f、e、d、c、b、a就對應1100_0000。0-F對應(0)1100_0000、(1)1111_1001、(2)1010_0100、(3)1011_0000、(4)1001_1001、(5)1001_0010、(6)1000_0010、(7)1111_1000、(8)1000_0000、(9)1001_0000、(A)1000_1000、(B)1000_0011、(C)1100_0110、(D)1010_0001、(E)1000_0110、(F)1000_1110。
編寫代碼
通過系統框圖可以看出,分為3個模塊:數碼管驅動模塊,芯片控制模塊和數碼管顯示模塊。數碼管顯示模塊是 頂層模塊 ,實質上完成兩個子模塊的實例化。上圖里整個系統連接方式是Cyclone IV E連接74HC595芯片連接數碼管。我們看數碼管驅動和芯片控制這兩個子模塊,下圖中數碼管的引腳圖需要段選信號和位選信號去驅動,74HC595芯片,除了幾個常接高低電平的引腳外,它的輸入引腳是接Cyclone IV E的,輸出引腳是控制數碼管的。頂層模塊中只需要考慮芯片和Cyclone IV E之間的連線,因此頂層模塊的輸入是時鐘和復位信號,輸出是芯片的輸入引腳:寄存器時鐘,存儲器時鐘,數據輸入。尤其要注意的是,因為我用的開發板沒有使能引腳,他不能作為輸出存在。我們再將頂層模塊拆分為數碼管驅動模塊和芯片控制模塊。數碼管驅動模塊考慮數碼管和芯片之間的連線,因此它的輸入是時鐘和復位信號,輸出是段選和位選信號。芯片控制模塊考慮的是芯片和數碼管以及Cyclone IV E的連線,因此它的輸入是時鐘、復位、段選和位選信號,使能信號,輸出是芯片的輸入引腳(寄存器時鐘,存儲器時鐘,數據輸入)。理清楚了這些,就很容易編寫代碼了。
1、數碼管驅動模塊seg_static
包括輸入:時鐘信號、復位信號,輸出:段選信號和位選信號。這個模塊最重要的是弄清楚怎么產生段選和位選信號。每隔0.5s,我們要實現00000000-FFFFFFFF的循環顯示,那么位選信號我們之前探討了必須為1111_1111才能使數碼管正常工作,而段選信號我們也探討了從0-F的段選信號,就可以畫出波形圖。根據波形圖可以編寫代碼。
我們需要三個中間信號:計數器cnt_wait,標志信號add_flag,顯示信號num。其中,cnt_wait從0計數到24999999即0.5s,每到計滿時標志信號add_flag拉高,且顯示信號跳轉到下一個狀態,顯示信號需要從0-F循環。
module seg_static
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [7:0] sel ,
output reg [7:0] seg
);
//parameter define
parameter CNT_WAIT_MAX = 25'd24_999_999; //計數器最大值(0.5s)
//十六進制數顯示編碼
parameter SEG_0 = 8'b1100_0000, SEG_1 = 8'b1111_1001,
SEG_2 = 8'b1010_0100, SEG_3 = 8'b1011_0000,
SEG_4 = 8'b1001_1001, SEG_5 = 8'b1001_0010,
SEG_6 = 8'b1000_0010, SEG_7 = 8'b1111_1000,
SEG_8 = 8'b1000_0000, SEG_9 = 8'b1001_0000,
SEG_A = 8'b1000_1000, SEG_B = 8'b1000_0011,
SEG_C = 8'b1100_0110, SEG_D = 8'b1010_0001,
SEG_E = 8'b1000_0110, SEG_F = 8'b1000_1110;
parameter IDLE = 8'b1111_1111; //不顯示狀態
//reg define
reg add_flag ;
reg [24:0] cnt_wait ;
reg [3:0] num ;
//cnt_wait:0.5秒計數
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_wait <= 25'd0;
else if(cnt_wait == CNT_WAIT_MAX)
cnt_wait <= 25'd0;
else
cnt_wait <= cnt_wait + 1'b1;
//add_flag:0.5s拉高一個標志信號
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
add_flag <= 1'b0;
else if(cnt_wait == CNT_WAIT_MAX - 1)
add_flag <= 1'b1;
else
add_flag <= 1'b0;
//num:從 4'h0 加到 4'hf 循環
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
num <= 4'd0;
else if(add_flag == 1'b1)
num <= num + 1'b1;
else
num <= num;
//sel:選中8個數碼管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 8'b00000000;
else
sel <= 8'b11111111;
//給要顯示的值編碼
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= IDLE;
else case(num)
4'd0: seg <= SEG_0;
4'd1: seg <= SEG_1;
4'd2: seg <= SEG_2;
4'd3: seg <= SEG_3;
4'd4: seg <= SEG_4;
4'd5: seg <= SEG_5;
4'd6: seg <= SEG_6;
4'd7: seg <= SEG_7;
4'd8: seg <= SEG_8;
4'd9: seg <= SEG_9;
4'd10: seg <= SEG_A;
4'd11: seg <= SEG_B;
4'd12: seg <= SEG_C;
4'd13: seg <= SEG_D;
4'd14: seg <= SEG_E;
4'd15: seg <= SEG_F;
default:seg <= IDLE ; //閑置狀態,不顯示
endcase
endmodule
計數器cnt_wait:0.5s計數器,計數值從0-24_999_999。復位有效時歸0,計數到24_999_999計滿時歸0,其他情況+1。
標志信號add_flag:計滿0.5s拉高一個脈沖,復位有效時歸0,計數到24_999_999-1時拉高,其他情況歸0。
顯示信號num:每當標志信號拉高時從0-F循環,復位有效時歸0,判斷標志信號為高電平時+1(F是1111,+1為10000溢出,但只保留后四位0000,因此F的下一個狀態還是0),其他情況保持原值。
位選信號sel:位選信號長期接高點平,數碼管才能顯示,因此復位有效時歸0,其他情況都為高電平。
段選信號seg:段選信號與顯示數值的對應關系之前已經討論過了。除了0-F的16種顯示情況外增加了一種不顯示的閑置狀態IDLE。復位有效時段選信號為IDLE,與num數值有關。使用CASE語句判斷num值并將對應的參數賦值給段選信號seg,注意要有default語句。
2、芯片控制模塊
包括輸入:時鐘信號、復位信號,段選信號,位選信號,輸出使能,輸出:芯片的四個輸入引腳(寄存器時鐘,存儲器時鐘,數據輸入)。這個模塊最重要的是弄清楚怎么通過控制Cyclone IV E和芯片連接的引腳,讓芯片產生正確的輸出,去驅動數碼管正確的顯示圖形。
注意:對于我使用的開發板,沒有oe對應引腳,它是作為輸入信號存在的(可閱讀https://blog.csdn.net/weixin_43614528/article/details/87878938?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%2287878938%22%2C%22source%22%3A%22unlogin%22%7D)74HC595驅動模塊觀察其代碼。而對于征途開發板,使能引腳為L11。
shcp是寄存器時鐘,上升沿時數據寫入移位寄存器,它有頻率限制,這里采用系統時鐘的4分頻,即12.5MHz。stcp是存儲器時鐘,串行輸入16位之后拉高。 en是使能信號,需要一直維持高電平 (圖中的使能信號oe剛好相反,這與開發板有關,因此要仔細閱讀用戶手冊)。
module hc595_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [7:0] sel ,
input wire [7:0] seg ,
input wire en ,
output reg stcp ,
output reg shcp ,
output reg ds
);
//reg define
reg [1:0] cnt_4 ; //分頻計數器
reg [3:0] cnt_bit ; //傳輸位數計數器
//wire define
wire [15:0] data ; //數碼管信號寄存
//將數碼管信號寄存
assign data={sel,seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7]};
//分頻計數器:0~3循環計數
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_4 <= 2'd0;
else if(cnt_4 == 2'd3)
cnt_4 <= 2'd0;
else
cnt_4 <= cnt_4 + 1'b1;
//cnt_bit:每輸入一位數據加一
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3 && cnt_bit == 4'd15)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
//stcp:14個信號傳輸完成之后產生一個上升沿
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp <= 1'b0;
else if(cnt_bit == 4'd15 && cnt_4 == 2'd3)
stcp <= 1'b1;
else
stcp <= 1'b0;
//shcp:產生四分頻移位時鐘
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shcp <= 1'b0;
else if(cnt_4 >= 4'd2)
shcp <= 1'b1;
else
shcp <= 1'b0;
//ds:將寄存器里存儲的數碼管信號輸入即
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ds <= 1'b0;
else if(cnt_4 == 2'd0)
ds <= data[cnt_bit];
else
ds <= ds;
endmodule
使能信號en:在這里沒有給他賦值,而在頂層模塊的實例化時接了高電平。
分頻計數器cnt_4:4分頻即0-3循環計數,復位有效時歸0,計數到3計滿時歸0,其他情況+1。
傳輸位數計數器cnt_bit:每傳輸一個數據+1,0-15循環計數。由于分頻之后四個時鐘周期為一個新的時鐘周期,一個新時鐘周期傳輸一個數據。復位有效時歸0,當cnt計數到3且cnt_bit計數到15計滿時歸0,cnt計數到3且cnt_bit沒計滿時+1,其他情況保持。
存儲器時鐘stcp:15個信號傳輸完成后拉高,復位有效時歸0,當cnt計數到3且cnt_bit計數到15計滿時拉高,其他情況保持低電平。
寄存器時鐘shcp:第九節分頻器中僅分頻的分頻器實現方法,4分頻后的時鐘脈沖周期是原來的4倍。復位有效時歸0,計數大于等于2(2,3)時拉高,其他情況(0,1)拉低。
串行數據輸出ds:對FPGA芯片來說是輸出,對74HC595芯片來說是輸入。復位有效時歸0,當分頻計數器計數值為0時開始傳輸數據,其他情況保持原值。傳輸的數據是FPGA芯片輸出的16位數據,74HC595芯片會串行輸出(一次輸出1bit)。數據data是seg[0]...seg[7],sel拼接起來的,sel是從高位到低位的順序,data[cnt_bit]是從低位到高位的輸出,因此是從位選的低位到高位,再從段選的高位到低位依次輸出。
3、頂層模塊
module seg_595_static
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire stcp ,
output wire shcp ,
output wire ds
);
//wire define
wire [7:0] sel;
wire [7:0] seg;
//---------- seg_static_inst ----------
seg_static seg_static_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.sel (sel ),
.seg (seg )
);
//---------- hc595_ctrl_inst ----------
hc595_ctrl hc595_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.sel (sel ),
.seg (seg ),
.en(1'b1),
.stcp (stcp ),
.shcp (shcp ),
.ds (ds )
);
endmodule
頂層模塊實質是兩個實例化。
編寫testbench
`timescale 1ns/1ns
module tb_seg_595_static();
//wire define
wire stcp ; //輸出數據存儲寄時鐘
wire shcp ; //移位寄存器的時鐘輸入
wire ds ; //串行數據輸入
wire oe ; //輸出使能信號
//reg define
reg sys_clk ;
reg sys_rst_n ;
//對sys_clk,sys_rst_n賦初始值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100
sys_rst_n <= 1'b1;
end
//clk:產生時鐘
always #10 sys_clk <= ~sys_clk;
//重新定義參數值,縮短仿真時間
defparam seg_595_static_inst.seg_static_inst.CNT_WAIT_MAX = 100;
//-------------seg_595_static_inst-------------
seg_595_static seg_595_static_inst(
.sys_clk (sys_clk ), //系統時鐘,頻率50MHz
.sys_rst_n (sys_rst_n ), //復位信號,低電平有效
.stcp (stcp ), //輸出數據存儲寄時鐘
.shcp (shcp ), //移位寄存器的時鐘輸入
.ds (ds ), //串行數據輸入
.oe (oe ) //輸出使能信號
);
endmodule
初始化:對時鐘信號和復位信號賦初值,延遲100ns后復位拉高。
產生時鐘:延遲10ns后取反,周期為20ns,50MHz的時鐘。
為了節省仿真時間,重新定義參數。當一個模塊引用另外一個模塊時,高層模塊可以改變低層模塊用parameter定義的參數值。低層模塊的參數可以通過層次路徑名重新定義。
實例化
對比波形
數碼管靜態驅動模塊仿真波形圖
這里看模塊的波形,可以把原來的波形delete,再把想要的如圖添加進去。后面要修改可以restart后break再重新run all。
可以看到數碼顯示的值(num)從0開始跳轉到了1,再跳轉到了2;同時段選信號(seg)的編碼與顯示的字符也相吻合
可以看到數碼管顯示的數值從4’hf跳轉回0
74HC595控制模塊仿真波形圖
分配管腳
-
FPGA
+關注
關注
1629文章
21729瀏覽量
602997 -
寄存器
+關注
關注
31文章
5336瀏覽量
120230 -
數碼管
+關注
關注
32文章
1882瀏覽量
91064 -
靜態顯示
+關注
關注
0文章
19瀏覽量
5989
發布評論請先 登錄
相關推薦
評論