【特权FPGA】之按键消抖
完整代码如下所示:
`timescale 1ns / 1ps// Company:
// Engineer: 特权
//
// Create Date:
// Design Name:
// Module Name:
// Project Name:
// Target Device:
// Tool versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 欢迎加入EDN的FPGA/CPLD助学小组一起讨论:http://group.ednchina.com/1375///说明:当三个独立按键的某一个被按下后,相应的LED被点亮;
// 再次按下后,LED熄灭,按键控制LED亮灭
/*****************************************************************************************************************/
module sw_debounce(clk,rst_n,sw1_n,sw2_n,sw3_n,led_d1,led_d2,led_d3);input clk; //主时钟信号,50MHz
input rst_n; //复位信号,低有效
input sw1_n,sw2_n,sw3_n; //三个独立按键,低表示按下
output led_d1,led_d2,led_d3; //发光二极管,分别由按键控制//--------------------------------------------------------------------------------------------------------------------reg[2:0] key_rst; // 20ms滤除抖动always @(posedge clk or negedge rst_n)if (!rst_n) key_rst <= 3'b111;else key_rst <= {sw3_n,sw2_n,sw1_n};reg[2:0] key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中always @ ( posedge clk or negedge rst_n )if (!rst_n) key_rst_r <= 3'b111;else key_rst_r <= key_rst;//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire[2:0] key_an ;
assign key_an = key_rst_r & (~key_rst);
/*
key_rst 1 1 1 0 0 1
~key_rst 0 0 0 1 1 0
key_rst_n _ 1 1 1 0 0 1
key_an 0 0 1 0 0
*/
//-------------------------------------------------------------------------------------------------------------------------reg[19:0] cnt; //计数寄存器always @ (posedge clk or negedge rst_n)if (!rst_n) cnt <= 20'd0; //异步复位else if(key_an) cnt <=20'd0;// 按键消抖代码核心else cnt <= cnt + 1'b1;reg[2:0] low_sw;always @(posedge clk or negedge rst_n)if (!rst_n) low_sw <= 3'b111;else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff,之所以这样设计,人按下按键通常至少也要几百毫秒的时间,这20ms的时间窗口内,什么也不需要做。low_sw <= {sw3_n,sw2_n,sw1_n};//---------------------------------------------------------------------------
reg [2:0] low_sw_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中always @ ( posedge clk or negedge rst_n )if (!rst_n) low_sw_r <= 3'b111;else low_sw_r <= low_sw;
/*
low_sw 111 111 111 110 110 110
~low_sw 000 000 000 001 001 001
low_sw_r 111 111 111 110 110 110led_ctrl 000 000 000 001 000 000 */
//当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);reg d1;
reg d2;
reg d3;always @ (posedge clk or negedge rst_n)if (!rst_n) begind1 <= 1'b0;d2 <= 1'b0;d3 <= 1'b0;endelse begin //某个按键值变化时,LED将做亮灭翻转if ( led_ctrl[0] ) d1 <= ~d1; if ( led_ctrl[1] ) d2 <= ~d2;if ( led_ctrl[2] ) d3 <= ~d3;endassign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;endmodule
边沿检测
always @(posedge clk or negedge rst_n)if (!rst_n) key_rst <= 3'b111;else key_rst <= {sw3_n,sw2_n,sw1_n};reg[2:0] key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中always @ ( posedge clk or negedge rst_n )if (!rst_n) key_rst_r <= 3'b111;else key_rst_r <= key_rst;//当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期
wire[2:0] key_an ;
assign key_an = key_rst_r & (~key_rst);
/*
key_rst 1 1 1 0 0 1
~key_rst 0 0 0 1 1 0
key_rst_n _ 1 1 1 0 0 1
key_an 0 0 1 0 0
*/
key_rst_n的数据延迟了一个时钟周期。
知识点1:
按位与(&):
2个多位操作数按位进行与运算,各位的结果按顺序组成一个新的多位数。例如,a=2’b10,b=2’b11,则a&b的结果为2’b10;[1]
知识点2:
设置两个寄存器,对前一状态和后一状态进行寄存,若前后两个状态不同,则检测到了边沿。对于上升沿和下降沿的确定可以用组合逻辑比较来确定。若前一状态D[1]为高电平,后一状态D[0]为低电平,则为下降沿,反之为上升沿。[2]
当然,TQ老师用的方法不一样,道理是相同的。个人推荐文献2中提到的办法。要注意的是,后者是双边沿检测。
//设置两个寄存器,实现前后电平状态的寄存
//相当于对dat_i 打两拍always @(posedge clk or negedge rst_n)beginif(rst_n == 1'b0)beginD <= 2'b00;endelse beginD <= {D[0], data}; //D[1]表示前一状态,D[0]表示后一状态(新数据) endend//组合逻辑进行边沿检测assign pos_edge = ~D[1] & D[0];assign neg_edge = D[1] & ~D[0];assign data_edge = pos_edge | neg_edge;
接下来是核心代码。按键按下了,计数器清零,等待20ms,然后再把按键值输出给led灯。
//-------------------------------------------------------------------------------------------------------------------------reg[19:0] cnt; //计数寄存器always @ (posedge clk or negedge rst_n)if (!rst_n) cnt <= 20'd0; //异步复位else if(key_an) cnt <=20'd0;// 按键消抖代码核心else cnt <= cnt + 1'b1;reg[2:0] low_sw;always @(posedge clk or negedge rst_n)if (!rst_n) low_sw <= 3'b111;else if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff,之所以这样设计,人按下按键通常至少也要几百毫秒的时间,这20ms的时间窗口内,什么也不需要做。low_sw <= {sw3_n,sw2_n,sw1_n};//---------------------------------------------------------------------------
reg d1;
reg d2;
reg d3;always @ (posedge clk or negedge rst_n)if (!rst_n) begind1 <= 1'b0;d2 <= 1'b0;d3 <= 1'b0;endelse begin //某个按键值变化时,LED将做亮灭翻转if ( led_ctrl[0] ) d1 <= ~d1; if ( led_ctrl[1] ) d2 <= ~d2;if ( led_ctrl[2] ) d3 <= ~d3;endassign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
参考文献
[1]FPGA_Verilog基础篇:verilog基本逻辑运算_verilog两组多位宽信号相与-CSDN博客
[2]FPGA基础学习——Verilog实现的边沿检测(上升沿下降沿检测)及Modelsim仿真_verilog 上升沿检测-CSDN博客