动态驱动原理
我们知道静态显示是让六个数码管的8位段选信号连在8根线 上且六个数码管的位选信号同时选中点亮。但是如果我们每次只选中一个数码管点亮呢?这样我们段选信号点亮的就只是我们选中数码管的值了,那是不是就可以给每个数码管显 示不一样的值了?但是这样我们又会发现一个新的问题:每次只点亮一个数码管,那么同 一时间六个数码管就只能看到一个数码管在亮,那不是同时显示不了六个不同的字符了吗?针对这个问题先为大家介绍两种现象:
首先是人眼视觉暂留:人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的 时间,光的作用结束后,视觉影像并不立即消失,这种残留的视觉称"后像",视觉的这一现象则被称为"视觉暂留"。
其次是数码管的余晖效应:当停止向发光二极管供电时发光二极管亮度仍能维持一段时间。

根据这两种现象我们可以想到,如果让数码管轮流显示,而且轮流显示速度很快,这样会不会看起来六个数码管都在显示呢?事实证明是可以的,这种方式称为动态扫描。为帮助大家理解,打个比方:如果选用1s去扫描一个数码管,那么我们在1s内只会看见一个数码管再亮,其他的均熄灭,也就是1s显示一帧数据。而我们人眼在高度集中时,最高会看见上百帧,所以我们可以1s显示1000帧数据,那么就会让我们人眼感觉六个数码管同时在亮,且不会有闪烁感。所以当我们的扫描间隔为1ms时不会有闪烁感。
最后总结我们的动态驱动数码管的方式:使用 1ms的刷新时间让六个数码管轮流显示:第1ms点亮第一个数码管,第2ms点亮第二个数码管,以此类推依次点亮六个数 码管,6ms一个轮回,也就是说每个数码管每 6ms点亮 1ms,这样就能让人眼感觉到数码管一直在亮了。点亮相应数码管的时候给其显示相应的值,这样就可以使六个数码管显示不同的值了,这就是驱动数码管动态显示的方法。
6位8段数码管动态显示的设计
设计目标:让六位数码管显示从十进制数 0 开始计数,每 0.1s 加 1,一直到加到十进制数 999999。到达999999之后回到0开始重新计数。
绘制模块框图及波形图

data_gen模块的输出引脚介绍:
data19:0:只负责输出无符号整数数值,因为fpga是数字芯片,处理小数会很复杂。
point5:0:输出小数点。在这里我们让其高电平有效(以本次使用的数码管为例,即当小数点对应段选信号为低,位选信号为高时点亮有效),本次实验是秒表计数器,需要第5个数码管显示小数点,即000_010。
sign:负号显示,高电平有效。因本次实验不显示负号,给其一直为低电平即可。
seg_en:数码管使能信号。因为我们一直在显示,所以一直给其拉高就行。

seg_dynamic模块需要将data,point,sign转化为sel,seg。那么如何提取data的个位,十位,百位,千位,万位,十万位呢?
最简单的方法就是取余+除法提取每一位数据,如下图所示。

下面介绍另一种方法十进制数转化为bcd码。
BCD码(Binary-Coded Decimal),又称二 - 十进制码,使用4位二进制数来表示1位十进制数中的0~9这10个数码,是一种二进制的数字编码形式,用二进制编码的十进制代码。
这里我们举个例子,例如我们需要显示十进制数据 234,根据我们前面讲的动态显示原理,在第一个显示周期我们点亮数码管1,并让其显示值4;在第二个显示 周期我们点亮数码管2,并让其显示值3;在第三个显示周期我们点亮数码管3,并让其显 示值2;第四、五、六个周期我们不点亮其余数码管即可,以此循环就完成了十进制数234 的显示。那个我们如何才能在点亮相应的值时给其显示相应的值呢?我们先看看 234 的十 进制数的二进制表示为:1110_1010;234 的 8421BCD 码为:0010_0011_0100;可以发现 如果我们使用二进制数赋值我们并不能准确的给到2、3、4值,而如果我们用8421BCD码 给其赋值的话,每4位8421BCD码代表一个十进制数,那么就能完美的进行显示了。二进制转BCD码步骤

