基于FPGA的数码管的静态显示-基础篇

数码管简介

数码管是一种半导体发光器件,其基本单元是发光二极管。数码管按段数一般分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管(多一个小数点显 示)。下面将详细介绍本次实验使用的八段数码管。

可以选择的显示设备有很多,而数码管是使用最多,最简单的显示设备之一,因为它具有响应时间短、体积小、重量轻、寿命长等优点,简单来说就是经济实惠。

在许多项目设计中,我们通常需要一些显示设备显示我们需要的信息,比如一些需要仪器仪表(水表、电表);工业自动化中,用于显示设备状态、传感器数据;一些家用电器,如微波炉、体重秤;交通信号灯;电子时钟等等。

八段数码管

八段数码管是一个8字型数码管,分为八段:a、b、c、d、e、 f、g、dp,其中dp为小数点,每一段即为一个发光二极管,这样的八段我们称之为段选信 号。数码管常用的有10根管脚,每一段有一根管脚,另外两根管脚为一个数码管的公共 端,两根互相连接。

数码管分为共阳极数码管和共阴极数码管。共阳极数码管就是把发光二极管的正极连 接在一起作为一个引脚,负极分开。相反的,共阴极数码管就是把发光二极管的阴极连接 在一起作为一个引脚,正极分开。这两者的区别在于,公共端是连接到地还是高电平,对于共阳极数码管需要给对应段低电平才会使其点亮,而对于共阴极数码管则需要给其高电平才会点亮。本次实验使用的是共阳极数码管,也就是说给对应段低电平才会被点亮。

数码管编码译码表

这个编码方向是从dp->a(低位->高位)的。给不同的段点亮可显示0~f的值,其中只有字母B和D是小写的,因为大B会和数字8重复,大D会和数字0重复,其余字母均为大写的。

段式数码管工作方式有两种:静态显示和动态显示。静态显示的特点是每个数码管的段选必须接一个 8位数据线来显示字形,显示字形可一直保持,直到送入新字形码为止。 那么如果点亮6个码管是不是需要48位数据线去分别控制每一个码管的段选?当然这种方法也可以,但是其占用的I/O口较多,因此硬件电路比较复杂,成本较高,所以很少使用。

我们将六个数码管的段选信号连接在一起,而位选(sel)独立控 制,这样六个数码管接在一起就少了 8×5个 I/O口。这里对位选信号特别说明一下:由上图可以看到每一个数码管都有一个位选信号,而这个位选信号就控制着数码管的亮灭。这样我们就可以通过位选信号去控制数码管亮,而在同一时刻,位选选通的数码管上显示的字形是一样的,因为我们将 6个数码管相对应的段选连在了一起,数码管的显示自然就相同了,数码管的这种显示方式即为静态显示 。而如果要让每个数码管显示的值不同,我们 要用到另外一种显示方式,即**动态显示。**这样我们控制数码管需要占用14个I/O口资源(8个段 选,6个位选)。如果想要更节省I/O资源的话,我们可以使用74hc595芯片控制数码管。

六位数码管等效电路图

74hc595简介

74HC595 是一个 8 位串行输入、并行输出的位移缓存器。其内部具有 8位移位寄存器和一个存储器,具有三态输出功能。三态是指1(高电平)、0(低电平)、Z(高阻态)。

74hc595芯片引脚图

根据查看74hc595芯片的数据手册,先介绍一下芯片引脚图:

输入引脚介绍:

SI:串行数据输入

G非:输出使能,有效时QA-QH输出0/1;无效时QA-QH输出1'bz(高阻态)

RCK:锁存时钟,当上升沿到来时,将移位寄存器中的数据存到锁存寄存器中,没有到来时,锁存寄存器内容保持不变

SCK:移位时钟,当上升沿到来时,将SI的值存入移位寄存器中的第一个D触发器中,其余的7个D触发器均存入前一个D触发器的内容;没有到来时,移位寄存器内容保持不变

SCLR非:低电平有效复位,将移位寄存器清0

输出引脚介绍:

QA-QH:并行输出,输出顺序如下图所示,Q0对应QA,后面以此类推。最先进入的被移位到最后一个D触发器。

QH':芯片级联端口引脚

74hc595芯片内部结构图

根据上面的引脚介绍,这里我们很容易理解这里的内部结构,8位移位寄存器和8位锁存寄存器均是由D触发器组成的。8位移位寄存器是由8个D触发器级联组成的,8位锁存寄存器用于锁存对应位的移位寄存器的值。当sck经过8个时钟上升沿,我们就可以进行一次锁存,所以我们可以知道Frck=Fsck/8。G非用于控制三态门的输出。

