按键消抖原理及VHDL代码实现
(已通过板级验证)
按键抖动原理

如上图所示,当我们按下FPGA上的button键之后,我们理想的状态如黑线所示,但是实际的情况是如红线所示,会有几次的来回抖动,然后才会变得稳定。这个问题就会导致FPGA检测到数次1,这样可能就会运行数次操作,从而得到错误的结果。
按键消抖原理

因为按下FPGA的按键时,弹簧片最低震动频率不会低于50 HZ,所以,两次下降沿出现的时间差不会超过20ms, 也就是说,当我们检测到一次下降沿/上升沿之后,再在20ms内检测是否有下降沿/上升沿产生,如果有,则重新检测,如果没有,则说明按键稳定。(一般而言,按钮被释放比按钮被按下去的反弹要少很多)
实验要求
通过实验开发板按键输入key0和key1,当按下key0的时候,+1,当按下key1时,-1。按键结果显示在四个led灯中。
key_state为当前按键状态:按下为0,释放为1;
key_flag为一个单脉冲信号,当key_state翻转时产生,也就是按键输入稳定20ms的时候。

按键消抖key_filter的VHDL代码实现
避免按键亚稳态输入
原理

通过两级D触发器,最终输出的key_sb能趋于稳定

避免亚稳态的VHDL实现:

按键的边沿检测
原理


通过两个寄存器来存储输入的key_sb信号的上一个状态以及上上个状态,然后通过组合逻辑得到上升沿和下降沿的边沿检测
计数器
当计数使能cnt_en有效时开始计数,无效时计数器清零。

状态机的实现
idle
当状态机第一个状态为idle,如果检测到下降沿,进入 wait_low_stable状态,并且将cnt_en有效,让计数器开始计数。 key_flag是一个脉冲信号,标志着进入稳定状态

wait_low_20ms
在wait_low_stable状态计数20ms,如果20ms内出现上升沿,将cnt_en信号置0,使计数器清零,并且回到idle状态,等候下降沿;
如果没有出现下降沿,则进入low_stable状态,cnt_en置0,将计数器清零,将key_flag信号拉高,并且输出此时的按键状态key_state(为按下状态0)。
FPGA为50MHZ,一个系统时钟为20ns,所以计数20ms需要1000000-1个时钟周期

low_stable
在low_stable状态时,将key_flag拉低(表现为一个时钟周期的脉冲信号)。
如果出现上升沿,则进入wait_high_stable状态,将cnt_en使能有效,开始计数;反之则保持,表示按键还处于被按下的状态

wait_high_stable
在wait_high_stable状态时,如果20ms内出现下降沿,就回到low_stable状态,并且cnt_en置0。
如果计数到20ms,表示按键已经被释放,回到idle状态,等待下一次按键被按下。并且将cnt_en无效,key_flag拉高,保持一个时钟,key_state输出当前状态(释放状态1)。

仿真
testbench
输入激励源的编写:
key_module 模块:产生key_in的信号,并且按下一次press,执行一次程序
library ieee;
use ieee.std_logic_1164.all;
entity key_module is
port (keya: out std_logic;
press: in std_logic);
end entity;
architecture beh of key_module is
begin
gencom: process
procedure key_press is -- procedure 相当于verilog中的 task
begin
for i in 0 to 30 loop
keya <= '1';
wait for 1000 ns;
keya <= '0';
wait for 4002 ns;
keya <= '1';
wait for 2002 ns;
keya <= '0';
wait for 5002 ns;
keya <= '1';
wait for 2002 ns;
keya <= '0';
end loop;
wait for 25010000 ns; -- 循环30次以上操作之后保持keya=0 >20ms
for i in 0 to 30 loop
keya <= '1';
wait for 1002 ns;
keya <= '0';
wait for 6002 ns;
keya <= '1';
wait for 5002 ns;
keya <= '0';
wait for 2002 ns;
keya <= '1';
end loop;
wait for 25010000 ns; -- 循环30次以上操作之后保持keya=1 > 20ms
end procedure;
begin
wait for 100 ns;
loop
wait until press='1'; -- 当press 为1, 执行一次按下释放的操作
key_press;
end loop;
end process;
end beh;
在这里插入代码片
然后在key_filter_tb的文件中调用key_mo模块,输入激励信号,产生仿真。
key_filte_tb的部分仿真代码如下:
clka<= not clka after 10 ns;
nrsta<= '0',
'1' after 500 ns;
pressa<= '0',
'1' after 10000 ns,
'0' after 10020 ns,
'1' after 80000001 ns,
'0' after 80000021 ns;
仿真结果:
能清楚看到,输入的keya信号在稳定之前一直在改变,20ms之后,key_flaga信号有一个脉冲信号产生,并且ket_state输出当前状态。在80ms处,再次产生press信号,key_press再次执行,产生keya波形。

