FPGA的采样频率很高,它可以在每个时钟周期的上升沿到来时对IO口的电平进行一次读取。通过读取相邻2个时钟周期内IO口的电平值并进行比较,若电平值发生改变(此代码中是判断电平从0变为1),则计数器清零,若持续20ms后电平值没有发生改变,则读取按键的键值,同时将这一键值存储起来,当下一个20ms后再次读取键值,将2次键值进行比较,若键值发生改变,则说明按键有动作(要么按下,要么松手)。此代码中是判断键值从0变为1,即松手检测。
// 按键去抖
// 实现一个简单的三个按键分别控制三个发光二极管亮或暗的控制。
// 例如,按键1控制发光二极管1。上电初始发光二极管1不亮,
// 当检测到按键1被按下后,发光二极管1则点亮,
// 按键1再次被按下时,发光二极管1则不亮,如此反复。
// 该实验需要把握好按键消抖检测的设计技巧。
// 注:此代码的按键操作是包括松手检测的,
// 即按键按下后要等到松手才算一次按键操作
module key_debounce(
clk,rst_n,
key1_n,key2_n,key3_n,
led1_n,led2_n,led3_n
);
input clk;
input rst_n;
input key1_n,key2_n,key3_n;
output led1_n,led2_n,led3_n;
reg [2:0] key_rst;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_rst <= 3'b111;
else
key_rst <= {key3_n,key2_n,key1_n}; // 读取当前时刻的按键值
end
reg [2:0] key_rst_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_rst_r <= 3'b111;
else
key_rst_r <= key_rst; // 将上一时刻的按键值进行存储
end
wire [2:0]key_an = key_rst_r & (~key_rst); // 当键值从0到1时key_an改变
//wire [2:0]key_an = key_rst_r ^ key_rst; // 注:也可以这样写
reg [19:0] cnt; // 延时用计数器
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= 20'd0;
else if(key_an)
cnt <= 20'd0;
else
cnt <= cnt + 20'd1;
end
reg [2:0] key_value;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_value <= 3'b111;
else if(cnt == 20'hfffff) // 2^20*1/(50MHZ)=20ms
key_value <= {key3_n,key2_n,key1_n}; // 去抖20ms后读取当前时刻的按键值
end
reg [2:0] key_value_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
key_value_r <= 3'b111;
else
key_value_r <= key_value; // 将去抖前一时刻的按键值进行存储
end
wire [2:0] key_ctrl = key_value_r & (~key_value); // 当键值从0到1时key_ctrl改变
reg d1;
reg d2;
reg d3;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin // 一个if内有多条语句时不要忘了begin end
d1 <= 0;
d2 <= 0;
d3 <= 0;
end
else
begin
if(key_ctrl[0]) d1 <= ~d1;
if(key_ctrl[1]) d2 <= ~d2;
if(key_ctrl[2]) d3 <= ~d3;
end
end
assign led1_n = d1? 1'b1:1'b0; // 此处只是为了将LED输出进行翻转,RTL级与下面注释代码无差别
assign led2_n = d2? 1'b1:1'b0;
assign led3_n = d3? 1'b1:1'b0;
endmodule