从零到一:基于DE2-115与Verilog的24h制数字钟实战,集成消抖与人性化调时

张开发
2026/6/8 1:14:25 15 分钟阅读
从零到一:基于DE2-115与Verilog的24h制数字钟实战,集成消抖与人性化调时
1. 从零开始搭建数字钟硬件与开发环境准备第一次接触FPGA开发的朋友可能会觉得数字钟项目有点复杂但实际拆解后就会发现它其实是由几个基础模块组合而成的。我当年在DE2-115开发板上完成这个项目时最大的感受就是选对工具和方法硬件开发也能像搭积木一样简单。先说说硬件准备。DE2-115开发板是Altera现在属于Intel的经典学习板自带50MHz时钟源、8个七段数码管和多个拨码开关/按键。这些正好满足我们的需求时钟源用于生成1Hz基准信号数码管显示时间按键用于调整时间。开发环境建议用Quartus II 13.1Web Edition免费版就够用这个版本对DE2-115的支持最稳定。Verilog代码的组织结构可以这样设计module digital_clock( input clk_50M, // 50MHz主时钟 input reset, // 复位信号 input mode_switch, // 自动/手动模式切换 input adjust_hour, // 调小时信号 input adjust_min, // 调分钟信号 output [6:0] seg_hour_ten, // 小时十位数码管 output [6:0] seg_hour_unit, // 小时个位数码管 // ...其他数码管输出省略 );2. 时钟系统的核心分频与计数模块设计2.1 把50MHz变成1Hz分频器的秘密开发板的50MHz时钟就像是个永不停歇的快枪手而我们需要的是每秒一次的心跳。这里就需要分频器出场了。具体实现时有个坑要注意50,000,000不是2的整数次幂直接计数会浪费资源。我的经验是先用500分频得到100kHz再用100,000分频得到1Hz这样综合后的电路更优化。实测过的分频器代码如下reg [25:0] counter; reg clk_1Hz; always (posedge clk_50M or posedge reset) begin if(reset) begin counter 0; clk_1Hz 0; end else if(counter 26d49_999_999) begin counter 0; clk_1Hz ~clk_1Hz; end else begin counter counter 1; end end2.2 计数模块60进制与24进制的舞蹈秒和分钟都是60进制小时是24进制这部分的Verilog实现有个小技巧统一采用BCD码计数这样后续显示译码会简单很多。我最初尝试用二进制计数结果显示时还要额外做二进制转BCD的电路徒增复杂度。分钟计数模块示例reg [3:0] min_unit; // 分钟个位 reg [3:0] min_ten; // 分钟十位 wire min_carry; // 向小时进位 always (posedge sec_carry or posedge reset) begin // sec_carry来自秒模块 if(reset) begin {min_ten, min_unit} 8h00; end else if(min_unit 4d9) begin min_unit 0; if(min_ten 4d5) begin min_ten 0; min_carry 1; end else begin min_ten min_ten 1; end end else begin min_unit min_unit 1; end end3. 用户体验的关键消抖与调时方案3.1 必须掌握的硬件消抖技术机械开关在闭合时会产生5-10ms的抖动直接读取会导致误触发。网上很多消抖方案要么太复杂状态机实现要么占用资源多延时法。我最终采用的方案是采样滤波法每10ms采样一次开关状态连续3次相同才认为有效。这个方案在DE2-115上实测消耗不到10个LE逻辑单元。消抖模块核心代码reg [1:0] sample_reg; // 采样移位寄存器 reg stable_out; // 稳定输出 reg [19:0] counter; // 10ms计数器 always (posedge clk_50M) begin if(counter 20d499_999) begin // 10ms计时 counter 0; sample_reg {sample_reg[0], raw_input}; if(sample_reg[1:0] 2b00 || sample_reg[1:0] 2b11) stable_out sample_reg[1]; end else begin counter counter 1; end end3.2 两种调时方案的实战对比方案A按键长按调时优点节省硬件资源只需两个按键缺点操作体验差需要持续按压适用场景按键资源紧张时方案B拨动开关数据选择器优点操作直观类似真实电子表缺点需要额外拨码开关实现技巧用拨码开关控制二选一MUX选择时钟源是分频器还是手动按键我强烈推荐方案B虽然多用了一个拨码开关但用户体验提升明显。具体实现时可以用Verilog的门级描述直接构建MUXwire min_clock_source (mode_switch) ? sec_carry : adjust_min_pulse;4. 显示优化与系统调试技巧4.1 七段数码管的驱动艺术DE2-115的数码管是共阳极的段选信号低电平有效。这里有个易错点开发板上的6位数码管是动态扫描的需要我们自己实现扫描逻辑。我的做法是用一个计数器循环产生位选信号同时配合显示数据缓存寄存器。显示驱动代码结构reg [2:0] scan_cnt; // 扫描计数器 always (posedge clk_1kHz) begin // 1kHz扫描时钟 scan_cnt scan_cnt 1; case(scan_cnt) 0: begin seg_data hour_ten; dig_sel 6b111110; end 1: begin seg_data hour_unit; dig_sel 6b111101; end // ...其他位类似 endcase end4.2 调试时我踩过的那些坑时钟不同步问题最初直接用手动按键的上升沿作为计数时钟导致与系统时钟不同步。解决方法是用系统时钟采样按键信号生成单周期脉冲。显示闪烁问题扫描频率太低会导致肉眼可见的闪烁。实测发现1kHz以上扫描频率最合适。综合警告处理Quartus经常会报inferred latch警告这通常是因为组合逻辑中未列出所有条件分支。解决方法是用default语句覆盖所有情况。调试小技巧在Quartus中设置Signal Tap Logic Analyzer实时抓取内部信号波形。比如可以同时监控原始按键信号和消抖后的信号直观验证消抖效果。5. 项目进阶与扩展思路完成基础功能后我尝试了几个增强功能整点报时功能增加一个蜂鸣器驱动电路当分钟和秒都为0时触发。注意要加使能开关防止深夜扰民。闹钟功能增加一组时间寄存器作为闹钟时间比较当前时间与闹钟时间匹配时触发。需要增加设置闹钟的按钮逻辑。亮度自动调节通过光敏电阻采集环境光强PWM调节数码管亮度。这个需要用到开发板上的ADC模块。Verilog设计有个特点功能增加不会显著提高代码复杂度只需要添加相应模块并正确连接即可。比如闹钟功能只需增加一个时间比较器模块reg [3:0] alarm_h_ten, alarm_h_unit; reg [3:0] alarm_m_ten, alarm_m_unit; wire alarm_trigger (hour_tenalarm_h_ten) (hour_unitalarm_h_unit) (min_tenalarm_m_ten) (min_unitalarm_m_unit);这个项目最让我惊喜的是当所有模块正确连接后看着数码管上的数字规律跳动的那一刻仿佛看到了数字世界的脉搏。硬件描述语言虽然入门门槛比C语言高但一旦掌握就能真正看见电子在芯片中的流动轨迹。

更多文章