1. 实验说明
在数码管显示数据的基础上,让六位数码管显示数字时钟,并且通过按键可以对时间进行修改。
实验目标:六位数码管分别显示时间的时分秒,且通过按键可实现加减调整时间及清零功能。
key1: 切换键:选择待调整的时间单位(时、分、秒)
key2: 时间加键
key3: 时间减键
key4: 时钟清零键
效果如下图:时钟清零——>分钟加减——>时钟加减——>时钟正常运行
2. 模块设计
各模块功能说明:
各模块原理之前在数码管动态显示的博客中有详细说明,不理解的朋友可以移步这篇文章:
https://mp.csdn.net/mp_blog/creation/editor/127933111
3. RTL代码设计
3.1 按键消抖模块
`timescale 1ns/1nsmodule key_filter
#(parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(input wire sys_clk , //系统时钟50Mhzinput 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;elsecnt_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;elsecnt_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;elsecnt_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;elsecnt_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 Helse 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; //清零elsekey_flag <= 4'b0000;endmodule
3.2 数据生成模块
`timescale 1ns/1nsmodule 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;elsecnt <= 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;elsecnt_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;elsecnt_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;elsecnt_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;elsecnt_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
3.3 数码管显示驱动模块
`timescale 1ns/1nsmodule 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;elsecnt_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;elsecnt_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;elsesel <= 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 ;endcaseelsedata_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}; //显示数字04'd1 : seg <= {dot_disp,7'b111_1001}; //显示数字14'd2 : seg <= {dot_disp,7'b010_0100}; //显示数字24'd3 : seg <= {dot_disp,7'b011_0000}; //显示数字34'd4 : seg <= {dot_disp,7'b001_1001}; //显示数字44'd5 : seg <= {dot_disp,7'b001_0010}; //显示数字54'd6 : seg <= {dot_disp,7'b000_0010}; //显示数字64'd7 : seg <= {dot_disp,7'b111_1000}; //显示数字74'd8 : seg <= {dot_disp,7'b000_0000}; //显示数字84'd9 : seg <= {dot_disp,7'b001_0000}; //显示数字94'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];elsedot_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};
//若上面都不满足都只显示一位数码管elsedata_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
3.4 二进制转BCD码模块
`timescale 1ns/1nsmodule 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)) //<=为小于等于,移位后判断begindata_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]);endelse if((cnt_shift <= 20) && (shift_flag == 1'b1))data_shift <= data_shift << 1;elsedata_shift <= data_shift;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginunit <= 4'b0;ten <= 4'b0;hun <= 4'b0;tho <= 4'b0;t_tho <= 4'b0;h_hun <= 4'b0;endelse if ((cnt_shift == 21) && (shift_flag == 1'b1))beginunit <= 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
3.5 顶层设计模块
`timescale 1ns/1nsmodule 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
4. 总结
1. 重点学习模块:数据生成模块、数码管显示驱动模块。
2. 在写rtl代码时,重要的一个提升代码效率的方法是逐步加功能,逐步验证其正确性。
说明:
本人学习时使用的是野火家Xilinx Spartan6系列开发板及配套教程,以上内容如有疑惑或错误欢迎评论区指出。
开发软件:ise14.7 仿真:modelsim 10.5
如需上述资料私信或留下邮箱。