按键消抖原理及VHDL代码实现

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

按键消抖原理及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的时候。
schematic

按键消抖key_filter的VHDL代码实现

下降沿
20ms内再次检测到下降沿
20ms内没有下降沿
检测到上升沿
20ms内再次检测到上升沿
20ms 内没有检测到上升沿
idle
wait_low_20ms
low_stable
wait_high_20ms

避免按键亚稳态输入

原理

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

避免亚稳态的VHDL实现:

vhdl_metastable

按键的边沿检测

原理

edge_clk

edge_detection

通过两个寄存器来存储输入的key_sb信号的上一个状态以及上上个状态,然后通过组合逻辑得到上升沿和下降沿的边沿检测

计数器

当计数使能cnt_en有效时开始计数,无效时计数器清零。
counter

状态机的实现

idle

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

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个时钟周期
wait_low

low_stable

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

wait_high_stable

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

仿真

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波形。
key_filter_simulation

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; 最后通过下载到实验开发板中,实验结果正确。
top_simulation

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值