由上图可知,我们可以使用fpga模拟74hc595芯片的功能。

模拟74hc595芯片功能

绘制模块框图

这里不再绘制波形图,可以根据上面的芯片内部结构图来编写代码。

编写模块代码

复制代码
module  device_74hc595(      //模拟74hc595芯片
    input   wire    sck     ,//移位时钟
    input   wire    rck     ,//锁存时钟
    input   wire    sclr_n  ,//低电平有效复位
    input   wire    si      ,//串行数据输入
    input   wire    g_n     ,//输出使能
    
    output  wire    qa      ,//并行输出
    output  wire    qb      ,
    output  wire    qc      ,
    output  wire    qd      ,
    output  wire    qe      ,
    output  wire    qf      ,
    output  wire    qg      ,
    output  wire    qh      ,
    output  wire    qh_1     //芯片级联端口
);
//定义8个移位寄存器
reg     qa_r1;
reg     qb_r1;
reg     qc_r1;
reg     qd_r1;
reg     qe_r1;
reg     qf_r1;
reg     qg_r1;
reg     qh_r1;
//8位的移位寄存器
always@(posedge sck or negedge sclr_n)
begin
    if(!sclr_n)
        begin
            qa_r1 <= 1'b0;
            qb_r1 <= 1'b0;
            qc_r1 <= 1'b0;
            qd_r1 <= 1'b0;
            qe_r1 <= 1'b0;
            qf_r1 <= 1'b0;
            qg_r1 <= 1'b0;
            qh_r1 <= 1'b0;
        end
    else
        begin
            qa_r1 <= si     ;
            qb_r1 <= qa_r1  ;
            qc_r1 <= qb_r1  ;
            qd_r1 <= qc_r1  ;
            qe_r1 <= qd_r1  ;
            qf_r1 <= qe_r1  ;
            qg_r1 <= qf_r1  ;
            qh_r1 <= qg_r1  ;
        end
end
//定义8个锁存寄存器
reg     qa_r2;
reg     qb_r2;
reg     qc_r2;
reg     qd_r2;
reg     qe_r2;
reg     qf_r2;
reg     qg_r2;
reg     qh_r2;
//8位的锁存寄存器
always@(posedge rck)
begin
    qa_r2 <= qa_r1;
    qb_r2 <= qb_r1;
    qc_r2 <= qc_r1;
    qd_r2 <= qd_r1;
    qe_r2 <= qe_r1;
    qf_r2 <= qf_r1;
    qg_r2 <= qg_r1;
    qh_r2 <= qh_r1;
end
//并行输出(三态门控制输出)
//标准写法组合逻辑可以输出1'bz(大小写都可以),时序逻辑不可以输出高阻态
//但是由于软件综合比较好,实际上时序逻辑和组合逻辑均可以输出高阻态
assign  qa = (g_n) ? 1'bz : qa_r2;
assign  qb = (g_n) ? 1'bz : qb_r2;
assign  qc = (g_n) ? 1'bz : qc_r2;
assign  qd = (g_n) ? 1'bz : qd_r2;
assign  qe = (g_n) ? 1'bz : qe_r2;
assign  qf = (g_n) ? 1'bz : qf_r2;
assign  qg = (g_n) ? 1'bz : qg_r2;
assign  qh = (g_n) ? 1'bz : qh_r2;
//级联端口
assign  qh_1 = qh_r1;
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  device_74hc595_tb();

reg     sck   ;//移位时钟
reg     rck   ;//锁存时钟
reg     sclr_n;//低电平有效复位
reg     si    ;//串行数据输入
reg     g_n   ;//输出使能

initial
begin
    sck    <= 1'b0;
    rck    <= 1'b0;
    sclr_n <= 1'b0;//有效复位
    si     <= 1'b0;
    g_n    <= 1'b0;//输出使能有效
    #123
    sclr_n <= 1'b1;//复位无效
    #20000
    g_n    <= 1'b1;//输出使能无效
    #1000
    g_n    <= 1'b0;
end
//串转并(1转8)
always #100    sck = ~sck;//移位时钟f=5Mhz,T=200ns
always #800    rck = ~rck;//锁存时钟f=5Mhz/8,T=1600ns

always #200    si = {$random} % 2;

