FPGA數碼管知識點整理

祈愿树下發表於2024-03-03

知識點:

  數碼管控制分為位選和段選,透過位控制哪一個數碼管亮,透過段選控制數碼管中某一段亮。

  我硬體買的上面的是共陽極的,也就是段選位給低電平就能亮。

  FPGA數碼管知識點整理

下面是段選的位控制要顯示的資料。比如數字0只要讓G位滅掉就行,透過給段選8'h1100_0000 (共陽極),將g和點滅掉就是0了

     FPGA數碼管知識點整理

數碼管的控制,透過動態控制,數碼管的位選和段選,來控制顯示數字

利用視覺殘留,每1ms輪換一個數碼管,20ms以內的閃爍,都可以被視覺殘留利用,讓人覺得一直在常亮,其實在閃爍

FPGA數碼管知識點整理

這樣如果需要8個數碼管亮滅,則需要16位IO管腳控制。這佔用太多資源。這邊會使用一個74HC595模組,來將資料進行移位操作+儲存操作,這樣就可以節省IO資源,其實就是將並轉串。

將上面的位選和段選的sel 和 seg資料作為資料輸入,這邊是並行資料進去,透過模組移位寄存,最後將資料序列輸出。

FPGA數碼管知識點整理


程式碼工程:

  先完成8+8的位選和段選邏輯程式碼的工作。

  這邊是將需要顯示的資料,儲存在32資料當中,每4位表示一位數字,0-16.

  然後透過sel和seg控制位選和段選,來顯示數碼管。

  時間是1ms切換一次位選,這樣一輪也才8ms<20ms,滿足視覺殘留需求

module hex8(
    clk,
    reset_n,
    disp_data,  //想要輸入的值
    sel,        //選擇那個數碼管
    seg         //選擇8段數碼管哪一個亮a~g
    );
    
    input   clk;
    input   reset_n;
    input   [31:0]  disp_data;  //每4位儲存一位資料
    
    output reg [7:0]   sel;
    output reg [7:0]   seg;
    
    reg [29:0]  div_cnt; //計數器,1ms 
    reg [2:0]   cnt_sel;
    
    parameter   MCNT = CLOCK_FREQ / TURN_FREQ - 1;
    parameter   CLOCK_FREQ = 50_000_000;
    parameter   TURN_FREQ = 1000;
    
    //20ns * 50_000 = 1ms
    
    always @(posedge clk or negedge reset_n)
        if(!reset_n)
            div_cnt <= 0;
        else if(div_cnt == MCNT)
            div_cnt <= 0;
        else 
            div_cnt <= div_cnt + 1'd1;
           
           //每1ms sel加一位,就是換下一個數碼管亮,總共20ms內就可以利用
           //人眼的視覺殘留造成數碼管一直亮的現象
    always @(posedge clk or negedge reset_n)
        if(!reset_n)
            cnt_sel <= 0;
        else if(div_cnt == MCNT)
            cnt_sel <= cnt_sel + 1'd1;
        
        //38譯碼器
        //數碼管位選,選擇當前數碼管
    always @(posedge clk)
        case(cnt_sel)
            0:  sel <= 8'b0000_0001;
            1:  sel <= 8'b0000_0010;
            2:  sel <= 8'b0000_0100;
            3:  sel <= 8'b0000_1000;
            4:  sel <= 8'b0001_0000;
            5:  sel <= 8'b0010_0000;
            6:  sel <= 8'b0100_0000;
            7:  sel <= 8'b1000_0000;
            
        endcase
        
        reg [3:0] data_temp;  //數碼管顯示內容控制段選訊號
        //這邊用阻塞賦值,wire是不是也可以?
            //數碼管段選,選擇顯示的內容
        always @(posedge clk)
            case(data_temp)
                0: seg <= 8'b1100_0000; //0
                1: seg <= 8'b1111_1001; //1
                2: seg <= 8'b1010_0100; //2
                3: seg <= 8'b1011_0000; //3
                4: seg <= 8'b1001_1001; //4
                5: seg <= 8'b1001_0010; //5
                6: seg <= 8'b1000_0010; //6
                7: seg <= 8'b1111_1000; //7
                8: seg <= 8'b1000_0000; //8
                9: seg <= 8'b1001_0000; //9
                10: seg <= 8'b1000_1000; //A
                11: seg <= 8'b1000_0011; //B
                12: seg <= 8'b1100_0110; //C
                13: seg <= 8'b1010_0001; //D
                14: seg <= 8'b1000_0110; //E
                15: seg <= 8'b1000_1110; //F
           endcase
                
                //disp_data  8個數碼管待顯示資料,每四個資料組成一個BCD碼
        always @(*)
            case(cnt_sel)
                0: data_temp <= disp_data[3:0]; //0 
                1: data_temp <= disp_data[7:4]; //1
                2: data_temp <= disp_data[11:8]; //2
                3: data_temp <= disp_data[15:12]; //3
                4: data_temp <= disp_data[19:16]; //4
                5: data_temp <= disp_data[23:20]; //5
                6: data_temp <= disp_data[27:24]; //6
                7: data_temp <= disp_data[31:28]; //7           
            endcase
            
            
