前记:师夷长技以自强
1.基本概念
轻触按键:开发板上经常用到的一种按键,内置一个反作用弹簧,理想情况下是产生一个低电平的脉冲信号。但由于机械构件接触的时候肯定会产生抖动,一般情况下抖动的总时间会持续20ms。
状态机:有一个状态变量有有限中取值情况,每一种取值对应硬件电路的所处的一种状态执行相应的程序的程序。设计状态机的关键是合理分状态,并明确状态的跳转条件。
2.基本原理
2.1状态机设计
把抖动的过程考虑在内的话,可以把按键模块分为四个状态:
IDLE-未按下时空闲状态
FILTER0-按下抖动滤除状态
DOWN-按下稳定状态
FILTER1-释放抖动滤除状态
其中的IDLE和DOWN两个状态都是只有一个出度的,而FILTER0和FILTER1只有一个出度。对应的状态转换条件如下
转移编号 | 转移条件 |
1 | Nedge(检测到下降沿) |
2 | Cnt_full(检测到下降沿启动计数,若计数值满时仍为低) |
3 | Pedge(检测到上升沿) |
4 | Pedge(在按下消抖过程中如果检测到上升沿) |
5 | Nedge(在松开消抖过程中如果检测到下降沿) |
6 | Cnt_full(检测到上升沿启动计数,若计数值满时仍为高) |
2.2 单bit异步信号同步设计
按键的输入信号key_in对于FPGA内部信号来说是一个异步信号,若直接使用容易出现时序违例导致亚稳态。这里采用触发器对信号打两拍的方式对系统时钟进行同步。
2.3 边缘检测
利用寄存器在时钟信号的控制下,输入状态即为下一时刻输出状态这一特性来判断。
2.4 测试思路
借助系统函数$random产生随机长度的电平信号,重复一定次数即可模拟按键的抖动过程。需要注意的一点是应该把抖动电平脉冲时间控制在20ms内,所以应该是25'd20_000_000,为了节省仿真时间可以只产生16位的随机数也就是0~65535.
3. 模块设计
3.1 模块接口框图
Key_flag:当按键状态变化时产生一个高电平
Key_state:1-常态,0-按下
3.2 代码设计
需要强调的是,对状态比较完美的编码是每种状态的编码值只有一个1。
module key_filter(
input Clk,
input Rst_n,
input Key_in,
output reg key_flag,
output reg key_state
);
reg [1:0]r_Key_in;
wire Neg_edge;
wire Pos_edge;
reg [3:0]state;
reg [19:0]Cnt_Clk;
reg en_Cnt;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
Cnt_Clk <= 0;
else if(en_Cnt)
if(Cnt_Clk == 20'd999_999)
Cnt_Clk <= 0;
else
Cnt_Clk <= Cnt_Clk + 20'b1;
else
Cnt_Clk <= 0;
always@(posedge Clk,negedge Rst_n)
if(!Rst_n)
r_Key_in <= 0;
else
r_Key_in <= {r_Key_in[0],Key_in};
assign Neg_edge = r_Key_in[1] & ~r_Key_in[0];
assign Pos_edge = ~r_Key_in[1] & r_Key_in[0];
localparam
IDLE = 4'b0001,
FILTER0 = 4'b0010,
DOWN = 4'b0100,
FILTER1 = 4'b1000;
always @ (posedge Clk,negedge Rst_n)
if(!Rst_n)begin
state <= IDLE;
en_Cnt <= 0;
key_flag <= 0;
key_state <= 1;
end
else begin
case(state)
IDLE:
begin
key_flag <= 0;
if(Neg_edge)begin
en_Cnt <= 1;
state <= FILTER0;
end
end
FILTER0:
begin
if(Pos_edge)begin
en_Cnt <= 0;
state <= IDLE;
end
else if(Cnt_Clk == 20'd999_999)begin
en_Cnt <= 0;
state <= DOWN;
key_flag <= 1;
key_state <= 0;
end
end
DOWN:
begin
key_flag <= 0;
if(Pos_edge)begin
en_Cnt <= 1;
state <= FILTER1;
end
end
FILTER1:
begin
if(Neg_edge)begin
en_Cnt <= 0;
state <= DOWN;
end
else if(Cnt_Clk == 20'd999_999)begin
en_Cnt <= 0;
state <= IDLE;
key_flag <= 1;
key_state <= 1;
end
end
endcase
end
endmodule
3.3 仿真
为了模拟按键的实际电平变化过程,特意写了一个名为press_key的task,里面调用系统函数random产生产生随机数。
`timescale 1ns/1ns
module key_filter_tb();
reg Clk;
reg Rst_n;
reg Key_in;
wire key_flag;
wire key_state;
key_filter key_filter1(
.Clk(Clk),
.Rst_n(Rst_n),
.Key_in(Key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk <= 0;
always #10 Clk <= ~Clk;
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
myrand = {$random}%65536;
#myrand
Key_in <= ~Key_in;
end
Key_in <= 1;
#50_000_000;
end
endtask
initial begin
Rst_n = 0;
Key_in =1'b1;
#100
Rst_n = 1'b1;
#30000
press_key;
#10000
press_key;
#10000
press_key;
#10000
$stop;
end
endmodule
3.4 仿真结果
在后续的FPGA设计总,key_filter都可作为一个用户输入模块来使用。