device_74hc595  device_74hc595_inst(
    .sck    (sck   ),
    .rck    (rck   ),
    .sclr_n (sclr_n),
    .si     (si    ),
    .g_n    (g_n   ),

    .qa     (),
    .qb     (),
    .qc     (),
    .qd     (),
    .qe     (),
    .qf     (),
    .qg     (),
    .qh     (),
    .qh_1   () 
);
endmodule

仿真验证

一开始的红线(不定态)产生的原因:

蓝色线(高组态)产生的原因:

si串行输入0010_0100,输出并行(qa-qh)也为0010_0100,仿真验证通过。

练习1

练习1:使用74hc595模块,完成串行转14位并行设计,并在仿真截图上标注出一次转换效果

绘制模块框图

编写模块代码

复制代码
module  device_74hc595_test1(
    input   wire    sck     ,//移位时钟
    input   wire    rck     ,//锁存时钟
    input   wire    sclr_n  ,//低电平有效复位
    input   wire    si      ,//串行数据输入
    input   wire    g_n     ,//输出使能
    
    output  wire    qa      ,//并行输出
    output  wire    qb      ,
    output  wire    qc      ,
    output  wire    qd      ,
    output  wire    qe      ,
    output  wire    qf      ,
    output  wire    qg      ,
    output  wire    qh      ,
    output  wire    qi      ,
    output  wire    qj      ,
    output  wire    qk      ,
    output  wire    ql      ,
    output  wire    qm      ,
    output  wire    qn
);
wire    si2;

device_74hc595  device_74hc595_test1_inst1(
    .sck    (sck   ),
    .rck    (rck   ),
    .sclr_n (sclr_n),
    .si     (si    ),
    .g_n    (g_n   ),

    .qa     (qa    ),
    .qb     (qb    ),
    .qc     (qc    ),
    .qd     (qd    ),
    .qe     (qe    ),
    .qf     (qf    ),
    .qg     (qg    ),
    .qh     (qh    ),
    .qh_1   (si2   ) 
);

device_74hc595  device_74hc595_test1_inst2(
    .sck    (sck   ),
    .rck    (rck   ),
    .sclr_n (sclr_n),
    .si     (si2   ),
    .g_n    (g_n   ),

    .qa     (qi   ),
    .qb     (qj   ),
    .qc     (qk   ),
    .qd     (ql   ),
    .qe     (qm   ),
    .qf     (qn   ),
    .qg     (     ),
    .qh     (     ),
    .qh_1   (     ) 
);
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  device_74hc595_test1_tb();

reg     sck   ;//移位时钟
reg     rck   ;//锁存时钟
reg     sclr_n;//低电平有效复位
reg     si    ;//串行数据输入
reg     g_n   ;//输出使能

initial
begin
    sck    <= 1'b0;
    rck    <= 1'b0;
    sclr_n <= 1'b0;//有效复位
    si     <= 1'b0;
    g_n    <= 1'b0;//输出使能有效
    #123
    sclr_n <= 1'b1;//复位无效
    #20000
    g_n    <= 1'b1;//输出使能无效
    #1000
    g_n    <= 1'b0;
end
//串转并(1转14)
always #100    sck = ~sck;//移位时钟f=5Mhz,T=200ns
always #1400   rck = ~rck;//锁存时钟f=5Mhz/14,T=2800ns

always #200    si = {$random} % 2;

device_74hc595_test1  device_74hc595_test1_inst(
    .sck    (sck   ),
    .rck    (rck   ),
    .sclr_n (sclr_n),
    .si     (si    ),
    .g_n    (g_n   ),

    .qa      (),//并行输出
    .qb      (),
    .qc      (),
    .qd      (),
    .qe      (),
    .qf      (),
    .qg      (),
    .qh      (),
    .qi      (),
    .qj      (),
    .qk      (),
    .ql      (),
    .qm      (),
    .qn      ()
);
endmodule

仿真验证

仿真没有问题,仿真通过。

练习2

练习2.整理数码管显示原理;74hc595芯片输入输出关系

