FPGA数码管知识点整理

知识点:

  数码管控制分为位选和段选,通过位控制哪一个数码管亮,通过段选控制数码管中某一段亮。

  我硬件买的上面的是共阳极的,也就是段选位给低电平就能亮。

  

下面是段选的位控制要显示的数据。比如数字0只要让G位灭掉就行,通过给段选8'h1100_0000  (共阳极),将g和点灭掉就是0了

    

数码管的控制,通过动态控制,数码管的位选和段选,来控制显示数字

利用视觉残留,每1ms轮换一个数码管,20ms以内的闪烁,都可以被视觉残留利用,让人觉得一直在常亮,其实在闪烁

这样如果需要8个数码管亮灭,则需要16位IO管脚控制。这占用太多资源。这边会使用一个74HC595模块,来将数据进行移位操作+存储操作,这样就可以节省IO资源,其实就是将并转串。

将上面的位选和段选的sel 和 seg数据作为数据输入,这边是并行数据进去,通过模块移位寄存,最后将数据串行输出。


代码工程:

  先完成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寄存器的手册中整理的时序图和功能表等。

直接看下面这个图,移位寄存器上升沿时候,数据进入移位寄存器,在存储寄存器上升沿时输出到并行端口。

这边的srclk是移位寄存器,rclk是存储寄存器

SRCLK - SHCP

RCLK - STCP

因为这边是两个595连起来,因为需要16位数据传输,所以是每16位数据移位结束后,再进行锁存数据。

这边在移位寄存器的上升沿,通过DS/DIO串行数据输入口,不断输入数据我们这边是将seg和sel信号16位输入给dio里面

这边根据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个数据,锁存数据,存储寄存器拉高

                

 

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

最终通过两个拨码开关选择显示我们需要的数字。这边的例化类似于引用头文件一样。

posted @ 2024-03-03 14:54  祈愿树下  阅读(91)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css