endmodule

然後透過將上面hex8.v中的seg和sel埠作為輸入引入暫存器595中的輸入。

透過移位寄存輸出序列資料控制數碼管,一下子少了很多IO使用。

下面三個圖分別是595暫存器的手冊中整理的時序圖和功能表等。

FPGA數碼管知識點整理

FPGA數碼管知識點整理

直接看下面這個圖,移位暫存器上升沿時候,資料進入移位暫存器,在儲存暫存器上升沿時輸出到並行埠。

這邊的srclk是移位暫存器,rclk是儲存暫存器

SRCLK - SHCP

RCLK - STCP

因為這邊是兩個595連起來,因為需要16位資料傳輸,所以是每16位資料移位結束後,再進行鎖存資料。

這邊在移位暫存器的上升沿,透過DS/DIO序列資料輸入口,不斷輸入資料我們這邊是將seg和sel訊號16位輸入給dio裡面

FPGA數碼管知識點整理

這邊根據595的特性,我們是3.3V電平,暫存器工作頻率採用12.5MHZ,正好是50MHZ的4分之一。

所以最小時間單位就是40ns =20ns *2

每40ns 移位暫存器電平轉換一次。下面的程式碼中以移位暫存器的電平為時間基準來對儲存暫存器和序列資料進行移位寄存處理。

一開始,肯定是移位暫存器拉高,將DIO中資料讀取出來放入移位暫存器的高位,然後下一位SRCLK的高電平時繼續讀取DIO的資料,然後將新的資料放入高位,上一個資料放入次高位,一直下去,直到將DIO的8位資料全部讀取,我們這邊是兩個595,所以是16位資料=sel+seg

而每16位資料儲存結束,RCLK的上升沿時刻,移位暫存器中的資料轉存到鎖存器當中儲存。這就是儲存暫存器的作用。

程式碼中根據最小時間單位,也就是移位暫存器的高低電平變化,來進行資料移位儲存。在移位暫存器變化時候,將seg與sel的值寫入dio當中,最後鎖存。

因為沒有覺得起始點區分,這邊放一段模擬之後的,模擬中給定了值

seg = 8'b0101_0101;sel = 8'b0000_0001;

我們可以看到,確實最後的結果是0101_0101_0000_0001

在黃線處是16位數移位完成,rclk高電平完成鎖存操作。以這個時候為開始就是程式碼中的

一開始是cnt還沒計時,將第一個值賦值給dio,這個時候移位暫存器還沒上升沿,儲存暫存器上升沿,鎖存資料。這是結束也是開始

dio <= seg[7];srclk <= 1'd0; rclk <= 1'd1;所以後面儲存暫存器一個計數後拉低,就可以開始移位暫存器操作了

然後cnt計數開始,40ns一個計數,在cnt計數滿時候,這個時候將儲存暫存器拉低,移位暫存器拉高,移位操作。

第二部就是srclk <= 1'd1; rclk <= 1'd0;

然後因為上面移位操作之後,最高位空出來,繼續賦值操作,且拉低移位暫存器。

2:begin dio <= seg[6]; srclk <= 1'd0; end

總結:就是存資料,移位,空出位置,下一個存進來,繼續移位,以此類推,最後存滿16個資料,鎖存資料,儲存暫存器拉高

FPGA數碼管知識點整理