十进制数 234 其对应的二进制数为 1110_1010,首先第一步我们在其前面补上若干个 0,那么这个 0的数量是如何决定的呢?参与转换的十进制有多少位,就需 要多少个相应的BCD码,比如234,该十进制数是 3位,而一位十进制数的 BCD码是四位,所以这里我们就需要12位BCD码,故我们就在前面补12个0。其余位数的十进制补 0 数量也是这样进行计算。
第二步我们需要进行判断运算移位操作,首先判断每一个BCD码其对应的十进制数是否大于4,如果大于4就对BCD码做加3操作,若小于等于4就让其值保持不变。当对每一个BCD码进行判断运算后,都需要将运算后的数据像左移1位。移完位后我们仍按前面所述进行判断运算,判断运算后需再次移位,以此循环,当我们进行 8 次判断移位后的 BCD码部分数据就是我们转换的数据,如图所示,当第8次移位后的8421BCD码数据对应的十进制正是 234。这里需要注意的是我们输入转换的二进制码有多少位我们就需要进行多少次判断移位操作,这里输入的是8位二进制,我们就进行8次判断移位操作。
bcd_8421模块

shift_flag:移位判断操作标志信号。前面说到我们需要对数据进行移位和判断,判断在前移位在后,所以这里我们声明一个标志信号,用于控制判断和移位的先后顺序,当 shift_flag 为低时对数据进行判断,当 shift_flag 为高时对数据进行移位。需要注意的是无论 是移位操作和判断操作都是在单个系统时钟下完成的,故我们判断20次移位20次在40个系统时钟内就能完成。(交替执行任务的类型,均可使用该标志信号的方法)
seg_dynamic模块


