基础实验(1)按键检测
按键检测实际上是一个很简单的实验,关键在于画出时序图。
如下图所示,按键按下和松开的时候,由于机械抖动,按键信号会有不稳定的波动,我们需要隔绝这部分的波动,然后等待稳定后,延时5ms后,仍然是低电平,则获得了正确的按键信号。
这里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的中间。
中间延时10ms,key_flag的拉高时刻应该干好是在程序的中间。 二是按键松开的过程中,产生的抖动。这时的计数周期刚好是10ms,也设置的时序也一致。
三是按键按下的过程中产生的抖动,只要检测到高电平,key_cnt便清零,重新开始计数。
实验结果
能够正确控制按键进行操作。
总结与讨论
多路按键检测实现。
// 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才被重新赋值,这是关键。