前言
在上一篇博文中我们了解了状态机的概念和作用并设计了一个简单的状态机,博文详情→fpga自学之路[2]状态机,本次我们将使用状态机来对实现机械按键的消抖功能。
1.知识准备
机械按键是在电子设计中被广泛使用的一种基本人机交互元件,从系统复位到控制设置都可以看到其身影。
其物理结构一般如图所示:
由于按键存在一个反作用弹簧,因此当按下或者松开时均会产生额外的物理抖动,物理抖动便会产生电平的不稳定,导致我们在按下按键使常常无法达到我们想要的效果。在按键从按下再到松开的过程中,其电平变化如图所示:
产生的抖动次数以及间隔时间均是随机的,这就需要通过滤波来消除抖动带来的影响。一般情况下抖动的总时间会持续 20ms以内。这种抖动可以通过设计硬件电路来消除,也可以通过软件的方式完成。我们接下来将使用硬件电路消除抖动,这一般适用于按键数目较少的场合。
2.功能设计
在FPGA设计中,通常使用状态机来进行按键消抖处理。
消抖模块端口列表如下:
名称 | 类型 | 功能 |
---|---|---|
sys_clk | input | 系统时钟50MHz |
sys_rst_n | input | 系统复位 |
key_in | input | 按键输入信号 |
key_state | output | 消抖后信号输出 |
明确了输入输出端口之后我们开始状态机的设计,需要用到的状态如下:
现态 | 次态 | 转移条件 |
---|---|---|
IDLE | FILTER0 | 检测到下降沿(按键被按下) |
FILTER0 | IDLE | 检测到上升沿(说明按键还在抖动) |
FILTER0 | DOWN | 一段时间电平不发生改变(计数值计满),即按键按下稳定时 |
DOWN | FILTER1 | 检测到上升沿(按键松开) |
FILTER1 | DOWN | 检测到下降沿(说明按键还在抖动) |
FILTER1 | IDLE | 一段时间电平不发生改变(计数值计满),即按键松开稳定时 |
根据图表编写代码如下:
module key
(
input sys_clk ,
input sys_rst_n ,
input key_in ,
output reg key_state
);
/* 计数器最大值设置 */
parameter CNT_MAX = 999_999;
/* 状态机编码为独热码 */
parameter IDLE = 4'b0001;
parameter FILTER0 = 4'b0010;
parameter DOWN = 4'b0100;
parameter FILTER1 = 4'b1000;
/* 输入时打两拍需要的信号 */
reg key_in_a,key_in_b;
/* 检测上升沿模块用到的变量 */
reg key_tmpa,key_tmpb;
wire pedge,nedge;
/* 判断按键是否稳定用到的变量*/
reg [19:0]cnt;
reg en_cnt;
reg cnt_full;
/* 设置状态机的四个状态*/
reg [3:0]state;
always@(posedge sys_clk or negedge sys_rst_n) //输入信号打两拍,同步到fpga工作的时钟域
if(!sys_rst_n)begin
key_in_a <= 1'b0;
key_in_b <= 1'b0;
end
else begin
key_in_a <= key_in;
key_in_b <= key_in_a;
end
always@(posedge sys_clk or negedge sys_rst_n) //检测上升沿模块
if(!sys_rst_n)begin
key_tmpa <= 1'b0;
key_tmpb <= 1'b0;
end
else begin
key_tmpa <= key_in_b;
key_tmpb <= key_tmpa;
end
assign nedge = !key_tmpa & key_tmpb; //获得下降沿信号
assign pedge = key_tmpa & (!key_tmpb); //获得上升沿信号
always@(posedge sys_clk or negedge sys_rst_n) //描述状态转移
if(!sys_rst_n)
state <= IDLE;
else begin
case(state)
IDLE :
begin
if(nedge)
state <= FILTER0;
else
state <= IDLE;
end
FILTER0 :
begin
if(cnt_full)
state <= DOWN;
else if(pedge)
state <= IDLE;
else
state <= FILTER0;
end
DOWN :
begin
if(pedge)
state <= FILTER1;
else
state <= DOWN;
end
FILTER1 :
begin
if(cnt_full)
state <= IDLE;
else if(nedge)
state <= DOWN;
else
state <= FILTER1;
end
default : state <= IDLE;
endcase
end
always@(posedge sys_clk or negedge sys_rst_n) //描述输出
if(!sys_rst_n)begin
en_cnt <= 1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDLE :
begin
if(nedge)
en_cnt <= 1'b1;
end
FILTER0 :
begin
if(cnt_full)begin
key_state <= 1'b0;
en_cnt <= 1'b0;
end
else if(pedge)begin
en_cnt <= 1'b0;
end
end
DOWN :
begin
if(pedge)
en_cnt <= 1'b1;
end
FILTER1 :
begin
if(cnt_full)begin
key_state <= 1'b1;
en_cnt <= 1'b0;
end
else if(nedge)
en_cnt <= 1'b0;
end
default :
begin
en_cnt <= 1'b0;
key_state <= 1'b1;
end
endcase
end
always@(posedge sys_clk or negedge sys_rst_n) //对按键稳定时间进行计数,判断是否稳定
if(!sys_rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= cnt;
always@(posedge sys_clk or negedge sys_rst_n) //计数器计满信号,表示可以进入下一个状态
if (!sys_rst_n)
cnt_full <= 1'b0;
else if (cnt == CNT_MAX)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
endmodule
RTL视图如下,
为了同步输入信号,我们打了两拍,可以看到在输入端口处生成了两个D触发器:
根据状态转换图可知状态数和转换方向没有问题,并且状态转移的条件也正确:
3.仿真验证
为了进一步验证状态输出以及状态转换条件是否正确,我们需要编写testbench文件
编写代码如下:
`timescale 1ns/1ns
module tb_key();
reg sys_clk;
reg sys_rst_n;
reg key_in;
reg [15:0]myrand;
task press_key;
begin
repeat(50) begin //五十次随机时间按下抖动
myrand = {$random}%65536;
#myrand key_in = ~key_in;
end
key_in = 0;
#50_000_000; //按下稳定
repeat(50) begin //50次随机时间释放抖动 myrand = {$random}%65536;
myrand = {$random}%65536;
#myrand key_in = ~key_in;
end
key_in = 1;
#50_000_000; //释放稳定
end
endtask
initial begin
sys_clk = 1'b0; sys_rst_n = 1'b0; key_in = 1'b1;
#50
sys_rst_n = 1'b1;
#30000
press_key;
#10000
press_key;
#20000
press_key;
#30000
press_key;
#10000
$stop;
end
always#10 sys_clk = ~sys_clk;
key key_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_in (key_in),
.key_state (key_state)
);
endmodule
在观察波形之前我们需要先将添加key_inst的波形,产生波形如下:
我们需要对局部进行放大,可以看到仿真开始时出现了我们设置的随机抖动,此时cnt在达到最大计数值20ms前被多次清零(按键不稳定造成),按键稳定后(计数值达到20ms时)电路将此时的电平状态输出。虽然总体上输出波形相比输入波形延迟了20ms,但在我们的设计中是可以接受的,从这里看出来我们的设计是正确的。
4.总结
本次我们设计了一个按键消抖电路,通过它可以通过按键交互的方式来验证我们的设计。在设计状态机时,我们需要明确状态数、状态转移的条件以及各个状态的输出情况,这也是状态机设计的难点。对于状态机而言,不同的规模电路通常使用不同的描述方法(一段,二段,三段式),而且需要考虑电路能否综合,所以更好的掌握状态机是一个循序渐进的过程,由于还在初学阶段,我们先偏重于理解状态机的思想,后续再对状态机的书写进行探究。