cnt_sel:位选数码管计数器。我们在理论学习中说到动态扫描方式是用 1ms 的刷新时间让六个数码管轮流显示:第1ms点亮第一个数码管,第2ms点亮第二个数码管,以此类 推依次点亮六个数码管,6ms 一个轮回,也就是说每个数码管每 6ms点亮一次。那问题是我们怎么去选中这个要显示的数码管并且给其要显示的值呢?这个时候我们就引入了一个 cnt_sel 信号,让其从 0~5 循环计数,1 个数代表一个数码管,可以看做是给数码管编号。 这样的话我们只要选择计数器的值就相当于选中了其中对应的数码管。特别要说明的是我们的cnt_sel计数器必须与数码管的刷新状态一致,也就是1ms计1个数。
data_disp:当前点亮数码管显示的值。若我们此时点亮的是第一个数码管,那么我们 就需要给第一个数码管显示值 6,若刷新到第二个数码管,那么我们就需要给第二个数码 管显示值 7,以此类推;当刷新到第五个数码管时,此时显示的是负号,那么我们该如何 表示呢?这里我们让该信号的值为10来表示,也就是说当data_disp的值为10时就让数码 管显示负号,同理这里我们定义 data_disp 的值为 11 时让数码管什么也不显示,即不点亮 数码管。
dot_disp:当前数码管显示的小数点,我们输入的 point 信号是点亮第二个数码管的小 数点,而我们的数码管是低电平点亮,所以这里当扫描到第二个数码管时让 dot_disp 信号 为低即可。
编写模块代码
模块data_gen
module data_gen
#(
parameter TIME_100MS = 32'd4_999_999
)
(
input wire clk ,
input wire rst_n ,
output reg [19:0] data ,
output reg [5:0] point ,
output reg sign ,
output reg seg_en
);
reg [31:0] cnt_100ms;
//cnt_100ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_100ms <= 32'd0;
else if(cnt_100ms == TIME_100MS)
cnt_100ms <= 32'd0;
else
cnt_100ms <= cnt_100ms + 1'b1;
end
//data
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data <= 20'd0;
else if(data == 20'd999_999 && cnt_100ms == TIME_100MS)
data <= 20'd0;
else if(cnt_100ms == TIME_100MS)
data <= data + 1'b1;
else
data <= data;
end
//point
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
point <= 6'd0;
else
point <= 6'b000_010;
end
//sign
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sign <= 1'b0;
else
sign <= 1'b0;
end
//seg_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
end
endmodule
模块bcd_8421
module bcd_8421
(
input wire clk ,
input wire rst_n ,
input wire [19:0] data ,
output reg [3:0] gewei ,
output reg [3:0] shiwei ,
output reg [3:0] baiwei ,
output reg [3:0] qianwei ,
output reg [3:0] wanwei ,
output reg [3:0] shiwanwei
);
reg [43:0] data_shift;
reg flag_shift;
reg [4:0] cnt_shift ;
//data_shift
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_shift <= 44'd0;
else if(cnt_shift == 5'd0)
data_shift <= {24'd0,data[19:0]};//补零
else if(flag_shift && cnt_shift <= 5'd20)
data_shift <= data_shift << 1'b1;
else if(!flag_shift && cnt_shift <= 5'd20)
begin
data_shift[23:20] <= (data_shift[23:20] > 4'd4) ? (data_shift[23:20] + 4'd3) : data_shift[23:20];
data_shift[27:24] <= (data_shift[27:24] > 4'd4) ? (data_shift[27:24] + 4'd3) : data_shift[27:24];
data_shift[31:28] <= (data_shift[31:28] > 4'd4) ? (data_shift[31:28] + 4'd3) : data_shift[31:28];
data_shift[35:32] <= (data_shift[35:32] > 4'd4) ? (data_shift[35:32] + 4'd3) : data_shift[35:32];
data_shift[39:36] <= (data_shift[39:36] > 4'd4) ? (data_shift[39:36] + 4'd3) : data_shift[39:36];
data_shift[43:40] <= (data_shift[43:40] > 4'd4) ? (data_shift[43:40] + 4'd3) : data_shift[43:40];
end
else
data_shift <= data_shift;
end
//flag_shift
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_shift <= 1'b0;
else
flag_shift <= ~flag_shift;
end
//cnt_shift
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_shift <= 5'd0;
else if(cnt_shift == 5'd21 && flag_shift)
cnt_shift <= 5'd0;
else if(flag_shift)
cnt_shift <= cnt_shift + 1'b1;
else
cnt_shift <= cnt_shift;
end
//输出信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
gewei <= 4'd0;
shiwei <= 4'd0;
baiwei <= 4'd0;
qianwei <= 4'd0;
wanwei <= 4'd0;
shiwanwei <= 4'd0;
end
else if(cnt_shift == 5'd21)
begin
gewei <= data_shift[23:20];
shiwei <= data_shift[27:24];
baiwei <= data_shift[31:28];
qianwei <= data_shift[35:32];
wanwei <= data_shift[39:36];
shiwanwei <= data_shift[43:40];
end
else
begin
gewei <= gewei ;
shiwei <= shiwei ;
baiwei <= baiwei ;
qianwei <= qianwei ;
wanwei <= wanwei ;
shiwanwei <= shiwanwei;
end
end
endmodule
seg_dynamic模块
module seg_dynamic
#(
parameter TIME_1MS = 16'd49_999
)
(
input wire clk ,
input wire rst_n ,
input wire [19:0] data ,
input wire [5:0] point ,
input wire sign ,
input wire seg_en ,
output reg [7:0] seg ,
output reg [5:0] sel
);
wire [3:0] gewei ;
wire [3:0] shiwei ;
wire [3:0] baiwei ;
wire [3:0] qianwei ;
wire [3:0] wanwei ;
wire [3:0] shiwanwei;
bcd_8421 bcd_8421_inst
(
.clk (clk ),
.rst_n (rst_n ),
.data (data ),
.gewei (gewei ),
.shiwei (shiwei ),
.baiwei (baiwei ),
.qianwei (qianwei ),
.wanwei (wanwei ),
.shiwanwei(shiwanwei)
);
reg [15:0] cnt_1ms ;
reg flag_1ms ;
reg [5:0] sel_reg ;
reg [2:0] cnt_sel ;
reg [23:0] data_reg ;
reg [3:0] data_disp ;
wire point_disp ;
//cnt_1ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_1ms <= 16'd0;
else if(cnt_1ms == TIME_1MS)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
end
//flag_1ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
flag_1ms <= 1'b0;
else if(cnt_1ms == TIME_1MS - 1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
end
//sel_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sel_reg <= 6'b000_001;
else if(flag_1ms)
sel_reg <= {sel_reg[4:0],sel_reg[5]};
else
sel_reg <= sel_reg;
end
//cnt_sel
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_sel <= 3'd0;
else if(cnt_sel == 3'd5 && flag_1ms)
cnt_sel <= 3'd0;
else if(flag_1ms)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
end
//data_reg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_reg <= 24'd0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if(shiwanwei || point[5])
data_reg <= {shiwanwei,wanwei,qianwei,baiwei,shiwei,gewei};
else if((wanwei || point[4]) && sign)
data_reg <= {4'd10,wanwei,qianwei,baiwei,shiwei,gewei};
else if((wanwei || point[4]) && !sign)
data_reg <= {4'd11,wanwei,qianwei,baiwei,shiwei,gewei};
else if((qianwei || point[3]) && sign)
data_reg <= {4'd11,4'd10,qianwei,baiwei,shiwei,gewei};
else if((qianwei || point[3]) && !sign)
data_reg <= {4'd11,4'd11,qianwei,baiwei,shiwei,gewei};
else if((baiwei || point[2]) && sign)
data_reg <= {4'd11,4'd11,4'd10,baiwei,shiwei,gewei};
else if((baiwei || point[2]) && !sign)
data_reg <= {4'd11,4'd11,4'd11,baiwei,shiwei,gewei};
else if((shiwei || point[1]) && sign)
data_reg <= {4'd11,4'd11,4'd11,4'd10,shiwei,gewei};
else if((shiwei || point[1]) && !sign)
data_reg <= {4'd11,4'd11,4'd11,4'd11,shiwei,gewei};
else if((gewei || point[0]) && sign)
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,gewei};
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,gewei};
end
//data_disp
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_disp <= 4'd0;
else case(cnt_sel)
3'd0:data_disp <= data_reg[3:0];
3'd1:data_disp <= data_reg[7:4];
3'd2:data_disp <= data_reg[11:8];
3'd3:data_disp <= data_reg[15:12];
3'd4:data_disp <= data_reg[19:16];
3'd5:data_disp <= data_reg[23:20];
default:data_disp <= 4'd0;
endcase
end
//point_disp
assign point_disp = ~point[cnt_sel];
//sel
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sel <= 6'b000_001;
else
sel <= sel_reg;
end
//seg
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
seg <= 8'hff;
else case(data_disp)
4'd0 : seg <= {point_disp,7'b100_0000}; //显示数字0 (8'hc0)
4'd1 : seg <= {point_disp,7'b111_1001}; //显示数字1 (8'hf9)
4'd2 : seg <= {point_disp,7'b010_0100}; //显示数字2 (8'ha4)
4'd3 : seg <= {point_disp,7'b011_0000}; //显示数字3 (8'hb0)
4'd4 : seg <= {point_disp,7'b001_1001}; //显示数字4 (8'h99)
4'd5 : seg <= {point_disp,7'b001_0010}; //显示数字5 (8'h92)
4'd6 : seg <= {point_disp,7'b000_0010}; //显示数字6 (8'h82)
4'd7 : seg <= {point_disp,7'b111_1000}; //显示数字7 (8'hf8)
4'd8 : seg <= {point_disp,7'b000_0000}; //显示数字8 (8'h80)
4'd9 : seg <= {point_disp,7'b001_0000}; //显示数字9 (8'h90)
4'd10 : seg <= 8'b1011_1111 ; //显示负号
4'd11 : seg <= 8'b1111_1111 ; //不显示任何字符
default:seg <= 8'b1100_0000;
endcase
end
endmodule
hc595_ctrl模块
module hc595_ctrl
#(
parameter CNT_MAX_DIV4 = 2'd3 ,
parameter CNT_MAX_BIT = 4'd13 //段选8bit,位选6bit
)
(
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
seg_595_dynamic模块
module seg_595_dynamic(
input wire clk ,
input wire rst_n ,
input wire [19:0] data ,
input wire [5:0] point ,
input wire sign ,
input wire seg_en ,
output wire ds ,
output wire shcp ,
output wire stcp ,
output wire oe
);
wire [7:0] seg;
wire [5:0] sel;
seg_dynamic
#(
.TIME_1MS ( 16'd49_999 )//1ms
)
seg_dynamic_inst
(
.clk (clk ) ,
.rst_n (rst_n ) ,
.data (data ) ,
.point (point ) ,
.sign (sign ) ,
.seg_en (seg_en ) ,
.seg (seg ) ,
.sel (sel )
);
hc595_ctrl
#(
.CNT_MAX_DIV4 ( 2'd3 ),
.CNT_MAX_BIT ( 4'd13 )
)
hc595_ctrl_inst
(
.clk (clk ) ,
.rst_n (rst_n ) ,
.seg (seg ) ,
.sel (sel ) ,
.ds (ds ) ,
.shcp (shcp ) ,
.stcp (stcp ) ,
.oe (oe )
);
endmodule
顶层模块top_seg_595
module top_seg_595(
input wire clk ,
input wire rst_n ,
output wire ds ,
output wire shcp ,
output wire stcp ,
output wire oe
);
wire [19:0] data;
wire [5:0] point;
wire sign;
wire seg_en;
data_gen
#(
.TIME_100MS ( 32'd4_999_999 )
)
data_gen_inst
(
.clk (clk ) ,
.rst_n (rst_n ) ,
.data (data ) ,
.point (point ) ,
.sign (sign ) ,
.seg_en (seg_en)
);
seg_595_dynamic seg_595_dynamic_inst(
.clk (clk ),
.rst_n (rst_n ),
.data (data ),
.point (point ),
.sign (sign ),
.seg_en(seg_en),
.ds (ds ),
.shcp (shcp ),
.stcp (stcp ),
.oe (oe )
);
endmodule
编写仿真代码
`timescale 1ns/1ps
module top_seg_595_tb();
reg clk ;
reg rst_n;
initial
begin
clk <= 1'b0;
rst_n <= 1'b0;
#123
rst_n <= 1'b1;
end
always #10 clk = ~clk;
top_seg_595 top_seg_595_inst(
.clk (clk ) ,
.rst_n (rst_n) ,
.ds () ,
.shcp () ,
.stcp () ,
.oe ()
);
endmodule
不再进行仿真,直接上板验证。
练习
练习:调节动态数码管设计实现0.01s计时器
只需要修改数据生成模块data_gen的计数器的最大值和point。
module data_gen
#(
parameter TIME_100MS = 32'd4_999_99 //10ms
)
(
input wire clk ,
input wire rst_n ,
output reg [19:0] data ,
output reg [5:0] point ,
output reg sign ,
output reg seg_en
);
reg [31:0] cnt_100ms;
//cnt_100ms
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_100ms <= 32'd0;
else if(cnt_100ms == TIME_100MS)
cnt_100ms <= 32'd0;
else
cnt_100ms <= cnt_100ms + 1'b1;
end
//data
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
data <= 20'd0;
else if(data == 20'd999_999 && cnt_100ms == TIME_100MS)
data <= 20'd0;
else if(cnt_100ms == TIME_100MS)
data <= data + 1'b1;
else
data <= data;
end
//point
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
point <= 6'd0;
else
point <= 6'b000_100;
end
//sign
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
sign <= 1'b0;
else
sign <= 1'b0;
end
//seg_en
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
end
endmodule
总结
分别描述动态静态数码管设计原理?(包括数码管原理,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芯片原理: 74HC595 是一个 8 位串行输入、并行输出的位移缓存器。其内部核心由8位移位寄存器和8位锁存寄存器组成,工作时,数据从SI引脚串行输入,并在移位时钟SCK的每个上升沿逐位移入移位寄存器,待8位数据全部移入后,只需在锁存时钟RCK引脚上产生一个上升沿,便可将移位寄存器中的数据一次性锁存至锁存寄存器,并经由QA至QH并行输出,同时其输出使能端G非低电平有效,可控制输出为高电平、低电平或高阻态;此外,该芯片支持级联扩展,通过将上一级的级联输出引脚接QH'至下一级的SI引脚,无需增加主控IO口即可扩展出更多并行输出端口,但使用时需特别注意,如果使用该芯片进行1路串行输入转n路并行输出时,锁存时钟频率应为移位时钟频率的n分之一。
**静态数码管的设计:**静态显示的特点是让每个数码管的段选必须接一个 8位数据线来显示字形,显示字形可一直保持,直到送入新字形码为止。对于n位8段数码管来说,我们将n个数码管的段选信号都连接在一起,而位选(sel)独立控制,这样n个数码管接在一起就少了 8×(n-1)个 I/O口。每一个数码管都有一个位选信号,而这个位选信号就可以控制数码管的亮灭。这样我们就可以通过位选信号去控制哪几个数码管亮,而在同一时刻,位选选通的数码管上显示的字形是一样的,因为我们将 n个数码管相对应的段选信号均连接在一起了,所以数码管上的显示字形就会是一样的,而这种显示方式即为数码管的静态显示。所以我们可以将位选信号sel均有效,seg段选信号均连接在一起,控制seg段选信号去显示字形即可完成静态数码管的设计。
动态数码管的设计: 如果要让每个数码管显示的值不同,那么这种显示方式就被称之为动态显示。动态数码管设计的核心原理是利用人眼的视觉暂留效应 和LED的余辉效应,通过快速地动态扫描各个数码管,使我们人眼看起来像所有数码管都在同时显示。如果选用1s去扫描一个数码管,那么我们在1s内只会看见一个数码管再亮,其他的数码管均熄灭,也就是1s显示一帧数据。而我们人眼在高度集中时,最高只会看见上百帧,所以我们可以在1s内去显示1000帧数据,那么就会让我们人眼感觉所有的数码管同时在亮,且不会有闪烁感。所以当我们的扫描间隔为1ms时不会有闪烁感。对于6位8段数码管来说,我们可以使用 1ms的刷新时间让六个数码管轮流显示:第1ms点亮第一个数码管,第2ms点亮第二个数码管,以此类推依次点亮六个数码管,6ms一个轮回,也就是说每个数码管每 6ms点亮 1ms,这样就能让人眼感觉到数码管一直在亮了。点亮相应数码管的时候给其显示相应的值,这样就可以使六个数码管显示不同的值了。
在具体实现中,动态显示需特别注意消影处理:在切换位选信号的瞬间,应先关闭所有段选(输出0x00或0xFF)或关闭位选,待新的位选和段选数据稳定建立后再开启,以避免前后两位数据串扰导致残影或暗亮。