应用实例:
(1)使用串口发送实现ACX720开发板时钟显示


前言

本章将实现 FPGA 驱动数码管动态显示并提取出实现的电路结构,从电路结构入手编写代码,仿真对设计进行验证。最终板级调试时使用 Virtual Input/Output(VIO,虚拟输入/输出端口工具),输入需要显示的数据,数码管则显示对应数值。

提示:以下是本篇文章正文内容,下面案例可供参考

一、数码管驱动原理

其中 8 段数码管的结构图如下图所示,

由上图可以看出数码管有两种结构:共阴极与共阳极。这两者的区别在于,公共端是连接到地还是高电平,对于共阴数码管需要给对应段以高电平才会使其点亮,而对于共阳极数码管则需要给低电平才会点亮。ACX720 上板载的是共阳数码管。同时为了显示数字或字符,必须对数字或字符进行编码译码。这里先不考虑小数点也就是简化为 7 段数码管,其编码译码格式如下表所示:

段式数码管工作方式有两种:静态显示方式和动态显示方式。静态显示的特点是每个数码管的段选必须接一个 8 位数据线来保持显示的字形码。当送入一次字形码后,显示字形可一直保持,直到送入新字形码为止。这种方法由于每一个数码管均需要独立的数据线因此硬件电路比较复杂,成本较高,很少使用。为了节约 IO 以及成本一般采用如下图所示的电路结构,这样 3 个数码管接在一起就比静态的少了 7*2 个 I/O。

这样就实现了另一种显示模式,动态显示。动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。

二、设计思路

现在举例假设将扫描时间定为 1S,这三个数码管分成 3s,第 1 秒时 sel 数据线上为b100,这时数码管 0 被选中,这时 a=0,数码管 0 的 LED0 就可以点亮;第 2 秒时 sel 数据线上为b010,这时数码管 1 被选中,这时 b=0,数码管 1 的 LED1 就可以点亮;第 3 秒时 sel 数据线上为b001,这时数码管 2 被选中,这时 c=0,数码管 2 的 LED2 就可以点亮。这时的效果会是数码管 0 的 LED0 亮一秒后数码管 1 的 LED1 亮一秒最后是数码管 2 的 LED2 亮一秒,这样再次循环。
这样如果使用 1ms 刷新时间的话由于数码管的余辉效应以及人的视觉暂留这样就会出现数码管 0 的 LED0、数码管 1 的 LED1 以及数码管 2 的 LED2 “同时”亮,并不会有闪烁感。

由上面的分析可以得出如下图的输入输出框图,其接口列表如下表所示:

原理图:

三、实现代码

module hex8(
    Clk,
    Reset_n,
    Disp_Data,
    SEL,
    SEG
);
    
    input Clk;
    input Reset_n;
    input [31:0]Disp_Data;
    output reg[7:0]SEL;
    output reg[7:0]SEG;//seg[0]-a,seg[1]-b...seg[7]-h
    
    reg clk_1k;
    reg [15:0]div_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        div_cnt <= 0;
    else if(div_cnt >= 49999)
        div_cnt <= 0;
    else
        div_cnt <= div_cnt + 1'b1;
        
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        clk_1k <= 0;
    else if(div_cnt == 49999)
        clk_1k <= 1'b1;
    else
        clk_1k <= 0;
      
    reg [2:0]num_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        num_cnt <= 0;
    else if(clk_1k)
        num_cnt <= num_cnt + 1'b1;
        
    always@(posedge Clk)
        case(num_cnt)
            0: SEL = 8'b00000001;
            1: SEL = 8'b00000010;
            2: SEL = 8'b00000100;
            3: SEL = 8'b00001000;
            4: SEL = 8'b00010000;
            5: SEL = 8'b00100000;
            6: SEL = 8'b01000000;
            7: SEL = 8'b10000000;
        endcase

    reg[3:0]disp_tmp;
    always@(posedge Clk)
        case(num_cnt)
            7: disp_tmp = Disp_Data[31:28];
            6: disp_tmp = Disp_Data[27:24];
            5: disp_tmp = Disp_Data[23:20];
            4: disp_tmp = Disp_Data[19:16];
            3: disp_tmp = Disp_Data[15:12];
            2: disp_tmp = Disp_Data[11:8];
            1: disp_tmp = Disp_Data[7:4];
            0: disp_tmp = Disp_Data[3:0];
        endcase   

     always@(posedge Clk)
        case(disp_tmp)
            0: SEG = 8'hc0;
            1: SEG = 8'hf9;
            2: SEG = 8'ha4;
            3: SEG = 8'hb0;
            4: SEG = 8'h99;
            5: SEG = 8'h92;
            6: SEG = 8'h82;
            7: SEG = 8'hf8;
            8: SEG = 8'h80;
            9: SEG = 8'h90;
            4'ha: SEG = 8'h88;
            4'hb: SEG = 8'h83;
            4'hc: SEG = 8'hc6;
            4'hd: SEG = 8'ha1;
            4'he: SEG = 8'h86;
            4'hf: SEG = 8'h8e;
        endcase   

