[FPGA 學習記錄] 數碼管動態顯示

數碼管動態顯示

文章目錄

  • 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. 第一部分是理論學習,在這一部分,我們會對數碼管的動態顯示的工作原理做一個詳細的講解;
  2. 第二部分是實戰演練,在這一部分,會通過實驗工程設計并實現數碼管的動態顯示。

首先是理論學習

1 理論學習

我們征途系列開發板使用的是六位八段數碼管,實物圖1如下

6位0.36英寸紅色數碼管 共陽極

焊接在開發板的右上角位置

dfvhdkyerj32qekfhdfiguyd9fg328872r6e9rfwy

1.1 數碼管動態掃描顯示原理

六位八段數碼管的內部結構圖如下

image-20231124162809795

由圖可知,六位八段數碼管當中,每個數碼位的段選信號全部連接到了一起,然后進行輸出;每個數碼位單獨引出一個位選信號用來控制數碼位的選擇,這種連接方式會使得被選中的數碼位顯示的內容都是相同的,因為這些被選中的數碼位的段選信號已經全部連接到了一起。如何使用這個六位八段數碼管來實現數碼管的動態顯示呢?我們需要使用一種方式:動態掃描

如何使用動態掃描的方式來實現數碼管的動態顯示呢?這里給大家舉一個例子:比如說,我們想要使用六位八段數碼管顯示數字 123456

image-20231124164305622

如何使用六位八段數碼管來顯示數字 123456 呢?

首先我們選中第一個數碼位,讓這個數碼位顯示數字 1;然后它顯示的時間設為 T \text{T} T,這個 T \text{T} T 可以看作一個周期

image-20231124164729258

當第一個數碼位完成一個 T \text{T} T 周期數字 1 的顯示之后,立刻選中第二個數碼位;注意,此時只選中了第二個數碼位讓它顯示數字 2,顯示的時間同樣是一個周期 T \text{T} T

image-20231124164904207

當第二個數碼位完成了一個周期 T \text{T} T 數字 2 的顯示之后,立刻選中第三個數碼位;這兒注意,也是只選中了第三個數碼位讓它顯示數字 3,同樣顯示時間為 T \text{T} T

image-20231124165016384

依次往下類推,那么此時就顯示 4

image-20231124165125710

然后是 5

image-20231124165137840

然后是 6

image-20231124165150869

當第六個數碼位完成了一個周期 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

間隔1秒動態顯示123456

如果進一步把這個 T \text{T} T 進行縮短,比如說縮短到 0.2s 這時候六位八段數碼管的六個數碼位會進行閃爍顯示,顯示的內容依次是 1、2、3、4、5、6

間隔20毫秒動態顯示123456

如果進一步縮短時間 T \text{T} T 為 1ms,這時候,六位八段數碼管的六個數碼位實際上也是依次進行閃爍的顯示,顯示的內容依次是 1、2、3、4、5、6 每個數字顯示時間是 1ms

間隔1毫秒動態顯示123456

但是它們切換的頻率太快了,我們的肉眼不能分辨這種閃爍,就誤以為六位八段數碼管的六個數碼位在同時進行顯示,而且顯示的內容是 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 的框圖如下

image-20231127150107105

輸入信號

  • 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
讓該模塊產生我們需要顯示的數據

image-20231127151634608

輸入信號

  • 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

image-20231127154004679

輸入信號

  • 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] 轉換成 dsshcpstcpoe_n 這四路信號傳入到 74HC595 控制芯片。

動態顯示驅動模塊 segment_dynamic

image-20231127154814410

輸入信號

  • sys_clk:系統時鐘信號
  • sys_rst_n:系統復位信號
  • data[19:0]:待顯示數據
  • point[5:0]:待顯示數據小數點位
  • sign:待顯示數據的符號位
  • seg_en:數碼管動態顯示模塊使能信號

輸出信號

  • sel[5:0]:位選信號
  • seg[7:0]:段選信號

74HC595 控制模塊 hc595_ctrl

