针对按键消抖模块有多种方式.
//===========================================================================
黑金教程中的按键消抖方式.
其中核心的去抖方法是使用边沿检测,这一点和特权同学的方式是一样的.不同的是,边沿检测和100us延时是同时执行的.在100us以后直接判断当次的边沿是上升沿还是下降沿.这种方法有一定的冒险性.
Verilog代码见key0.v
//=========================================================================== // Created by : Casio // Filename : key0.v // Author : // Created On : 2013-04-25 15:01 // Last Modified : 2013-04-25 16:12 // // // Revision:: // // Description: // // //=========================================================================== module key0( //Input Ports clk, rst_n, pin_in, //Output Ports pin_out ); //=========================================================================== //Input and output declaration //=========================================================================== input clk; input rst_n; input pin_in; output pin_out; //=========================================================================== //Wire and reg declaration //=========================================================================== wire clk; wire rst_n; //=========================================================================== //Wire and reg in the module //=========================================================================== wire h2l_sig; wire l2h_sig; //=========================================================================== //边沿检测模块 //=========================================================================== //-------------------------------------- //100us延时 parameter T100US = 11'd1200; reg [10:0] count1; reg is_en; always @ (posedge clk or negedge rst_n) if (!rst_n) begin count1 <= 11'd0; is_en <= 1'b0; end else if (count1 == T100US) is_en <= 1'b1; else count1 <= count1 + 1'b1; //-------------------------------------- //边沿检测 reg h2l_f1; reg h2l_f2; reg l2h_f1; reg l2h_f2; always @ (posedge clk or negedge rst_n) if (!rst_n) begin h2l_f1 <= 1'b1; h2l_f2 <= 1'b1; l2h_f1 <= 1'b0; l2h_f2 <= 1'b0; end else begin h2l_f1 <= pin_in; h2l_f2 <= h2l_f1; l2h_f1 <= pin_in; l2h_f2 <= l2h_f1; end assign h2l_sig = is_en ? (h2l_f2 & !h2l_f1) : 1'b0; assign l2h_sig = is_en ? (l2h_f2 & !l2h_f1) : 1'b0; //=========================================================================== //LED灯输出模块 //=========================================================================== //-------------------------------------- //10ms延时 parameter T1MS = 17'd120000; reg [16:0] count1MS; always @ (posedge clk or negedge rst_n) if (!rst_n) count1MS <= 17'd0; else if (is_count && count1MS == T1MS) count1MS <= 17'd0; else if (is_count) count1MS <= count1MS + 1'b1; else if (!is_count) count1MS <= 17'd0; //-------------------------------------- //MS累加计数 reg [3:0] cntMS; always @ (posedge clk or negedge rst_n) if (!rst_n) cntMS <= 4'd0; else if (is_count && count1MS == T1MS) cntMS <= cntMS + 1'b1; else if (!is_count) cntMS <= 4'd0; //-------------------------------------- //led灯输出 reg is_count; reg pin_out_r; reg [1:0] i; always @ (posedge clk or negedge rst_n) if (!rst_n) begin is_count <= 1'b0; pin_out_r <= 1'b0; i <= 2'd0; end else case (i) 2'd0: if (h2l_sig) i <= 2'd1; else if (l2h_sig) i <= 2'd2; 2'd1: if (cntMS == 4'd10) begin is_count <= 1'b0; pin_out_r <= 1'b1; i <= 2'd0; end else is_count <= 1'b1; 2'd2: if (cntMS == 4'd10) begin is_count <= 1'b0; pin_out_r <= 1'b0; i <= 2'd0; end else is_count <= 1'b1; endcase assign pin_out = pin_out_r; //-------------------------------------- endmodule
RTL视图 (无)
特权同学的按键消抖代码中规中矩,使用边沿检测方式判断电平变化.
Verilog代码见key1.v
//=========================================================================== // Created by : Casio // Filename : key1.v // Author : // Created On : 2013-04-25 16:50 // Last Modified : 2013-04-25 19:44 // // // Revision:: // // Description:采用特权同学的计算方式 // //Resource Usage //Total logic elements 38 //-- Combinational with no register 8 //-- Register only 4 //-- Combinational with a register 26 // //Logic element usage by number of LUT inputs //-- 4 input functions 7 //-- 3 input functions 2 //-- 2 input functions 20 //-- 1 input functions 5 //-- 0 input functions 0 // //Logic elements by mode //-- normal mode 19 //-- arithmetic mode 19 //-- qfbk mode 0 //-- register cascade mode 0 //-- synchronous clear/load mode 20 //-- asynchronous clear/load mode 30 // //Total registers 30 //Total logic cells in carry chains 20 //I/O pins 6 //Maximum fan-out node clk //Maximum fan-out 30 //Total fan-out 167 //Average fan-out 3.80 //=========================================================================== module key1( //Input Ports clk, rst_n, sw1_n, sw2_n, //Output Ports led_d1, led_d2 ); //=========================================================================== //Input and output declaration //=========================================================================== input clk; //主时钟信号,50MHz input rst_n; //复位信号,低有效 input sw1_n; //三个独立按键,低表示按下 input sw2_n; output led_d1; //发光二极管,分别由按键控制 output led_d2; //=========================================================================== //Wire and reg declaration //=========================================================================== wire clk; wire rst_n; wire sw1_n; wire sw2_n; wire led_d1; wire led_d2; //=========================================================================== //Wire and reg in the module //=========================================================================== //=========================================================================== //按键去抖模块 //=========================================================================== //边沿变化检测,每个CLK时钟都检测按键边沿变化 reg[1:0] key_rst; always @(posedge clk or negedge rst_n) if (!rst_n) key_rst <= 2'b11; else key_rst <= {sw2_n,sw1_n}; reg[1:0] key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中 always @ ( posedge clk or negedge rst_n ) if (!rst_n) key_rst_r <= 2'b11; else key_rst_r <= key_rst; //当寄存器key_rst由1变为0时,led_an的值变为高,维持一个时钟周期 wire[1:0] key_an = key_rst_r & ( ~key_rst); //--------------------------------------------------------------------------- //计数寄存器 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[1:0] low_sw; //每20毫秒判断检测,两次检测的时间间隔是20MS. always @(posedge clk or negedge rst_n) if (!rst_n) low_sw <= 2'b11; else if (cnt == 20'hfffff) //只有满20ms,才会将按键值记录到寄存器中 low_sw <= {sw2_n,sw1_n}; reg [1:0] low_sw_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中 always @ ( posedge clk or negedge rst_n ) if (!rst_n) low_sw_r <= 2'b11; else low_sw_r <= low_sw; //当寄存器low_sw由1变为0时,led_ctrl的值变为高,维持一个时钟周期 wire[1:0] led_ctrl = low_sw_r & ( ~low_sw); //此处的检测是瞬时的 //=========================================================================== //LED显示模块 //=========================================================================== reg d1; reg d2; always @ (posedge clk or negedge rst_n) if (!rst_n) begin d1 <= 1'b0; d2 <= 1'b0; end else begin //某个按键值变化时,LED将做亮灭翻转 if ( led_ctrl[0] ) d1 <= ~d1; if ( led_ctrl[1] ) d2 <= ~d2; end assign led_d1 = d1 ? 1'b1 : 1'b0; assign led_d2 = d2 ? 1'b1 : 1'b0; endmodule
RTL视图 (无)
如初出现电平变化,则启动20ms延时.如果在20ms计数以内电平依旧出现变化,则计数器清零,直到电平稳定,如果电平在20ms内一直稳定,则记录下20ms后的电平,并与之前稳定的电平进行判断比较,如果是电平变化,则进行相应输出.图形表示如下:
这种方式在进行双边沿检测时会多出一倍的资源量,所以在减小资源的情况下,不推荐使用.
//===========================================================================
红星开发板的按键消抖方式:
Verilog代码见key2.v
`define UD #1 module key2 ( //Input ports. SYSCLK, RST_B, KEY_B, //Output ports. LED_B ); //=========================================================================== //Input and output declaration //=========================================================================== input SYSCLK; //System clock, 27MHz. input RST_B; //Global reset, low active. input [3:0] KEY_B; //Key input, low active. output [3:0] LED_B; //Led output, low active. //=========================================================================== //Wire and reg declaration //=========================================================================== wire SYSCLK; wire RST_B; wire [3:0] KEY_B; reg [3:0] LED_B; //=========================================================================== //Wire and reg in the module //=========================================================================== reg [19:0] TIME_CNT; //Counter, count press key time. reg [3:0] KEY_REG; //Save the input one cycle every 20ms. reg [3:0] LED_B_N; //Next value of LED_B. wire [19:0] TIME_CNT_N; //Next value of TIME_CNT. wire [3:0] KEY_REG_N; //Next value of KEY_REG. wire [3:0] PRESS; //Decide which key in press. //=========================================================================== //Logic //=========================================================================== //Some control single. assign PRESS = KEY_REG & (~KEY_REG_N); //Save the key value when some key is press. always @ (posedge SYSCLK or negedge RST_B) begin if(!RST_B) KEY_REG <= `UD 4'hF; else KEY_REG <= `UD KEY_REG_N; end assign KEY_REG_N = (TIME_CNT == 20'h0) ? KEY_B : KEY_REG; //Count the time the key is pressed. always @ (posedge SYSCLK or negedge RST_B) begin if(!RST_B) TIME_CNT <= `UD 20'h0; else TIME_CNT <= `UD TIME_CNT_N; end assign TIME_CNT_N = TIME_CNT +1'h1; //Output control. always @ (posedge SYSCLK or negedge RST_B) begin if(!RST_B) LED_B <= `UD 4'hF; else LED_B <= `UD LED_B_N; end always @ (*) begin case(PRESS) 4'b0001 : LED_B_N = {LED_B[3:1] , (~LED_B[0]) }; 4'b0010 : LED_B_N = {LED_B[3:2] , (~LED_B[1]) , LED_B[0] }; 4'b0100 : LED_B_N = {LED_B[3] , (~LED_B[2]) , LED_B[1:0]}; 4'b1000 : LED_B_N = { (~LED_B[3]) , LED_B[2:0]}; default : LED_B_N = LED_B; endcase end endmodule
RTL视图 (无)
该开发板的消抖方式十分简陋,就是每隔20ms把按键采样值与之前的存储值进行比较,如果产生下降沿,就认为按键按下.
这个做法的弊端是计数器属于一直工作状态.没有开关去停止.而且单纯使用一个间隔采样去判断,则对采样时间间隔有了很多的漏洞.如下图:
可以发现,由于按键的时间是不能确定的,所以这个时候,间隔采样将遇到亚稳态的问题.所以该方法不可取.
//===========================================================================
国外的一个按键消抖方式(推荐)。
这套代码在关键点上和特权同学的思想有异曲同工之妙,都是通过电平的不同开始进行计数,并且在抖动期间,计数器反复清零,只有在稳定的时候才进行计数,当计数一定数值后确定电平.再进行输出.不同的是.这套代码使用了一个异步锁存器来对信号进行输出,而如果去掉这个异步锁存器,则可以看到,信号和按键是随动的.这是这个代码的灵活的地方,上下边沿都进行了判断.而且资源消耗很少.
RTL图如下.
VHDL代码见(当时也是从网上下载的,只有VHDL。我对代码做了详细的注释说明)
--Resource Usage --Total logic elements 24 ---- Combinational with no register 6 ---- Register only 0 ---- Combinational with a register 18 -- --Logic element usage by number of LUT inputs ---- 4 input functions 5 ---- 3 input functions 1 ---- 2 input functions 16 ---- 1 input functions 2 ---- 0 input functions 0 -- --Logic elements by mode ---- normal mode 9 ---- arithmetic mode 15 ---- qfbk mode 0 ---- register cascade mode 0 ---- synchronous clear/load mode 16 ---- asynchronous clear/load mode 0 -- --Total registers 18 --Total logic cells in carry chains 16 --I/O pins 3 --Maximum fan-out node clk --Maximum fan-out 17 --Total fan-out 92 --Average fan-out 3.41 --这个是老外给出的一个按键消抖程序.写VHDL一定要时刻记得,这是一个硬件设计,信号来了就会生效,因为是一个电路,而不是程序逻辑.判断条件仅仅是 --触发器电路的抽象概念,一定要时刻记住,是电路,一个process就是一个电路模块,signal就是模块与模块之间的电路连线.变量则可以看做是一个临时存储单元. --书中对于信号与变量说的很清楚: 常量与信号是全局的,而变量是局部的,变量只能在顺序代码中执行,并且它们的值是不能直接向外传递的. library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity buttom is Port ( btn_0 : in STD_LOGIC; -- 按键输入 clk : in STD_LOGIC; -- 时钟 led : out STD_LOGIC); -- 信号输出 end buttom; architecture Behavioral of buttom is --消抖延时计数值(常量) constant CNTR_MAX : std_logic_vector(15 downto 0) := (others => '1'); --计数变量(使用信号是因为要用硬件的角度去看,它在一个模块中是输出,在另一个模块中是输入,是一根信号线) signal btn0_cntr : std_logic_vector(15 downto 0) := (others => '0'); --输出连接信号线 signal led_r : std_logic := '0'; -- 同样是一个信号连接线,连接两个模块. signal btn0_reg : std_logic := '0'; begin -- 如果btn_0(按键)按下,则发生和btn0_reg的电平异号,开始计数. -- 在抖动期间会发生btn_0经常异号的情况,所以计数器会时刻清零,直到按键稳定开始计数 btn0_counter_process : process (CLK) begin if (rising_edge(CLK)) then if ((btn0_reg = '1') xor (btn_0 = '1')) then if (btn0_cntr = CNTR_MAX) then btn0_cntr <= (others => '0'); else btn0_cntr <= btn0_cntr + 1; end if; else btn0_cntr <= (others => '0'); end if; end if; end process; btn0_debounce_process : process (CLK) begin if (rising_edge(CLK)) then if (btn0_cntr = CNTR_MAX) then btn0_reg <= not(btn0_reg); end if; end if; end process; -- 注释掉的功能是锁存器,目的是只有在btn0_reg上升沿时候才进行操作 process(btn0_reg) begin if (rising_edge(btn0_reg)) then led_r <= not (led_r); end if; end process; led <= led_r; -- end of comment -- --如果要求按键随动则注释掉上面,把下面这句打开.如果要锁存就要打开上面,注释掉下面. --led <= btn0_reg; end Behavioral;
然后是我自己改写的Verilog代码,功能和RTL与VHDL完全一致。
//=========================================================================== // Created by : Casio // Filename : key4.v // Author : // Created On : 2013-04-25 21:44 // Last Modified : 2013-05-13 20:37 // // // Revision:: // // Description: // // //=========================================================================== module key4( //Input Ports input clk, //时钟 input rst_n, //复位 input btn0, //按键输入 //Output Ports output led //信号输出 ); parameter CNTR_MAX = 16'hFFFF; //计数器阈值 //=========================================================================== //Wire and reg in the module //=========================================================================== wire xor_out; reg [15:0] btn0_cntr; //16位计数器 reg [15:0] btn0_cntrN; //触发器左端输入 reg led_r; //输出寄存器 reg led_rN; reg btn0_reg; //连接异步触发器的信号线 reg btn0_regN; //触发器左端输入线 //=========================================================================== //Logic //=========================================================================== assign xor_out = btn0_reg ^ btn0; //用异或门判断两个触发状态是否在同一侧 always @ (posedge clk or negedge rst_n) begin if (!rst_n) btn0_cntr <= 16'h0; else btn0_cntr <= btn0_cntrN; end always @ (*) begin if (xor_out) begin if (btn0_cntr == CNTR_MAX) btn0_cntrN = 16'h0; else btn0_cntrN = btn0_cntr + 16'h1; end else btn0_cntrN = 16'h0; end always @ (posedge clk or negedge rst_n) begin if (!rst_n) btn0_reg <= 1'b0; else btn0_reg <= btn0_regN; end always @ (*) begin if (btn0_cntr == CNTR_MAX) btn0_regN = ~btn0_reg; //反向 else btn0_regN = btn0_reg; end //此部分为异步锁存器,如果去掉此部分,则按键随动,否则按键状态锁存 always @ (posedge btn0_reg or negedge rst_n) begin if (!rst_n) led_r <= 1'b0; else led_r <= led_rN; end always @ (*) begin led_rN = ~led_r; end assign led = led_r; //如果要求按键随动,则打开下面这句 //assign led = btn0_reg; endmodule