前言
数码管显示分为静态显示和动态显示;
静态显示:一个数码管共8段,每个数码管段选接入8位的数据线来显示字符,送入一个字符永久保持,直到送入一个新的字符;每个数码管驱动需要独立的数据线,硬件电路比较复杂,成本高,一般不用。
动态显示:每个数码管段选并联到8位数据线上,利用位选选择哪一个数码管显示,轮流的进行位选送入相应的字符。循环扫描所有数码管,利用人视觉暂留作用,使人感觉数码管都在同时显示。

十六线制驱动数码管
总体实现:设置扫描时钟频率,比如使用1ms的刷新时间让8个数码管轮流显示,即第1ms选择数码管1,让其显示相应的段码;第2ms选择数码管2,让其显示相应的段码;以此类推依次显示8个数码管,最后循环以上操作。
根据数码管动态显示原理,需要8位数据线进行位选、8位数据线进行段选共16位数据线进行驱动,而且还要实现的是位选与段选的配合:当位选段选择某个数码管时段选端要显示其相应的字符【都是组合逻辑实现,段选位选同时输出】。
模块设计
再说一遍,总体要实现的是同时输出8位位选信号以及对应的8位段码
1、分频模块:
分频计数产生1KHz的扫描时钟信号Clk_1k,即对应数码管的刷新频率;
2、循环移位模块:
1KHz时钟作为驱动信号,驱动8位循环移位模块进行位选,即输出作为8选1多路器的选择端;
3、8选1多路器:
根据位选信号,选择输出对应要显示的字符;
这里给所有数码管要显示字符都已经给定,即4位要显示的数据;
4、段选输出–4输出查找表:
对8选1多路器输出的字符进行译码,段选输出;
5、位选输出:
2选1多路器,使能数码管时输出循环移位模块的位选信号。
逻辑电路图:

代码
1、分频模块
// 分频计数模块
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 15'b0;
else if(!En)
div_cnt <= 15'b0; // 数码管没有使能,分频也就没有意义
else if(div_cnt == 249999)
div_cnt <= 15'b0;
else
div_cnt <= div_cnt + 1'b1;
// 分频计数产生1K扫描时钟信号
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
Clk_1k <= 1'b0;
else if(div_cnt == 249999)
Clk_1k <= ~Clk_1k;
else
Clk_1k <= Clk_1k;
2、循环移位模块
1KHz时钟信号驱动8位循环移位寄存器,其输出作为8选1多路器的选择端;
always@(posedge Clk_1k or negedge Rst_n)
if(!Rst_n)
sel_r <= 8'b0000_0001;
else if(sel_r == 8'b1000_0000) // 说明已经循环移位一遍了,重新开始扫描
sel_r <= 8'b0000_0001;
else
sel_r <= sel_r << 1;
3、8选1多路器
8个数码管要显示的字符全部寄存在Data_disp,每个数码管占4位,当选择哪一个数码管将对应的字符输出到译码电路。
always@(*)
case(sel_r)
8'b0000_0001:data_tmp = Data_disp[3:0];
8'b0000_0010:data_tmp = Data_disp[7:4];
8'b0000_0100:data_tmp = Data_disp[11:8];
8'b0000_1000:data_tmp = Data_disp[15:12];
8'b0001_0000:data_tmp = Data_disp[19:16];
8'b0010_0000:data_tmp = Data_disp[23:20];
8'b0100_0000:data_tmp = Data_disp[27:24];
8'b1000_0000:data_tmp = Data_disp[31:28];
default:data_tmp = 4'b0000;
endcase
4、段选输出,译码
always@(*)
case(data_tmp)
4'h0:Seg = 7'b1000000;
4'h1:Seg = 7'b1111001;
4'h2:Seg = 7'b0100100;
4'h3:Seg = 7'b0110000;
4'h4:Seg = 7'b0011001;
4'h5:Seg = 7'b0010010;
4'h6:Seg = 7'b0000010;
4'h7:Seg = 7'b1111000;
4'h8:Seg = 7'b0000000;
4'h9:Seg = 7'b0010000;
4'ha:Seg = 7'b0001000;
4'hb:Seg = 7'b0000011;
4'hc:Seg = 7'b1000110;
4'hd:Seg = 7'b0100001;
4'he:Seg = 7'b0000110;
4'hf:Seg = 7'b0001110;
endcase
5、位选输出
assign Sel = (En) ? sel_r : 8'b0000_0000;
波形分析

1、开始时,sel=8’b000_0001,选择数码管1,要显示数据data_disp[0]为8,对应段码为7’b000_0000;
clk_1K上升沿来临,sel=8’b0000_0010,选择数码管2,要显示数据data_disp[1]为0,对应段码为7’b1000_0000,验证成功;
2、clk_1K上升沿来临,sel=8’b0000_0100,选择数码管3,要显示数据data_disp[2]为5,对应段码为7’b001_0010,验证成功。
三线制数码管驱动
上述实现使用FPGA16个IO,占用FPGA资源。
实际电路设计中,FPGA驱动数码管还要级联两片8位74HC595移位寄存器构成16位的寄存器,并将级联后的输出连接到数码管的位选和段选。

原理:
将FPGA输出的8位位选、8位段选数据通过串行端口DIO输入到两片74HC595中,然后并行输出到数码管的位选和段选段;也就是说通过3个IO控制8个8段数码管。
所以,最主要的是74HC595的驱动。
74HC595
简介
1、芯片引脚:

2、引脚功能简介:
DS:串行数据输入
SHCP:移位寄存器时钟输入,工作时钟12.5MHz
STCP:存储寄存器时钟输入
Q0~Q7:串行数据输出
OE/:输出使能输入【输出存储寄存器中的数据】
Q7S:串行数据输出,与下一片8位的74HC595串行输入端相连;这样可以构成16位并行数据输出。
3、移位寄存器原理:
每来临一个SHCP时钟上升沿,将DS端串行数据移入到移位寄存器;
当下一个上升沿来临,上一个移入的数据会往下移动一位;
当一次输入的串行数据超过8位,后面的数据会通过Q7S端输出。
4、串转并原理:
- 先把要传输的串行数据送到DS端口;
- 产生SHCP时钟上升沿,将DS端口数据移入到移位寄存器;
- 产生STCP时钟上升沿,将移位寄存器里的数据送入到存储寄存器;
- 将OE/引脚置为低电平,存储寄存器的数据会在Q0~Q7并行输出。


时序图
那么如何驱动74HC595将串行数据转为并行数据?
首先根据时序图模拟出SHCP、STCP、DS三个信号的时序,这三个信号再在相应时刻进行操作【时序图模拟出来就实现了数据的传输】。
具体实现:
这里使用一种思想:
为了方便控制整个时序的实现,使得SHCP、DS、STCP信号同步,使用计数替代计时,计数到某个值产生相应的时钟电平。
1、计数替换计时:
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
div_cnt <= 0;
else if(div_cnt == CNT_MAX - 1'b1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'b1;
wire sck_plus; // 计数标志信号
// sck_plus时钟周期是系统时钟周期的两倍
assign sck_plus = (div_cnt == CNT_MAX - 1'b1); // 每当计数到1是sck_plus为1,其他情况sck_plus为0,
reg [5:0]SHCP_EDGE_CNT;
// 序列机计数器
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)
SHCP_EDGE_CNT <= 0;
else if(sck_plus)begin
if(SHCP_EDGE_CNT == 32) // 传16位数据,需要SHCP16个时钟周期共32个时钟边沿
SHCP_EDGE_CNT <= 0;
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT + 1'b1;
end
else
SHCP_EDGE_CNT <= SHCP_EDGE_CNT;
2、模拟时序图:
以SHCP为基准,根据SHCP的变化来确定其他信号的变化,执行相应的功能。
一共输送16位数据,SHCP需经历16次上升沿,32次翻转,计数到32,在每个计数值执行相应的操作。
always@(posedge Clk or negedge Rst_n)
if(!Rst_n)begin
ST_CP <= 1'b0;
DS <= 1'b0;
SH_CP <= 1'b0;
end
else begin
case(SHCP_EDGE_CNT) // 根据边沿计数器的值确定做什么事情,使用计数替代计时
0: begin
SH_CP <= 0; // 确定第0时刻,传输最高位数据
ST_CP <= 1'b0;
DS <= r_data[15]; // /第一个下降沿,先将串行数据最高位送到DS端
end
1: begin
SH_CP <= 1'b1; // 第1时刻,SHCP产生上升沿,这个时候会将第0时刻送到DS端的数据移入到移位寄存器,所以SHCP为下降沿时将数据送到DS端
ST_CP <= 1'b0; // 保持不变,为低电平
end
2: begin
SH_CP <= 1'b0; // 第2时刻,SHCP产生下降沿
ST_CP <= 1'b0; // 仍保持不变,16位数据未传输完成
DS <= r_data[14]; // SHCP下降沿时又将下一位数据送到DS端口
end
3: begin SH_CP <= 1'b1; end // 产生上升沿,先将第1时刻移入到移位寄存器的数据往下移位,再将第2时刻送到DS端数据移入到移位寄存器
4: begin SH_CP <= 1'b0; DS <= r_data[13]; end
5: begin SH_CP <= 1'b1; end
6: begin SH_CP <= 1'b0; DS <= r_data[12]; end
7: begin SH_CP <= 1'b1; end
8: begin SH_CP <= 1'b0; DS <= r_data[11]; end
9: begin SH_CP <= 1'b1; end
10: begin SH_CP <= 1'b0; DS <= r_data[10]; end
11: begin SH_CP <= 1'b1; end
12: begin SH_CP <= 1'b0; DS <= r_data[9]; end
13: begin SH_CP <= 1'b1; end
14: begin SH_CP <= 1'b0; DS <= r_data[8]; end
15: begin SH_CP <= 1'b1; end
16: begin SH_CP <= 1'b0; DS <= r_data[7]; end
17: begin SH_CP <= 1'b1; end
18: begin SH_CP <= 1'b0; DS <= r_data[6]; end
19: begin SH_CP <= 1'b1; end
20: begin SH_CP <= 1'b0; DS <= r_data[5]; end
21: begin SH_CP <= 1'b1; end
22: begin SH_CP <= 1'b0; DS <= r_data[4]; end
23: begin SH_CP <= 1'b1; end
24: begin SH_CP <= 1'b0; DS <= r_data[3]; end
25: begin SH_CP <= 1'b1; end
26: begin SH_CP <= 1'b0; DS <= r_data[2]; end
27: begin SH_CP <= 1'b1; end
28: begin SH_CP <= 1'b0; DS <= r_data[1]; end
29: begin SH_CP <= 1'b1; end
30: begin SH_CP <= 1'b0; DS <= r_data[0]; end // 将最后一位数据送到DS端
31: begin SH_CP <= 1'b1; end // 将最后一位数据移入到移位寄存器【使用仿真看看是不是这样的】
32: begin ST_CP <= 1'b1; end // 此时串行数据都移入到移位寄存器,ST_CP产生上升沿,移位寄存器的数据进入存储寄存器,OE/使能,输出数据到数码管
default:
begin
SH_CP <= 1'b0;
DS <= 1'b0;
ST_CP <= 1'b0;
end
endcase
end
每两个系统时钟周期计数一次,用此计数值模拟出时序图:
1、从0开始计数,SHCP_EDGE_CNT为0,模拟出时序SH_CP <= 0; ST_CP <= 1’b0; DS <= r_data[15];
2、计数到1,SH_CP <= 1’b1; ST_CP <= 1’b0;
3、计数到2,SH_CP <= 1’b0; ST_CP <= 1’b0;DS <= r_data[14];
…
31、计数到30、begin SH_CP <= 1’b0; DS <= r_data[0]; end
32、计数到31、 begin SH_CP <= 1’b1; end
33、计数到32、 begin ST_CP <= 1’b1; end
可以看到
- SH_CP每计数一次翻转一次,即每两个系统时钟周期翻转一次,实现了SH_CP时钟频率为12.5MHz;
- 当SH_CP下降沿,数据提前送到DS端,等到下一SH_CP上升沿将此数据移入到移位寄存器;
- 计数到31,产生SH_CP上升沿,将最后一位数据移到移位寄存器;
- 计数到32,此时串行数据都移入到移位寄存器,产生ST_CP上升沿,移位寄存器数据进入存储寄存器,使能OE/,16位并行数据送到数码管。