FPGA新手必看:用VHDL在Quartus II里从零搭建一个带校时和整点报时的数字钟

张开发
2026/4/16 23:36:36 15 分钟阅读

分享文章

FPGA新手必看:用VHDL在Quartus II里从零搭建一个带校时和整点报时的数字钟
FPGA实战从零构建VHDL数字钟的模块化设计指南当我在大学第一次接触FPGA开发时数字钟项目就像一道难以逾越的高墙。那些看似简单的时分秒显示背后隐藏着分频、计数、消抖、报时等复杂模块的精密协作。本文将带你用VHDL在Quartus II环境中从工程创建到硬件下载完整实现一个带校时和整点报时功能的数字钟系统。1. 工程搭建与核心模块规划在Cyclone III开发板上启动Quartus II 13.0新建工程时需特别注意器件选择Edit Device中指定EP3C10E144C8芯片。建议采用自顶向下的设计方法将系统划分为五个关键模块模块名称功能描述关键信号时钟分频器将50MHz系统时钟分频为1Hz基准信号clk_50m - clk_1s按键消抖模块消除机械按键的抖动干扰key_raw - key_stableBCD计数器组实现24/60进制的时分秒计数carry_out[2:0]数码管驱动动态扫描显示6位时间信息seg[7:0], sel[5:0]整点报时器在59:59时触发蜂鸣器buzz_en 注意初学者常犯的错误是直接开始编写VHDL代码。建议先绘制如图1所示的RTL结构图明确各模块接口关系。2. 关键模块的VHDL实现技巧2.1 智能分频器设计传统分频器采用计数器累加方式但当需要同时生成不同频率信号时如1Hz时钟、1kHz扫描频率可优化为多通道分频结构entity clk_div is Port ( clk_50m : in STD_LOGIC; clk_1s : out STD_LOGIC; clk_1k : out STD_LOGIC); end clk_div; architecture Behavioral of clk_div is signal cnt_1s : integer range 0 to 49999999 : 0; signal cnt_1k : integer range 0 to 24999 : 0; begin process(clk_50m) begin if rising_edge(clk_50m) then -- 1Hz分频通道 if cnt_1s 49999999 then clk_1s not clk_1s; cnt_1s 0; else cnt_1s cnt_1s 1; end if; -- 1kHz分频通道 if cnt_1k 24999 then clk_1k not clk_1k; cnt_1k 0; else cnt_1k cnt_1k 1; end if; end if; end process; end Behavioral;2.2 基于状态机的按键消抖机械按键的抖动通常持续5-10ms采用双寄存器采样法配合状态机可实现稳定检测entity debounce is Port ( clk_1k : in STD_LOGIC; key_in : in STD_LOGIC; key_out : out STD_LOGIC); end debounce; architecture FSM of debounce is type state_type is (IDLE, CHECK, CONFIRM); signal state : state_type : IDLE; signal cnt : integer range 0 to 9 : 0; signal q1, q2 : STD_LOGIC : 0; begin process(clk_1k) begin if rising_edge(clk_1k) then q1 key_in; q2 q1; case state is when IDLE if q1 / q2 then state CHECK; cnt 0; end if; when CHECK if cnt 9 then state CONFIRM; else cnt cnt 1; end if; when CONFIRM key_out q2; state IDLE; end case; end if; end process; end FSM;3. BCD计数器的精妙设计3.1 60进制秒计数器实现秒计数需要处理两个关键点个位十进制计数和十位六进制计数。采用行为级描述更直观entity sec_counter is Port ( clk_1s : in STD_LOGIC; reset : in STD_LOGIC; sec_ge : out STD_LOGIC_VECTOR(3 downto 0); sec_shi : out STD_LOGIC_VECTOR(3 downto 0); carry_out : out STD_LOGIC); end sec_counter; architecture Behavioral of sec_counter is signal cnt_ge : integer range 0 to 9 : 0; signal cnt_shi : integer range 0 to 5 : 0; begin process(clk_1s, reset) begin if reset 1 then cnt_ge 0; cnt_shi 0; elsif rising_edge(clk_1s) then if cnt_ge 9 then cnt_ge 0; if cnt_shi 5 then cnt_shi 0; carry_out 1; else cnt_shi cnt_shi 1; carry_out 0; end if; else cnt_ge cnt_ge 1; carry_out 0; end if; end if; sec_ge std_logic_vector(to_unsigned(cnt_ge, 4)); sec_shi std_logic_vector(to_unsigned(cnt_shi, 4)); end process; end Behavioral;3.2 带校时功能的24小时制计数器时计数器需要支持两种时钟源正常进位和手动校时。采用选择信号实现双输入切换entity hour_counter is Port ( clk_normal : in STD_LOGIC; clk_adj : in STD_LOGIC; sel_adj : in STD_LOGIC; reset : in STD_LOGIC; hour_ge : out STD_LOGIC_VECTOR(3 downto 0); hour_shi : out STD_LOGIC_VECTOR(3 downto 0)); end hour_counter; architecture Behavioral of hour_counter is signal cnt_ge : integer range 0 to 9 : 0; signal cnt_shi : integer range 0 to 2 : 0; signal clk_src : STD_LOGIC; begin clk_src clk_adj when sel_adj 1 else clk_normal; process(clk_src, reset) begin if reset 1 then cnt_ge 0; cnt_shi 0; elsif rising_edge(clk_src) then if cnt_shi 2 and cnt_ge 3 then cnt_ge 0; cnt_shi 0; elsif cnt_ge 9 then cnt_ge 0; cnt_shi cnt_shi 1; else cnt_ge cnt_ge 1; end if; end if; hour_ge std_logic_vector(to_unsigned(cnt_ge, 4)); hour_shi std_logic_vector(to_unsigned(cnt_shi, 4)); end process; end Behavioral;4. Modelsim仿真与硬件调试4.1 建立Testbench的要点完整的仿真需要构建包含所有模块的测试环境。关键激励信号应包含50MHz系统时钟周期20ns模拟按键抖动的脉冲序列手动校时触发信号library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity tb_digital_clock is end tb_digital_clock; architecture Behavioral of tb_digital_clock is component digital_clock Port ( clk_50m : in STD_LOGIC; key_min : in STD_LOGIC; key_hour : in STD_LOGIC; seg : out STD_LOGIC_VECTOR(7 downto 0); sel : out STD_LOGIC_VECTOR(5 downto 0); buzz : out STD_LOGIC); end component; signal clk_50m : STD_LOGIC : 0; signal key_min, key_hour : STD_LOGIC : 1; signal seg : STD_LOGIC_VECTOR(7 downto 0); signal sel : STD_LOGIC_VECTOR(5 downto 0); signal buzz : STD_LOGIC; -- 模拟按键抖动波形 procedure press_key(signal key : out STD_LOGIC) is begin key 0; wait for 5 ms; key 1; wait for 2 ms; key 0; wait for 3 ms; key 1; wait for 1 ms; key 0; wait for 500 ms; -- 稳定按下 key 1; wait for 10 ms; end procedure; begin uut: digital_clock port map ( clk_50m clk_50m, key_min key_min, key_hour key_hour, seg seg, sel sel, buzz buzz ); -- 50MHz时钟生成 clk_50m not clk_50m after 10 ns; stimulus: process begin wait for 100 ns; -- 测试分钟校时 press_key(key_min); -- 测试小时校时 wait for 1 sec; press_key(key_hour); -- 观察整点报时 wait for 58 min; wait until rising_edge(clk_50m); wait; end process; end Behavioral;4.2 硬件调试常见问题排查当下载到Cyclone III开发板后若出现显示异常建议按以下顺序排查时钟信号检查用示波器测量clk_1s信号是否为精确1Hz方波确认分频计数器位宽是否足够50MHz→1Hz需25位计数器按键响应测试短按按键时应只触发一次动作长按按键时应持续快速调整时间数码管显示问题检查段选和位选信号极性共阴/共阳确认扫描频率在100Hz-1kHz范围内整点报时异常在59分50秒时用逻辑分析仪捕捉报时触发信号调整蜂鸣器驱动电路的占空比改善音效5. 系统优化与扩展思路基础功能实现后可以考虑以下增强功能动态亮度调节process(clk_1k) variable pwm_cnt : integer range 0 to 255 : 0; begin if rising_edge(clk_1k) then pwm_cnt : pwm_cnt 1; if pwm_cnt brightness then seg seg_data; else seg (others 1); end if; end if; end process;温度补偿时钟通过DS18B20获取环境温度根据温度偏差调整分频系数计算公式补偿值 基准值 × (1 0.0002 × (T - 25))实际项目中我曾遇到一个有趣的问题当同时按下校时按键和复位键时计数器会进入异常状态。后来通过增加状态互锁逻辑解决了这个问题——这提醒我们在FPGA设计中边界条件的测试同样重要。

更多文章