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

可以选择的显示设备有很多,而数码管是使用最多,最简单的显示设备之一,因为它具有响应时间短、体积小、重量轻、寿命长等优点,简单来说就是经济实惠。
在许多项目设计中,我们通常需要一些显示设备显示我们需要的信息,比如一些需要仪器仪表(水表、电表);工业自动化中,用于显示设备状态、传感器数据;一些家用电器,如微波炉、体重秤;交通信号灯;电子时钟等等。

八段数码管
八段数码管是一个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,误差小到可以忽略。