FPGA学习-Verilog实现独立按键消抖


前言

利用verilog语言实现独立按键消抖,文章首先对按键抖动产生的原因、消抖原理进行简要解释;之后详细阐述各模块verilog语言实现方法;最后利用四个独立按键控制led亮灭,在vivado下进行源码设计与仿真。(完成程序代码附在文章结尾)


一、独立按键消抖原理

按键一般是机械弹性开关,由于机械触点的弹性作用,机械触点断开、闭合时会伴随着一连串的抖动,这个抖动会使得按键输出的高低电平连续变化,而这并不是真正的按下按键,如果直接作为开关控制后续电路,就会造成电路的不稳定,因此,需要采用按键消抖。
机械按键按下时候有一个不稳定的抖动期,这个时间大概在20ms以内,我们可以利用这个20ms的抖动期,当检测到按键电平变化时20ms计数器重新计数,若计数器达到20ms,证明按键电平变化以后的20ms内没有再发生电平变化,可以认为是按键真正被按下,将此时的按键状态放入寄存器进而控制后续电路。

二、按键消抖程序实现(Verilog)

1.按键触发判断

  • 只要有按键状态变化,20ms计数器就应重新开始计数。而判断按键状态是否变化应该比较按键前后两个状态,由此采用移位寄存器,缓存按键前后的状态,再进行逻辑运算判断按键被按下还是松开。

注意:只要有按键被按下就要采用延时20ms消抖,同时按键全被松开也会对应按键电平的变化,仍然要采用消抖

代码如下:

wire key ; //用于按键触发判断
reg[3:0] key_r ; //缓存标志key的信息
assign key = key_h[3]&key_h[2]&key_h[1]&key_h[0] ;
always@(posedge sys_clk_i or negedge ext_rst_n)begin
	if(!ext_rst_n) key_r <= 4'b1111;
	else key_r <= {key_r[2:0],key}; //左移寄存器,最低位保存按键最新的状态
end

wire key_neg = key_r[3] & ~key_r[2] ; //key_r[2]是现态,[3]是前一个状态,采用左移寄存器
wire key_pos = ~key_r[3] & key_r[2] ;//按键全部被释放

2.计数器模块实现

  • 由于FPGA系统时钟是50MHz,需要计数到20ms 20ms/20ns得到结果为1_000_000次,表示需要每一个时钟上升沿计数,从0开始计数,计数到999_999,二十进制换算,需要采用20bit的寄存器来保存计数次数。
  • 计数至20ms时应进行清零操作
//20ms计数,一旦出现按键按下或释放重新开始计数
reg[19:0]cnt ;
always@(posedge sys_clk_i or negedge ext_rst_n)begin
	if(!ext_rst_n) cnt <= 20'd0 ;
	else if(key_neg || key_pos ) cnt <= 20'd0 ;
	else if(cnt == 20'd999_999) cnt <= 20'd0 ;
	else cnt <= cnt + 20'd1 ;
end

3.按键状态更新

模块输入的按键值【key_h】状态表示未消抖前按键的状态,而控制后续电路的应该是消抖后的按键状态,因此需要设置一个4bit的寄存器,用于保存消抖后的状态。
同时,由于我们时利用按键前后的状态比较来判断按键是否被按下,所以需要有两个4bit的寄存器,分别保存前后状态。

  • 【key_press】用于表示按键是否被按下
reg[3:0]key_value[1:0] ;
always@(posedge sys_clk_i or negedge ext_rst_n)begin
	if(!ext_rst_n)begin
		key_value[0] = 4'b1111; //保存最新的状态
		key_value[1] = 4'b1111;
	end
	else begin
		if(cnt == 20'd999_999) key_value[0] = key_h ;
		key_value[1] <= key_value[0];
	end
end
wire[3:0}key_press ;
assign key_press = key_value[1] & ~key_value[0] ;

4.按键控制led亮灭

//led状态控制模块
always@(posedge sys_clk_i or negedge ext_rst_n)begin
	if(!ext_rst_n) led <= 4'b1111 ;
	else begin
		if(key_press[0]) led[0] <= ~led[0];
		if(key_press[1]) led[1] <= ~led[1];
		if(key_press[2]) led[2] <= ~led[2];
		if(key_press[3]) led[3] <= ~led[3];
	end
end

三、仿真测试文件编写

`timescale 1ns / 1ps
module sim_keyboard(

    );
reg sys_clk_i ;
reg ext_rst_n ;
reg[3:0]key_h ;
wire[3:0]led ;
keyboard utt_keyboard(
	.sys_clk_i(sys_clk_i),
	.ext_rst_n(ext_rst_n),
	.key_h(key_h), //按键未按下时高电平,按下后低电平
	.led(led)
    );
initial begin
	sys_clk_i = 1'b0 ;
	ext_rst_n = 1'b0 ;
	key_h = 4'b1111 ;
	#1000
	@(posedge sys_clk_i) ; #2 ;
	ext_rst_n = 1'b1 ;
	@(posedge sys_clk_i) ; #2 ;

	//模拟按键抖动
	key_h[0] = 1'b0 ;
	#1000_000  ;//1ms
	key_h[0] = 1'b1 ;
	#5_000_000  ;//5ms
	key_h[0] = 1'b0 ;
	#3_000_000  ;//3ms
	key_h[0] = 1'b1 ;
	#1_000_000 
	key_h[0] = 1'b0 ;
	#1000_000  ;//1ms
	key_h[0] = 1'b1 ;
	#5_000_000  ;//5ms
	key_h[0] = 1'b0 ;
	#3_000_000  ;//3ms
	key_h[0] = 1'b1 ;
	#3_000_000  ;//3ms
	key_h[0] = 1'b0 ;
	#50_000_000  ;//50ms
	key_h[0] = 1'b1 ;
	#30_000_000  ;//30ms
	key_h[0] = 1'b0 ;
	#50_000_000  ;//50ms
	key_h[0] = 1'b1 ;
	$finish ;
end

always #10 sys_clk_i = ~sys_clk_i ;

endmodule

四、编译结果

  • 可以看到计数器计数到999_999,此时【key_h】的值为1110,【key_value[0]】值仍然为1111,在下一个时钟上升沿,消抖完成,【key_value[0]】会保存1110,表示按键key_h[0]被按下
    在这里插入图片描述
  • 下一个时钟上升沿计数器清零,重新开始20ms计数,key_value[0]即为消抖完成后案件的现态,key_value[1]表示按键的前一个状态,【key_press[0]】值为1,表示按键被按下。
    在这里插入图片描述
  • 测试文件模拟按键抖动过程,仿真结果可以看到当按键值【key_h】变化时,【key_pos】【key_neg】检测到边沿变化,但并不会引起【led】电平变化,说明消抖模块是有效的
    在这里插入图片描述
    代码
    链接:https://pan.baidu.com/s/1euNaVxH-goOatb3lxpe4aQ
    提取码:mcuh