FPGA按键消抖模块设计详解
FPGA按键消抖模块设计详解
在FPGA开发中,按键消抖是一个常见的需求。本文将通过一个具体的实例,介绍如何使用状态机实现按键消抖功能。本文将详细介绍状态机的设计、边沿检测以及计数器的实现方法。
本篇文章通过使用Quartus ii 13.1进行代码编写,并编译完成,实现按键消抖功能。
在实际电路设计过程中,按键消抖可以通过添加电容的硬件操作实现,本篇文章通过按键消抖的实现来学习一段式状态机和testbench文件的编写。
状态机代码编写部分
首先,要了解我们按键消抖模块包括三个输入信号,两个输出信号。分别是时钟输入信号,系统复位信号,按键输入信号三个输入;以及按键被按下标志信号和按键状态。
input Clk; //系统时钟
input Rst_n; //系统复位
input key_in; //按键输入
output reg key_flag; //按键被按下标志
output reg key_state; //按键状态,不按为1,按下为0
主要通过一段式状态机来实现功能。我定义了四个不同的状态,方便代码的编写。分别是空闲状态(IDEL),按下滤波状态(FILTER0),按下状态(DOWN)和释放滤波状态(FILTER1)。
初始时,状态设置为空闲状态,等待到按键输入产生下降沿时,进入FILTER0状态,同时发送使能信号(en_cnt)开始计数,一般认为20ms内一直保持低电平即为按下,反之为抖动;未检测到下降沿,一直在空闲状态检测。
IDEL :
begin
if(nedge) begin //检测到下降沿,跳转至下一状态,同时开始计数
state <= FILTER0;
en_cnt <= 1'b1;
end
else begin //其余时间,状态不变
state <= IDEL;
end
end
进入FILTER0状态后,首先判断计数是否达到20ms(通过计数器设置计数满时,产生信号cnt_full),当检测到cnt_full信号,认为按键按下,并非抖动,可进入DOWN状态,同时停止使能信号(en_cnt)。赋予key_flag(按键被按下标志)高电平、key_state(按键状态)低电平;同时判断是否产生上升沿,如果上升沿出现在cnt_full信号前,说明此次判断为抖动,不是按下,停止使能计数,返回空闲状态(IDEL)重新开始;即未检测到cnt_full,又未检测到上升沿时,保持状态FILTER0不变。
FILTER0 :
if(cnt_full) begin //如果计数器记满了,跳转至下一状态,记满,使能信号拉低
key_flag <= 1'b1; //按键按下
key_state <= 1'b0; //按键状态为按下,低电平
en_cnt <= 1'b0;
state <= DOWN;
end
else if(pedge) begin //如果计数器还没记满就检测到上升沿,回到等待状态
en_cnt <= 1'b0; //使能信号拉低
state <= IDEL;
end
else begin //其余时间,保持状态不变
state <= FILTER0;
end
在DOWN状态开始时,首先将key_flag(按键被按下标志)恢复到低电平,一般我们仅需该信号产生一个周期的脉冲即可。同时检测上升沿的到来,当检测到上升沿到来,开启使能en_cnt,并进入FILTER1状态;其余时间,保持DOWN状态不变。
DOWN :
begin
key_flag <= 1'b0; //按键按下产生一个周期的脉冲
if(pedge) begin //检测到上升沿,跳转至下一状态,开始计数
en_cnt <= 1'b1;
state <= FILTER1;
end
else
state <= DOWN; //其余时间,状态机状态不变
end
进入FILTER1状态后,当检测到cnt_full信号,停止使能信号en_cnt,同时拉高key_state(按键状态),此处计数原则与上述FILTER0一致;如果在cnt_full到来前检测到下降沿,说明此次判断为抖动,返回DOWN状态重新判断;其余时间保持DOWN状态不变。
FILTER1 :
if(cnt_full) begin //计数器记满,跳转至等待状态,按键状态为未按下
key_state <= 1'b1;
state <= IDEL;
end
else if(nedge) begin //检测到下降沿,返回按下状态,使能计数拉低
en_cnt <= 1'b0;
state <= DOWN;
end
else //其余时间,保持状态不变
state <= FILTER1;
边沿检测部分
利用非阻塞赋值逐级保存按键输入信号(key_in),下降沿来临时key_tempa = 0,key_tempb = 1,进行下图所示操作。反之,上升沿来临时key_tempa = 1,key_tempb = 0。
//边沿检测
always @(posedge Clk or negedge Rst_n) begin
if(!Rst_n) begin
key_tempa <= 1'b0;
key_tempb <= 1'b0;
end
else begin
key_tempa <= key_in_sb;
key_tempb <= key_tempa;
end
end
assign nedge = (!key_tempa) & key_tempb; //下降沿检测
assign pedge = key_tempa & (!key_tempb); //上升沿检测
计数部分
使用50M时钟,计数到20ms需要计数到20'd999_999,当检测到使能信号(en_cnt)开始计数,记满拉高cnt_full信号。
//计数器
always @(posedge Clk or negedge Rst_n) begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt) //接收到使能信号时,开始计数
cnt <= cnt + 1'b1;
else //其余时间,计数器为0
cnt <= 20'd0;
end
always @(posedge Clk or negedge Rst_n) begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 20'd999_999) //当记满时,cnt_full拉高
cnt_full <= 1'b1;
else //其余时间,cnt_full保持低电平
cnt_full <= 1'b0;
end
testbench仿真文件编写见下一篇。