一.需要按键消抖原因
由于按键的弹性作用,在按下按键或释放按键时,按键会有抖动(在高低电平之间任意变化),而不是立刻稳定。这样就有可能读取到多次低电平,导致输出的信号不稳定。因此需要对按键进行消抖。
举个例子:
`timescale 1ns / 1ps
module key_test
(
input clk,
input [3:0] key,
output[3:0] led
);
reg[3:0] led_r;
always@(posedge clk)
begin
case(key)
4'b1110:led_r[0]<=!led_r[0];
4'b1101:led_r[1]<=!led_r[1];
4'b1011:led_r[2]<=!led_r[2];
4'b0111:led_r[3]<=!led_r[3];
default:led_r<=led_r;
endcase
end
assign led = led_r;
endmodule
此段代码目的是四个按键对应四个LED灯。按键按下时,对应LED灯的状态翻转一次(从亮到灭或从灭到亮)。但是由于没有经过按键消抖,会发现LED灯常常不受控制。这就是因为按键抖动导致到多次读取到低电平,使得输出不稳定。
二.按键消抖原理
按键消抖即使为了解决上述的问题。在按键按下或者释放后,先延时一段时间(10ms~20ms),待信号稳定再进行读取。
1.key_debounce.v(按键消抖模块)
module key_debounce(
input sys_clk, //外部50M时钟
input sys_rst_n, //外部复位信号,低有效
input [2:0] key, //外部按键输入,按下后为低电平
output reg key_flag, //按键数据有效信号,即表示延时结束,按键已稳定
output reg [2:0] key_value //按键消抖后的数据
);
//reg define
reg [19:0] delay_cnt; //消抖延时的计数器
reg [2:0] key_reg; //按键值存储
//*****************************************************
//** main code
//*****************************************************
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
begin
key_reg <= 3'b111; //按键值复位,全为高电平
delay_cnt <= 19'd0; //计数器清零
end
else
begin
key_reg <= key; //非阻塞赋值,因此下行if判断中的key_reg仍为前一次的数据,而非此次的key
if(key_reg != key) //一旦检测到按键状态发生变化(有按键被按下或释放)
delay_cnt <= 19'd1_000_000; //给延时计数器重新装载初始值(计数时间为20ms)
else if(key_reg == key)
begin //在按键状态稳定时,计数器递减,开始20ms倒计时
if(delay_cnt > 19'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= delay_cnt;
end
end
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
begin
key_flag <= 1'b0;
key_value <= 3'b111;
end
else
begin
if(delay_cnt == 19'd1) //减到1而不是0的原因是:复位情况和无按键按下时cnt恒为零,则key_flag会一直为1
begin //同时,当计数器递减到1时,说明按键稳定状态维持了20ms
key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟周期的标志信号
key_value <= key; //并寄存此时按键的值
end
else
begin
key_flag <= 1'b0; //延时未到,不给出有效信号
key_value <= key_value;
end
end
end
endmodule
这种按键消抖方式,实际上是等待按键稳定后再进行20ms的延时进行确认。因为按键抖动时,计数器在不断重装载值。
2.led_control.v(LED控制模块)
module led_control(
//input
input sys_clk, //系统时钟
input sys_rst_n, //复位信号,低电平有效
input key_flag, //按键有效信号
input [2:0] key_value, //消抖后的按键信号
output reg [2:0] led //蜂鸣器控制信号
);
//*****************************************************
//** main code
//*****************************************************
always @ (posedge sys_clk or negedge sys_rst_n)
begin
if(!sys_rst_n)
led <= 3'b000;
else if(key_flag) //判断按键是否有效按下
case(key_value)
3'b110:led[0]<=!led[0];
3'b101:led[1]<=!led[1];
3'b011:led[2]<=!led[2];
default:led<=led;
endcase
end
endmodule
3.top_key_led.v(主函数)
module top_key_led(
input sys_clk, //时钟信号50Mhz
input sys_rst_n, //复位信号
input [2:0] key, //按键信号
output [2:0] led //蜂鸣器控制信号
);
//wire define
wire [2:0] key_value;
wire key_flag;
//*****************************************************
//** main code
//*****************************************************
//例化按键消抖模块
key_debounce u_key_debounce(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_flag (key_flag),
.key_value (key_value)
);
//例化蜂鸣器控制模块
led_control u_led_control(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_flag (key_flag),
.key_value (key_value),
.led (led)
);
endmodule
Verilog语法通过以上方式例化要使用的模块,类似于C语言中的函数调用。需要注意,例化模块需要给出一个例化的名字,如代码中的和u_key_debounce
和u_led_control
,这点更像Java等面向对象语言中给类创建对象的操作。
RTL设计: