12.基础实验(1)按键检测

基础实验(1)按键检测

按键检测实际上是一个很简单的实验,关键在于画出时序图。
如下图所示,按键按下和松开的时候,由于机械抖动,按键信号会有不稳定的波动,我们需要隔绝这部分的波动,然后等待稳定后,延时5ms后,仍然是低电平,则获得了正确的按键信号。

image-20201221220957256
这里key_cnt只有在key为低电平时,才开始计数。当key_cnt==2499999,即计数到5ms的时间后,key_flag拉高。等待key为高电平后,key_flag又重新被拉低,等待下次按键检测的过程。

设计分析

时序图出来了,就搞定了。
时序图如下。

时序图设计的代码如下。

{ signal: [
  { name: "key",  wave: "h..........lh.l.h..l...................................h.l.h..l.h.......", period:0.2 },
  { name: "key_cnt",  wave: "=..==============", data: [,,,"0", "1", "2", , "END",]},
  { name: "cnt_flag", wave: "0..d.0....1..0d0." },
  { name: "led_flag", wave: "0.........10....." },
]}

实验步骤

编写代码如下。

module key_debounce(
	input 	wire			clk,
	input 	wire 			rst_n,
	input 	wire 			key,
	output 	wire	[3:0]	led
	);

localparam 			CNT_END = 250000 - 1;

reg 	[17:0]		cnt;
reg 				cnt_flag;
wire 				rst;
reg 				key_flag;
reg 	[3:0]		shift_led 	= 4'b0001;

assign rst = ~rst_n;
assign led = shift_led;

//cnt
always @(posedge clk) 
begin
	if (rst == 1'b1) 
		cnt <= 'd0;		
	else if (key == 1'b0)           //按键消抖
		cnt <= cnt + 1'b1;
	else
		cnt <= 'd0;
end

//cnt_flag
always @(posedge clk) 
begin
	if (rst == 1'b1) 
		cnt_flag <= 1'b0;
	else if (key == 1'b1)
		cnt_flag <= 1'b0;
  else if (cnt == CNT_END)      //保证只有一次计数。
		cnt_flag <= 1'b1;
end

//key_flag
always @(posedge clk) 
begin
	if (rst == 1'b1) 
		key_flag <= 1'b0;
  else if (cnt_flag == 1'b0 && cnt == CNT_END)   //按键检测
		key_flag <= 1'b1;
	else 
		key_flag <= 1'b0;
end

//shift_led
always @(posedge clk) 
begin
	if (rst == 1'b1) 
		shift_led <= 4'b0001;
	else if (key_flag == 1'b1)
		shift_led <= {shift_led[2:0], shift_led[3]};
end

endmodule 

软件仿真

采用modelism软件仿真,使用随机数模拟按键按下和按键松开的过程中,按键不断抖动的过程,同时对时间进行计数,查看相应时间的多少。按键按下后,应该刚好计数5ms。注意这里的时钟频率是25MHz,与时序图中的频率不一致。

testbench部分代码实现如下。

	reg srstb;
	initial begin
		srstb <= 0;
		repeat(10)@(posedge clk)
		srstb <= 1;
	end

	// (*NOTE*) replace reset, clock, others

	parameter CLK_FREQ = 25000000;
	parameter  CNT_MAX = CLK_FREQ/20 - 1;

	initial
	begin
		random_key();
	end

	task random_key;
		integer i;
		begin 
			@ (posedge srstb);
			key = 1;
			repeat(5) @ (posedge clk);
			for (i = 0; i < 100; i = i + 1)
			begin
				key = {$random}; 
				@ (posedge clk);
			end
			key = 0;
			repeat(CNT_MAX*2) @ (posedge clk);
			for (i = 0; i < 100; i = i + 1)
			begin
				key = {$random}; 
				@ (posedge clk);
			end
			key = 1;
			repeat(5) @ (posedge clk);
		end 
	endtask 

发现仿真波形与设计时序一致。

一是key_flag拉高刚好在计数到5ms时,输入时钟频率设置为25MHz。即刚好在10ms的中间。

image-20201223214230879 中间延时10ms,key_flag的拉高时刻应该干好是在程序的中间。 image-20201223214058297 二是按键松开的过程中,产生的抖动。

image-20201223214409026

这时的计数周期刚好是10ms,也设置的时序也一致。
image-20201223214626299
三是按键按下的过程中产生的抖动,只要检测到高电平,key_cnt便清零,重新开始计数。

image-20201223214343424

实验结果

能够正确控制按键进行操作。

总结与讨论

多路按键检测实现。

// Module Function:按键消抖
 
module debounce (clk,rst,key,key_pulse);
 
        parameter       N  =  1;                      //要消除的按键的数量
 
	input             clk;
        input             rst;
        input 	[N-1:0]   key;                        //输入的按键					
	output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲	
 
        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值
        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值
 
        wire    [N-1:0]   key_edge;                   //检测到按键由高到低变化是产生一个高脉冲
 
        //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中
        always @(posedge clk  or  negedge rst)
          begin
             if (!rst) begin
                 key_rst <= {N{1'b1}};                //初始化时给key_rst赋值全为1,{}中表示N个1
                 key_rst_pre <= {N{1'b1}};
             end
             else begin
                 key_rst <= key;                     //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
                 key_rst_pre <= key_rst;             //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
             end    
           end
 
        assign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
 
        reg	[17:0]	  cnt;                       //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器     
 
        //产生20ms延时,当检测到key_edge有效是计数器清零开始计数
        always @(posedge clk or negedge rst)
           begin
             if(!rst)
                cnt <= 18'h0;
             else if(key_edge)
                cnt <= 18'h0;
             else
                cnt <= cnt + 1'h1;
             end  
 
        reg     [N-1:0]   key_sec_pre;                //延时后检测电平寄存器变量
        reg     [N-1:0]   key_sec;                    
 
 
        //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效
        always @(posedge clk  or  negedge rst)
          begin
             if (!rst) 
                 key_sec <= {N{1'b1}};                
             else if (cnt==18'h3ffff)
                 key_sec <= key;  
          end
       always @(posedge clk  or  negedge rst)
          begin
             if (!rst)
                 key_sec_pre <= {N{1'b1}};
             else                   
                 key_sec_pre <= key_sec;             
         end      
       assign  key_pulse = key_sec_pre & (~key_sec);     
 
endmodule

参考链接:Lattice Diamond中VerilogHDL按键延时消抖
关于这部分代码,我现在才看懂,主要有以下几个方面。

  • 按键按下后,key被拉低,下降沿,这可以作为开始计数的标志;
  • 边沿检测标志被拉高后的部分,需要仔细考量,虽然只有三个always语句块。
    • 一是前期的按键消抖,这时cnt虽然开始计数,但是却不可能计数达到清零的地址,这样就可以完成消抖的过程
    • 二是中期检测按键被按下,由于key_sec直到cnt为18’h3ffff才被重新赋值,而且key_sec_pre比key_sec延后一个周期,因此key_sec刚好为零时,key_sec_pre还没有拉低,直到下一个时刻才被拉低,因此可以key_sec和key_sec_pre共同作为按键检测的判断信号输出。
    • 三是后期消抖,key_sec在key拉高之前一直为0,而key_sec_pre也为0,直到key拉高的时候,cnt==18’h3ffff时,key_sec检测到高电平了,key_sec_pre重新被拉高,才会有后来的按键消抖的继续。也就是说,cnt计数的过程在按键消抖后的低电平和按键拉高后的高电平都在计数,直到计数到18’h3ffff时,key_sec才被重新赋值,这是关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值