这是FPGA之旅的第二个设计实例了,按键在项目中的作用是非常大的,使用的很频繁,本例将带大家设计一个实用的按键模块。
按键为输入设备,通过电路图可以知道,当按键按下的时候,FPGA会检测到低电平,按键没有按下的时候,FPGA检测到的是高电平。

直接来一段最简单的按键检测的代码编写,都不用仿真。
按键按下,LED灯状态取反。
module KEY(
input clk,
input rst_n,
input key,
output reg led
);
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
led <= 1'b0;
else if(key == 1'b0)
led <= ~led;
else
led <= led;
end
endmodule
当按键按下后,LED的状态取反,这个在仿真的时候是可以看到变化的,但是实际上板测试的话,是没有效果的,因为clk的时钟周期一般为20ns,每次按键按下的持续时间可以达到ms以上,所以LED会多次取反,所以这么简单粗暴是不可以的,需要我们做一些额外处理。此外在按键按下的瞬间,电平会出现不稳定的情况,也需要进行处理。解决这些问题,正是这个例程的重点。
按键按下的时候,一共有两个问题
第一个问题可以通过按键消抖来解决,第二个问题,可以在按键消抖的基础上,增加一些判断来解决,于是就有了以下三种模式
这里通过状态机的方式来实现,第一步就是要分析一共有几个状态。
模式一,可以在消抖态完成后,生效。
模式二,可以在释放态,生效。
模式三,可以在延时态,生效。
完美,这不就全部都解决了嘛! 代码如下。
//按键消抖
module btn_dis_shake(
input clk,
input rst_n,
input ikey, //按键输入
output okey //按键输出
);
//模式
//0 按下生效,抬起,算一次
//1 按下抬起,算一次
//2 按下后,一段时间算一次
parameter mode = 2;
localparam S_IDLE = 'd0;
localparam S_DIS_SHAKE = 'd1;
localparam S_DEALY = 'd2;
localparam S_UP = 'd3;
localparam DIS_SHAKE = 'd6000; //消抖延时
localparam DELAY = 'd50000; //模式2中,一段时间
reg[3:0] state , next_state;
wire neg_key,pos_key; //按键下降沿上升沿
reg key0,key1; //按键状态储存
reg[30:0] delay_cnt;
assign neg_key = key1 & (~key0); //判断按键信号的下降沿
assign pos_key = (~key1) & key0; //判断按键信号的上升沿
//根据模式来判断按键输出
assign okey = (mode == 0 && state == S_DIS_SHAKE && delay_cnt == DIS_SHAKE) ? 1'b1 : (mode == 1 && state == S_UP)?1'b1:(mode==2 && state == S_DEALY && delay_cnt == DELAY) ? 1'b1:1'b0;
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
key0 <= 1'b1;
key1 <= 1'b1;
end
else
begin
key0 <= ikey;
key1 <= key0;
end
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
state <= S_IDLE;
else
state <= next_state;
end
always@(*)
begin
case(state)
S_IDLE:
if(neg_key == 1'b1)
next_state <= S_DIS_SHAKE;
else
next_state <= S_IDLE;
S_DIS_SHAKE: //按下消抖
if(delay_cnt == DIS_SHAKE)
next_state <= S_DEALY;
else if(pos_key == 1'b1)
next_state <= S_IDLE;
else
next_state <= S_DIS_SHAKE;
S_DEALY: //延时
if(delay_cnt == DELAY && pos_key == 1'b1)
next_state <= S_UP;
else if( pos_key == 1'b1)
next_state <= S_UP;
else
next_state <= S_DEALY;
S_UP:
next_state <= S_IDLE;
default: next_state <= S_IDLE;
endcase
end
//延时计数
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
delay_cnt <= 'd0;
else if(state != next_state)
delay_cnt <= 'd0;
else if(state == S_DIS_SHAKE)
delay_cnt <= delay_cnt + 1'b1;
else if(state == S_DEALY && delay_cnt == DELAY)
delay_cnt <= 'd0;
else if(state == S_DEALY)
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= 'd0;
end
endmodule
代码是写完了,对不对呢 ? 上仿真!!!仿真的时候别忘记了将DIS_SHAKE这个参数调小一点了,可以设置为2就可以了,否则,你可以试试哦,就只对模式一进行仿真,其他的模式,也可以自行尝试喔!
`timescale 1ns/1ps
module testbeach();
reg clk;
reg rst_n;
reg ikey;
wire okey;
always #50 clk <= ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b1;
ikey = 1'b1;
#100
rst_n = 1'b0;
#100
rst_n = 1'b1;
ikey = 1'b0; //按下
#400
ikey = 1'b1; //释放
#200
ikey = 1'b0; //按下
#600
ikey = 1'b1; //释放
end
btn_dis_shake #(.mode(0))btn_dis_shakeHP(
.clk (clk),
.rst_n (rst_n),
.ikey (ikey), //按键输入
.okey (okey) //按键输出
);
endmodule
当当当当!!! 完美对应起来,测试通过!
