【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
之前我们谈到按键输入,其中涉及的功能就是,当按键按下的时候,led灯亮起来。等到按键弹起来的时候,led灯灭掉。这个功能实现起来是比较简单的。只需要把key和led之间做绑定就可以了。
今天,我们实现一个复杂点的功能。这就是,当第一次按键按下、弹出来的时候,led灯亮起;第二次按键按下、弹出来的时候,led灯灭掉。这个时候应该怎么做呢?其实应该做到这两点,第一,做到按键消抖;第二,实现边沿检测。
1、按键消抖
要做好按键消抖,那么就要设计一个简单的状态机。一个是idle状态,一个是消抖过程中的延时状态。在做好状态切换前,先锁存一下输入的数据,这个之前在按键输入那一章讲到过,
// delay to get signal from in
always@(posedge clk or negedge rst)
if(!rst)
tmp0 <= 1'b0;
else
tmp0 <= in;
always @(posedge clk or negedge rst)
if(!rst)
tmp1 <= 1'b0;
else
tmp1 <= tmp0;
有了tmp0和tmp1这两个信号,就可以判断信号什么时候发生了改变,
wire diff;
assign diff = tmp0 ^ tmp1;
接着,当diff产生的时候,就可以产生一个状态机和计数器,其中状态机的代码如下,
// state machine
always @(posedge clk or negedge rst)
if(!rst)
state <= 2'b00;
else
state <= next_state;
always@(*)
if(!rst)
next_state <= 2'b00;
else begin
case (state)
2'b00:
if(diff)
next_state <= 2'b01;
else
next_state <= 2'b00;
2'b01:
if(count == 32'd199_9999)
next_state <= 2'b00;
else
next_state <= 2'b01;
default:
next_state <= 2'b00;
endcase
end
这是一个经典的三段式状态机写法。没有diff发生的时候,next_state是2'b00。如果diff发生,那么next_state切换成2’b01。等到count变成32’d199_9999的时候,next_state重新变成2'b00。在状态机中谈到了count,接着可以看看count是怎么写的,
always@(posedge clk or negedge rst)
if(!rst)
count <= 32'b0;
else if(state == 2'b01) begin
if(diff)
count <= 32'b0;
else if(count != 32'd199_9999)
count <= count + 1;
else
count <= 32'b0;
end
从上面的代码可以看出,rst复位的时候,count为0。等到state切换为2'b01的时候,count准备递增。如果在这过程中发生了二次抖动,则count重新计数,否则在计数到32'd199_9999之前一直在递增。等递增到32'd199_9999的时候,恢复为0。在state处于其他状态的时候,count都是设置为0。
2、边沿触发检测
要做到边沿触发检测,一般需要两级寄存器进行检测。等到发现两个寄存器的数值不同的时候,这个时候其实就发生了边沿触发,
// delay one clock for out2
always@(posedge clk or negedge rst)
if(!rst)
out1 <= 1'b0;
else if(count == 32'd199_9999)
out1 <= tmp1;
always@(posedge clk or negedge rst)
if(!rst)
out2 <= 1'b0;
else
out2 <= out1;
// output signal here
always@(posedge clk or negedge rst)
if(!rst)
led <= 1'b0;
else if(out2 && ~out1)
led <= ~led;
这里当count为32'd199_9999的时候,把tmp1的信号传递给out1,下一个时钟,再从out1传递给out2。在这过程当中,如果某一个时刻发现out2和out1不相等的时候,其实边沿触发就发生了。out2&~out1代表的是下降沿,~out2&out1代表的是上升沿。
3、开始实验
等到代码都编写好之后,最重要的就是把pin脚配置好,烧入到fpga当中。
注:
最后给出完整的代码,有需要的同学可以好好看下。
//
// led_test
// author: feixiaoxing
// 2022.12.13
//
module led_test(clk, rst, in, led);
input clk;
input rst;
input in;
output led;
wire clk;
wire rst;
wire in;
reg led;
reg out1;
reg out2;
reg[31:0] count;
reg tmp0;
reg tmp1;
reg[1:0] state;
reg[1:0] next_state;
wire diff;
assign diff = tmp0 ^ tmp1;
always @(posedge clk or negedge rst)
if(!rst)
state <= 2'b00;
else
state <= next_state;
always@(*)
if(!rst)
next_state <= 2'b00;
else begin
case (state)
2'b00:
if(diff)
next_state <= 2'b01;
else
next_state <= 2'b00;
2'b01:
if(count == 32'd499_9999)
next_state <= 2'b00;
else
next_state <= 2'b01;
default:
next_state <= 2'b00;
endcase
end
always@(posedge clk or negedge rst)
if(!rst)
count <= 32'b0;
else if(state == 2'b01) begin
if(diff)
count <= 32'b0;
else if(count != 32'd499_9999)
count <= count + 1;
else
count <= 32'b0;
end else
count <= 32'b0;
// delay to get signal from in
always@(posedge clk or negedge rst)
if(!rst)
tmp0 <= 1'b0;
else
tmp0 <= in;
always @(posedge clk or negedge rst)
if(!rst)
tmp1 <= 1'b0;
else
tmp1 <= tmp0;
// delay one clock for out2
always@(posedge clk or negedge rst)
if(!rst)
out1 <= 1'b0;
else if(count == 32'd499_9999)
out1 <= tmp1;
always@(posedge clk or negedge rst)
if(!rst)
out2 <= 1'b0;
else
out2 <= out1;
// output signal here
always@(posedge clk or negedge rst)
if(!rst)
led <= 1'b0;
else if(out2 && ~out1)
led <= ~led;
endmodule