一.简介
这是FPGA之旅的第二个设计实例了,按键在项目中的作用是非常大的,使用的很频繁,本例将带大家设计一个实用的按键模块。
欢迎关注微信公众号 FPGA之旅 哦
可以在上面联系,有什么问题的话
二. 按键电路
按键为输入设备,通过电路图可以知道,当按键按下的时候,FPGA会检测到低电平,按键没有按下的时候,FPGA检测到的是高电平。
三. Verilog代码编写
直接来一段最简单的按键检测的代码编写,都不用仿真。
按键按下,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
当当当当!!! 完美对应起来,测试通过!
公众号:FPGA之旅