image-20231127160616230

輸入信號

  • sys_clk:系統時鐘信號
  • sys_rst_n:系統復位信號
  • sel[5:0]:位選信號
  • seg[7:0]:段選信號

輸出信號(就是傳入到 74HC595 芯片的四路信號)

  • ds:串行數據
  • shcp:移位寄存器時鐘
  • stcp:存儲寄存器時鐘
  • oe_n:輸出使能

各子功能模塊的模塊框圖繪制完成,下面開始系統框圖的繪制。

首先是數碼管動態顯示模塊 segment_595_dynamic

image-20231127161650380

它的模塊功能由兩個子功能模塊實現,輸入到數碼管動態顯示模塊 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

image-20231127162442251

傳入到頂層模塊 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 的波形圖

image-20231127174420027

由模塊框圖可知,輸入信號有兩路:時鐘信號 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 1cnt_100ms 的計數周期是 0.1 s 0.1\text{s} 0.1s 就是 100 ms 100\text{ms} 100mscnt_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=4999999cnt_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 同樣是高電平有效,當數碼管進行負數顯示的時候,在顯示的數據之前會加一個負號用來區分正負數

fsdof74873wqerdoaisefueroit2_negative

在本次的顯示當中是顯示數字 ( 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]

image-20231203202420870

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

image-20231127203435064

看一下報錯信息

Error (12007): Top-level design entity "top_segment_595" is undefined
提示我們不存在 `top_segment_595` 模塊,這個問題在數碼管的靜態顯示工程中我們也遇到過,在那個時候我們提到了這個問題有兩種解決方式:第一種方式是重新編寫一個頂層文件,將子功能模塊例化到頂層文件,然后進行編譯;第二種方式是將子功能模塊強制置為頂層。之前我們使用的是第一種方式解決這個問題,在這兒我們使用第二種方式解決這個問題:選中 `data_gen` 子功能模塊,點擊鼠標右鍵,選擇第三項 Set as Top-Level Entity 將它置為頂層

image-20231127204143555

然后再次進行編譯,編譯通過點擊 OK

image-20231127204518207

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

image-20231127210810514

2.2.2.4.3 波形仿真

然后進行仿真設置,開始仿真;仿真完成之后打開 sim 窗口添加模塊波形;波形界面全選、分組、消除前綴;然后點擊 Restart,將時間參數先設置為 10us,運行一次。

發現 cnt_flag 波形有問題

image-20231127212651253

查看 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
image-20231127215039803

下面看一下 cnt_flag 信號

image-20231127215644416
那么下面就看一下待顯示數據 data

image-20231127220934056
下面是小數點信號 point,始終為 0 沒有問題

image-20231127221516373

符號位 sign 始終為低電平,也沒有問題

image-20231127221650742

然后是使能信號 seg_en,初值為低電平,復位信號無效時一直保持高電平

image-20231127221825356

這三路信號與我們繪制的波形圖是完全一致的,我們的數據生成模塊 data_gen 通過了仿真驗證。

我們已經實現了系統框圖的繪制,完成了數據生成模塊 data_gen 的功能實現;接下來我們將繼續生成子功能模塊。

首先先來看一下系統框圖

image-20231127162442251

由系統框圖我們可以知道:頂層模塊 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 的編碼格式

十進制數0123456789
8421 碼0000000100100011010001010110011110001001
5421 碼0000000100100011010010001001101010111100
2421 碼0000000100100011010010111100110111101111
余 3 碼0011010001010110011110001001101010111100
余 3 循環碼0010011001110101010011001101111111101010

接下來我們講解一下如何使用 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?

image-20231128180518756