led_control vhdl实现
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity led_control is
port( clk: in std_logic;
nrst: in std_logic;
key_flag0: in std_logic;
key_state0: in std_logic; -- key 0 ++
key_flag1: in std_logic;
key_state1: in std_logic; -- key 1 --
led: out std_logic_vector(3 downto 0)
);
end entity;
architecture beh of led_control is
signal led_tem: std_logic_vector(3 downto 0);
signal press_key0, press_key1: std_logic;
begin
press_key0<=key_flag0 and not (key_state0); --- 标志着按键被按下,只产生一个时钟的脉冲信号
press_key1<=key_flag1 and not (key_state1);
process(clk)
begin
if(nrst='0') then
led_tem <= "0000";
else if( rising_edge(clk)) then
if( press_key0='1') then
led_tem <= led_tem+"0001";
else if( press_key1='1') then
led_tem<= led_tem-"0001";
end if;
end if;
end if;
end if;
end process;
led<=not led_tem; -- 因为实验开发板上led输入0,led亮,所以需要把数据取反。
end beh;
顶层文件
最后在顶层直接调用key_filter和led_control模块,下载到
library ieee;
use ieee.std_logic_1164.all;
entity led_top is
port( clk: in std_logic;
nrst: in std_logic;
keyin0: in std_logic;
keyin1: in std_logic;
led: out std_logic_vector(3 downto 0)
);
end entity;
architecture beh of led_top is
component key_filter is
port( clk: in std_logic;
nrst: in std_logic;
key: in std_logic; -- input signal
key_flag: out std_logic; -- pluse signal to show stable
key_state: out std_logic -- output signal
);
end component;
component led_control is
port( clk: in std_logic;
nrst: in std_logic;
key_flag0: in std_logic;
key_state0: in std_logic; -- key 0 ++
key_flag1: in std_logic;
key_state1: in std_logic; -- key 1 --
led: out std_logic_vector(3 downto 0)
);
end component;
signal key_flag0, key_flag1: std_logic;
signal key_state0, key_state1: std_logic;
begin
key0_com: component key_filter
port map( clk => clk,
nrst => nrst,
key => keyin0,
key_flag => key_flag0,
key_state => key_state0
);
key1_com: component key_filter
port map( clk => clk,
nrst => nrst,
key => keyin1,
key_flag => key_flag1,
key_state => key_state1
);
led_com: component led_control
port map( clk => clk,
nrst => nrst,
key_flag0 => key_flag0,
key_state0 => key_state0,
key_flag1 => key_flag1,
key_state1 => key_state1,
led => led
);
end beh;
testbench
部分testbench代码如下所示:
keyin0和keyin1由调用的key_module模块产生
clk_tb<=not clk_tb after 10 ns;
nrst_tb <= '0',
'1' after 1000 ns;
press0<= '0',
'1' after 100000 ns,
'0' after 100020 ns;
press1<='0',
'1' after 80000000 ns,
'0' after 80000020 ns;
仿真结果
分别按一次keyin0和keyin1, 按keyin0, led灯显示 1110,表示亮一个灯,+1;
然后由1110 变为 1111, 表示一个灯都不亮,-1; 最后通过下载到实验开发板中,实验结果正确。

本文详细介绍了按键抖动的原理及其在FPGA中的消除方法,使用VHDL代码实现了一个按键消抖滤波器,通过状态机和计数器确保按键信号在20ms内稳定。实验要求是根据按键输入更新LED显示,通过仿真验证了设计的正确性。
4545

被折叠的 条评论
为什么被折叠?