数码管显示原理:数码管(LED Segment Display)内部是由多个发光二极管(LED)组成的,常见的数码管有 7段 (显示数字)数码管和 8段(多一个小数点 DP)数码管。7段数码管是一个数字8字型数码管,被分为7段:a,b,c,d,e,f,g。 8段数码管也是一个8字型数码管,分为八段:a、b、c、d、e、 f、g、dp,其中dp为小数点,每一段都是一个发光二极管,这样的八段或者是7段均称之为段选信号。数码管分为共阳极数码管和共阴极数码管。共阳极数码管就是把发光二极管的正极均连接在一起,作为一个引脚接VCC,负极分开,所以共阳极数码管各个段是低电平点亮。相反的,共阴极数码管就是把发光二极管的阴极连接 在一起,作为一个引脚接地,正极分开,所以共阴极数码管各个段是高电平点亮。也就是说对于一位8段数码管,我们只需要控制段选信号去显示我们想要的字符图形。对于多位的8段数码管,假设有n位,我们需要控制8*n个引脚,为了节省io口资源,我们可以将每一位数码管的段选信号均连接在一起,这样可以减少8*(n-1)个引脚,只需要位选信号和段选信号(n+8个引脚),如果想要进一步的节省io口资源,我们可以使用1路串行输入、8路并行输出的位移缓存器74hc595芯片,其内部具有 8位移位寄存器和8位锁存寄存器。通过74hc595芯片的级联,我们至少可以使用3个引脚去控制N位8段数码管的位选和段选,进行显示我们想要的字符图样。

74hc595芯片输入输出关系:

输入引脚介绍:

SI:串行数据输入

G非:输出使能,有效时QA-QH输出0/1;无效时QA-QH输出1'bz(高阻态)

RCK:锁存时钟,当上升沿到来时,将移位寄存器中的数据存到锁存寄存器中,没有到来时,锁存寄存器内容保持不变

SCK:移位时钟,当上升沿到来时,将SI的值存入移位寄存器中的第一个D触发器中,其余的7个D触发器均存入前一个D触发器的内容;没有到来时,移位寄存器内容保持不变

SCLR非:低电平有效复位,将移位寄存器清0

输出引脚介绍:

QA-QH:并行输出,输出顺序如下图所示,Q0对应QA,后面以此类推。最先进入的被移位到最后一个D触发器。

QH':芯片级联端口引脚

6位8段数码管的静态显示

练习3:整理静态数码管设计流程,实现六位数码管显示0~f数值循环显示,每个显示0.5s时间。

本次实验数码管为共阳极数码管,即段选为低电平点亮。从图中可以看到,其位选为高电平时才能点亮对应数码管,SI串行输入数据顺序:DIG6、DIG5、DIG4、DIG3、DIG2、DIG1、dp、g、f、e、d、c、b、a。DIG1-DIG6对应数码管从左往右的顺序,我们一般喜欢将左边视为数据高位。所以DIG1(sel5)依次类推,DIG6(sel0)对应的就是开发板上最右侧数码管。开发板上搭载了两片74HC595芯片用于输出数码管驱动信号,其中MR复位引脚我们接到了VCC防止数据清零,所以这两片74HC595芯片我们只用四个IO口控制即可。 我们将两片74HC595进行级联,一片的 Q7S输出端接到另一片的数据输入端DS,这样我们输入的14位串行输入数码管信号的前六位就会在第二片就行输出。(注意:因为是移位寄存器,如果一次共输入 14位数据,那么第一位输入的串行数据会在第二片 74HC595芯片的Q5输出)。

绘制模块框图及波形图

shcp4分频的原因:shcp (移位寄存器时钟),这个是ds数据进入移位寄存器的时钟,它的频率是有限制的,我们可以从数据手册中看到,shcp和stcp的最大频率是有限制的,由原理图可以看到我们使用的电 压是 3.3v,同时温度不同其支持的最大频率也不同,可根据自己的实验环境进行频率的选取,本实验使用系统时钟(50MHz)四分频得到的 shcp 时钟(12.5MHz)去进行驱动,而 stcp 时钟是在我们串行输入 14 位数码管之后拉高的,其频率远远小于 shcp,所以这里我们只要确定 shcp 的频率即可。

编写模块代码

顶层模块seg_595_static

复制代码
module  seg_595_static(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  wire        ds      ,
    output  wire        shcp    ,
    output  wire        stcp    ,
    output  wire        oe
);

wire [7:0]   seg;
wire [5:0]   sel;

seg_static  seg_static_1
(
    .clk   (clk  )  ,
    .rst_n (rst_n)  ,

    .seg   (seg  )  ,
    .sel   (sel  )
);

hc595_ctrl  hc595_ctrl_1
(
    .clk  (clk  )  ,
    .rst_n(rst_n)  ,
    .seg  (seg  )  ,
    .sel  (sel  )  ,

    .ds   (ds   )  ,
    .shcp (shcp )  ,
    .stcp (stcp )  ,
    .oe   (oe   )
);
endmodule

功能模块seg_static

复制代码
module  seg_static
#(
    parameter   CNT_MAX_500MS = 25'd24_999_999//25'd24_999_999
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    
    output  reg [7:0]   seg     ,
    output  reg [5:0]   sel
);