十進制數 ( 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?

68w3eyiofsdnfdifiw3o4rmgdf_digit3

然后第二個顯示周期內點亮 DIG5 數碼位,讓它顯示十位 ( 3 ) 10 (3)_{10} (3)10?

d8y948we7r5soidjfgdfg98w34kfs_sel1_digit3

在第三個顯示周期內點亮 DIG4 數碼位,讓它顯示百位 ( 2 ) 10 (2)_{10} (2)10?

3496fvb76xdfowse4jt45tko_sel2_digit2

如果說掃描的頻率足夠快,加上人眼的視覺暫留特性以及數碼管的余暉效應,我們的肉眼就以為數碼管在同時顯示 ( 233 ) 10 (233)_{10} (233)10?

sel012_digit233_vbi76fworhwdfvisdhfwl

這樣就實現了 ( 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 120 得到 ( 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 240 得到 ( 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 個 00000000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變0000000000001110_1001
整體向左移一個二進制位,第 1 次按位左移000000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變000000000001110_1001
整體向左移一個二進制位,第 2 次按位左移00000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變00000000001110_1001
整體向左移一個二進制位,第 3 次按位左移0000000001110_1001
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 30000000010100_1001
整體向左移一個二進制位,第 4 次按位左移0000000101001001
各個 BCD 碼分別和 5 比較,保持原值不變0000000101001001
整體向左移一個二進制位,第 5 次按位左移000000101001001
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3000000101100001
整體向左移一個二進制位,第 6 次按位左移00000101100001
各個 BCD 碼分別和 5 比較, 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101100 位加 300001000101101
整體向左移一個二進制位,第 7 次按位左移0001000101101
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 30001000110011
整體向左移一個二進制位,第 8 次按位左移001000110011
輸出結果 ( 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 碼的轉碼

image-20231129222017563

輸入信號

  • 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 模塊框圖

image-20231129234951681

修改后的 segment_595_dynamic 模塊框圖

image-20231129235108516

修改后的 top_segment_595 模塊框圖

image-20231130094756670

2.2.3.3 波形繪制

接下來就開始 BCD 轉碼模塊 bcd_8421 的波形圖的繪制

image-20231130134008155

首先是輸入信號時鐘信號 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 個 00000000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變0000000000001110_1001
整體向左移一個二進制位,第 1 次按位左移000000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變000000000001110_1001
整體向左移一個二進制位,第 2 次按位左移00000000001110_1001
各個 BCD 碼分別和 5 比較,保持原值不變00000000001110_1001
整體向左移一個二進制位,第 3 次按位左移0000000001110_1001
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 30000000010100_1001
整體向左移一個二進制位,第 4 次按位左移0000000101001001
各個 BCD 碼分別和 5 比較,保持原值不變0000000101001001
整體向左移一個二進制位,第 5 次按位左移000000101001001
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 3000000101100001
整體向左移一個二進制位,第 6 次按位左移00000101100001
各個 BCD 碼分別和 5 比較, 1 0 1 、 1 0 0 10^{1}\text{、}10^{0} 101100 位加 300001000101101
整體向左移一個二進制位,第 7 次按位左移0001000101101
各個 BCD 碼分別和 5 比較, 1 0 0 10^{0} 100 位加 30001000110011
整體向左移一個二進制位,第 8 次按位左移001000110011
輸出結果 ( 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

image-20231130140932156

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

image-20231130143335348

然后進行仿真設置

image-20231130143623589

2.2.3.6.3 波形仿真

下面開始仿真,結合我們繪制的波形圖查看一下仿真波形圖。

首先是移位標志信號 shift_flag

image-20231201102314897

移位標志信號 shift_flag 的初值是低電平,每個系統時鐘周期進行取反。

接下來看一下移位計數器 cnt_shift

image-20231201103214831

移位數據 data_shift 的波形就不再進行查看了,因為數據量太大了。

接下來看一下輸出信號

image-20231201104916854

那么這樣我們就實現了 BCD 編碼模塊的功能,下面就可以實現動態顯示驅動模塊 segment_dynamic

2.2.4 動態顯示驅動模塊 segment_dynamic

動態顯示驅動模塊的模塊框圖之前已經繪制完成了

image-20231129234951681

2.2.4.1 波形繪制

接下來就繪制 segment_dynamic 模塊的波形圖

image-20231201190637933

輸入信號有六路:時鐘信號 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×106nsTsys_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 呢?我們來算一下:

image-20231201170328856

可以看到, ( 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 的編碼

待顯示內容段碼(二進制格式)段碼(十六進制格式)
dpgfedcba
0110000008’hC0
1111110018’hF9
2101001008’hA4
3101100008’hB0
4100110018’h99
5100100108’h92
6100000108’h82
7111110008’hF8
8100000008’h80
9100100008’h90
A100010008’h88
b100000118’h83
C110001108’hC6
d101000018’hA1
E100001108’h86
F100011108’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

image-20231202103119615

查看一下報錯信息

Error (10170): Verilog HDL syntax error at segment_dynamic.v(129) near text "1"; expecting ";"

發現問題代碼在 129 行附近,查看修改后保存

image-20231202104859898

回到 Quartus II 再次編譯,依然報錯

Error (10228): Verilog HDL error at bcd_8421.v(2): module "bcd_8421" cannot be declared more than once

tb_bcd_8421.v 文件的第 3 行:tb_bcd_8421.v 刪除,再次編譯工程,編譯通過點擊 OK

image-20231202110311454

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

image-20231202164440743

查看錯誤信息:

Error (10170): Verilog HDL syntax error at tb_segment_dynamic.v(37) near text “#”; expecting “;”

提示仿真代碼第 37 行附近缺少分號,查看代碼發現實例化模塊的名稱位置放錯了,修改保存

image-20231202165516816

再次進行全編譯,編譯通過點擊 OK

image-20231202165614850

下面進行仿真設置

image-20231202165804322

2.2.4.4.3 波形仿真

接下來進行仿真,時間參數設置為 10us,運行一次,全局視圖

image-20231202200011647

我們發現 flag_1ms 的波形沒有變化,回到 segment_dynamic.v 文件中查看,發現給 flag_1ms 賦值高電平時的條件編寫錯誤,修改完保存

image-20231202200226702

回到我們的仿真界面,然后全選、刪除所有波形,回到 Library 窗口,找到修改的代碼對它進行重編譯

image-20231202200534462

然后回到 sim 窗口重新添加波形,然后回到波形界面,全選、分組;然后點擊 Restart,重新運行 10us 運行一次

image-20231202201329909

再次回到 segment_dynamic.v 文件進行排錯,發現當 cnt_sel 為 0 時,cnt_sel-3'd1 作為 point 的索引值出現了負數,修改并保存

image-20231202202058800

再次仿真

image-20231202202228816

接下來參照繪制的波形圖查看一下仿真波形
首先是從 bcd_8421 模塊引出的 BCD 碼

image-20231202202550311

他們的初值是 0,沒有問題;然后個位是 6 沒有問題,十位是 7,然后是 8、9;最高的兩位始終保持 0,這兒沒有問題。

然后看一下數據寄存

image-20231203092340970我們繼續往下看
image-20231203092955678

下面看一下 1ms 計數器
image-20231203093633504
那么下面是標志信號

image-20231203094016864

然后是掃描計數器
image-20231203094441734
下面是位選信號的寄存
image-20231203095330893
下面就是顯示數據波形

image-20231203101759194

可以看到 data_dispsel_reg 錯開了一位,回到代碼查看 data_disp 的賦值語句塊,修改(時序邏輯節拍延遲,cnt_sel 自加一的條件中有 flag_1ms==1'b1 部分,然后在這里的條件又有 flag_1ms==1'b1 導致延遲兩個 flag_1ms 的節拍)并保存

image-20231203103019695

再次仿真查看波形

image-20231203103854816
下面是小數點信號
image-20231203104212156
下面就是位選信號和段選信號,先來看一下位選信號

image-20231203104912443

位選信號是在位選寄存信號打一拍得到的,在圖中紅框這個位置確實延遲了一個時鐘周期,然后他們倆的數據也是對應的。

下面看一下段選信號

image-20231203105457353

可以看到,段選信號是有問題的。回到 segment_dynamic.v 查看,修改給 dot_disp 賦值的部分,因為給 seg 賦值時需要使用 dot_disp 替換最高有效位

image-20231203111625798

還有需要修改 segment_dynamic.v 的 119 行,這里我們錯誤的把段碼賦值給 data_disp

image-20231203112127824

為了和繪制的波形圖保持一致,還需要修改代碼第 149 行

image-20231203112700220

這時再仿真查看波形,觀察段選信號 seg

image-20231203113125616

當選中第一個數碼位,段碼 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

image-20231203122815110

查看錯誤信息:

Error (10170): Verilog HDL syntax error at segment_595_dynamic.v(22) near text “#”; expecting “;”

鼠標左鍵雙擊該條 error,跳轉到代碼文件中檢查

image-20231203123757056

發現又犯了同樣的錯誤:對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面對帶參數的模塊進行實例化時,實例化名稱的位置應當在參數后面,但愿之后我能記住吧…………

修改完成保存,回到 Quartus II 進行重新編譯,編譯通過點擊 OK

image-20231203124447518

對于動態顯示模塊不再進行單獨的仿真,等到頂層模塊的代碼編寫完成了,再進行整體的仿真。

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

image-20231203132006703

我們來看一下報錯信息

Error (12006): Node instance "hc595_ctrl_inst" instantiates undefined entity "hc595_ctrl"

在頂層模塊 top_segment_595.v 中對 hc595_ctrl 模塊進行了實例化,但是我們并沒有將 hc595_ctrl.v 文件添加到 Quartus II 的工程中,所以提示我們實體沒有定義。回到實驗工程,添加 hc595_ctrl.v 文件,然后重新編譯,編譯通過點擊 OK

image-20231203132557571

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

image-20231203150441481

查看錯誤信息:

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 “=”

Error (10112): Ignored design unit "tb_top_segment_595" at tb_top_segment_595.v(5) due to previous errors

雙擊錯誤信息,跳轉到 tb_top_segment_595.v 文件的第 29 行;觀察發現參數重定義時本應該使用賦值運算符“=”而不是括號,我們這里使用錯誤,進行修改并保存

image-20231203150945985

回到實驗工程,再次進行編譯;編譯通過點擊 OK

image-20231203151108145

下面我們看一下 RTL 視圖。首先看頂層

image-20231203151208806

頂層內部包含兩個子功能模塊:數據生成模塊和動態顯示模塊,與我們繪制的框圖是一致的

image-20231130094756670

動態顯示模塊內部又包含動態顯示驅動模塊和 HC595 控制模塊

image-20231203151638743

segment_595_dynamic 模塊的框圖是對應的

image-20231129235108516
然后動態顯示驅動模塊內部又包含了一個 BCD 編碼模塊

image-20231203152234834

segment_dynamic 模塊框圖也是對應的

image-20231129234951681

接下來進行仿真設置,這里一定記得選擇頂層仿真模塊,因為是對頂層模塊進行邏輯仿真

image-20231203152609143

2.2.6.3.3 波形仿真

設置完成開始仿真。仿真編譯完成點擊 sim 窗口添加模塊波形,回到 wave 窗口全選、分組、消除前綴;然后點擊 Restart,時間參數先設置為 10us 運行一次、全局視圖

image-20231203154600662

在實驗工程當中,一些子功能模塊在編寫的時候已經進行了仿真驗證,這兒就不再查看它們的波形,只看一下我們剛剛編寫的頂層模塊和前面沒有仿真的動態顯示模塊的波形。

首先先來看一下動態顯示模塊,主要是看兩個位置
image-20231203160517745
首先找到動態顯示模塊,然后找到動態顯示驅動模塊,看一下 selseg 這兩路信號。首先是位選:

image-20231203161007924

兩個 sel 信號它們的數據是相同的,沒有問題。然后是段選信號

image-20231203161115807

段選信號也是沒有問題的。下面就是 dsshcpstcpoe_n 這四路信號,HC595 控制模塊輸出的四路信號,我們來看一下。首先是 ds 信號

image-20231203161413812

這兒的 ds 信號波形是一致的,沒有問題。然后是 oe_n 信號

image-20231203161621968

這兒這兩路 oe_n 信號是相同的,沒有問題。然后是移位寄存器時鐘,我們看一下

image-20231203161751590

那么這兩路信號也是沒有問題的。然后是存儲寄存器時鐘

image-20231203161843721

這兩個信號也是沒有問題的。這樣就表明:動態顯示模塊它是沒有問題的
下面我們看一下頂層模塊,頂層模塊主要看一下這倆部分

image-20231203162133823

首先是待顯示數據 data 我們來看一下

image-20231203162307944

這兩路信號波形是一致的,沒有問題。然后是小數點位和符號位

image-20231203162438325

它們都始終保持低電平,沒有問題。然后是使能信號

image-20231203162629160

使能信號它的初值為低電平,后面一直保持高電平,沒有問題,兩個信號是一致的
下面看一下 dsshcpstcpoe_n 這四路信號;首先是 ds

image-20231203163033473

這三路 ds 信號波形是一致的,沒有問題。然后是 oe_n

image-20231203163158566

始終為低電平,沒有問題。

然后是移位寄存器時鐘

image-20231203163327493

波形一致沒有問題。然后是存儲寄存器時鐘

image-20231203163431027

波形一致沒有問題
這樣就表示頂層模塊沒有問題,仿真驗證通過

2.2.7 上板驗證

2.2.7.1 引腳綁定

接下來回到實驗工程準備上板驗證;上板驗證開始之前綁定管腳:ds–>R1、oe_n–>L11、shcp–>B1、stcp–>K9、sys_clk–>E1、sys_rst_n–>M15

image-20231203164306366

引腳綁定完成,重新進行編譯;編譯完成點擊 OK

image-20231203164418801

接下來參照下圖所示連接板卡、電源、下載器,下載器的另一端連接到電腦,為開發板進行上電

2.2.7.2 結果驗證

然后回到實驗工程,打開下載界面,然后下載程序

image-20231203164743269

上板驗證視頻:請點擊跳轉到B站觀看:顯示0到352626、顯示630984到990981

可以發現:數碼管在進行一個循環的計數,計數的初值是 0,最大值是 999999,計數到最大值會歸零開始下一個循環的計數
這里計數的時間應該是挺長的,我們就不再進行等待了。上板驗證通過


參考資料:

34-第二十三講-數碼管動態顯示(一)

35-第二十三講-數碼管動態顯示(二)

36-第二十三講-數碼管動態顯示(三)

37-第二十三講-數碼管動態顯示(四)

38-第二十三講-數碼管動態顯示(五)

39-第二十三講-數碼管動態顯示(六)

40-第二十三講-數碼管動態顯示(七)

20. 數碼管的動態顯示


  1. 圖片來源:立創商城 ??

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/207468.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/207468.shtml
英文地址,請注明出處:http://en.pswp.cn/news/207468.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何解決el-table中動態添加固定列時出現的行錯位

問題描述 在使用el-table組件時&#xff0c;我們有時需要根據用戶的操作動態地添加或刪除一些固定列&#xff0c;例如操作列或選擇列。但是&#xff0c;當我們使用v-if指令來控制固定列的顯示或隱藏時&#xff0c;可能會出現表格的行錯位的問題&#xff0c;即固定列和非固定列…

el-tree數據量過大,造成瀏覽器卡死、崩潰

el-tree數據量過大&#xff0c;造成瀏覽器卡死、崩潰 場景&#xff1a;樹形結構展示&#xff0c;數據超級多&#xff0c;超過萬條&#xff0c;每次打開都會崩潰 我這里采用的是引入新的插件虛擬樹&#xff0c;它是參照element-plus 中TreeV2改造vue2.x版本虛擬化樹形控件&…

2024年強烈推薦mac 讀寫NTFS工具Tuxera NTFS for Mac2023中文破解版

大家好啊&#xff5e;今天要給大家推薦的是 Tuxera NTFS for Mac2023中文破解版&#xff01; 小可愛們肯定知道&#xff0c;Mac系統一直以來都有一個小小的痛點&#xff0c;就是無法直接讀寫NTFS格式的移動硬盤和U盤。但是&#xff0c;有了Tuxera NTFS for Mac2023&#xff0c;…

正則表達式:字符串處理的瑞士軍刀

&#x1f90d; 前端開發工程師&#xff08;主業&#xff09;、技術博主&#xff08;副業&#xff09;、已過CET6 &#x1f368; 阿珊和她的貓_CSDN個人主頁 &#x1f560; 牛客高級專題作者、在牛客打造高質量專欄《前端面試必備》 &#x1f35a; 藍橋云課簽約作者、已在藍橋云…

記一次xss通殺挖掘歷程

前言 前端時間&#xff0c;要開放一個端口&#xff0c;讓我進行一次安全檢測&#xff0c;發現的一個漏洞。 經過 訪問之后發現是類似一個目錄索引的端口。(這里上厚碼了哈) 錯誤案例測試 亂輸內容asdasffda之后看了一眼Burp的抓包&#xff0c;抓到的內容是可以發現這是一個…

MuJoCo機器人動力學仿真平臺安裝與教程

MuJoCo是一個機器人動力學仿真平臺&#xff0c;它包括一系列的物理引擎、可視化工具和機器人模擬器等工具&#xff0c;用于研究和模擬機器人的運動和動力學特性。以下是MuJoCo的安裝教程&#xff1a; 下載和安裝MuJoCo Pro。可以從MuJoCo的官方網站上下載最新版本的安裝包。根…

【Python機器學習系列】一文徹底搞懂機器學習中表格數據的輸入形式(理論+源碼)

一、問題 機器學習或者深度學習在處理表格數據&#xff08;Tabular data&#xff09;、圖像數據&#xff08;Image data&#xff09;、文本數據&#xff08;Text data&#xff09;、時間序列數據&#xff08;Time series data&#xff09;上得到了廣泛的應用。 其中&#xff0c…

微信小程序 - 創建 ZIP 壓縮包

微信小程序 - 創建 ZIP 壓縮包 場景分享代碼片段導入 JSZip創建ZIP文件追加寫入文件測試方法參考資料 場景 微信小程序只提供了解壓ZIP的API&#xff0c;并沒有提供創建ZIP的方法。 當我們想把自己處理好的保存&#xff0c;打包ZIP保存下來時就需要自己實現了。 分享代碼片段…

無重復字符的最長子串(LeetCode 3)

文章目錄 1.問題描述2.難度等級3.熱門指數4.解題思路方法一&#xff1a;暴力法方法二&#xff1a;滑動窗口 參考文獻 1.問題描述 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的最長子串的長度。 s 由英文字母、數字、符號和空格組成。 示例 1&#xff1a; 輸…

基于Java商品銷售管理系統

基于Java商品銷售管理系統 功能需求 1、商品管理&#xff1a;系統需要提供商品信息的管理功能&#xff0c;包括商品的錄入、編輯、查詢和刪除。每個商品應包含基本信息如名稱、編碼、類別、價格、庫存量等。 2、客戶管理&#xff1a;系統需要能夠記錄客戶的基本信息&#xf…

算法:常見的哈希表算法

文章目錄 兩數之和判斷是否互為字符重排存在重復元素存在重復元素字母異位詞分組 本文總結的是關于哈希表常見的算法 哈希表其實就是一個存儲數據的容器&#xff0c;所以其實它本身的算法難度并不高&#xff0c;只是利用哈希表可以對于一些場景進行優化 兩數之和 class Solut…

Michael.W基于Foundry精讀Openzeppelin第41期——ERC20Capped.sol

Michael.W基于Foundry精讀Openzeppelin第41期——ERC20Capped.sol 0. 版本0.1 ERC20Capped.sol 1. 目標合約2. 代碼精讀2.1 constructor() && cap()2.2 _mint(address account, uint256 amount) 0. 版本 [openzeppelin]&#xff1a;v4.8.3&#xff0c;[forge-std]&…

AI智能降重軟件大全,免費最新AI智能降重軟件

在當今信息爆炸的時代&#xff0c;內容創作者們面臨著巨大的寫作壓力&#xff0c;如何在保持高質量的前提下提高效率成為擺在許多人面前的難題。AI智能降重軟件因其獨特的算法和功能逐漸成為提升文案質量的得力助手。本文將專心分享一些優秀的AI智能降重軟件。 147SEO改寫軟件 …

云貝教育 |【技術文章】PostgreSQL中誤刪除數據怎么辦(一)

原文鏈接&#xff1a;【PostgreSQL】PostgreSQL中誤刪除數據怎么辦&#xff08;一&#xff09; - 課程體系 - 云貝教育 (yunbee.net) 在我們學習完PG的MVCC機制之后&#xff0c;對于DML操作&#xff0c;被操作的行其實并未被刪除&#xff0c;只能手工vacuum或自動vacuum觸發才會…

【分享】我想上手機器學習

目錄 前言 一、理解機器學習 1.1 機器學習的目的 1.2 機器學習的模型 1.3 機器學習的數據 二、學習機器學習要學什么 2.1 學習機器學習的核心內容 2.2 怎么選擇模型 2.3 怎么獲取訓練數據 2.4 怎么訓練模型 三、機器學習的門檻 3.1 機器學習的第一道門檻 3.2 機器…

最新版IDEA專業版大學生申請免費許可證教學(無需學校教育郵箱+官方途徑+非破解手段)

文章目錄 前言1. 申請學籍在線驗證報告2. 進入IDEA官網進行認證3. 申請 JB (IDEA) 賬號4. 打開 IDEA 專業版總結 前言 當你進入本篇文章時, 你應該是已經遇到了 IDEA 社區版無法解決的問題, 或是想進一步體驗 IDEA 專業版的強大. 本文是一篇學生申請IDEA免費許可證的教學, 在學…

unity 2d 入門 飛翔小鳥 小鳥碰撞 及死亡(九)

1、給地面&#xff0c;柱體這種添加2d盒裝碰撞器&#xff0c;小鳥移動碰到就不會動了 2、修改小鳥的腳本&#xff08;腳本命名不規范&#xff0c;不要在意&#xff09; using System.Collections; using System.Collections.Generic; using UnityEngine;public class Fly : Mo…

kafka高吞吐、低延時、高性能的實現原理

作者&#xff1a;源碼時代-Raymon老師 Kafka的高吞吐、低延時、高性能的實現原理 Kafka是大數據領域無處不在的消息中間件&#xff0c;目前廣泛使用在企業內部的實時數據管道&#xff0c;并幫助企業構建自己的流計算應用程序。Kafka雖然是基于磁盤做的數據存儲&#xff0c;但…

可信固件-M (TF-M)

概述&#xff1a; 參考: Trusted Firmware-M Documentation — Trusted Firmware-M v2.0.0 documentation 開源代碼托管&#xff1a; trusted-firmware-m.git - Trusted Firmware for M profile Arm CPUs STM32 U5支持TF-M : STM32U5 — Trusted Firmware-M v2.0.0 document…

Meta Platforms推出Imagine:基于Emu的免費AI文本到圖像生成器服務

Meta Platform是Facebook、Instagram 和 WhatsApp 的母公司&#xff0c;也是領先的開源AI人工智能大語言模型 Llama 2的創建者。Meta Platforms 推出了一個名為 Imagine 的獨立文本到圖像 AI 生成器服務。Imagine 是基于 Meta 自己的 AI 模型 Emu 構建的&#xff0c;Emu 是在11…