module hc595_driver(
    clk,
    reset_n,
    seg,
    sel,
    dio,    
    srclk,  //儲存暫存器的時鐘輸入,上升沿時移位暫存器中的資料進入儲存暫存器
    rclk    //移位暫存器的時鐘輸入,上升沿時移位暫存器中資料一次移動一位。下降沿不變
    );
    
    input   clk;
    input   reset_n;
    input [7:0] seg;
    input [7:0] sel;
    
    output reg dio;    //ser  序列資料輸入端
    output reg srclk;
    output reg rclk;
    
    
    //595 頻率  2V 5M  4.5V  24M   50Mhz  20ns一個週期,一位10ns
    //取12.5Mhz   對應3.3V  正好是50Mhz的四分頻  80ns一週期,一位就是40ns
    parameter   MCNT = CLOCK_FREQ / (SRCLK_FREQ * 2) - 1;  //1
    parameter   CLOCK_FREQ = 50_000_000;  //50MHZ時鐘
    parameter   SRCLK_FREQ = 12_500_000;  //
     
    
    reg [29:0] div_cnt;  
    
    always @(posedge clk or negedge reset_n)
        if(!reset_n)
            div_cnt <= 0;
        else if(div_cnt == MCNT)    //20ns * 2 = 40ns
            div_cnt <= 0;
        else
            div_cnt <= div_cnt + 1'd1;
        
     reg [4:0] cnt;
     
     always @(posedge clk or negedge reset_n)
        if(!reset_n)
            cnt <= 0;
        else if(div_cnt == MCNT)       //40ns   移位暫存器的時鐘
            cnt <= cnt + 1'd1;
        
    always @(posedge clk or negedge reset_n)
        if(!reset_n) begin
        dio <= 1'd0; 
        srclk <= 1'd0; 
        rclk <= 1'd0;
        end
        else begin
            case(cnt)
                0:begin dio <= seg[7]; srclk <= 1'd0; rclk <= 1'd1; end
                1:begin rclk <= 1'd0;  srclk <= 1'd1; end
                2:begin dio <= seg[6]; srclk <= 1'd0; end
                3:begin                srclk <= 1'd1; end
                4:begin dio <= seg[5]; srclk <= 1'd0; end
                5:begin                srclk <= 1'd1; end
                6:begin dio <= seg[4]; srclk <= 1'd0; end
                7:begin                srclk <= 1'd1; end
                8:begin dio <= seg[3]; srclk <= 1'd0; end
                9:begin                srclk <= 1'd1; end
                10:begin dio <= seg[2]; srclk <= 1'd0; end
                11:begin                srclk <= 1'd1; end
                12:begin dio <= seg[1]; srclk <= 1'd0; end
                13:begin                srclk <= 1'd1; end
                14:begin dio <= seg[0]; srclk <= 1'd0; end
                15:begin                srclk <= 1'd1; end
                16:begin dio <= sel[7]; srclk <= 1'd0; end
                17:begin                srclk <= 1'd1; end
                18:begin dio <= sel[6]; srclk <= 1'd0; end
                19:begin                srclk <= 1'd1; end
                20:begin dio <= sel[5]; srclk <= 1'd0; end
                21:begin                srclk <= 1'd1; end
                22:begin dio <= sel[4]; srclk <= 1'd0; end
                23:begin                srclk <= 1'd1; end
                24:begin dio <= sel[3]; srclk <= 1'd0; end
                25:begin                srclk <= 1'd1; end
                26:begin dio <= sel[2]; srclk <= 1'd0; end
                27:begin                srclk <= 1'd1; end
                28:begin dio <= sel[1]; srclk <= 1'd0; end
                29:begin                srclk <= 1'd1; end
                30:begin dio <= sel[0]; srclk <= 1'd0; end
                31:begin                srclk <= 1'd1; end
                
             endcase   
         end
    
 
endmodule

最後寫一個測試檔案,例化上面兩個頂層檔案


module hex8_hc595_test(
    clk,
    reset_n,
    SW,
    dio,    
    srclk,  //儲存暫存器的時鐘輸,上升沿時移位暫存器中的資料進入儲存暫存器
    rclk
    );
    
    input   clk;
    input   reset_n;
input   [1:0] SW;
    
    output  dio;    //ser  序列資料輸入端
    output  srclk;
    output  rclk;
    
    reg [31:0] disp_data;
    wire    [7:0] sel,seg;
    
    hc595_driver    hc595_driver_inst(
    .clk(clk),    
    .reset_n(reset_n),
    .seg(seg),    
    .sel(sel),    
    .dio(dio),    
    .srclk(srclk),  
    .rclk(rclk)    
    

    );
    
    hex8 hex8_inst(
    
    .clk(clk),      
    .reset_n(reset_n),  
    .disp_data(disp_data),
    .sel(sel),      
    .seg(seg)       
   
    );
    
    always @(*)
        case(SW)
            0:disp_data <= 32'h01234567;
            1:disp_data <= 32'h89abcdef;
            2:disp_data <= 32'h02468ace;
            3:disp_data <= 32'h13578bdf;
        endcase
endmodule

最終透過兩個撥碼開關選擇顯示我們需要的數字。這邊的例化類似於引用標頭檔案一樣。

相關文章