文章目錄
- 1 理論學習
- 1.1 數碼管動態掃描顯示原理
- 2 實戰演練
- 2.1 實驗目標
- 2.2 程序設計
- 2.2.1 框圖繪制
- 2.2.2 數據生成模塊 data_gen
- 2.2.2.1 波形繪制
- 2.2.2.2 代碼編寫
- 2.2.2.3 代碼編譯
- 2.2.2.4 邏輯仿真
- 2.2.2.4.1 仿真代碼編寫
- 2.2.2.4.2 仿真代碼編譯
- 2.2.2.4.3 波形仿真
- 2.2.3 BCD 轉碼模塊 bcd_8421
- 2.2.3.1 BCD 碼簡介
- 2.2.3.2 框圖繪制
- 2.2.3.3 波形繪制
- 2.2.3.4 代碼編寫
- 2.2.3.5 代碼編譯
- 2.2.3.6 邏輯仿真
- 2.2.3.6.1 仿真代碼編寫
- 2.2.3.6.2 仿真代碼編譯
- 2.2.3.6.3 波形仿真
- 2.2.4 動態顯示驅動模塊 segment_dynamic
- 2.2.4.1 波形繪制
- 2.2.4.2 代碼編寫
- 2.2.4.3 代碼編譯
- 2.2.4.4 邏輯仿真
- 2.2.4.4.1 仿真代碼編寫
- 2.2.4.4.2 仿真代碼編譯
- 2.2.4.4.3 波形仿真
- 2.2.5 動態顯示模塊 segment_595_dynamic
- 2.2.5.1 代碼編寫
- 2.2.5.2 代碼編譯
- 2.2.6 頂層模塊 top_segment_595
- 2.2.6.1 代碼編寫
- 2.2.6.2 代碼編譯
- 2.2.6.3 邏輯仿真
- 2.2.6.3.1 仿真代碼編寫
- 2.2.6.3.2 仿真代碼編譯
- 2.2.6.3.3 波形仿真
- 2.2.7 上板驗證
- 2.2.7.1 引腳綁定
- 2.2.7.2 結果驗證
Quartus II 報錯信息匯總:
Error (12007): Top-level design entity “top_segment_595” is undefined
Error (10170): Verilog HDL syntax error at segment_dynamic.v(129) near text “1”; expecting “;”
Error (10228): Verilog HDL error at bcd_8421.v(2): module “bcd_8421” cannot be declared more than once
Error (12006): Node instance “hc595_ctrl_inst” instantiates undefined entity “hc595_ctrl”
Error (10112): Ignored design unit “tb_top_segment_595” at tb_top_segment_595.v(5) due to previous errors
在上一小節當中,我們對數碼管的靜態顯示做了一個詳細的講解;但是如果單單只掌握數碼管的靜態顯示這種顯示方式是遠遠不夠的,因為數碼管的靜態顯示當中,被選中的數碼位它們顯示的內容都是相同的,這種顯示方式在實際應用當中顯然是不合適的;我們希望控制每個數碼位能夠獨立的顯示我們想要顯示的內容,如何實現這一操作呢?就是本小節所要講解的內容:數碼管的動態顯示。
本小節的主要內容分為兩個部分:
- 第一部分是理論學習,在這一部分,我們會對數碼管的動態顯示的工作原理做一個詳細的講解;
- 第二部分是實戰演練,在這一部分,會通過實驗工程設計并實現數碼管的動態顯示。
首先是理論學習
1 理論學習
我們征途系列開發板使用的是六位八段數碼管,實物圖1如下
焊接在開發板的右上角位置
1.1 數碼管動態掃描顯示原理
六位八段數碼管的內部結構圖如下
由圖可知,六位八段數碼管當中,每個數碼位的段選信號全部連接到了一起,然后進行輸出;每個數碼位單獨引出一個位選信號用來控制數碼位的選擇,這種連接方式會使得被選中的數碼位顯示的內容都是相同的,因為這些被選中的數碼位的段選信號已經全部連接到了一起。如何使用這個六位八段數碼管來實現數碼管的動態顯示呢?我們需要使用一種方式:動態掃描
如何使用動態掃描的方式來實現數碼管的動態顯示呢?這里給大家舉一個例子:比如說,我們想要使用六位八段數碼管顯示數字 123456
如何使用六位八段數碼管來顯示數字 123456 呢?
首先我們選中第一個數碼位,讓這個數碼位顯示數字 1;然后它顯示的時間設為 T \text{T} T,這個 T \text{T} T 可以看作一個周期
當第一個數碼位完成一個 T \text{T} T 周期數字 1 的顯示之后,立刻選中第二個數碼位;注意,此時只選中了第二個數碼位讓它顯示數字 2,顯示的時間同樣是一個周期 T \text{T} T
當第二個數碼位完成了一個周期 T \text{T} T 數字 2 的顯示之后,立刻選中第三個數碼位;這兒注意,也是只選中了第三個數碼位讓它顯示數字 3,同樣顯示時間為 T \text{T} T
依次往下類推,那么此時就顯示 4
然后是 5
然后是 6
當第六個數碼位完成了一個周期 T \text{T} T 數字 6 的顯示之后,再重新選中第一個數碼位;這兒也是只選中了第一個數碼位,讓它繼續顯示數字 1,然后顯示的時間仍然是 T \text{T} T 周期,這樣依次往下循環。
通過上述動態顯示過程的描述,我們知道這樣一個循環是六個周期就是 6 T 6\text{T} 6T;如果說給這個 T \text{T} T 規定一個確切的時間會怎樣呢?首先給 T \text{T} T 規定一個確切的時間 1s;如果說 T \text{T} T 等于 1s,六位八段數碼管的六個數碼位會依次顯示 1、2、3、4、5、6 每個數字顯示的時間為 1s
如果進一步把這個 T \text{T} T 進行縮短,比如說縮短到 0.2s 這時候六位八段數碼管的六個數碼位會進行閃爍顯示,顯示的內容依次是 1、2、3、4、5、6
如果進一步縮短時間 T \text{T} T 為 1ms,這時候,六位八段數碼管的六個數碼位實際上也是依次進行閃爍的顯示,顯示的內容依次是 1、2、3、4、5、6 每個數字顯示時間是 1ms
但是它們切換的頻率太快了,我們的肉眼不能分辨這種閃爍,就誤以為六位八段數碼管的六個數碼位在同時進行顯示,而且顯示的內容是 123456。
這樣就使用動態掃描的方式實現了數碼管的動態顯示。
使用動態掃描的方式實現數碼管的動態顯示,實際上是利用了兩個現象:人眼的視覺暫留特性和數碼管的余暉效應。人眼在觀察景物時,光信號傳入到大腦神經需要經過一段時間,光的作用結束之后我們的視覺影像并不會立刻的消失,這種殘留的視覺被稱為后像,這種現象就被稱為視覺暫留;數碼管的余暉效應是什么意思呢?當我們停止向發光二極管供電時,發光二極管的亮度仍能夠維持一段時間。我們的動態掃描利用這兩個特性就實現了數碼管的動態顯示。
以上內容就是數碼管動態顯示的工作原理,接下來就開始進行實戰演練
2 實戰演練
在實戰演練部分,我們會通過實驗工程設計并實現數碼管的動態顯示。
2.1 實驗目標
首先,先來說一下我們的實驗目標。我們的實驗目標是使用六位八段數碼管,實現數碼管的動態顯示,顯示的內容是十進制的 ( 0 ) 10 (0)_{10} (0)10? 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10?,當計數到最大值讓它歸零,循環顯示;每 0.1s 加 1,也就是說第一個 0.1s 顯示的是 0,第二個 0.1s 顯示的是 1,第三個 0.1s 顯示的是 2,然后依次往后排。實驗目標的效果如下
六位數碼管顯示0~359999
六位數碼管顯示360000~719999
六位數碼管顯示720000~999999
2.2 程序設計
了解了實驗目標之后,下面開始程序的設計。
首先建立一個文件體系
segment_595_dynamic
├─doc
├─quartus_prj
├─rtl
└─sim
2.2.1 框圖繪制
然后打開 doc 文件夾建立一個 Visio 文件,用來繪制框圖和波形圖
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
│
├─quartus_prj
├─rtl
└─sim
首先,先來繪制模塊框圖,頂層模塊 top_segment_595
的框圖如下
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
輸出信號(傳入到 74HC595)
- ds:串行數據
- shcp:移位寄存器時鐘
- stcp:存儲寄存器時鐘
- oe_n:輸出使能
下面結合我們的實驗目標和層次化的設計思想,將整個系統工程進行子功能的劃分。我們的實驗目標是使用六位八段數碼管實現數碼管的動態顯示,顯示的內容是 ( 0 ) 10 (0)_{10} (0)10? 到 ( 999999 ) 10 (999999)_{10} (999999)10?;顯示的這個數據肯定需要一個模塊來產生,我們就先定義一個新的子功能模塊:
data_gen
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
輸出信號
- data[19:0]:待顯示的數據。它的最大值是 ( 999999 ) 10 (999999)_{10} (999999)10?,換算成二進制就是 ( 1111 _ 0100 _ 0010 _ 0011 _ 1111 ) 2 (1111\_0100\_0010\_0011\_1111)_{2} (1111_0100_0010_0011_1111)2?,所以它的位寬是 20,就是
[19:0]
- point[5:0]:(數碼管動態顯示模塊
segment_595_dynamic
可能會用于溫濕度的顯示,所以說要增加小數點的生成)小數點信號。六位八段數碼管有 6 個小數點段,所以該信號的位寬是 6 位寬 - sign:(數碼管動態顯示模塊
segment_595_dynamic
可能會用于電壓測量的顯示,所以說要生成符號位)符號位。數值的負號(高電平進行負號的顯示) - seg_en:(為了能夠更好的控制數碼管動態顯示模塊
segment_595_dynamic
,生成一個使能信號)當使能信號為有效的高電平時,數碼管可以正常的顯示;當使能信號為低電平時,數碼管就不工作
有了數據產生模塊 data_gen
之后,接下來就是數碼管動態顯示模塊 segment_595_dynamic
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
- data[19:0]:待顯示數據
- point[5:0]:待顯示數據的小數點
- sign:待顯示數據的負號
- seg_en:控制數碼管動態顯示模塊工作與否的使能信號
數碼管動態顯示模塊 segment_595_dynamic
在后面的實驗工程中會經常用到,這里為了提高它的復用性,所以增加小數點 point[5:0]
、符號 sign
、使能 seg_en
等信號端口。
輸出信號(就是傳入到 74HC595 芯片的四路信號)
- ds:串行數據
- shcp:移位寄存器時鐘
- stcp:存儲寄存器時鐘
- oe_n:輸出使能
下面對數碼管動態顯示模塊 segment_595_dynamic
繼續進行功能的劃分,劃分的方式參照數碼管的靜態顯示將它劃分為兩個功能模塊:第一個模塊是動態顯示驅動模塊 segment_dynamic
、第二個模塊是 74HC595 控制模塊 hc595_ctrl
。
為什么要進行模塊的進一步劃分呢?因為 74HC595 控制模塊 hc595_ctrl
在數碼管的靜態顯示當中已經完全實現了,我們可以直接調用。我們使用動態顯示驅動模塊 segment_dynamic
生成位選信號 sel[5:0]
和段選信號 seg[7:0]
,然后 74HC595 控制模塊 hc595_ctrl
將位選信號 sel[5:0]
和段選信號 seg[7:0]
轉換成 ds
、shcp
、stcp
、oe_n
這四路信號傳入到 74HC595 控制芯片。
動態顯示驅動模塊 segment_dynamic
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
- data[19:0]:待顯示數據
- point[5:0]:待顯示數據小數點位
- sign:待顯示數據的符號位
- seg_en:數碼管動態顯示模塊使能信號
輸出信號
- sel[5:0]:位選信號
- seg[7:0]:段選信號
74HC595 控制模塊 hc595_ctrl
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
- sel[5:0]:位選信號
- seg[7:0]:段選信號
輸出信號(就是傳入到 74HC595 芯片的四路信號)
- ds:串行數據
- shcp:移位寄存器時鐘
- stcp:存儲寄存器時鐘
- oe_n:輸出使能
各子功能模塊的模塊框圖繪制完成,下面開始系統框圖的繪制。
首先是數碼管動態顯示模塊 segment_595_dynamic
它的模塊功能由兩個子功能模塊實現,輸入到數碼管動態顯示模塊 segment_595_dynamic
當中的時鐘信號 sys_clk
和復位信號 sys_rst_n
分別傳給兩個子功能模塊,數碼管動態顯示驅動模塊 segment_dynamic
產生的位選信號 sel[5:0]
和段選信號 seg[7:0]
傳給 74HC595 控制模塊 hc595_ctrl
。
下面開始系統框圖的繪制。頂層模塊 top_segment_595
包含兩個子功能模塊:一個是數據產生模塊 data_gen
,另一個是數碼管動態顯示模塊 segment_595_dynamic
傳入到頂層模塊 top_segment_595
的時鐘信號 sys_clk
和復位信號 sys_rst_n
要傳給兩個子功能模塊,然后數據生成模塊 data_gen
產生的待顯示數據 data[19:0]
、小數點位 point[5:0]
、符號位 sign
和使能信號 seg_en
要傳給數碼管動態顯示模塊 segment_595_dynamic
。通過這個系統框圖,我們可以了解各個模塊之間的層次關系以及信號的走向。
系統框圖繪制完成之后,接下來就開始實現各個子功能模塊的功能。
2.2.2 數據生成模塊 data_gen
2.2.2.1 波形繪制
首先是數據生成模塊 data_gen
,我們先來繪制一下數據生成模塊 data_gen
的波形圖
由模塊框圖可知,輸入信號有兩路:時鐘信號 sys_clk
和復位信號 sys_rst_n
。
在實驗目標當中我們提到:我們想要使用六位八段數碼管實現數碼管的動態顯示,顯示的內容是十進制的數字 ( 0 ) 10 (0)_{10} (0)10? 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10? 的循環計數,計數的間隔是每 0.1 s 0.1\text{s} 0.1s 加 1 1 1;這個 0.1 s 0.1\text{s} 0.1s 的計數就需要一個計數器來實現,所以我們需要聲明一個計數器變量 cnt_100ms
對 0.1 s 0.1\text{s} 0.1s 進行計數。首先,當復位信號 sys_rst_n
有效時給計數器變量 cnt_100ms
賦一個初值 0 0 0;復位信號 sys_rst_n
無效時 cnt_100ms
每個系統時鐘周期自加 1 1 1,cnt_100ms
的計數周期是 0.1 s 0.1\text{s} 0.1s 就是 100 ms 100\text{ms} 100ms,cnt_100ms
計數器完成 0.1 s 0.1\text{s} 0.1s 的計數,要計數多少個系統時鐘周期呢?我們知道系統時鐘的頻率是 50 MHz 50\text{MHz} 50MHz 換算為周期就是 T s y s _ c l k = 20 ns \text{T}_{sys\_clk}=20\text{ns} Tsys_clk?=20ns,計數器要完成 0.1 s 0.1\text{s} 0.1s 即 100 ms 100\text{ms} 100ms 的計數, 100 ms 100\text{ms} 100ms 換算成 ns \text{ns} ns 就是 T c n t _ 100 m s = 1 × 1 0 8 ns \text{T}_{cnt\_100ms}=1\times10^8\text{ns} Tcnt_100ms?=1×108ns,計數器若想要完成 0.1 s 0.1\text{s} 0.1s 的計數,需要在頻率為 50 MHz 50\text{MHz} 50MHz 的系統時鐘下完成 T c n t _ 100 m s / T s y s _ c l k = ( 20 ns ) / ( 1 × 1 0 8 ns ) = 5 × 1 0 6 \text{T}_{cnt\_100ms}/\text{T}_{sys\_clk}=(20\text{ns})/(1\times10^8\text{ns})=5\times10^6 Tcnt_100ms?/Tsys_clk?=(20ns)/(1×108ns)=5×106 個系統時鐘周期的計數;因為計數器是從 0 0 0 開始計數,所以說計數的最大值應該是 5 × 1 0 6 ? 1 = 4999999 5\times10^6-1=4999999 5×106?1=4999999,cnt_100ms
計數的最大值就是 4 _ 999 _ 999 4\_999\_999 4_999_999。當 cnt_100ms
計數到最大值就讓它歸零,開始下一個周期的計數。
然后還需要聲明一個變量就是 cnt_flag
信號,那么為什么要聲明這個 cnt_flag
信號呢?我們需要使用這個 cnt_flag
信號作為條件,控制輸出的待顯示數據 data[19:0]
讓它進行自加。那么有的朋友可能想到:我們也可以使用計數器,當 cnt_100ms
計數到最大值作為一個條件,控制輸出的待顯示數據 data[19:0]
讓它進行自加,那么這樣也是可以的;但是我們聲明這個 cnt_flag
信號是為了讓約束條件更加的簡潔清晰。首先,當復位信號有效時給 cnt_flag
賦一個初值 0 0 0,當 cnt_100ms
計數到最大值減一( 4 _ 999 _ 998 4\_999\_998 4_999_998)的時候將 cnt_flag
拉高一個時鐘周期,其他時刻 cnt_flag
保持低電平。
輸出信號有四路:第一路是待顯示的數據 data[19:0]
、第二路是小數點位 point[5:0]
、第三路是符號位 sign
、第四路是使能信號 seg_en
。
首先看一下待顯示數據 data[19:0]
的波形。復位信號有效時給 data[19:0]
賦一個初值 20'd0
,在第一個 0.1 s 0.1\text{s} 0.1s 顯示時間內數碼管顯示數字 0 0 0,所以 data[19:0]
在 cnt_flag
信號出現有效的高脈沖前一直保持初值;當 cnt_flag
信號出現有效的高脈沖時就表示 0.1 s 0.1\text{s} 0.1s 計數完成,然后輸出的待顯示數據 data[19:0]
要自加一,因為是時序邏輯,延遲了一個時鐘周期是沒有問題的;當 cnt_flag
信號為有效的高脈沖 data[19:0]
自加一,自加到最大值 20'd999_999
歸零,開始下一個周期的計數。
因為我們征途系列開發板使用的是六位八段數碼管,每個數碼位都有一個小數點位,所以說 point
它的位寬為六位寬 point[5:0]
,每一位表示每個數碼位的小數點位。在這里我們定義為高電平使小數點位有效,通過實驗目標可知:我們的顯示過程中并沒有使用到小數點位,所以說讓 point[5:0]
一直保持為無效的低電平。
符號位 sign
同樣是高電平有效,當數碼管進行負數顯示的時候,在顯示的數據之前會加一個負號用來區分正負數
在本次的顯示當中是顯示數字 ( 0 ) 10 (0)_{10} (0)10? 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10? 沒有負號顯示,所以說讓 sign
一直保持為無效的低電平。
最后一路輸出信號:使能信號 seg_en
。使能信號 seg_en
控制數碼管的顯示與否,當它為有效的高電平時,數碼管可以進行正常的顯示;當它為無效的低電平時,數碼管就不能進行顯示。在這個實驗當中我們實現的是十進制的數字 ( 0 ) 10 (0)_{10} (0)10? 到最大值 ( 999999 ) 10 (999999)_{10} (999999)10? 的循環計數,所以使能信號 seg_en
就要一直拉高,這樣數碼管才能夠正常的顯示。首先復位期間先給賦一個初值低電平,然后當復位信號無效時,讓它一直保持高電平。
數據生成模塊 data_gen
的整體波形圖繪制完成后,接下來就參照這個波形圖進行代碼的編寫
cnt_100ms
計數的最大值是 5 × 1 0 6 ? 1 = ( 4 _ 999 _ 999 ) 10 5\times10^6-1=(4\_999\_999)_{10} 5×106?1=(4_999_999)10?,換算成二進制就是 ( 4 _ 999 _ 999 ) 10 = ( 100 _ 1100 _ 0100 _ 1011 _ 0011 _ 1111 ) 2 (4\_999\_999)_{10}=(100\_1100\_0100\_1011\_0011\_1111)_{2} (4_999_999)10?=(100_1100_0100_1011_0011_1111)2?,所以 cnt_100ms
的位寬是 23 位寬 cnt_100ms[22:0]
2.2.2.2 代碼編寫
數據生成模塊的代碼編寫完成后,我們保存為 data_gen.v
//模塊開始 模塊名稱 端口列表
module data_gen
#(parameter CNT_MAX = 23'd4_999_999,//計數 0.1s 計數最大值parameter DATA_MAX= 20'd999_999 //待顯示數據最大值
)
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效output reg [19:0] data , //待顯示數據output wire [5:0] point , //小數點output wire sign , //負號output reg seg_en //數碼管動態顯示模塊工作使能
);// 中間變量
reg [22:0] cnt_100ms; //100ms 計數器
reg cnt_flag; //100ms 計時時間到達標志//變量賦值
always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_100ms <= 23'd0;else if (cnt_100ms == CNT_MAX)cnt_100ms <= 23'd0;elsecnt_100ms <= cnt_100ms + 23'd1;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_flag <= 1'b0;else if (cnt_flag == (CNT_MAX-1))cnt_flag <= 1'b1;elsecnt_flag <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)data <= 20'd0;else if ((cnt_flag==1'b1) && (data==DATA_MAX))data <= 20'd0;else if (cnt_flag==1'b1)data <= data + 20'd1;elsedata <= data;assign point = 6'b000_000;assign sign = 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)seg_en <= 1'b0;elseseg_en <= 1'b1;//模塊結束
endmodule
文件體系
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
├─quartus_prj
├─rtl
│ data_gen.v
└─sim
2.2.2.3 代碼編譯
然后建立一個新的實驗工程,添加剛剛編寫的代碼文件,進行全編譯;出現了報錯信息,我們點擊 OK
看一下報錯信息
然后再次進行編譯,編譯通過點擊 OK
2.2.2.4 邏輯仿真
2.2.2.4.1 仿真代碼編寫
接下來就需要編寫仿真文件
//時間參數
`timescale 1ns/1ns//模塊開始 模塊名稱 端口列表(空)
module tb_data_gen();//變量聲明
reg sys_clk;
reg sys_rst_n;//聲明變量將輸出信號引出
wire [19:0] data ;
wire [5:0] point ;
wire sign ;
wire seg_en;//變量初始化
initialbeginsys_clk = 1'b1;sys_rst_n <= 1'b0;#35sys_rst_n <= 1'b1;end//產生頻率為 50MHz 的系統時鐘
always #10 sys_clk = ~sys_clk;//模塊的實例化
data_gen
#(.CNT_MAX (23'd49),//計數 0.1s 計數最大值.DATA_MAX(20'd9 ) //待顯示數據最大值
)
data_gen_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待顯示數據.point (point ), //小數點.sign (sign ), //負號.seg_en (seg_en ) //數碼管動態顯示模塊工作使能
);//模塊結束
endmodule
仿真模塊編寫完成后,保存為 tb_data_gen.v
文件體系
segment_595_dynamic
├─doc
│ segment_595_dynamic.vsdx
│
├─quartus_prj
│ │ top_segment_595.qpf
│ │ top_segment_595.qsf
│ │
│ ├─db
│ │ logic_util_heursitic.dat
│ │ ......
│ │ top_segment_595.vpr.ammdb
│ │
│ ├─incremental_db
│ │ │ README
│ │ │
│ │ └─compiled_partitions
│ │ top_segment_595.db_info
│ │ ......
│ │ top_segment_595.root_partition.map.kpt
│ │
│ ├─output_files
│ │ top_segment_595.asm.rpt
│ │ ......
│ │ top_segment_595.sta.summary
│ │
│ └─simulation
│ └─modelsim
│ top_segment_595.sft
│ ......
│ top_segment_595_v.sdo
│
├─rtl
│ data_gen.v
│
└─simtb_data_gen.v
2.2.2.4.2 仿真代碼編譯
然后回到實驗工程,添加仿真模塊;進行全編譯;編譯完成點擊 OK
2.2.2.4.3 波形仿真
然后進行仿真設置,開始仿真;仿真完成之后打開 sim 窗口添加模塊波形;波形界面全選、分組、消除前綴;然后點擊 Restart,將時間參數先設置為 10us,運行一次。
發現 cnt_flag
波形有問題
查看 data_gen.v
中關于 cnt_flag
的賦值部分,發現第 33 行給 cnt_100ms
賦值高電平的條件編寫錯誤,將 (cnt_flag == (CNT_MAX-1))
修改成 (cnt_100ms == (CNT_MAX-1))
并且保存
data_gen.v
//模塊開始 模塊名稱 端口列表
module data_gen
#(parameter CNT_MAX = 23'd4_999_999,//計數 0.1s 計數最大值parameter DATA_MAX= 20'd999_999 //待顯示數據最大值
)
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效output reg [19:0] data , //待顯示數據output wire [5:0] point , //小數點output wire sign , //負號output reg seg_en //數碼管動態顯示模塊工作使能
);// 中間變量
reg [22:0] cnt_100ms; //100ms 計數器
reg cnt_flag; //100ms 計時時間到達標志//變量賦值
always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_100ms <= 23'd0;else if (cnt_100ms == CNT_MAX)cnt_100ms <= 23'd0;elsecnt_100ms <= cnt_100ms + 23'd1;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_flag <= 1'b0;else if (cnt_100ms == (CNT_MAX-1))cnt_flag <= 1'b1;elsecnt_flag <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)data <= 20'd0;else if ((cnt_flag==1'b1) && (data==DATA_MAX))data <= 20'd0;else if (cnt_flag==1'b1)data <= data + 20'd1;elsedata <= data;assign point = 6'b000_000;assign sign = 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)seg_en <= 1'b0;elseseg_en <= 1'b1;//模塊結束
endmodule
重新編譯 data_gen.v
模塊,將時間參數設置為 12us 再次仿真,參照我們繪制的波形圖,來看一下仿真波形
首先是計數器 cnt_100ms
下面看一下 cnt_flag
信號
那么下面就看一下待顯示數據 data
下面是小數點信號 point
,始終為 0 沒有問題
符號位 sign
始終為低電平,也沒有問題
然后是使能信號 seg_en
,初值為低電平,復位信號無效時一直保持高電平
這三路信號與我們繪制的波形圖是完全一致的,我們的數據生成模塊 data_gen
通過了仿真驗證。
我們已經實現了系統框圖的繪制,完成了數據生成模塊 data_gen
的功能實現;接下來我們將繼續生成子功能模塊。
首先先來看一下系統框圖
由系統框圖我們可以知道:頂層模塊 top_segment_595
包含兩個子功能模塊,一個是已經實現了的數據生成模塊 data_gen
,另一個是動態顯示模塊 segment_595_dynamic
;動態顯示模塊 segment_595_dynamic
又包含兩個子功能模塊,一個是動態顯示驅動模塊 segment_dynamic
,另一個是 74HC595 控制模塊 hc595_ctrl
。74HC595 控制模塊 hc595_ctrl
在上一小節數碼管的靜態顯示當中已經完全實現了,這兒就可以直接調用。
2.2.3 BCD 轉碼模塊 bcd_8421
2.2.3.1 BCD 碼簡介
接下來就需要實現動態顯示驅動模塊 segment_dynamic
。在實現動態顯示驅動模塊 segment_dynamic
之前,我們這兒要補充一個知識點:BCD 碼。在這里為什么要補充 BCD 碼的相關知識呢?
動態顯示驅動模塊 segment_dynamic
的作用是將傳入的待顯示的十進制數據 data[19:0]
轉化為可以輸出的位選信號 sel[5:0]
和段選信號 seg[7:0]
,傳入的數據 data[19:0]
是由數據生成模塊 data_gen
產生并傳入的,data[19:0]
是使用二進制表示的多位十進制數,這種編碼方式并不能夠直接用于產生位選信號 sel[5:0]
和段選信號 seg[7:0]
;我們需要將 data[19:0]
轉換為以 BCD 碼表示的十進制數,然后通過得到的 BCD 碼表示的十進制數來產生位選信號 sel[5:0]
和段選信號 seg[7:0]
,這是為什么呢?在解答這個問題之前我們先來學習一下什么是 BCD 碼
BCD 碼的英文全稱是 Binary-Coded Decimal,翻譯為二進制編碼的十進制,又稱為二—十進制碼,它使用 4 位二進制數來表示一位十進制數中的 0~9 這十個數碼,是一種二進制的數字編碼形式,是用二進制編碼的十進制代碼。簡要概括一下就是:BCD 碼它是使用 4 位二進制數來表示一位十進制數 0~9,一種編碼形式。
BCD 碼根據權值的有無可以分為有權碼和無權碼,“權”表示權值;有權碼的 4 位二進制數的每一位都有一個固定的權值,而無權碼沒有權值;常見的有權碼有 8421 碼、5421 碼和 2421 碼,8421 碼它的權值從左到右是 8、4、2、1
8421?碼: bit3  ̄ bit2  ̄ bit1  ̄ bit0  ̄ 權值: 8 4 2 1 \begin{align} \text{8421 碼:}&\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{權值:}&\,\,\,\,8\,\qquad4\,\qquad2\,\qquad1 \end{align} 8421?碼:權值:?bit3?bit2?bit1?bit0?8421??
5421 碼它的權值從左到右就是 5、4、2、1
5421?碼: bit3  ̄ bit2  ̄ bit1  ̄ bit0  ̄ 權值: 5 4 2 1 \begin{align} \text{5421 碼:}&\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{權值:}&\,\,\,\,5\,\qquad4\,\qquad2\,\qquad1 \end{align} 5421?碼:權值:?bit3?bit2?bit1?bit0?5421??
2421 碼它的權值從左到右就是 2、4、2、1
2421?碼: bit3  ̄ bit2  ̄ bit1  ̄ bit0  ̄ 權值: 2 4 2 1 \begin{align} \text{2421 碼:}&\underline{\text{bit3}}\quad\underline{\text{bit2}}\quad\underline{\text{bit1}}\quad\underline{\text{bit0}} \\ \text{權值:}&\,\,\,\,2\,\qquad4\,\qquad2\,\qquad1 \end{align} 2421?碼:權值:?bit3?bit2?bit1?bit0?2421??
其中 8421 碼是最為常用的 BCD 編碼,也是本實驗當中使用的編碼形式;常用的無權碼有余 3 碼、余 3 循環碼。
下面這張表格表示的就是幾種常見的 BCD 編碼方式所對應的十進制數 0~9 的編碼格式
十進制數 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
8421 碼 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 |
5421 碼 | 0000 | 0001 | 0010 | 0011 | 0100 | 1000 | 1001 | 1010 | 1011 | 1100 |
2421 碼 | 0000 | 0001 | 0010 | 0011 | 0100 | 1011 | 1100 | 1101 | 1110 | 1111 |
余 3 碼 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 |
余 3 循環碼 | 0010 | 0110 | 0111 | 0101 | 0100 | 1100 | 1101 | 1111 | 1110 | 1010 |
接下來我們講解一下如何使用 BCD 碼來表示十進制數,我們以 8421 碼為例:比如說十進制數數字 7,它的 8421 碼編碼格式是 0111;那么我們怎么通過 0111 這個編碼格式得到十進制數數字 7 呢?將 BCD 碼的每位二進制數與其對應的權值相乘,之后將所有的乘積相加,相加和就是 BCD 碼所表示的十進制數;我們來計算一下
8421?碼: 0  ̄ 1  ̄ 1  ̄ 1  ̄ 權值: 8 4 2 1 相乘: ? ? ? ? ? 乘積: 0 4 2 1 相加和 ? 十進制數: 0 ? + ? 4 ? + ? 2 ? + ? 1 = 7 \begin{align} \text{8421 碼:}&\underline{\text{0}}\quad\underline{\text{1}}\quad\underline{\text{1}}\quad\underline{\text{1}} \\ \text{權值:}&8\quad4\quad2\quad1 \\ \text{相乘:}&\!\Downarrow\quad\Downarrow\quad\Downarrow\quad\Downarrow \\ \text{乘積:}&0\quad4\quad2\quad1 \\ \text{相加和}\Leftrightarrow\text{十進制數:}&0\!+\!4\!+\!2\!+\!1=7 \end{align} 8421?碼:權值:相乘:乘積:相加和?十進制數:?0?1?1?1?8421????04210+4+2+1=7??
其他的有權碼也是使用這種方式:將各位二進制數與對應位的權值相乘,乘積相加就可以得到有權碼所表示的十進制數。
無權碼的計算方式我們這兒就不再進行講解了,感興趣的朋友可以自行查閱相關資料。
了解了有權碼的編碼方式之后,我們來看下前面提出的問題:為什么數據生成模塊 data_gen
生成的二進制編碼的十進制數,并不能夠直接用于產生位選信號 sel[5:0]
和段選信號 seg[7:0]
;而 BCD 碼表示的十進制數可以直接用于產生位選信號 sel[5:0]
和段選信號 seg[7:0]
呢?
我們這兒舉一個例子,比如說使用多位數碼管來表示一個十進制數 ( 233 ) 10 (233)_{10} (233)10?,十進制數 ( 233 ) 10 (233)_{10} (233)10? 使用二進制表示應該是 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2?
十進制數 ( 233 ) 10 (233)_{10} (233)10? 使用 8421 碼表示應該是 ( 0010 _ 0011 _ 0011 ) 8421 (0010\_0011\_0011)_{8421} (0010_0011_0011)8421?
知道了十進制數 ( 233 ) 10 (233)_{10} (233)10? 的兩種表示方式之后,接下來繼續進行分析:如果使用動態掃描的方式在多位數碼管上顯示 ( 233 ) 10 (233)_{10} (233)10?,就需要在第一個顯示周期內點亮數碼管的 DIG6 數碼位(從左到右依次為DIG1,DIG2,DIG3,DIG4,DIG5,DIG6),讓它顯示個位 ( 3 ) 10 (3)_{10} (3)10?
然后第二個顯示周期內點亮 DIG5 數碼位,讓它顯示十位 ( 3 ) 10 (3)_{10} (3)10?
在第三個顯示周期內點亮 DIG4 數碼位,讓它顯示百位 ( 2 ) 10 (2)_{10} (2)10?
如果說掃描的頻率足夠快,加上人眼的視覺暫留特性以及數碼管的余暉效應,我們的肉眼就以為數碼管在同時顯示 ( 233 ) 10 (233)_{10} (233)10?
這樣就實現了 ( 233 ) 10 (233)_{10} (233)10? 的顯示。這種顯示方式就要求我們能夠從傳入的數據當中,直接提取出待顯示數據 data[19:0]
的個位、十位和百位;而傳入的使用二進制表示的十進制數,從其中并不能直接提取出個位、十位和百位;而 BCD 碼它所表示的十進制數是可以直接進行個位、十位、百位信息的提取:最低 4 位 ( 0011 ) 2 (0011)_{2} (0011)2? 表示個位 ( 3 ) 10 (3)_{10} (3)10?,中間 4 位 ( 0011 ) 2 (0011)_{2} (0011)2? 表示十位 ( 3 ) 10 (3)_{10} (3)10?,最高 4 位 ( 0010 ) 2 (0010)_{2} (0010)2? 表示百位 ( 2 ) 10 (2)_{10} (2)10?
這就是 BCD 碼所表示的十進制數據能夠直接用于產生位選和段選信號,而二進制表示的十進制數據并不能直接用于產生位選和段選信號的原因。
了解了這些之后,我們引入了一個新的問題:如何將二進制碼轉化為 BCD 碼?接下來就通過一個例子來講解一下轉換的方法,在這里我們以三位十進制數 ( 233 ) 10 (233)_{10} (233)10? 為例。
十進制數 ( 233 ) 10 (233)_{10} (233)10? 的二進制編碼形式是 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2?。實現轉換的第一步:在輸入的二進制碼之前補上若干個 0;0 的個數規定為: 十進制數的十進制位個數 × 4 \text{十進制數的十進制位個數}\times4 十進制數的十進制位個數×4。
參與轉換的十進制數有 n n n 個十進制位,那么就需要 n n n 個 BCD 碼; ( 233 ) 10 (233)_{10} (233)10? 的十進制位分別是個位 ( 3 ) 10 (3)_{10} (3)10?、十位 ( 3 ) 10 (3)_{10} (3)10?、百位 ( 2 ) 10 (2)_{10} (2)10? 總共三個位,所以 n = 3 n=3 n=3 需要 3 3 3 個 BCD 碼;每個 BCD 碼是 4 個位寬, n × 4 = 3 × 4 = 12 n\times4=3\times4=12 n×4=3×4=12 所以 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2? 前面就需要補 12 12 12 個 0
得到 ( 0000 _ 0000 _ 0000 _ 1110 _ 1001 ) 2 (0000\_0000\_0000\_1110\_1001)_{2} (0000_0000_0000_1110_1001)2?
如果是其他數值的多位十進制數,比如說 ( 114514 ) 10 = ( 1 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (114514)_{10}=(1\_1011\_1111\_0101\_0010)_{2} (114514)10?=(1_1011_1111_0101_0010)2? 它的十進制位個數是 6 6 6,每個 BCD 碼需要 4 4 4 位二進制數,所以 ( 1 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (1\_1011\_1111\_0101\_0010)_{2} (1_1011_1111_0101_0010)2? 前面就要補 24 24 24 個 0
得到 ( 0 _ 0000 _ 0000 _ 0000 _ 0000 _ 0000 _ 0001 _ 1011 _ 1111 _ 0101 _ 0010 ) 2 (0\_0000\_0000\_0000\_0000\_0000\_0001\_1011\_1111\_0101\_0010)_{2} (0_0000_0000_0000_0000_0000_0001_1011_1111_0101_0010)2?
在這里我們就完成了補 0
的操作,得到了一組新的數據 ( 0000 _ 0000 _ 0000 _ 1110 _ 1001 ) 2 (0000\_0000\_0000\_1110\_1001)_{2} (0000_0000_0000_1110_1001)2?,接下來就要對這組數據進行判斷運算和移位操作。
首先判斷 BCD 碼部分,判斷每一個 BCD 碼所表示的十進制數是否大于等于 5,如果說每一個 BCD 碼所表示的十進制數大于等于 5 就將它與 3 相加;如果說每一個 BCD 碼所表示的十進制數小于 5 就讓它保持原值不變。不論每一個 BCD 碼所表示的十進制數大于等于 5 或者小于 5,完成判斷運算之后都要向左移一個二進制位。
按照上述兩條規則,得到下面的表格(上例十進制數 ( 233 ) 10 (233)_{10} (233)10? 的二進制編碼形式 ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2? 轉換成 8421BCD 碼過程)
執行的操作 | 1 0 2 10^{2} 102 | 1 0 1 10^{1} 101 | 1 0 0 10^{0} 100 | 待轉換數據(二進制數) |
---|---|---|---|---|
補 12 12 12 個 0 | 0000 | 0000 | 0000 | 1110_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0000 | 1110_1001 |
整體向左移一個二進制位,第 1 次按位左移 | 0000 | 0000 | 0001 | 110_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0001 | 110_1001 |
整體向左移一個二進制位,第 2 次按位左移 | 0000 | 0000 | 0011 | 10_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0011 | 10_1001 |
整體向左移一個二進制位,第 3 次按位左移 | 0000 | 0000 | 0111 | 0_1001 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0000 | 0000 | 1010 | 0_1001 |
整體向左移一個二進制位,第 4 次按位左移 | 0000 | 0001 | 0100 | 1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0001 | 0100 | 1001 |
整體向左移一個二進制位,第 5 次按位左移 | 0000 | 0010 | 1001 | 001 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0000 | 0010 | 1100 | 001 |
整體向左移一個二進制位,第 6 次按位左移 | 0000 | 0101 | 1000 | 01 |
各個 BCD 碼分別和 5 比較, 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101、100 位加 3 | 0000 | 1000 | 1011 | 01 |
整體向左移一個二進制位,第 7 次按位左移 | 0001 | 0001 | 0110 | 1 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0001 | 0001 | 1001 | 1 |
整體向左移一個二進制位,第 8 次按位左移 | 0010 | 0011 | 0011 | |
輸出結果 | ( 2 ) 10 (2)_{10} (2)10? | ( 3 ) 10 (3)_{10} (3)10? | ( 3 ) 10 (3)_{10} (3)10? |
首先是完成補 0 操作得到的第一組數據,它的 BCD 碼最高位表示的是 0、次高位也是 0、最低位也是 0,判斷后它們都小于 5,保持原值不變;
經過判斷運算之后,整體向左移一位得到了第二組數據;
完成移位操作之后繼續進行判斷運算,最高位同樣是 0、次高位也是 0、最低位是 1,都小于 5,繼續保持原值不變;
然后完成判斷運算,第 2 次進行按位左移操作就得到了第四組數據;
繼續判斷運算,最高位同樣是 0、次高位也是 0、最低位是 3,3 個 8421 碼都小于 5,保持原值不變;
然后第 3 次按位左移就得到第六組數據;
判斷運算,最高位同樣是 0、次高位也是 0、最低位是 7,最低位的 7 大于 5,最低位加上一個 3 等于 1010,最高、次高位保持原值不變,得到一組新的數據;
然后第 4 次進行按位左移就得到了第八組數據,最高位是 0、次高位是 1、最低位是 4,都小于 5,繼續保持原值不變;
第 5 次進行移位得到第十組數據,最高位是 0、次高位是 2、最低位是 9,最低位的 9 大于 5,最低位加上一個 3 等于 1100,最高、次高位保持原值不變,得到一組新的數據;
……
……
最后完成了 8 次的判斷運算和移位操作得到十進制數 ( 233 ) 10 (233)_{10} (233)10? 它的 BCD 碼編碼形式,將每個 BCD 碼換算成十進制數就是 ( 233 ) 10 (233)_{10} (233)10?。這里需要注意:移位次數 8 等于待轉換數據的二進制位數即位寬, ( 1110 _ 1001 ) 2 (1110\_1001)_{2} (1110_1001)2? 的位寬是 8,上例就移位了 8 次;如果說輸入的是 ( 999 _ 999 ) 10 (999\_999)_{10} (999_999)10? 對應的二進制碼 ( 1111 _ 0100 _ 0010 _ 0011 _ 1111 ) 2 (1111\_0100\_0010\_0011\_1111)_{2} (1111_0100_0010_0011_1111)2?,它的位寬是 20 就需要移位 20 次。
通過上述的方式,我們就可以將十進制數的二進制碼轉換為十進制數的 BCD 碼。
以上部分就是本小節將會涉及到的 BCD 碼的相關知識的一個講解。
2.2.3.2 框圖繪制
了解了 BCD 碼的相關知識之后,我們需要建立一個新的子功能模塊 bcd_8421
用來實現 BCD 碼的轉碼
輸入信號
- sys_clk:系統時鐘信號
- sys_rst_n:系統復位信號
- data[19:0]:十進制數的二進制編碼
實驗目標當中六位八段數碼管顯示的最大值是 ( 999 _ 999 ) 10 (999\_999)_{10} (999_999)10?,六位十進制數就需要使用 6 個 BCD 碼表示,在這里我們將每個 BCD 碼都單獨進行輸出,輸出信號就應該是六路 BCD 碼
輸出信號
- unit[3:0]:個位 BCD 碼
- ten[3:0]:十位 BCD 碼
- hun[3:0]:百位 BCD 碼。hun 是 hundred 的簡寫
- tho[3:0]:千位 BCD 碼。tho 是 thousand 的簡寫
- t_tho[3:0]:萬位 BCD 碼。t_tho 是 ten thousand 的簡寫
- h_tho[3:0]:十萬位 BCD 碼。h_tho 是 one hundred thousand 的簡寫
由于加入了新的子功能模塊 bcd_8421
,包含 bcd_8421
的模塊也要做一下修改
修改后的 segment_dynamic
模塊框圖
修改后的 segment_595_dynamic
模塊框圖
修改后的 top_segment_595
模塊框圖
2.2.3.3 波形繪制
接下來就開始 BCD 轉碼模塊 bcd_8421
的波形圖的繪制
首先是輸入信號時鐘信號 sys_clk
、復位信號 sys_rst_n
以及二進制編碼形式的十進制數 data[19:0]
,輸入的十進制數據我們定義成一個固定值 20'd114514
,方便后面波形的繪制。輸入信號的波形繪制完成之后,我們需要分析一下接下來該如何繪制。我們回看轉碼過程表格
執行的操作 | 1 0 2 10^{2} 102 | 1 0 1 10^{1} 101 | 1 0 0 10^{0} 100 | 待轉換數據(二進制數) |
---|---|---|---|---|
補 12 12 12 個 0 | 0000 | 0000 | 0000 | 1110_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0000 | 1110_1001 |
整體向左移一個二進制位,第 1 次按位左移 | 0000 | 0000 | 0001 | 110_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0001 | 110_1001 |
整體向左移一個二進制位,第 2 次按位左移 | 0000 | 0000 | 0011 | 10_1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0000 | 0011 | 10_1001 |
整體向左移一個二進制位,第 3 次按位左移 | 0000 | 0000 | 0111 | 0_1001 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0000 | 0000 | 1010 | 0_1001 |
整體向左移一個二進制位,第 4 次按位左移 | 0000 | 0001 | 0100 | 1001 |
各個 BCD 碼分別和 5 比較,保持原值不變 | 0000 | 0001 | 0100 | 1001 |
整體向左移一個二進制位,第 5 次按位左移 | 0000 | 0010 | 1001 | 001 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0000 | 0010 | 1100 | 001 |
整體向左移一個二進制位,第 6 次按位左移 | 0000 | 0101 | 1000 | 01 |
各個 BCD 碼分別和 5 比較, 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101、100 位加 3 | 0000 | 1000 | 1011 | 01 |
整體向左移一個二進制位,第 7 次按位左移 | 0001 | 0001 | 0110 | 1 |
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3 | 0001 | 0001 | 1001 | 1 |
整體向左移一個二進制位,第 8 次按位左移 | 0010 | 0011 | 0011 | |
輸出結果 | ( 2 ) 10 (2)_{10} (2)10? | ( 3 ) 10 (3)_{10} (3)10? | ( 3 ) 10 (3)_{10} (3)10? |
這張表格展示了二進制向 BCD 編碼轉換的一個完整過程,可以幫助我們進行波形圖的繪制。首先我們知道,二進制向 BCD 碼轉碼需要通過判斷運算加移位運算實現,并且運算次數規定為和輸入的二進制碼的位寬相同,比如說,我們輸入的十進制數是 ( 114514 ) 10 (114514)_{10} (114514)10?,二進制編碼形式是 20'd114514
20 位寬就需要進行 20 次的判斷運算加移位操作才能夠實現二進制向 BCD 碼的一個轉換,所以說我們需要一個移位計數器 cnt_shift[4:0]
對判斷運算和移位操作的次數進行計數;其次在進行判斷計算和移位操作的過程中產生的中間數據(24bitBCD碼加上20bit二進制碼)需要使用一個變量 data_shift[43:0]
儲存。需要注意的是,二進制到 BCD 碼轉換的過程中,判斷運算與移位操作各自在一個時鐘周期內完成,而且這兩個過程有先后順序:判斷運算在前,移位操作在后;所以聲明移位標志信號 shift_flag
區分這兩個操作步驟;并且移位次數的更新都是在判斷計算和移位操作之后完成的,所以移位標志信號 shift_flag
也可以作為移位次數計數器 cnt_shift[4:0]
計數的一個條件。
完成了變量的聲明之后,接下來開始變量信號波形的繪制。首先是移位計數器 cnt_shift[4:0]
,當復位信號 sys_rst_n
有效時給它賦一個初值 0,移位計數器從 0 開始計數,因為移位次數是 20 次,cnt_shift[4:0]
計數 20 次時最大值應該是 19,但是我們這里還需要增加兩個計數狀態,cnt_shift[4:0]
一共計數 22 次,為什么呢?根據上面的表格可知,移位計數器 cnt_shift[4:0]
為 0 時,實際上是對應二進制的補零操作,當移位計數器 cnt_shift[4:0]
計數范圍為 1~20 時才是進行判斷運算和移位操作的時候,當移位計數器 cnt_shift[4:0]
計數到最大值 21 時對應的操作是輸出結果,提取轉換完成的 BCD 碼。移位計數器 cnt_shift[4:0]
計數范圍確定之后,我們要考慮一下移位計數器 cnt_shift[4:0]
的計數條件是什么?我們前面已經提到,可以使用移位標志信號 shift_flag
作為移位計數器 cnt_shift[4:0]
的計數條件,所以說我們首先繪制移位標志信號 shift_flag
的波形。
當復位信號有效時給移位標志信號 shift_flag
賦一個初值 0,移位標志信號 shift_flag
的作用是區分判斷運算與移位操作,這兩個階段各自在一個時鐘周期內完成,那么當復位信號無效時在每個系統時鐘周期下對移位標志信號 shift_flag
進行不斷的取反。當移位標志信號 shift_flag
為低電平時進行判斷運算,當移位標志信號 shift_flag
為高電平時進行移位操作,移位標志信號 shift_flag
為高電平也作為條件控制移位計數器 cnt_shift[4:0]
進行計數;移位計數器 cnt_shift[4:0]
計數到最大值 21 并且移位標志信號 shift_flag
為高電平,移位計數器 cnt_shift[4:0]
歸零開始下一個周期的計數。當移位標志信號 shift_flag
為高電平時移位計數器 cnt_shift[4:0]
進行加一計數,當移位標志信號 shift_flag
為低電平時移位計數器 cnt_shift[4:0]
保持原來的值不變,因為是時序邏輯,所以說延遲一個時鐘周期;當移位計數器 cnt_shift[4:0]
計數到最大值 21 而且移位標志信號 shift_flag
為高電平,移位計數器 cnt_shift[4:0]
歸零開始下一個周期的計數。
下面開始中間變量移位數據 data_shift[43:0]
的波形繪制。當復位信號有效時給它賦一個初值 0,當復位信號無效時并且移位計數器 cnt_shift[4:0]
為 0 時,將輸入的二進制數 20'd114514
更新到移位數據的 [19:0] 位,就是給 data[19:0]
前面補 0:{24'b0,data[19:0]}
;當移位計數器 cnt_shift[4:0]
的計數范圍為 1~20 時,如果移位標志信號 shift_flag
為低電平就進行判斷運算的操作,如果說移位標志信號 shift_flag
為高電平就進行移位的操作;當移位計數器 cnt_shift[4:0]
的計數值為 21 時,移位數據 data_shift[43:0]
保持原來的值不變,移位標志信號 shift_flag
為高電平移位計數器 cnt_shift[4:0]
歸零,移位數據 data_shift[43:0]
也要進行重新的更新,開始下一次的轉碼。
接下來開始輸出信號波形的繪制。當復位信號有效時給輸出信號賦一個初值 0;當移位計數器 cnt_shift[4:0]
的計數值為 21 時就提取輸出信號對應的 BCD 碼,輸出信號個位 unit[3:0]
所對應的 BCD 碼就應該是 data_shift[23:20]
,輸出信號十位 ten[3:0]
對應的 BCD 碼就應該是 data_shift[27:24]
,百位 hun[3:0]
對應的是 data_shift[31:28]
,千位 tho[3:0]
對應的是 data_shift[35:32]
,萬位 t_tho[3:0]
對應的應該是 data_shift[39:36]
,十萬位 h_tho[3:0]
對應的就應該是 data_shift[43:40]
。
2.2.3.4 代碼編寫
完成了 BCD 編碼模塊 的波形圖繪制后,接下來可以根據這個波形圖進行代碼的編寫,保存為 bcd_8421.v
//模塊開始 模塊名稱 端口列表
module bcd_8421
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效input wire [19:0] data , //待轉碼十進制數的二進制編碼output reg [3:0] unit , //轉碼結果的個位 BCD 碼output reg [3:0] ten , //轉碼結果的十位 BCD 碼output reg [3:0] hun , //轉碼結果的百位 BCD 碼output reg [3:0] tho , //轉碼結果的千位 BCD 碼output reg [3:0] t_tho , //轉碼結果的萬位 BCD 碼output reg [3:0] h_tho //轉碼結果的十萬位 BCD 碼
);//變量聲明
reg shift_flag ;
reg [4:0] cnt_shift ;
reg [43:0] data_shift ;//變量賦值
always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)shift_flag <= 1'b0;elseshift_flag <= ~shift_flag;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_shift <= 5'd0;else if ((cnt_shift==5'd21) && (shift_flag==1'b1))cnt_shift <= 5'd0;else if (shift_flag == 1'b1)cnt_shift <= cnt_shift + 5'd1;elsecnt_shift <= cnt_shift;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)data_shift <= 44'd0;else if (cnt_shift==5'd0)data_shift <= {24'b0,data};else if ((shift_flag==1'b0) && ((cnt_shift>=5'd1)&&(cnt_shift<=5'd20)))begindata_shift[23:20] <= (data_shift[23:20]>=4'd5)? (data_shift[23:20]+4'd3): (data_shift[23:20]);data_shift[27:24] <= (data_shift[27:24]>=4'd5)? (data_shift[27:24]+4'd3): (data_shift[27:24]);data_shift[31:28] <= (data_shift[31:28]>=4'd5)? (data_shift[31:28]+4'd3): (data_shift[31:28]);data_shift[35:32] <= (data_shift[35:32]>=4'd5)? (data_shift[35:32]+4'd3): (data_shift[35:32]);data_shift[39:36] <= (data_shift[39:36]>=4'd5)? (data_shift[39:36]+4'd3): (data_shift[39:36]);data_shift[43:40] <= (data_shift[43:40]>=4'd5)? (data_shift[43:40]+4'd3): (data_shift[43:40]);endelse if ((shift_flag==1'b1) && ((cnt_shift>=5'd1)&&(cnt_shift<=5'd20)))data_shift <= data_shift<<1;elsedata_shift <= data_shift;//輸出信號的賦值
always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)beginunit <= 4'b0;ten <= 4'b0;hun <= 4'b0;tho <= 4'b0;t_tho <= 4'b0;h_tho <= 4'b0;endelse if (cnt_shift==5'd21)beginunit <= data_shift[23:20];ten <= data_shift[27:24];hun <= data_shift[31:28];tho <= data_shift[35:32];t_tho <= data_shift[39:36];h_tho <= data_shift[43:40];end//模塊結束
endmodule
2.2.3.5 代碼編譯
BCD 編碼模塊的代碼編寫完成后,回到實驗工程添加 bcd_8421.v
模塊,然后進行全編譯查找語法錯誤;編譯完成點擊 OK
2.2.3.6 邏輯仿真
2.2.3.6.1 仿真代碼編寫
下面開始編寫我們的仿真代碼,保存為 tb_bcd_8421.v
// 時間參數
`timescale 1ns/1ns
`include "../rtl/bcd_8421.v"module tb_bcd_8421();reg sys_clk ;
reg sys_rst_n ;
reg [19:0] data ;wire [3:0] unit ;
wire [3:0] ten ;
wire [3:0] hun ;
wire [3:0] tho ;
wire [3:0] t_tho;
wire [3:0] h_tho;initialbeginsys_clk = 1'b1;sys_rst_n <= 1'b0;#35sys_rst_n <= 1'b1;data <= 20'd114_514;#1000data <= 20'd358_946;#2000data <= 20'd716_230;#3000data <= 20'd999_999;endalways #10 sys_clk = ~sys_clk;bcd_8421 bcd_8421_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待轉碼十進制數的二進制編碼.unit (unit ), //轉碼結果的個位 BCD 碼.ten (ten ), //轉碼結果的十位 BCD 碼.hun (hun ), //轉碼結果的百位 BCD 碼.tho (tho ), //轉碼結果的千位 BCD 碼.t_tho (t_tho), //轉碼結果的萬位 BCD 碼.h_tho (h_tho) //轉碼結果的十萬位 BCD 碼
);endmodule
2.2.3.6.2 仿真代碼編譯
仿真模塊編寫完成后,回到實驗工程,然后添加 tb_bcd_8421.v
模塊;仿真模塊添加完成之后進行全編譯,編譯通過點擊 OK
然后進行仿真設置
2.2.3.6.3 波形仿真
下面開始仿真,結合我們繪制的波形圖查看一下仿真波形圖。
首先是移位標志信號 shift_flag
移位標志信號 shift_flag
的初值是低電平,每個系統時鐘周期進行取反。
接下來看一下移位計數器 cnt_shift
移位數據 data_shift
的波形就不再進行查看了,因為數據量太大了。
接下來看一下輸出信號
那么這樣我們就實現了 BCD 編碼模塊的功能,下面就可以實現動態顯示驅動模塊 segment_dynamic
2.2.4 動態顯示驅動模塊 segment_dynamic
動態顯示驅動模塊的模塊框圖之前已經繪制完成了
2.2.4.1 波形繪制
接下來就繪制 segment_dynamic
模塊的波形圖
輸入信號有六路:時鐘信號 sys_clk
、復位信號 sys_rst_n
、待顯示數據 data[19:0]
、小數點位 point[5:0]
、符號位 sign
和使能信號 seg_en
。待顯示數據 data[19:0]
我們賦給它一個固定的值 20'd9876
,這個數據是隨機定義的,大家可以自己設置。小數點位 point[5:0]
給它賦一個固定值:6'b000_010
,表示我們選中倒數第二個小數點位;通過 data[19:0]
、point[5:0]
這兩路信號可以得到我們想要顯示的數據應該是 987.6 987.6 987.6。
符號位 sign
首先給它一個初值 0,然后當復位信號無效時將它拉高并且讓它一直保持高電平,這就表示我們想要顯示的是負數;通過 data[19:0]
、point[5:0]
、sign
這三路信號可以知道我們想要顯示的數據應該是 ? 987.6 -987.6 ?987.6。
使能信號 seg_en
在復位信號有效期間賦給它一個初值 0,當復位信號無效時讓它一直保持高電平,這就表示數碼管一直處于顯示狀態。
六路輸入信號的波形繪制完成,接下來應該怎么繪制呢?
首先我們要聲明六路信號 unit[3:0]
、ten[3:0]
、hun[3:0]
、tho[3:0]
、t_tho[3:0]
、h_tho[3:0]
將 BCD 編碼模塊 bcd_8421
輸出的 BCD 編碼引出來。
首先賦給它們一個初值 0,輸入的十進制數據 data[19:0]
等于 20'd9876
,(最低位)個位 unit[3:0]
應該是數字 6,然后十位 ten[3:0]
應該是 7,然后百位 hun[3:0]
應該是 8,千位 tho[3:0]
就應該是 9,萬位 t_tho[3:0]
和十萬位 h_tho[3:0]
就是 0。這兒有一點要注意:BCD 編碼模塊完成二進制到 BCD 的轉碼需要一段時間(22 個系統時鐘周期),在波形圖上使用省略號形狀表示一段時間的延時。
六路 BCD 編碼信號已經全部引出來后,我們再聲明一個變量 data_reg[23:0]
對待顯示數據 data[19:0]
進行寄存(只是對符號位和數據位進行寄存,不包含小數點位)。首先賦給它一個初值 0,然后使用數據位和符號位給它賦值,數據位就是 unit[3:0]
、ten[3:0]
、hun[3:0]
、tho[3:0]
、t_tho[3:0]
、h_tho[3:0]
這六路 BCD 編碼信號,首先 data_reg[23:0]
最低 4 位是個位 unit[3:0]
就是 6、然后是十位 7、然后是百位 8、然后是千位 9,因為符號位 sign
為高電平需要顯示負號,data_reg[23:0]
最高 4 位不顯示用 X 來表示。
接下來還要聲明一個計數器變量 cnt_1ms[15:0]
,為什么要聲明這個計數器變量呢?因為之前已經約定好了,在數碼管的動態掃描顯示過程中,每個數碼位只顯示 1ms 的時長;對 1ms 進行計數就需要計數器,所以說我們要聲明這一個計數器變量 cnt_1ms[15:0]
。
首先賦給計數器 cnt_1ms[15:0]
一個初值 0, T c n t _ 1 m s = 1 ms = 1 × 1 0 6 ns, T s y s _ c l k = 20 ns \text{T}_{cnt\_1ms}=1\text{ms}=1\times10^6\text{ns}\text{,}\text{T}_{sys\_clk}=20\text{ns} Tcnt_1ms?=1ms=1×106ns,Tsys_clk?=20ns 這就表示要完成 1 ms 1\text{ms} 1ms 的計數,要計數 T c n t _ 1 m s / T s y s _ c l k = ( 1 × 1 0 6 ) ns / 20 ns = 5 × 1 0 4 \text{T}_{cnt\_1ms}/\text{T}_{sys\_clk}=(1\times10^6)\text{ns}/20\text{ns}=5\times10^4 Tcnt_1ms?/Tsys_clk?=(1×106)ns/20ns=5×104 個系統時鐘周期,因為計數器從 0 開始計數,這就表示計數的最大值應該是 5 × 1 0 4 ? 1 = 49999 5\times10^4-1=49999 5×104?1=49999,聲明計數器變量時為什么它的位寬是 16 呢?我們來算一下:
可以看到, ( 49999 ) 10 (49999)_{10} (49999)10? 的二進制形式為 1100 _ 0011 _ 0100 _ 1111 1100\_0011\_0100\_1111 1100_0011_0100_1111 總共 16 位,所以它的位寬應該是 16 位寬 [15:0]
。當 1ms 計數器計數到最大值歸零,開始下一個周期的計數。
1ms 計數器的波形繪制完成之后,我們還要聲明一個變量 flag_1ms
就是 1ms 標志信號,為什么要聲明一個標志信號呢?這個標志信號可以作為一個條件,控制數碼位的選擇
首先賦給 flag_1ms
一個初值 0,當計數器 cnt_1ms[15:0]
計數到最大值減一的時候,讓它保持一個系統時鐘周期的高脈沖,其他時刻讓它保持低電平。
1ms 標志信號的波形繪制完成后,我們還要聲明一個計數器變量 cnt_sel[2:0]
,為什么還要聲明一個計數器變量呢?數碼管的動態顯示使用的是動態掃描的方式,只有每個數碼位都完成 1ms 的顯示,才是一個完整的掃描周期,為了對這個掃描周期進行計數,所以說我們要聲明一個計數器變量;我們的開發板使用的是六位八段數碼管,就表示一個掃描周期就是六個數碼位分別進行顯示,掃描計數器變量它的計數最大值就應該是 6,因為是從 0 開始計數 0~6 計數了七次(加消隱)。
首先賦給 cnt_sel[2:0]
一個初值 0,當 flag_1ms
信號為高電平時就表示有一個數碼位完成了 1ms 的顯示,掃描計數器就加一;當掃描計數器計數到最大值 6 而且 flag_1ms
信號為高電平時將 cnt_sel[2:0]
歸零,開始下一個周期的計數。
接下來繪制輸出位選信號 sel_reg[5:0]
的波形。首先賦給它一個初值 6'b111_111
,這表示選中所有數碼位進行消隱,當掃描計數器 cnt_sel[2:0]
為 0、標志信號 flag_1ms
為高電平時給它賦值 6'b000_001
表示選中第一個數碼位,就是個位;當掃描計數器 cnt_sel[2:0]
為 1、標志信號 flag_1ms
為高電平時將位選信號 sel_reg[5:0]
向左移一位就得到了 6'b000_010
,這表示選中了第二個數碼位;當掃描計數器 cnt_sel[2:0]
為 2、標志信號 flag_1ms
為高電平時繼續左移,位選信號 sel_reg[5:0]
的值就應該是 6'b000_100
,表示選中了第三個數碼位;……;當掃描計數器 cnt_sel[2:0]
的計數值再次為 1、標志信號 flag_1ms
為高電平時就對位選信號 sel_reg[5:0]
重新賦值,為什么要重新賦值呢?因為此時再進行移位的話 sel_reg[5:0]
是消隱時的 6'b111_111
表示所有的數碼位都選中,所以說在這兒要給它賦一個新的值 6'b000_001
這就表示選中第一個數碼位,然后繼續進行移位操作。
在開始給段選信號 seg[7:0]
賦值之前,我們還需要聲明兩個變量 data_disp[3:0]
、dot_disp
data_disp[3:0]
這個變量表示即將要顯示的數據。首先給它賦一個初值 0,當選中第一個數碼位的時候讓它進行顯示,顯示的內容應該是 unit[3:0]
對應的數字 6;當選中第二個數碼位,讓它顯示 7;當選中第三個數碼位讓它顯示的應該是 8;然后是第四個數碼位就是 9;當顯示第五個數碼位要顯示負號,負號在這兒用 10 表示,你也可以選擇除了 0~9 之外的其他數值來表示;最高位是不顯示的,用 11 表示。然后完成了一個周期的掃描,消隱后又回到了最低位讓它顯示數字 6;第二位讓它顯示 7。
dot_disp
表示即將顯示的小數點位,它的初值應該是高電平(共陽極數碼管),表示小數點位不顯示,什么時候顯示小數點位呢?由 point[5:0]
的值 6'b000_010
可知在第二位的時候顯示小數點位,所以說應該是在 cnt_sel[2:0]
等于 2 期間讓它保持低電平,表示在第二個數碼位顯示小數點;然后其他時刻保持高電平,表示不顯示小數點位。
輸出的段選信號 seg[7:0]
因為我們使用的是共陽數碼管,所以說初值賦給它 8'hFF
就表示不點亮進行消隱;其他時刻,段選信號 seg[7:0]
要根據待顯示數據 data[19:0]
進行一個編碼,因為這兒直接輸出的數據是不能夠用于顯示的,必須進行編碼;因為是使用的時序邏輯,所以說應該延遲一個時鐘周期;第一個數碼位顯示的是數字 6,我們來看一下數字 6 的編碼
待顯示內容 | 段碼(二進制格式) | 段碼(十六進制格式) | |||||||
---|---|---|---|---|---|---|---|---|---|
dp | g | f | e | d | c | b | a | ||
0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 8’hC0 |
1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 8’hF9 |
2 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 8’hA4 |
3 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 8’hB0 |
4 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 8’h99 |
5 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 8’h92 |
6 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 8’h82 |
7 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 8’hF8 |
8 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8’h80 |
9 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 8’h90 |
A | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 8’h88 |
b | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 8’h83 |
C | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 8’hC6 |
d | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 8’hA1 |
E | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 8’h86 |
F | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 8’h8E |
數字 6 所對應的十六進制格式是 8'h82
;然后是第二個數碼位,第二個數碼位對應的數字是 7,我們看一下 7,字符 7 對應的應該是 8'hF8
,但是有一點要注意:小數點位要點亮,所以說 dp 應該是 0,這樣看就應該是 8'h78
而不是 8'hF8
;然后是數字 8,數字 8 它的段碼是 8'h80
;然后是數字 9,數字 9 的段碼是 8'h90
;然后是用 10 表示的負號,負號應該怎么顯示呢?負號只需要點亮段選信號 g 就可以了,也就是說其他段都為高電平, g 段為低電平,這樣就應該是 8'hBF
;到了 11,11 就表示這個數碼位不進行顯示也就是說不點亮,就是 8'hFF
。經過一個周期的掃描,完成了數據的顯示;接著又進行消隱,然后回到了第一個數碼位就是 8'h82
。
段選信號的波形繪制完成。這樣我們就完成了動態顯示驅動模塊 segment_dynamic
整個模塊的波形圖的繪制。
2.2.4.2 代碼編寫
接下來就開始代碼的編寫
//模塊開始 模塊名稱 端口列表
module segment_dynamic
#(parameter CNT_MAX = 16'd49_999//1ms 計數器計數最大值
)
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效input wire [19:0] data , //待轉碼十進制數的二進制編碼input wire [5:0] point , //小數點input wire sign , //符號(負號是否顯示)input wire seg_en , //數碼管顯示使能output reg [7:0] seg , //段選output reg [5:0] sel //位選
);wire [3:0] unit ; //轉碼結果的個位 BCD 碼
wire [3:0] ten ; //轉碼結果的十位 BCD 碼
wire [3:0] hun ; //轉碼結果的百位 BCD 碼
wire [3:0] tho ; //轉碼結果的千位 BCD 碼
wire [3:0] t_tho; //轉碼結果的萬位 BCD 碼
wire [3:0] h_tho; //轉碼結果的十萬位 BCD 碼reg [23:0] data_reg; //寄存待顯示數據的 BCD 碼
reg [15:0] cnt_1ms; //1ms 計數器
reg flag_1ms; //1ms 計數時間到達
reg [2:0] cnt_sel; //位選計數
reg [5:0] sel_reg; //寄存位選值
reg [3:0] data_disp; //將要顯示的 BCD 碼
reg dot_disp; //將要顯示的小數點always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)data_reg <= 24'b0;//如果待顯示十進制數的十萬位不等于 0 或者需要顯示小數點,十進制數則顯示在 6 個數碼位上else if ((h_tho!=4'b0000) || (point[5]==1'b1))data_reg <= {h_tho, t_tho, tho, hun, ten, unit};//如果待顯示十進制數是負數且它的萬位不等于 0 或者需要顯示小數點,則十進制數的絕對值顯示在 5 個數碼位上//例如:待顯示的十進制數為 -11451,數碼管應該顯示 -11451else if ((sign==1'b1) && ((t_tho!=4'b0000)||(point[4]==1'b1)))data_reg <= {4'd10, t_tho, tho, hun, ten, unit};//4'd10我們定義為顯示負號//如果待顯示十進制數是正數且它的萬位不等于 0 或者需要顯示小數點,則十進制數顯示在 5 個數碼位上else if ((sign==1'b0) && ((t_tho!=4'b0000)||(point[4]==1'b1)))data_reg <= {4'd11, t_tho, tho, hun, ten, unit};//4'd11我們定義為不顯示//如果待顯示十進制數是負數且它的千位不等于 0 或者需要顯示小數點,則十進制數的絕對值顯示在 4 個數碼位上else if ((sign==1'b1) && ((tho!=4'b0000)||(point[3]==1'b1)))data_reg <= {4'd11, 4'd10, tho, hun, ten, unit};//如果待顯示十進制數是正數且它的千位不等于 0 或者需要顯示小數點,則十進制數顯示在 4 個數碼位上else if ((sign==1'b0) && ((tho!=4'b0000)||(point[3]==1'b1)))data_reg <= {4'd11, 4'd11, tho, hun, ten, unit};//如果待顯示十進制數是負數且它的百位不等于 0 或者需要顯示小數點,則十進制數的絕對值顯示在 3 個數碼位上else if ((sign==1'b1) && ((hun!=4'b0000)||(point[2]==1'b1)))data_reg <= {4'd11, 4'd11, 4'd10, hun, ten, unit};//如果待顯示十進制數是正數且它的百位不等于 0 或者需要顯示小數點,則十進制數顯示在 3 個數碼位上else if ((sign==1'b0) && ((hun!=4'b0000)||(point[2]==1'b1)))data_reg <= {4'd11, 4'd11, 4'd11, hun, ten, unit};//如果待顯示十進制數是負數且它的十位不等于 0 或者需要顯示小數點,則十進制數的絕對值顯示在 2 個數碼位上else if ((sign==1'b1) && ((ten!=4'b0000)||(point[1]==1'b1)))data_reg <= {4'd11, 4'd11, 4'd11, 4'd10, ten, unit};//如果待顯示十進制數是正數且它的十位不等于 0 或者需要顯示小數點,則十進制數顯示在 2 個數碼位上else if ((sign==1'b0) && ((ten!=4'b0000)||(point[1]==1'b1)))data_reg <= {4'd11, 4'd11, 4'd11, 4'd11, ten, unit};//如果待顯示十進制數是負數且它的個位不等于 0 或者需要顯示小數點,則十進制數的絕對值顯示在 1 個數碼位上else if ((sign==1'b1) && ((unit!=4'b0000)||(point[0]==1'b1)))data_reg <= {4'd11, 4'd11, 4'd11, 4'd11, 4'd10, unit};//其它情況就只顯示在 1 個數碼位上elsedata_reg <= {4'd11, 4'd11, 4'd11, 4'd11, 4'd11, unit};always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_1ms <= 16'd0;else if (cnt_1ms == CNT_MAX)cnt_1ms <= 16'd0;elsecnt_1ms <= cnt_1ms + 16'd1;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)flag_1ms <= 1'b0;else if (flag_1ms == (CNT_MAX-1))flag_1ms <= 1'b1;elseflag_1ms <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)cnt_sel <= 3'd0;else if ((flag_1ms==1'b1) && (cnt_sel==3'd6))cnt_sel <= 3'd0;else if (flag_1ms == 1'b1)cnt_sel <= cnt_sel + 3'd1;elsecnt_sel <= cnt_sel;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)sel_reg <= 6'b111_111;//消隱else if ((flag_1ms==1'b1) && (cnt_sel==3'd6))sel_reg <= 6'b111_111;//消隱else if ((flag_1ms==1'b1) && (cnt_sel==3'd0))sel_reg <= 6'b000_001;else if (flag_1ms==1'b1)sel_reg <= sel_reg << 1'b1;elsesel_reg <= sel_reg;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)data_disp <= 4'b0000;else if ((seg_en==1'b1) && (flag_1ms==1'b1)) case (cnt_sel)3'd0: data_disp <= 4'b1111;//消隱,不顯示(共陽數碼管,高電平熄滅)3'd1: data_disp <= data_reg[3:0]; //個3'd2: data_disp <= data_reg[7:4]; //十3'd3: data_disp <= data_reg[11:8]; //百3'd4: data_disp <= data_reg[15:12]; //千3'd5: data_disp <= data_reg[19:16]; //萬3'd6: data_disp <= data_reg[23:20]; //十萬default: data_disp <= 4'b0000;//顯示數字0endcaseelsedata_disp <= data_disp;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)dot_disp <= 1'b1;//共陽數碼管,高電平小數點段dp熄滅else if (flag_1ms==1'b1)dot_disp <= point[cnt_sel-1]==1'b1? 1'b0: 1'b1;elsedot_disp <= dot_disp;always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)seg <= 8'hFF;else case (data_disp)4'd0 : seg <= {dot_disp,7'b100_0000}; //數字04'd1 : seg <= {dot_disp,7'b111_1001}; //數字14'd2 : seg <= {dot_disp,7'b010_0100}; //數字24'd3 : seg <= {dot_disp,7'b011_0000}; //數字34'd4 : seg <= {dot_disp,7'b001_1001}; //數字44'd5 : seg <= {dot_disp,7'b001_0010}; //數字54'd6 : seg <= {dot_disp,7'b000_0010}; //數字64'd7 : seg <= {dot_disp,7'b111_1000}; //數字74'd8 : seg <= {dot_disp,7'b000_0000}; //數字84'd9 : seg <= {dot_disp,7'b001_0000}; //數字94'd10 : seg <= 8'b1011_1111 ; //負號"-"4'd11 : seg <= 8'b1111_1111 ; //不顯示default:seg <= 8'b1100_0000; //數字0,不帶小數點endcase//同步 sel_reg 和 seg 的時鐘
always@(posedge sys_clk or negedge sys_rst_n)if (sys_rst_n == 1'b0)sel <= 6'b111_111;elsesel <= sel_reg;bcd_8421 bcd_8421_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待轉碼十進制數的二進制編碼.unit (unit ), //轉碼結果的個位 BCD 碼.ten (ten ), //轉碼結果的十位 BCD 碼.hun (hun ), //轉碼結果的百位 BCD 碼.tho (tho ), //轉碼結果的千位 BCD 碼.t_tho (t_tho), //轉碼結果的萬位 BCD 碼.h_tho (h_tho) //轉碼結果的十萬位 BCD 碼
);//模塊結束
endmodule
那么這樣我們就完成了動態顯示驅動模塊 segment_dynamic
的代碼編寫,保存為 segment_dynamic.v
2.2.4.3 代碼編譯
回到實驗工程添加 segment_dynamic.v
模塊;然后進行全編譯,編譯出錯點擊 OK
查看一下報錯信息
發現問題代碼在 129 行附近,查看修改后保存
回到 Quartus II 再次編譯,依然報錯
將 tb_bcd_8421.v
文件的第 3 行:tb_bcd_8421.v
刪除,再次編譯工程,編譯通過點擊 OK
2.2.4.4 邏輯仿真
2.2.4.4.1 仿真代碼編寫
下面我們開始仿真代碼的編寫
`timescale 1ns/1ns //時間參數//模塊開始 模塊名稱 端口列表
module tb_segment_dynamic();reg sys_clk ;
reg sys_rst_n;
reg [19:0] data ;
reg [5:0] point ;
reg sign ;
reg seg_en ;//聲明變量,將兩路輸出信號引出來
wire [7:0] seg;
wire [5:0] sel;//對聲明的變量進行初始化
initialbeginsys_clk = 1'b1;sys_rst_n <= 1'b0;data <= 20'd0;point <= 6'b000_000;sign <= 1'b0;seg_en <= 1'b0;#35sys_rst_n <= 1'b1;data <= 20'd9876;point <= 6'b000_010;//選中第二個數碼位讓它顯示小數點sign <= 1'b1;//負號seg_en <= 1'b1;//數碼管進行顯示endalways #10 sys_clk = ~sys_clk; //時鐘信號segment_dynamic segment_dynamic_inst
#(.CNT_MAX (16'd9)//1ms 計數器計數最大值
)
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待轉碼十進制數的二進制編碼.point (point ), //小數點.sign (sign ), //符號(負號是否顯示).seg_en (seg_en ), //數碼管顯示使能.seg (seg ), //段選.sel (sel ) //位選
);//模塊結束
endmodule
仿真代碼編寫完成后保存為 tb_segment_dynamic.v
2.2.4.4.2 仿真代碼編譯
回到實驗工程添加 tb_segment_dynamic.v
文件;然后進行全編譯,編譯報錯點擊 OK
查看錯誤信息:
Error (10170): Verilog HDL syntax error at tb_segment_dynamic.v(37) near text “#”; expecting “;”
提示仿真代碼第 37 行附近缺少分號,查看代碼發現實例化模塊的名稱位置放錯了,修改保存
再次進行全編譯,編譯通過點擊 OK
下面進行仿真設置
2.2.4.4.3 波形仿真
接下來進行仿真,時間參數設置為 10us,運行一次,全局視圖
我們發現 flag_1ms 的波形沒有變化,回到 segment_dynamic.v
文件中查看,發現給 flag_1ms 賦值高電平時的條件編寫錯誤,修改完保存
回到我們的仿真界面,然后全選、刪除所有波形,回到 Library 窗口,找到修改的代碼對它進行重編譯
然后回到 sim 窗口重新添加波形,然后回到波形界面,全選、分組;然后點擊 Restart,重新運行 10us 運行一次
再次回到 segment_dynamic.v
文件進行排錯,發現當 cnt_sel
為 0 時,cnt_sel-3'd1
作為 point
的索引值出現了負數,修改并保存
再次仿真
接下來參照繪制的波形圖查看一下仿真波形
首先是從 bcd_8421
模塊引出的 BCD 碼
他們的初值是 0,沒有問題;然后個位是 6 沒有問題,十位是 7,然后是 8、9;最高的兩位始終保持 0,這兒沒有問題。
然后看一下數據寄存
我們繼續往下看
下面看一下 1ms 計數器
那么下面是標志信號
然后是掃描計數器
下面是位選信號的寄存
下面就是顯示數據波形
可以看到 data_disp
和 sel_reg
錯開了一位,回到代碼查看 data_disp
的賦值語句塊,修改(時序邏輯節拍延遲,cnt_sel
自加一的條件中有 flag_1ms==1'b1
部分,然后在這里的條件又有 flag_1ms==1'b1
導致延遲兩個 flag_1ms
的節拍)并保存
再次仿真查看波形
下面是小數點信號
下面就是位選信號和段選信號,先來看一下位選信號
位選信號是在位選寄存信號打一拍得到的,在圖中紅框這個位置確實延遲了一個時鐘周期,然后他們倆的數據也是對應的。
下面看一下段選信號
可以看到,段選信號是有問題的。回到 segment_dynamic.v
查看,修改給 dot_disp
賦值的部分,因為給 seg
賦值時需要使用 dot_disp
替換最高有效位
還有需要修改 segment_dynamic.v
的 119 行,這里我們錯誤的把段碼賦值給 data_disp
了
為了和繪制的波形圖保持一致,還需要修改代碼第 149 行
這時再仿真查看波形,觀察段選信號 seg
當選中第一個數碼位,段碼 82 與繪制波形是對應的,沒有問題;當選中第二個數碼位的時候是 78 沒有問題;第三個 80,第四個 90,第五個 bf 都沒有問題;當選中第六個數碼位,段碼是 ff 與繪制波形圖也是一致的
那么這樣仿真驗證就通過了。
到了這里,我們已經實現了動態顯示驅動模塊它的模塊功能,接下來就可以實現其他模塊的模塊功能。
2.2.5 動態顯示模塊 segment_595_dynamic
動態顯示驅動模塊 segment_dynamic
的功能實現之后,我們就可以通過動態顯示驅動模塊 segment_dynamic
和 HC595 控制模塊 hc595_ctrl
生成動態顯示模塊 segment_595_dynamic
。
2.2.5.1 代碼編寫
首先,復用一下 HC595 控制模塊:將 segment_595_static/rtl/hc595_ctrl.v
復制粘貼到 segment_595_dynamic/rtl/hc595_ctrl.v
;HC595 控制模塊復用完成后,接下來就編寫動態顯示模塊
//模塊開始 模塊名稱 端口列表
module segment_595_dynamic
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效input wire [19:0] data , //待轉碼十進制數的二進制編碼input wire [5:0] point , //小數點input wire sign , //符號(負號是否顯示)input wire seg_en , //數碼管顯示使能output wire ds , //輸出給74HC595的串行數據output wire shcp , //移位寄存器時鐘output wire stcp , //存儲寄存器時鐘output wire oe_n //74HC595的輸出使能,低電平有效
);//聲明兩個變量,將 segment_dynamic 模塊輸出的位選、段選輸入給 hc595_ctrl
wire [5:0] sel;
wire [7:0] seg;segment_dynamic segment_dynamic_inst
#(.CNT_MAX (16'd49_999)//1ms 計數器計數最大值
)
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待轉碼十進制數的二進制編碼.point (point ), //小數點.sign (sign ), //符號(負號是否顯示).seg_en (seg_en ), //數碼管顯示使能.seg (seg ), //段選.sel (sel ) //位選
);hc595_ctrl hc595_ctrl_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.sel (sel ), //六位數碼管位選.seg (seg ), //六位數碼管段選.ds (ds ), //輸出給74HC595的串行數據.shcp (shcp ), //移位寄存器時鐘.stcp (stcp ), //存儲寄存器時鐘.oe_n (oe_n ) //74HC595的輸出使能,低電平有效
);//模塊結束
endmodule
2.2.5.2 代碼編譯
參照著模塊框圖完成動態顯示模塊的代碼編寫后,將它保存為 segment_595_dynamic.v
;然后回到實驗工程添加剛剛編寫的模塊;然后進行編譯,編譯報錯點擊 OK
查看錯誤信息:
Error (10170): Verilog HDL syntax error at segment_595_dynamic.v(22) near text “#”; expecting “;”
鼠標左鍵雙擊該條 error,跳轉到代碼文件中檢查
發現又犯了同樣的錯誤:對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面、對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面、對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面,但愿之后我能記住吧…………
修改完成保存,回到 Quartus II 進行重新編譯,編譯通過點擊 OK
對于動態顯示模塊不再進行單獨的仿真,等到頂層模塊的代碼編寫完成了,再進行整體的仿真。
2.2.6 頂層模塊 top_segment_595
2.2.6.1 代碼編寫
下面就參照頂層模塊的框圖編寫最終的頂層模塊 top_segment_595
//模塊開始 模塊名稱 端口列表
module top_segment_595
(input wire sys_clk , //系統時鐘,50MHzinput wire sys_rst_n , //系統復位,低電平有效output wire ds , //輸出給74HC595的串行數據output wire shcp , //移位寄存器時鐘output wire stcp , //存儲寄存器時鐘output wire oe_n //74HC595的輸出使能,低電平有效
);wire [19:0] data ;
wire [5:0] point ;
wire sign ;
wire seg_en;data_gen
#(.CNT_MAX (23'd4_999_999),//計數 0.1s 計數最大值.DATA_MAX (20'd999_999 ) //待顯示數據最大值
)
data_gen_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待顯示數據.point (point ), //小數點.sign (sign ), //負號.seg_en (seg_en) //數碼管動態顯示模塊工作使能
);segment_595_dynamic segment_595_dynamic
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.data (data ), //待轉碼十進制數的二進制編碼.point (point ), //小數點.sign (sign ), //符號(負號是否顯示).seg_en (seg_en ), //數碼管顯示使能.ds (ds ), //輸出給74HC595的串行數據.shcp (shcp ), //移位寄存器時鐘.stcp (stcp ), //存儲寄存器時鐘.oe_n (oe_n ) //74HC595的輸出使能,低電平有效
);//模塊結束
endmodule
2.2.6.2 代碼編譯
那么這樣,頂層模塊的代碼編寫完成,保存為 top_segment_595.v
;回到實驗工程添加頂層模塊(這兒有一點要注意:要把它置為頂層),然后進行全編譯,報錯點擊 OK
我們來看一下報錯信息
在頂層模塊 top_segment_595.v
中對 hc595_ctrl
模塊進行了實例化,但是我們并沒有將 hc595_ctrl.v
文件添加到 Quartus II 的工程中,所以提示我們實體沒有定義。回到實驗工程,添加 hc595_ctrl.v
文件,然后重新編譯,編譯通過點擊 OK
2.2.6.3 邏輯仿真
2.2.6.3.1 仿真代碼編寫
接下來編寫仿真文件對頂層文件進行一個整體的仿真
//時間參數
`timescale 1ns/1ns//模塊開始 模塊名稱 端口列表
module tb_top_segment_595();//聲明變量 時鐘信號和復位信號
reg sys_clk;
reg sys_rst_n;wire ds ;
wire shcp;
wire stcp;
wire oe_n;//對時鐘信號和復位信號進行初始化
initialbeginsys_clk = 1'b1;sys_rst_n <= 1'b0;#35sys_rst_n <= 1'b1;end//生成時鐘信號
always #10 sys_clk = ~sys_clk;//為了縮短仿真時間,對子功能模塊當中的兩個計時參數進行重定義
defparam top_segment_595_inst.data_gen_inst.CNT_MAX(23'd49);
defparam top_segment_595_inst.segment_595_dynamic.segment_dynamic_inst.CNT_MAX(16'd9);//實例化頂層模塊
top_segment_595 top_segment_595_inst
(.sys_clk (sys_clk ), //系統時鐘,50MHz.sys_rst_n(sys_rst_n), //系統復位,低電平有效.ds (ds ), //輸出給74HC595的串行數據.shcp (shcp ), //移位寄存器時鐘.stcp (stcp ), //存儲寄存器時鐘.oe_n (oe_n ) //74HC595的輸出使能,低電平有效
);//模塊結束
endmodule
2.2.6.3.2 仿真代碼編譯
頂層模塊的仿真代碼編寫完成后,保存為 tb_top_segment_595.v
;回到實驗工程添加仿真模塊;然后進行全編譯,編譯失敗點擊 OK
查看錯誤信息:
Error (10170): Verilog HDL syntax error at tb_top_segment_595.v(29) near text “(”; expecting “.”, or “[”, or “=”
Error (10170): Verilog HDL syntax error at tb_top_segment_595.v(30) near text “(”; expecting “.”, or “[”, or “=”
雙擊錯誤信息,跳轉到 tb_top_segment_595.v
文件的第 29 行;觀察發現參數重定義時本應該使用賦值運算符“=”而不是括號,我們這里使用錯誤,進行修改并保存
回到實驗工程,再次進行編譯;編譯通過點擊 OK
下面我們看一下 RTL 視圖。首先看頂層
頂層內部包含兩個子功能模塊:數據生成模塊和動態顯示模塊,與我們繪制的框圖是一致的
動態顯示模塊內部又包含動態顯示驅動模塊和 HC595 控制模塊
和 segment_595_dynamic
模塊的框圖是對應的
然后動態顯示驅動模塊內部又包含了一個 BCD 編碼模塊
和 segment_dynamic
模塊框圖也是對應的
接下來進行仿真設置,這里一定記得選擇頂層仿真模塊,因為是對頂層模塊進行邏輯仿真
2.2.6.3.3 波形仿真
設置完成開始仿真。仿真編譯完成點擊 sim 窗口添加模塊波形,回到 wave 窗口全選、分組、消除前綴;然后點擊 Restart,時間參數先設置為 10us 運行一次、全局視圖
在實驗工程當中,一些子功能模塊在編寫的時候已經進行了仿真驗證,這兒就不再查看它們的波形,只看一下我們剛剛編寫的頂層模塊和前面沒有仿真的動態顯示模塊的波形。
首先先來看一下動態顯示模塊,主要是看兩個位置
首先找到動態顯示模塊,然后找到動態顯示驅動模塊,看一下 sel
、seg
這兩路信號。首先是位選:
兩個 sel
信號它們的數據是相同的,沒有問題。然后是段選信號
段選信號也是沒有問題的。下面就是 ds
、shcp
、stcp
、oe_n
這四路信號,HC595 控制模塊輸出的四路信號,我們來看一下。首先是 ds 信號
這兒的 ds 信號波形是一致的,沒有問題。然后是 oe_n 信號
這兒這兩路 oe_n 信號是相同的,沒有問題。然后是移位寄存器時鐘,我們看一下
那么這兩路信號也是沒有問題的。然后是存儲寄存器時鐘
這兩個信號也是沒有問題的。這樣就表明:動態顯示模塊它是沒有問題的
下面我們看一下頂層模塊,頂層模塊主要看一下這倆部分
首先是待顯示數據 data 我們來看一下
這兩路信號波形是一致的,沒有問題。然后是小數點位和符號位
它們都始終保持低電平,沒有問題。然后是使能信號
使能信號它的初值為低電平,后面一直保持高電平,沒有問題,兩個信號是一致的
下面看一下 ds
、shcp
、stcp
、oe_n
這四路信號;首先是 ds
這三路 ds 信號波形是一致的,沒有問題。然后是 oe_n
始終為低電平,沒有問題。
然后是移位寄存器時鐘
波形一致沒有問題。然后是存儲寄存器時鐘
波形一致沒有問題
這樣就表示頂層模塊沒有問題,仿真驗證通過
2.2.7 上板驗證
2.2.7.1 引腳綁定
接下來回到實驗工程準備上板驗證;上板驗證開始之前綁定管腳:ds–>R1、oe_n–>L11、shcp–>B1、stcp–>K9、sys_clk–>E1、sys_rst_n–>M15
引腳綁定完成,重新進行編譯;編譯完成點擊 OK
接下來參照下圖所示連接板卡、電源、下載器,下載器的另一端連接到電腦,為開發板進行上電
2.2.7.2 結果驗證
然后回到實驗工程,打開下載界面,然后下載程序
上板驗證視頻:請點擊跳轉到B站觀看:顯示0到352626、顯示630984到990981
可以發現:數碼管在進行一個循環的計數,計數的初值是 0,最大值是 999999,計數到最大值會歸零開始下一個循環的計數
這里計數的時間應該是挺長的,我們就不再進行等待了。上板驗證通過
參考資料:
34-第二十三講-數碼管動態顯示(一)
35-第二十三講-數碼管動態顯示(二)
36-第二十三講-數碼管動態顯示(三)
37-第二十三講-數碼管動態顯示(四)
38-第二十三講-數碼管動態顯示(五)
39-第二十三講-數碼管動態顯示(六)
40-第二十三講-數碼管動態顯示(七)
20. 數碼管的動態顯示
圖片來源:立創商城 ??