FPGA学习-Verilog实现独立按键消抖
(完成程序代码附在文章结尾)利用verilog语言实现独立按键消抖,文章首先对按键抖动产生的原因、消抖原理进行简要解释;之后详细阐述各模块verilog语言实现方法;最后利用四个独立按键控制led亮灭,在Vivado下进行源码设计与仿真。
前言
利用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
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)