endmodule

四、hex8_tb文件

`timescale 1ns / 1ps
module hex8_tb();

    reg Clk;
    reg Reset_n;
    reg [31:0]Disp_Data;
    wire [7:0]SEL;
    wire [7:0]SEG;
    
    hex8 hex8(
        Clk,
        Reset_n,
        Disp_Data,
        SEL,
        SEG
    );  
    
    initial Clk = 1;
    always#10 Clk = ~Clk;
    
    initial begin
        Reset_n = 0;
        Disp_Data = 32'h00000000;
        #201;
        Reset_n = 1;
        #2000;
        Disp_Data = 32'h12345678;
        #10000000;
        Disp_Data = 32'h9abcdef0;
        #10000000;
        $stop;
    end
    
endmodule

五、上板测试

由于我使用的ACX720开发板数码管输出没有八个管脚,acx720 电路设计用到了芯片 74HC595,该芯片的作用是移位寄存器,通过移位的方式,节省 FPGA 的管脚。FPGA 只需要输出 3 个管脚,即可达到发送数码管数据的目的,与传统段
选位选方式相比,大大节省了 IO 设计资源。74HC595 的具体使用方法和技术参数如下:在数据手册中可以看出,不同工作温度和工作电压下 74HC595 的芯片工作频率值不相同,分别如表 15.5 与 15.6 所示。由于在学习板中芯片采用 3.3V 供电,这样在设计其工作频率时,直接使用 50M晶振四分频后的时钟作为其工作时钟,使其在 3.3V 状态下工作于12.5M 频率。为了能够上板显示,与是按74HC595芯片的驱动来设计,将8位SEL,和8位SEG输入,变成三位输出。

1.74HC595时序图

从上面的分析可得出所示的框图,其接口列表如上图。

驱动输入输出端口功能描述:

2. HC595_Driver设计

代码设计如下:

module HC595_Driver(
    Clk,
    Reset_n,
    Data,
    S_EN,
    SH_CP,
    ST_CP,
    DS
);
    
    input Clk;
    input Reset_n;
    input [15:0]Data;
    input S_EN;
    output reg SH_CP;
    output reg ST_CP;
    output reg DS;
    parameter CNT_MAX = 2;
    
    reg [15:0]r_data;
    always@(posedge Clk)
        if(S_EN)
            r_data <= Data;

    reg [7:0]divider_cnt;//·ÖƵ¼ÆÊýÆ÷
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        divider_cnt <= 0;
    else if(divider_cnt == CNT_MAX - 1'd1)
        divider_cnt <= 0;
    else
        divider_cnt <= divider_cnt + 1'b1;
    
    wire sck_plus;
    assign sck_plus = (divider_cnt == CNT_MAX - 1'd1);
    
    reg [5:0]SHCP_EDGE_CNT;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        SHCP_EDGE_CNT <= 0;
    else if(sck_plus)begin
        if(SHCP_EDGE_CNT == 6'd32)
            SHCP_EDGE_CNT <= 0;
        else
            SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
    end
        
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        SH_CP <= 0;
        ST_CP <= 0;
        DS <= 0;   
    end
    else begin
        case(SHCP_EDGE_CNT)
            0: begin SH_CP <= 0;ST_CP <= 1'd0;DS <= r_data[15];end
            1: SH_CP <= 1'd1;
            2: begin SH_CP <= 0;DS <= r_data[14];end
            3: SH_CP <= 1'd1;
            4: begin SH_CP <= 0;DS <= r_data[13];end   
            5: SH_CP <= 1'd1;
            6: begin SH_CP <= 0;DS <= r_data[12];end
            7: SH_CP <= 1'd1;
            8: begin SH_CP <= 0;DS <= r_data[11];end   
            9: SH_CP <= 1'd1;
            10: begin SH_CP <= 0;DS <= r_data[10];end
            11: SH_CP <= 1'd1;
            12: begin SH_CP <= 0;DS <= r_data[9];end  
            13: SH_CP <= 1'd1;
            14: begin SH_CP <= 0;DS <= r_data[8];end
            15: SH_CP <= 1'd1;
            16: begin SH_CP <= 0;DS <= r_data[7];end   
            17: SH_CP <= 1'd1;
            18: begin SH_CP <= 0;DS <= r_data[6];end
            19: SH_CP <= 1'd1;
            20: begin SH_CP <= 0;DS <= r_data[5];end   
            21: SH_CP <= 1'd1;
            22: begin SH_CP <= 0;DS <= r_data[4];end
            23: SH_CP <= 1'd1;
            24: begin SH_CP <= 0;DS <= r_data[3];end   
            25: SH_CP <= 1'd1;
            26: begin SH_CP <= 0;DS <= r_data[2];end
            27: SH_CP <= 1'd1;
            28: begin SH_CP <= 0;DS <= r_data[1];end
            29: SH_CP <= 1'd1;
            30: begin SH_CP <= 0;DS <= r_data[0];end
            31: SH_CP <= 1'd1;
            32: ST_CP <= 1'd1;
            default:
                begin
                    SH_CP <= 0;
                    ST_CP <= 0;
                    DS <= 0;   
                end
        endcase
    end

endmodule

3.HC595_Driver_tb文件

HC595_Driver_tb文件展示:

`timescale 1ns / 1ps

