在数码管显示数据的基础上,让六位数码管显示数字时钟,并且通过按键可以对时间进行修改。
实验目标:六位数码管分别显示时间的时分秒,且通过按键可实现加减调整时间及清零功能。
key1: 切换键:选择待调整的时间单位(时、分、秒)
key2: 时间加键
key3: 时间减键
key4: 时钟清零键
效果如下图:时钟清零——>分钟加减——>时钟加减——>时钟正常运行
各模块功能说明:
各模块原理之前在数码管动态显示的博客中有详细说明,不理解的朋友可以移步这篇文章:
https://mp.csdn.net/mp_blog/creation/editor/127933111
`timescale 1ns/1ns
module key_filter
#(
parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //全局复位
input wire [3:0] key_in , //按键输入信号
output reg [3:0] key_flag //key_flag为1时表示消抖后检测到按键被按下
//key_flag为0时表示没有检测到按键被按下
);
//reg define
reg [19:0] cnt_20ms0 ; //计数器
reg [19:0] cnt_20ms1 ; //计数器
reg [19:0] cnt_20ms2 ; //计数器
reg [19:0] cnt_20ms3 ; //计数器
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms0 <= 20'b0;
else if(key_in[0] == 1'b1)
cnt_20ms0 <= 20'b0;
else if(cnt_20ms0 == CNT_MAX && key_in[0] == 1'b0)
cnt_20ms0 <= cnt_20ms0;
else
cnt_20ms0 <= cnt_20ms0 + 1'b1;
//cnt_20ms1:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms1 <= 20'b0;
else if(key_in[1] == 1'b1)
cnt_20ms1 <= 20'b0;
else if(cnt_20ms1 == CNT_MAX && key_in[1] == 1'b0)
cnt_20ms1 <= cnt_20ms1;
else
cnt_20ms1 <= cnt_20ms1 + 1'b1;
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms2 <= 20'b0;
else if(key_in[2] == 1'b1)
cnt_20ms2 <= 20'b0;
else if(cnt_20ms2 == CNT_MAX && key_in[2] == 1'b0)
cnt_20ms2 <= cnt_20ms2;
else
cnt_20ms2 <= cnt_20ms2 + 1'b1;
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms3 <= 20'b0;
else if(key_in[3] == 1'b1)
cnt_20ms3 <= 20'b0;
else if(cnt_20ms3 == CNT_MAX && key_in[3] == 1'b0)
cnt_20ms3 <= cnt_20ms3;
else
cnt_20ms3 <= cnt_20ms3 + 1'b1;
//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 4'b0000;
else if(cnt_20ms3 == CNT_MAX - 1'b1)
key_flag <= 4'b0001; //切换 S MIN H
else if(cnt_20ms2 == CNT_MAX - 1'b1)
key_flag <= 4'b0010; //减数
else if(cnt_20ms1 == CNT_MAX - 1'b1)
key_flag <= 4'b0100; //加数
else if(cnt_20ms0 == CNT_MAX - 1'b1)
key_flag <= 4'b1000; //清零
else
key_flag <= 4'b0000;
endmodule
`timescale 1ns/1ns
module data_gen
#(
parameter cnt_1ms_MAX = 16'd49_999 ,
parameter cnt_s_MAX = 10'd999 ,
parameter cnt_1s_MAX = 6'd59 ,
parameter cnt_1min_MAX = 6'd59 ,
parameter cnt_1h_MAX = 5'd23
)
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [3:0] key_flag ,
output reg [19:0] data ,
output reg seg_en ,
output wire [5:0] point ,
output wire sign
);
reg [15:0] cnt_1ms ;
reg [9:0] cnt_s ;
reg [5:0] cnt_1s ;
reg [5:0] cnt_1min ;
reg [4:0] cnt_1h ;
reg [2:0] cnt ;
assign point = 6'b010100;
assign sign = 1'b0;
//时分秒数值选择
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt <= 3'b0;
else if(cnt == 3'd3 && key_flag == 4'b1000)
cnt <= 3'd1;
else if(key_flag == 4'b1000)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
//1ms计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt_1ms <= 16'b0;
else if(cnt_1ms == cnt_1ms_MAX)
cnt_1ms <= 16'b0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//1s计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt_s <= 10'b0;
else if(cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)
cnt_s <= 10'b0;
else if(cnt_1ms == cnt_1ms_MAX)
cnt_s <= cnt_s + 1'b1;
else
cnt_s <= cnt_s;
//60s计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt_1s <=6'b0;
else if((cnt_1s == cnt_1s_MAX && cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)||(cnt_1s == cnt_1s_MAX && key_flag == 4'b0100 )) //重点1: 转换条件
cnt_1s <= 6'b0;
else if(cnt == 3'd3 && key_flag == 4'b0100)
cnt_1s <= cnt_1s + 1'b1;
else if(cnt == 3'd3 && key_flag == 4'b0010 && cnt_1s ==6'b0 )
cnt_1s <= cnt_1s_MAX;
else if(cnt == 3'd3 && key_flag == 4'b0010)
cnt_1s <= cnt_1s - 1'b1;
else if(cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)
cnt_1s <= cnt_1s + 1'b1;
else
cnt_1s <= cnt_1s;
//1min计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt_1min <= 6'b0;
else if ((cnt_1min == cnt_1min_MAX && cnt_1s == cnt_1s_MAX && cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)||(cnt_1min == cnt_1min_MAX && key_flag == 4'b0100 ))
cnt_1min <= 6'b0;
else if(cnt == 3'd2 && key_flag == 4'b0100)
cnt_1min <= cnt_1min + 1'b1;
else if(cnt == 3'd2 && key_flag == 4'b0010 && cnt_1min == 6'b0)
cnt_1min <= cnt_1min_MAX;
else if(cnt == 3'd2 && key_flag == 4'b0010)
cnt_1min <= cnt_1min - 1'b1;
else if(cnt_1s == cnt_1s_MAX && cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)
cnt_1min <= cnt_1min + 1'b1;
else
cnt_1min <= cnt_1min;
//1h计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0 || key_flag == 4'b0001)
cnt_1h <= 5'b0;
else if (( cnt_1h == cnt_1h_MAX && cnt_1min == cnt_1min_MAX && cnt_1s == cnt_1s_MAX && cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX) ||(cnt_1h == cnt_1h_MAX && key_flag == 4'b0100 ))
cnt_1h <= 5'b0;
else if(cnt == 3'd1 && key_flag == 4'b0100)
cnt_1h <= cnt_1h + 1'b1;
else if(cnt == 3'd1 && key_flag == 4'b0010 && cnt_1h == 5'b0)
cnt_1h <= cnt_1h_MAX;
else if(cnt == 3'd1 && key_flag == 4'b0010)
cnt_1h <= cnt_1h - 1'b1;
else if(cnt_1min == cnt_1min_MAX && cnt_1s == cnt_1s_MAX && cnt_s == cnt_s_MAX && cnt_1ms == cnt_1ms_MAX)
cnt_1h <= cnt_1h + 1'b1;
else
cnt_1h <= cnt_1h;
//生产待显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 20'd0;
else
data <= cnt_1h*10000 + cnt_1min*100 + cnt_1s; //重点2: 时钟数合并
//数码管使能控制
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg_en <= 1'b0;
else
seg_en <= 1'b1;
endmodule
`timescale 1ns/1ns
module seg_dynamic
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [19:0] data ,
input wire [5:0] point ,
input wire seg_en ,
input wire sign ,
output reg [5:0] sel ,
output reg [7:0] seg
);
parameter CNT_MAX = 16'd49_999;
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
reg [23:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [3:0] data_disp ; //当前数码管显示的数据
reg dot_disp ;
/************************* 二、6毫秒计数 ************************/
//cnt_1ms:1ms循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//cnt_sel:从0到5循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (cnt_1ms == CNT_MAX))
cnt_sel <= 3'd0;
else if(cnt_1ms == CNT_MAX)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
/*********************************************************************/
/******************** 三、位选段选显示驱动 ********************/
//数码管位选信号驱动
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000_000;
else if((cnt_sel == 3'd0) && (cnt_1ms == CNT_MAX))
sel <= 6'b000_001;
else if(cnt_1ms == CNT_MAX)
sel <= sel << 1;
else
sel <= sel;
//控制数码管的位选信号,使六个数码管轮流显示
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if((seg_en == 1'b1) && (cnt_1ms == CNT_MAX))
case(cnt_sel)
3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值
3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值
3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值
3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值
3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值
3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//控制数码管段选信号,显示数字
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= {dot_disp,7'b100_0000}; //显示数字0
4'd1 : seg <= {dot_disp,7'b111_1001}; //显示数字1
4'd2 : seg <= {dot_disp,7'b010_0100}; //显示数字2
4'd3 : seg <= {dot_disp,7'b011_0000}; //显示数字3
4'd4 : seg <= {dot_disp,7'b001_1001}; //显示数字4
4'd5 : seg <= {dot_disp,7'b001_0010}; //显示数字5
4'd6 : seg <= {dot_disp,7'b000_0010}; //显示数字6
4'd7 : seg <= {dot_disp,7'b111_1000}; //显示数字7
4'd8 : seg <= {dot_disp,7'b000_0000}; //显示数字8
4'd9 : seg <= {dot_disp,7'b001_0000}; //显示数字9
4'd10 : seg <= 8'b1011_1111 ; //显示负号
4'd11 : seg <= 8'b1111_1111 ; //不显示任何字符
default:seg <= 8'b1100_0000;
endcase
//dot_disp:小数点低电平点亮,需对小数点有效信号取反
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dot_disp <= 1'b1;
else if(cnt_1ms == CNT_MAX)
dot_disp <= ~point[cnt_sel];
else
dot_disp <= dot_disp;
/*********************************************************************/
/*********************** 一、数据处理 ****************************/
//data_reg:无效位是否以零显示
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if((h_hun) || (point[5]))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的万位为非零数据或需显示小数点,则值显示在5个数码管上
else if(((t_tho) || (point[4])) && (sign == 1'b1))//显示负号
data_reg <= {4'd10,t_tho,tho,hun,ten,unit};//4'd10我们定义为显示负号
else if(((t_tho) || (point[4])) && (sign == 1'b0))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};//4'd11我们定义为不显示
//若显示的十进制数的千位为非零数据或需显示小数点,则值显示4个数码管
else if(((tho) || (point[3])) && (sign == 1'b1))
data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
else if(((tho) || (point[3])) && (sign == 1'b0))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的百位为非零数据或需显示小数点,则值显示3个数码管
else if(((hun) || (point[2])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
else if(((hun) || (point[2])) && (sign == 1'b0))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的十位为非零数据或需显示小数点,则值显示2个数码管
else if(((ten) || (point[1])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
else if(((ten) || (point[1])) && (sign == 1'b0))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的个位且需显示负号
else if(((unit) || (point[0])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
//若上面都不满足都只显示一位数码管
else
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//二进制转BCD
bcd_8421 bcd_8421_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.data (data ), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
/*********************************************************************/
endmodule
`timescale 1ns/1ns
module bcd_8421
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [19:0] data ,
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun //十万位BCD码
);
//wire [19:0] data; //待显示的数值
reg [4:0] cnt_shift ; //操作次数计数
reg [43:0] data_shift ; //数值转换寄存器
reg shift_flag ; //移位或判断标志
//assign data = 20'd520520;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'b0;
else if(shift_flag == 1'b1) //防止在初始化时的 0值时开始加数
cnt_shift <= cnt_shift + 1'b1;
else if(shift_flag == 1'b1 && cnt_shift == 5'd21)
cnt_shift <= 5'b0;
else
cnt_shift <= cnt_shift;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0) //计数器为0时赋初值
data_shift <= {24'b0,data};
else if((cnt_shift <= 20)&&(shift_flag == 1'b0)) //<=为小于等于,移位后判断
begin
data_shift[23:20] <= (data_shift[23:20] > 4) ?
(data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ?
(data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ?
(data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ?
(data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ?
(data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ?
(data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (shift_flag == 1'b1))
data_shift <= data_shift << 1;
else
data_shift <= data_shift;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if ((cnt_shift == 21) && (shift_flag == 1'b1))
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
`timescale 1ns/1ns
module top_seg_dynamic
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [3:0] key_in,
output wire [5:0] sel ,
output wire [7:0] seg
);
//各模块间的连接线
wire [19:0] data ; //数码管要显示的值
wire [5:0] point ; //小数点显示,高电平有效top_seg_595
wire seg_en ; //数码管使能信号,高电平有效
wire sign ; //符号位,高电平显示负号
wire [3:0] key_flag;
key_filter key_filter_inst
(
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n ),
.key_in (key_in ),
.key_flag (key_flag )
);
data_gen data_gen_inst
(
.key_flag (key_flag ),
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.data (data ) ,
.seg_en (seg_en ) ,
.point (point ) ,
.sign (sign )
);
seg_dynamic seg_dynamic_inst
(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.data (data ),
.seg_en (seg_en),
.point (point ),
.sign (sign ),
.sel (sel ),
.seg (seg )
);
endmodule
1. 重点学习模块:数据生成模块、数码管显示驱动模块。
2. 在写rtl代码时,重要的一个提升代码效率的方法是逐步加功能,逐步验证其正确性。
说明:
本人学习时使用的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出。
开发软件:ise14.7 仿真:modelsim 10.5
如需上述资料私信或留下邮箱。