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
  • 16
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这里提供一个基本的独立按键消抖的示例代码,使用while循环来实现: ```c #include <stdio.h> #include <stdbool.h> #include <unistd.h> #define BUTTON_PIN 2 // 按钮引脚 #define DEBOUNCE_DELAY_US 5000 // 消抖延迟,单位:微秒 int main() { bool button_state = false; // 按钮状态 bool last_button_state = false; // 上一次按钮状态 unsigned long last_debounce_time = 0; // 上一次消抖时间 int button_press_count = 0; // 按钮按下次数 // 初始化GPIO引脚 // ... while (true) { // 读取按钮状态 button_state = read_button(BUTTON_PIN); // 如果按钮状态发生了改变 if (button_state != last_button_state) { // 记录当前时间 last_debounce_time = micros(); } // 如果距离上一次消抖时间超过了指定延迟 if ((micros() - last_debounce_time) > DEBOUNCE_DELAY_US) { // 如果当前按钮状态与上一次按钮状态不同 if (button_state != last_button_state) { // 更新按钮状态 last_button_state = button_state; // 如果当前按钮状态为按下状态 if (button_state == true) { printf("Button pressed %d times.\n", ++button_press_count); } } } // 等待一段时间再进行下一次循环,可以根据需要调整等待时间 usleep(1000); } return 0; } ``` 该示例代码中,使用了一个last_debounce_time变量来记录上一次消抖时间,如果距离上一次消抖时间超过了指定延迟,就判断当前按钮状态是否与上一次按钮状态不同,如果不同,就更新按钮状态,并根据需要执行相应的操作。在每次循环结束后,等待一段时间再进行下一次循环,以避免对CPU资源的浪费。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值