module HC595_Driver_tb;

    reg Clk;
    reg Reset_n;
    reg [15:0]Data;
    reg S_EN;
    wire SH_CP;
    wire ST_CP;
    wire DS;
    
    HC595_Driver HC595_Driver(
        Clk,
        Reset_n,
        Data,
        S_EN,
        SH_CP,
        ST_CP,
        DS
    );
    
    initial Clk = 1;
    always #10 Clk = ~Clk;
    
    initial begin
        Reset_n = 0;
        Data = 0;
        S_EN = 0;
        #201;
        Reset_n = 1;
        #500;
        Data = 16'h47a9;
        S_EN = 1;
        #20;
        S_EN = 0;
        #4000;
        
        Data = 16'h5832;
        S_EN = 1;
        #20;
        S_EN = 0;
        #4000;       
        $stop;    
    end
endmodule

4.上板代码展示

于是上板代码如下:

module top_test(
    Clk,
    Reset_n,
    SEL,
    SEG,
    SH_CP,
    ST_CP,
    DS
);
    
    input Clk;
    input Reset_n;

    output [7:0]SEL;
    output [7:0]SEG;//seg[0]-a,seg[1]-b...seg[7]-h
    
    output SH_CP;
    output ST_CP;
    output DS;
    
    wire [31:0]Disp_Data;
    
    hex8 hex8(
        Clk,
        Reset_n,
        Disp_Data,
        SEL,
        SEG
    ); 
    wire [15:0]Data;
    assign Data = {SEG,SEL};
    
    wire S_EN;
    assign S_EN = 1;
    
    HC595_Driver HC595_Driver(
        Clk,
        Reset_n,
        Data,
        S_EN,
        SH_CP,
        ST_CP,
        DS
    );
    
    assign Disp_Data = 32'h12345678;    
    
endmodule

【附件:】链接:https://pan.baidu.com/s/1bDwngTNKkZrw6Koj0mjNGw?pwd=sc5t
提取码:sc5t