reg [24:0]  cnt_500ms;
reg [3:0]   cnt_data ;
//cnt_500ms
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_500ms <= 25'd0;
    else    if(cnt_500ms == CNT_MAX_500MS)
        cnt_500ms <= 25'd0;
    else
        cnt_500ms <= cnt_500ms + 1'b1;
end
//cnt_data
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_data <= 4'h0;
    else    if(cnt_data == 4'hf && cnt_500ms == CNT_MAX_500MS)
        cnt_data <= 4'h0;
    else    if(cnt_500ms == CNT_MAX_500MS)
        cnt_data <= cnt_data + 1'b1;
    else
        cnt_data <= cnt_data;
end
//seg
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        seg <= 8'hff;//数码管8段全灭
    else case(cnt_data)
        4'h0 : seg <= 8'hc0;
        4'h1 : seg <= 8'hf9;
        4'h2 : seg <= 8'ha4;
        4'h3 : seg <= 8'hb0;
        4'h4 : seg <= 8'h99;
        4'h5 : seg <= 8'h92;
        4'h6 : seg <= 8'h82;
        4'h7 : seg <= 8'hf8;
        4'h8 : seg <= 8'h80;
        4'h9 : 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;
        default:seg <= 8'hff;//数码管8段全灭
    endcase
end
//sel
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        sel <= 6'd0;
    else
        sel <= 6'b111_111;
end
endmodule

功能模块hc595_ctrl

复制代码
module  hc595_ctrl
#(
    parameter   CNT_MAX_DIV4 = 2'd3     ,
    parameter   CNT_MAX_BIT  = 4'd13
)
(
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire [7:0]  seg     ,
    input   wire [5:0]  sel     ,
    
    output  reg         ds      ,
    output  reg         shcp    ,
    output  reg         stcp    ,
    output  reg         oe
);

reg [13:0]  data    ;
reg [1:0]   cnt_div4;
reg [3:0]   cnt_bit ;
//data   
always@(*)
begin
    if(!rst_n)
        data = 14'd0;
    else
        data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel[5:0]};
end
//cnt_div4    
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_div4 <= 2'd0;
    else    if(cnt_div4 == CNT_MAX_DIV4)
        cnt_div4 <= 2'd0;
    else
        cnt_div4 <= cnt_div4 + 1'b1;
end
//cnt_bit
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        cnt_bit <= 4'd0;
    else    if(cnt_bit == CNT_MAX_BIT && cnt_div4 == CNT_MAX_DIV4)
        cnt_bit <= 4'd0;
    else    if(cnt_div4 == CNT_MAX_DIV4)
        cnt_bit <= cnt_bit + 1'b1;
    else
        cnt_bit <= cnt_bit;
end
// ds  
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        ds <= 1'd0;
    else
        ds <= data[cnt_bit];
end
// shcp
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        shcp <= 1'b0;
    else    if(cnt_div4 == CNT_MAX_DIV4 - 1'b1 || cnt_div4 == CNT_MAX_DIV4)
        shcp <= 1'b1;
    else
        shcp <= 1'b0;
end
// stcp
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        stcp <= 1'b0;
    else    if(cnt_bit == CNT_MAX_BIT && cnt_div4 == CNT_MAX_DIV4)
        stcp <= 1'b1;
    else
        stcp <= 1'b0;
end
// oe
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        oe <= 1'b1;
    else
        oe <= 1'b0;
end
endmodule

编写仿真代码

复制代码
`timescale  1ns/1ps
module  seg_595_static_tb();

reg         clk   ;
reg         rst_n ;

wire        ds    ;
wire        shcp  ;
wire        stcp  ;
wire        oe    ;

initial
begin
    clk   = 1'b0;
    rst_n = 1'b0;
    #123
    rst_n = 1'b1;
end

always #10 clk = ~clk;

seg_595_static  seg_595_static_inst(
    .clk    (clk  ) ,
    .rst_n  (rst_n) ,

    .ds     (ds   ) ,
    .shcp   (shcp ) ,
    .stcp   (stcp ) ,
    .oe     (oe   )
);
endmodule

仿真验证

仿真验证通过。假设当seg数值切换时,stcp还没有锁存,导致在seg图形字样切换时产生了误差,stcp的周期为14*4*20=1120ns,约为1us锁存一次,即为1us的误差,而我们的seg是500ms切换一次数据,误差率在1us/1120ns,误差小到可以忽略。