开发板的使用是AX301,学习资料可以在我的另一篇文章中找到。链接在如下:https://blog.csdn.net/qq_24213087/article/details/108238682
一、动态数码管介绍
上一篇我们详细地说了一下数码管的原理图和静态显示的主要方式。我们回顾一下我们使用数码管的原理图。
六位一体数码管的显示是属于动态显示, 由于人的视觉暂留现象及发光二极管的余辉效应,这个迂回效应使用的原理和我们看电影的效果是一样的。电影的放映就是一幅幅的图片,只要保证每秒20帧以上的速率,我们大脑会留下一个反应的时间,这个时间就是余晖效应带来的。所以我们看到的电影是动态连续的。因此我们将数码管的动态扫描时间大于50HZ时,尽管实际上各位数码管并非同时点亮,但给人的印象就是一组稳定的显示数据,不会有闪烁感。这里放一张我最喜欢的电影海报。
二、时钟实验
接下来言归正传,我们通过一个时钟的实验完成动态数码管的显示。首先我们使用的AX301是50MHz的晶振最小产生的时间延迟是20ns。因此我们需要对我们的时钟进行分频到1ms的时钟。
/* time : 2020.9.12
主要功能 : 实现时钟分频功能,分别实现1ms , 20ms
端口定义 : clock: 分频后的时钟
reset: 复位信号
clk_div1: 分频时钟,1ms
clk_div2: 分频时钟,20ms
=============================================================================================*/
module time_div(clock, reset, clk_div1, clk_div2);
input clock, reset;
output clk_div1, clk_div2;
reg clk_div1, clk_div2;
reg [15:0] cnt1;
reg [19:0] cnt2;
parameter MAX_TIME1 = 25_000;
parameter MAX_TIME2 = 1_000_000;
//1ms
always@(posedge clock or negedge reset)begin
if(!reset)
begin
clk_div1 = 1'b0;
cnt1 = 16'd0;
end
else if(cnt1 < MAX_TIME1)
begin
cnt1 = cnt1 + 1'd1;
end
else if(cnt1 == MAX_TIME1)
begin
cnt1 = 16'd0;
clk_div1 = ~clk_div1;
end
else
begin
cnt1 = cnt1;
end
end
//20ms
always@(posedge clock or negedge reset)begin
if(!reset)
begin
clk_div2 = 1'b0;
cnt2 = 20'd0;
end
else if(cnt2 < MAX_TIME2)
begin
cnt2 = cnt2 + 1'd1;
end
else if(cnt2 == MAX_TIME2)
begin
cnt2 = 20'd0;
clk_div2 = ~clk_div2;
end
else
begin
cnt2 = cnt2;
end
end
endmodule
时钟分频ok后,我们继续要考虑时钟设计的计数问题,在ms级的计数就是正常的十进制,但是对于秒级和分级就是60进制。因此我们需要将每毫秒的计数值进行分类。
/* time : 2020.9.12
主要功能 : 实现时钟计时功能,计时精度1ms
端口定义 : clock: 分频后的时钟
reset: 复位信号
time_sum: 当前时间
=============================================================================================*/
module time_ms (clock, reset, time_sum);
input clock,reset;
output [23:0] time_sum;
reg [23:0] time_sum ;
reg [19:0] time_MS;
parameter MAX_TIMER = 600_000;
//计算当前时间存入time_MS
always@(posedge clock or negedge reset)begin
if(!reset)
begin
time_MS = 20'd0;
end
else if(time_MS < MAX_TIMER)
begin
time_MS = time_MS + 1'd1;
end
else
begin
time_MS = 20'd0;
end
end
//将time_MS转变为 分_秒_毫秒 的形式存入time_sum
always@(time_MS)begin
time_sum[23:20] = time_MS/60000;
time_sum[19:16] = ((time_MS/1000)%60)/10;
time_sum[15:12] = ((time_MS/1000)%60)%10;
time_sum[11:8] = (time_MS%1000)/100;
time_sum[7:4] = ((time_MS%1000)%100)/10;
time_sum[3:0] = ((time_MS%1000)%100)%10;
end
endmodule
接下来就到了这个实验最重要的部分,屏幕扫描这个功能,实现对位选的控制。
/* time : 2020.9.12
主要功能 : 实现位选扫屏功能,计时精度1ms,位选信号确定后,并给出当前应显示的段选信号
端口定义 : clock: 分频后的时钟
reset: 复位信号
sel: 位选信号
time_sum: 当前时间
cnt: 位选标志
=============================================================================================*/
module scan(clock, reset, sel, time_sum, ctl_seg, dot);
input clock, reset;
input [23:0] time_sum;
output [5:0] sel;
output [3:0] ctl_seg;
output dot;
reg [3:0] ctl_seg;
reg [5:0] sel = 6'b0;
reg dot;
reg [2:0] cnt = 0;
//段选
always@(posedge clock)begin
if(cnt < 3'd5)
begin
cnt <= cnt + 1;
end
else
begin
cnt <= 0;
end
end
always@(posedge clock)begin
case (cnt)
3'd0: begin
sel = 6'b111_110;
ctl_seg = time_sum[3:0];
dot = 1;
end
3'd1: begin
sel = 6'b111_101;
ctl_seg = time_sum[7:4];
dot = 1;
end
3'd2: begin
sel = 6'b111_011;
ctl_seg = time_sum[11:8];
dot = 1;
end
3'd3: begin
sel = 6'b110_111;
ctl_seg = time_sum[15:12];
dot = 0;
end
3'd4: begin
sel = 6'b101_111;
ctl_seg = time_sum[19:16];
dot = 1;
end
3'd5: begin
sel = 6'b011_111;
ctl_seg = time_sum[23:20];
dot = 0;
end
default: begin
sel = sel;
ctl_seg = ctl_seg;
dot = 1;
end
endcase
end
endmodule
最后就是对每个数码管显示的字符进行段选控制实现当前数字的显示
/* time :2020.9.12
主要功能 :实现段选功能
端口定义 : clock: 分频后的时钟
reset: 复位信号
seg_led: 段选信号
cnt: 位选后当前应显示的数值标志
=============================================================================================*/
module seg_led_show(clock, reset, seg_led, ctl_seg, dot);
input clock, reset;
input [3:0] ctl_seg;
input dot;
output [7:0] seg_led;
reg [7:0] seg_led = 8'b1111_1111;
always@(ctl_seg)begin
case (ctl_seg)
8'd0: seg_led = {
dot,7'b1000000}; //0
8'd1: seg_led = {
dot,7'b1111001}; //1
8'd2: seg_led = {
dot,7'b0100100}; //2
8'd3: seg_led = {
dot,7'b0110000}; //3
8'd4: seg_led = {
dot,7'b0011001}; //4
8'd5: seg_led = {
dot,7'b0010010}; //5
8'd6: seg_led = {
dot,7'b0000010}; //6
8'd7: seg_led = {
dot,7'b1111000}; //7
8'd8: seg_led = {
dot,7'b0000000}; //8
8'd9: seg_led = {
dot,7'b0010000}; //9
default: seg_led = 8'b1111_1111;
endcase
end
endmodule
最最后就是我们的顶层代码,实现各个模块之间的连接
/* time : 2020.9.12
主要功能 : 实现时钟功能,六位数码管,依次为分、秒、毫秒。例如1.20.123,为1分钟20秒123毫秒
端口定义 : clock: 分频后的时钟
reset: 复位信号
seg_led: 段选信号
sel: 位选信号
=============================================================================================*/
module top(clock, reset, sel, seg_led);
input clock, reset;
output [5:0] sel;
output [7:0] seg_led;
//reg [5:0] sel;
//reg [7:0] seg_led;
wire clk_div1, clk_div2;
wire [23:0] time_sum;
wire [4:0] ctl_seg;
wire dot;
time_div time_div(
.clock(clock),
.reset(reset),
.clk_div1(clk_div1),
.clk_div2(clk_div2)
);
time_ms time_ms(
.clock(clk_div1),
.reset(reset),
.time_sum(time_sum)
);
scan scan(
.clock(clk_div1),
.reset(reset),
.sel(sel),
.time_sum(time_sum),
.ctl_seg(ctl_seg),
.dot(dot)
);
seg_led_show seg_led_show(
.clock(clk_div1),
.reset(reset),
.seg_led(seg_led),
.ctl_seg(ctl_seg),
.dot(dot)
);
endmodule
总结一下写代码的一些小知识点,首先由于模块的编写慢慢地加多,当我们在修改其中一个模块的一个端口后一定要注意下其他模块中这个端口的修改,以及顶层模块该端口的修改。尤其是位宽的这个数值的修改。稍不注意就会让你出现不可理解的BUG。愿我们的代码BUG越来越少,
最后放一个小视频,展示一下这个实验的一个成果。
AX301数码管显示时钟小视频
声明:该文只适用于学习,其内容包含来自书本的摘抄和总结,欢迎大家补充,共同学习进步。