如何在Basys3 FPGA上用VHDL实现有限状态机 解决单次按键多触发问题
问题原因
你的猜测是对的,同时还有额外的亚稳态风险,问题核心分为两点:
- 异步输入未同步:按键是外部异步信号,直接输入同步时钟域的逻辑会有亚稳态风险,导致采样结果不稳定。
- 电平触发逻辑不合理:Basys3默认系统时钟为100MHz,单次按键按下的时长通常在100ms以上,这段时间内会经过上千万个时钟周期,你当前的状态机只要检测到P为高就跳转,自然会被触发多次。
解决方法
你需要对按键输入做三级处理:同步→消抖→边沿检测,最终生成单周期的触发脉冲,只有脉冲有效时才允许状态跳转。
- 两级同步:用两个寄存器打拍,把异步的按键输入同步到时钟域,消除亚稳态
- 按键消抖:检测按键电平稳定持续20ms以上(覆盖机械按键的抖动区间),输出稳定的按键电平信号
- 边沿检测:对消抖后的稳定信号检测上升沿,生成一个仅持续1个时钟周期的触发脉冲,保证按一次按键仅触发一次状态跳转
修改后代码示例
library IEEE; USE ieee.std_logic_1164.all; USE ieee.numeric_std.all; ENTITY trasher is PORT ( clock : IN STD_LOGIC; P : IN STD_LOGIC; reset : IN STD_LOGIC; LED3, LED1,LED2,LED0 : OUT STD_LOGIC); END ENTITY; Architecture RTL of trasher is TYPE State_type IS (A, B, C, D); SIGNAL State : State_Type; -- 按键处理相关信号 SIGNAL p_sync1, p_sync2 : STD_LOGIC; -- 两级同步寄存器 SIGNAL p_debounced : STD_LOGIC; -- 消抖后信号 SIGNAL p_debounced_prev : STD_LOGIC; -- 消抖信号打拍,用于边沿检测 SIGNAL key_pulse : STD_LOGIC; -- 单周期触发脉冲 SIGNAL debounce_cnt : unsigned(20 downto 0); -- 100MHz下21位计数器最大计数2^21=2097152,对应约20.97ms,覆盖消抖区间 CONSTANT DEBOUNCE_THRESHOLD : unsigned(20 downto 0) := to_unsigned(2000000, 21); -- 20ms阈值 BEGIN -- 1. 按键两级同步 PROCESS(clock, reset) BEGIN IF reset = '1' THEN p_sync1 <= '0'; p_sync2 <= '0'; ELSIF rising_edge(clock) THEN p_sync1 <= P; p_sync2 <= p_sync1; END IF; END PROCESS; -- 2. 按键消抖 PROCESS(clock, reset) BEGIN IF reset = '1' THEN debounce_cnt <= (others => '0'); p_debounced <= '0'; ELSIF rising_edge(clock) THEN IF p_sync2 /= p_debounced THEN debounce_cnt <= debounce_cnt + 1; IF debounce_cnt = DEBOUNCE_THRESHOLD THEN p_debounced <= p_sync2; debounce_cnt <= (others => '0'); END IF; ELSE debounce_cnt <= (others => '0'); END IF; END IF; END PROCESS; -- 3. 上升沿检测,生成单周期脉冲 PROCESS(clock, reset) BEGIN IF reset = '1' THEN p_debounced_prev <= '0'; ELSIF rising_edge(clock) THEN p_debounced_prev <= p_debounced; END IF; END PROCESS; key_pulse <= p_debounced AND NOT p_debounced_prev; -- 状态机逻辑,仅在key_pulse有效时跳转 PROCESS (clock, reset) BEGIN IF (reset = '1') THEN State <= A; ELSIF rising_edge(clock) THEN CASE State IS WHEN A => IF key_pulse='1' THEN State <= B; END IF; WHEN B => IF key_pulse='1' THEN State <= C; END IF; WHEN C => IF key_pulse='1' THEN State <= D; END IF; WHEN D=> IF key_pulse='1' THEN State <= B; -- 若不需要松手自动回A可删除下方ELSE分支 ELSE State <= A; END IF; WHEN others => State <= A; END CASE; END IF; END PROCESS; -- 输出逻辑不变 LED0 <= '1' WHEN State=A ELSE '0'; LED1 <= '1' WHEN State=B ELSE '0'; LED2 <= '1' WHEN State=C ELSE '0'; LED3 <= '1' WHEN State=D ELSE '0'; END rtl;
内容的提问来源于stack exchange,提问作者coding-newb
相关产品推荐
相关产品推荐

