基于FPGA的数字钟设计---第四版--郝旭帅电子设计团队
欢迎各位朋友关注“郝旭帅电子设计团队”,本篇为各位朋友介绍基于FPGA的数字钟设计---第四版。
功能说明:
1. 利用六个数码管显示,模式一:时间的时、分、秒,模式二:闹钟的 时、分、打开关闭闹钟(A关闭、C打开)
2. 利用按键可以切换显示模式。
3. 时间和闹钟都是24小时制。
4. 在模式一 调整按键不按下时,正常显示时分秒。
按下第一次,进入调整状态,时间停止,并且小时开始闪烁,通过加减按键可以进行调整。
按下第二次,分钟开始闪烁,通过加减按键可以进行调整。
按下第三次,秒开始闪烁,通过加减按键可以进行调整。
按下第四次,进入正常运行状态。
后续再按下时,重复以上的过程
5. 在模式二 调整按键不按下时,正常显示闹钟信息。
按下第一次,进入调整状态,小时开始闪烁,通过加减按键可以进行调整。
按下第二次,分钟开始闪烁,通过加减按键可以进行调整。
按下第三次,闹钟打开、关闭开始闪烁,通过加减按键可以进行调整。
按下第四次,进入正常运行状态。
后续再按下时,重复以上的过程
6. 在调整状态下,闹钟不响。
7. 当闹钟打开,非调整状态下,闹钟时间与时间相同时,蜂鸣器响一分钟。可以按任何按键关闭闹钟
使用平台:本次设计应用Altera的平台设计(芯片:EP4CE10F17C8N)。
仿真平台:Modelsim。
作者QQ:746833924
说明:本篇设计中不涉及到IP和原语,代码在其他平台依然可以适用;当其他板卡电路不同时,会导致不同的现象出现,如有需要修改代码请联系作者;如需作者使用的板卡,请联系作者;
设计思想如下:
key_ctrl模块负责将外部的按键信号进行消抖,并且产生对应边沿变化时的脉冲;digital_clock_ctrl模块负责根据脉冲信号和设计逻辑产生对应数字逻辑和蜂鸣器控制信号;seven_tube_drive(七段数码管驱动)模块负责将digital_clock_ctrl模块产生的数字逻辑显示到数码管上。
key_ctrl模块设计思想为:按键信号是由外部机械式按键产生,每次按下或者抬起时,会产生一定的抖动。如果直接对其进行边沿检测就会导致多次触发。故而需要设计按键消抖,进而对消抖之后的波形进行边沿检测。消抖原理为:外部按键信号发生改变后,如果能够持续20ms,没有新的改变,就认为此次改变不是抖动,而是真正的按下,然后进行采样即可。
// 记录任意边沿之后没有遇到新的边沿的时间长度是否达到20Ms //--------------------------------------------------------------------------------------- always@(posedgeclk)begin if(rst_n ==1'b0) cnt_20ms <=20'd0; else if(pulse_key_negedge ==1'b1||pulse_key_posedge ==1'b1) cnt_20ms <=20'd1; else if(cnt_20ms >20'd0&&cnt_20ms <T_20ms) cnt_20ms <=cnt_20ms +1'b1; else cnt_20ms <=20'd0; end // --------------------------------------------------------------------------------------- // 任意边沿之后没有遇到新的边沿的时间长度达到20Ms,认为按键稳定,此时采样 //-------------------------------------------------------------------------------------- always@(posedgeclk)begin if(rst_n ==1'b0) key_wave <=1'b1; else if(cnt_20ms ==T_20ms) key_wave <=key_rr; else key_wave <=key_wave; end //-------------------------------------------------------------------------------------- // 对消抖之后的按键信号进行边沿检测 //--------------------------------------------------------------------------------------------- initialkey_wave_r =1'b1; always@(posedgeclk)key_wave_r <=key_wave; assignflag_neg =(key_wave_r ==1'b1&&key_wave ==1'b0)?1'b1:1'b0; assignflag_pos =(key_wave_r ==1'b0&&key_wave ==1'b1)?1'b1:1'b0; //--------------------------------------------------------------------------------------------
digital_clock_ctrl模块的设计思想:首先根据外部的调整脉冲,确定显示模式(state_show:0是时间模式,1是闹钟模式),以及需要调整的位置(state_adjust:0是不调整,1是调整时间/闹钟的小时, 2是调整时间/闹钟的分钟,3是调整时间的秒/闹钟的开闭 )。如果调整期间按下切换模式,自动确认调整生效并且调整位置归零。
// 切换显示模式 always @ (posedge clk) begin if (rst_n == 1'b0) state_show <= 1'b0; else if (key_sel_flag == 1'b1) state_show <= ~state_show; else state_show <= state_show; end // 切换调整模式和正常模式 always @ (posedge clk) begin if (rst_n == 1'b0) state_adjust <= 2'b00; else if (key_sel_flag == 1'b1) state_adjust <= 2'd0; else if (key_adjust_flag == 1'b1) state_adjust <= state_adjust + 1'b1; // 0-3无限循环 else state_adjust <= state_adjust; end
// 1秒钟计时 // 只有时间正常模式或者闹钟模式才开始计时 always @ (posedge clk) begin if (rst_n == 1'b0) cnt <= 26'd0; else if ((state_show == 1'b0 && state_adjust == 2'd0) || (state_show == 1'b1)) if (cnt < T_1s - 1'b1) cnt <= cnt + 1'b1; else cnt <= 26'd0; else cnt <= 26'd0; end
时间模式下:秒计时和调整。
// 秒计时 // 在秒调整模式中,加和减按键做对应的工作。 // 在正常模式,正常计时 always @ (posedge clk) begin if (rst_n == 1'b0) sec <= 6'd0; else if (state_adjust == 2'd3 && state_show == 1'b0) if (key_add_flag == 1'b1) if (sec < 6'd59) sec <= sec + 1'b1; else sec <= 6'd0; else if (key_sub_flag == 1'b1) if (sec > 6'd0) sec <= sec - 1'b1; else sec <= 6'd59; else sec <= sec; else if (cnt == T_1s - 1'b1) if (sec < 6'd59) sec <= sec + 1'b1; else sec <= 6'd0; else sec <= sec; end
闹钟模式下,开关的调整。
initial alarm_state = 6'h0A; always @ (posedge clk) begin if (rst_n == 1'b0) alarm_state <= 6'h0A; else if (state_adjust == 2'd3 && state_show == 1'b1) if (key_add_flag == 1'b1 || key_sub_flag == 1'b1) if (alarm_state == 6'h0A) alarm_state <= 6'h0C; else alarm_state <= 6'h0A; else alarm_state <= alarm_state; else alarm_state <= alarm_state; end
对于时间模式下的分钟和小时,以及闹钟模式下的分钟和小时,原理都和上述是相同的,在此不在叙述。
如果在调整模式中,启动一个一秒钟的计时器。
// 处于调整模式时,开始计时一秒钟 always @ (posedge clk) begin if (rst_n == 1'b0) flash_cnt <= 26'd0; else if (state_adjust == 2'd0) flash_cnt <= 26'd0; else if (flash_cnt < T_1s - 1'b1) flash_cnt <= flash_cnt + 1'b1; else flash_cnt <= 26'd0; end
在时间/闹钟模式下将需要显示的时、分、秒的十位和个位计算出来。在对应的调整模式下,需要前半秒输出对应的数字,后半秒输出全F。在数码管驱动中,当输入正常数字0~9显示数字,当输入F时数码管熄灭,此时数码管就闪烁起来了。data0是最左侧数码管,其他的一次类推。
//计算得出显示的数值 always @ (posedge clk) begin if (state_show == 1'b0) data0 <= (state_adjust == 2'd1 && flash_cnt > T_1s/2) ? 4'hf : hour/10; else data0 <= (state_adjust == 2'd1 && flash_cnt > T_1s/2) ? 4'hf : alarm_hour/10; end always @ (posedge clk) begin if (state_show == 1'b0) data1 <= (state_adjust == 2'd1 && flash_cnt > T_1s/2) ? 4'hf : hour%10; else data1 <= (state_adjust == 2'd1 && flash_cnt > T_1s/2) ? 4'hf : alarm_hour%10; end always @ (posedge clk) begin if (state_show == 1'b0) data2 <= (state_adjust == 2'd2 && flash_cnt > T_1s/2) ? 4'hf : min/10; else data2 <= (state_adjust == 2'd2 && flash_cnt > T_1s/2) ? 4'hf : alarm_min/10; end always @ (posedge clk) begin if (state_show == 1'b0) data3 <= (state_adjust == 2'd2 && flash_cnt > T_1s/2) ? 4'hf : min%10; else data3 <= (state_adjust == 2'd2 && flash_cnt > T_1s/2) ? 4'hf : alarm_min%10; end always @ (posedge clk) begin if (state_show == 1'b0) data4 <= (state_adjust == 2'd3 && flash_cnt > T_1s/2) ? 4'hf : sec/10; else data4 <= 4'hf; end always @ (posedge clk) begin if (state_show == 1'b0) data5 <= (state_adjust == 2'd3 && flash_cnt > T_1s/2) ? 4'hf : sec%10; else data5 <= (state_adjust == 2'd3 && flash_cnt > T_1s/2) ? 4'hf : alarm_state[3:0]; end
对于闹钟的处理为,当时间和闹钟都处于非调整状态,然后闹钟处于打开状态,此时,闹钟的小时和分钟与时间的小时和分钟相同时,拉高蜂鸣器响的使能,按下任意按键或者闹钟的小时和分钟与时间的小时和分钟不相同时,拉低蜂鸣器响的使能。
如果一分钟没有到,外部按下了按键,那么后面即使闹钟的小时和分钟与时间的小时和分钟相同,也不能拉高蜂鸣器响的使能了,所以在拉高时,要用到相等的一个上升沿。
assign alarm_en = (alarm_hour == hour && alarm_min == min) ? 1'b1 : 1'b0; always @ (posedge clk) begin if (rst_n == 1'b0) alarm_en_r <= 1'b0; else alarm_en_r <= alarm_en; end always @ (posedge clk) begin if (rst_n == 1'b0) beep_alarm_en <= 1'b0; else if (state_adjust == 2'd0 && alarm_en == 1'b1 && alarm_en_r == 1'b0 && alarm_state == 6'h0C) beep_alarm_en <= 1'b1; else if (alarm_en == 1'b0 || key_add_flag == 1'b1 || key_adjust_flag == 1'b1 || key_sel_flag == 1'b1 || key_sub_flag == 1'b1) beep_alarm_en <= 1'b0; else beep_alarm_en <= beep_alarm_en; end
以上即为digital_clock_ctrl模块的设计思想;
七段数码管为普通六位一体的共阳极数码,采用动态驱动的方式,在此不再赘述。
蜂鸣器驱动设计为外部标志拉高蜂鸣器就响,拉低就不响了。
在本设计中,蜂鸣器采用无源蜂鸣器,需要外部给予一定频率的方波才可以响,在此选择500Hz的方波(即1ms取反一次)。
always @ (posedge clk) begin if (rst_n == 1'b0) cnt_1ms <= 32'd0; else if (beep_alarm_en == 1'b1 && cnt_1ms < T_1ms - 1'b1) cnt_1ms <= cnt_1ms + 1'b1; else cnt_1ms <= 32'd0; end always @ (posedge clk) begin if (rst_n == 1'b0) beep <= 1'd0; else if (cnt_1ms == T_1ms - 1'b1) beep <= ~beep; else beep <= beep; end
当然我们也可以使用按键进行调节到我们想要的任何时间点,以及闹钟的时间和是否打开闹钟。
下板后,演示视频(链接)如下:
https://www.bilibili.com/video/BV1rE421N7ra/?spm_id_from=333.999.0.0&vd_source=b5405faeab8632f02533bcbfc5e52e55
本设计所有内容(设计代码、设计工程)链接为:
链接:
https://pan.baidu.com/s/1Aj8VPYQgQ_xkVz7cv4k5OQ
提取码:4s50
本篇内容中有部分资源来源于网络,如有侵权,请联系作者。
如果您觉得本公众号还不错的话,可以推给身边的朋友们,感谢并祝好!