用FPGA和Verilog做个电子时钟:基于4位数码管的完整项目实战(含PLL配置)

张开发
2026/4/19 17:27:57 15 分钟阅读

分享文章

用FPGA和Verilog做个电子时钟:基于4位数码管的完整项目实战(含PLL配置)
基于FPGA的4位数码管电子时钟从模块设计到系统整合实战第一次接触FPGA开发板时看到那些闪烁的LED和跳动的数码管总有种想要亲手实现一个完整电子时钟的冲动。不同于简单的计数器实验一个真正的电子时钟需要考虑时、分、秒的进位关系精确的时钟源管理以及更人性化的显示方式。本文将带你从零开始用Verilog HDL在FPGA上实现一个功能完整的4位数码管电子时钟重点解决60进制和24进制计数逻辑、数码管冒号显示、PLL精确时钟配置等核心问题。1. 项目需求分析与系统架构1.1 电子时钟的功能定义一个基础电子时钟需要满足以下核心需求时间显示范围支持24小时制00:00-23:59或12小时制可选显示精度时:分:秒HH:MM:SS的完整显示显示介质4位八段数码管带小数点时间源基于FPGA内部PLL产生的精确1Hz时钟信号考虑到4位数码管的显示限制我们需要采用时分秒轮显方案第1-2位小时HH第3-4位分钟MM秒数SS通过小数点闪烁表示如HH:MM时dp点亮表示当前秒数为偶数1.2 系统架构设计整个系统采用层次化设计主要模块及数据流如下--------------- | PLL IP | | (时钟管理) | -------------- | v ------------- -------------- ------------- | 顶层模块 |---| 计数器模块 |---| 数码管驱动 | | (top_clock) | | (time_counter)| | (seg7_drv) | ------------- --------------- ------------- | | v v 外部时钟输入 数码管位选/段选信号关键信号说明clk_1HzPLL生成的精确1Hz时钟time_data[23:0]24位时间数据[23:20]小时十位[19:16]小时个位[15:12]分钟十位...seg_data[7:0]数码管段选信号含小数点dpdig_sel[3:0]数码管位选信号2. 核心模块实现细节2.1 时间计数器模块time_counter这是整个系统的核心需要实现60进制秒/分和24进制时的计数逻辑。以下是改进后的计数器模块关键代码module time_counter( input clk_1Hz, // 1Hz时钟输入 input rst_n, // 异步复位 output reg [23:0] time_data // 时间数据输出 ); // 时间计数逻辑 always (posedge clk_1Hz or negedge rst_n) begin if (!rst_n) begin time_data 24h000000; // 复位时00:00:00 end else begin // 秒计数60进制 if (time_data[3:0] 4d9) begin time_data[3:0] 4d0; if (time_data[7:4] 4d5) begin time_data[7:4] 4d0; // 触发分钟进位 end else begin time_data[7:4] time_data[7:4] 1; end end else begin time_data[3:0] time_data[3:0] 1; end // 分钟计数60进制逻辑类似... // 小时计数24进制 if (/* 分钟进位信号 */) begin if (time_data[19:16] 4d9 || (time_data[23:20] 4d1 time_data[19:16] 4d3)) begin // 处理23-00的特殊情况 end end end end endmodule注意实际实现时需要完整处理秒→分→时的三级进位逻辑此处为简化示意2.2 数码管驱动优化seg7_drv为支持时钟显示我们需要对原始数码管驱动做以下改进冒号显示利用数码管的小数点(dp)段作为秒间隔指示时分切换通过分时复用同时显示小时和分钟关键修改点// 在seg7_drv模块中添加冒号控制逻辑 reg blink_cnt; // 闪烁计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin blink_cnt 0; end else if (div_cnt 8hff) begin blink_cnt ~blink_cnt; // 每完整扫描一轮取反 end end // 修改段选输出逻辑 always (*) begin case(div_cnt[7:6]) 2b00: begin // 显示小时十位 dtube_data {seg_table[display_num[23:20]], ~blink_cnt}; end 2b01: begin // 显示小时个位 dtube_data {seg_table[display_num[19:16]], 1b1}; end // ...其他位显示 endcase end数码管扫描时序优化扫描周期激活数码管显示内容小数点状态00-3FDIG1小时十位闪烁40-7FDIG2小时个位常灭80-BFDIG3分钟十位闪烁C0-FFDIG4分钟个位常灭2.3 PLL精确时钟配置使用FPGA内部的PLL IP核生成精确的1Hz时钟信号以Xilinx 7系列FPGA为例PLL参数配置输入时钟25MHz开发板常见晶振频率输出时钟1Hz通过分频系数实现抖动优化启用Spread Spectrum选项Vivado中PLL配置代码clk_wiz_0 pll_inst ( .clk_in1(ext_clk_25m), // 输入25MHz .reset(!ext_rst_n), // 异步复位 .clk_out1(clk_25m), // 25MHz其他模块使用 .clk_out2(clk_1Hz), // 1Hz主时钟 .locked(pll_locked) // 锁定信号 );提示实际分频系数需要根据具体FPGA型号调整高端器件可直接输出低频低端器件可能需要二级分频3. 工程实现技巧与调试3.1 跨时钟域处理由于系统存在多个时钟域25MHz扫描时钟和1Hz计时时钟需要特别注意时钟域同步// 将1Hz信号同步到25MHz时钟域 reg [1:0] sync_1Hz; always (posedge clk_25m) begin sync_1Hz {sync_1Hz[0], clk_1Hz}; end wire clk_1Hz_synced (sync_1Hz 2b01);亚稳态防护对所有跨时钟域信号添加双寄存器同步对复位信号进行去抖和同步处理3.2 上板调试技巧LED辅助调试// 在顶层模块添加调试LED assign led_debug { pll_locked, // PLL锁定指示 clk_1Hz, // 1Hz时钟观测 time_data[0] // 秒信号最低位 };常见问题排查表现象可能原因解决方案数码管显示乱码段选极性配置错误检查共阴/共阳设置时间走时不准PLL配置错误测量实际输出频率冒号不闪烁闪烁计数器未正确工作添加SignalTap观察blink_cnt显示有重影位选信号消隐时间不足增加位间消隐间隔4. 功能扩展与优化方向4.1 显示模式扩展12/24小时制切换reg mode_12h; // 024小时制112小时制 always (*) begin if (mode_12h time_data[23:20] 4d1) begin hour_display time_data - 24h120000; // 12小时制转换 end end日期显示轮换通过按键切换显示时间/日期使用长按短按实现多功能控制4.2 高级功能实现RTC时间校准添加DS1302等RTC芯片接口实现时间读取和校准功能网络时间同步通过UART接收GPS或NTP时间实现自动校准功能低功耗设计// 动态时钟门控 always (posedge clk_25m) begin if (idle_mode) begin clk_enable 0; // 关闭非必要时钟 end end4.3 性能优化建议扫描频率优化计算最佳刷新率避免闪烁通常200-1000Hz动态调整扫描间隔减轻FPGA负担代码优化技巧// 使用参数化设计增强可移植性 parameter CLK_FREQ 25_000_000; localparam CNT_1HZ CLK_FREQ - 1; // 使用generate简化多位显示逻辑 generate for (i0; i4; ii1) begin : seg_gen always (posedge clk) begin if (dig_sel[i]) begin seg_data seg_table[time_data[i*4:4]]; end end end endgenerate在Altera Cyclone IV开发板上实测优化后的设计仅占用逻辑单元~320LEs存储器0bitsPLL1个这个电子时钟项目虽然基础但涵盖了FPGA开发的多个关键技能点从时钟管理、状态机设计到外设驱动再到系统级调试。当看到自己编写的代码让数码管准确显示时间的那一刻那种成就感正是硬件开发的魅力所在。建议在完成基础功能后尝试添加闹钟、温湿度显示等扩展功能这将帮助你更深入地掌握FPGA系统设计。

更多文章