在数字电路中,开关用于用于产生高、低电平,按键用于产生单次脉冲。由于开关和按键为机械部件,每次按下或者释放时,由于簧片的弹性会产生短暂的抖动,然后才能稳定接通或者断开。
抖动现象会导致按键电路的输出产生毛刺,如图1所示,从而可能导致系统产生误动作。
开关和按键的抖动时间一般在20ms以内。为了防止因按键抖动引起的系统误动作,必须对按键电路进行消抖,只在按键闭合或者断开稳定后才允许输出。
图1 按键抖动现象
按键消抖有软件消抖和硬件消抖两类方法。软件消抖是在嵌入式系统中,检测到按键按下时,应用软件延时20ms后再次检测按键的状态,如果两次状态相同,则确认按键已经按下。
这种处理方式虽然简单,但是会浪费cpu资源。
硬件消抖有多种方法。第一种方法是应用施密特电路的回差特性配合积分电路实现按键消抖,应用电路如图2所示。
图2 应用积分电路实现按键消抖
第二种方法是应用锁存器的保持功能实现开关消抖,应用电路如图3所示。
图3 应用锁存器实现开关消抖
除了上述两种按键消抖方法外,在基于fpga的数字系统设计中,也可以应用状态机设计按键消抖电路,在fpga内部实现。
基于状态机设计按键消抖电路时,需要将按键的一次动作分解为:按下前、按下时、稳定期和释放时4个状态,如图1中所示,分别用key_idle、key_pressed、key_active和key_release表示。
设按键输入用key_in表示,低电平有效,设计消抖时间为20ms,则按键消抖状态机的状态转换关系如图4所示。
图4 按键消抖状态机
根据上述状态转换关系,描述按键消抖模块的verilog hdl代码参考如下:
module key_debounce #(parameter debounce_time = 1000_000 )( // 50mhz时钟时,对应消抖时间为20msinput clk_50, // 50mhz时钟,周期为20ns input rst_n, // 复位信号 input key_in, // 按键输入 output reg key_out // 消抖后输出 ); // 内部状态定义,循环编码方式 localparam key_idle = 2'b00, // 按下前 key_pressed = 2'b01, // 按下时 key_active = 2'b11, // 稳定期 key_release = 2'b10; // 释放时 // 内部变量定义 reg [19:0] debounce_cnt; // 消抖计数变量 reg [1:0] current_state,next_state; // 现态和次态 reg [0:1] keytmp; // 同步寄存器 // 内部线网定义 wire cnt_en,cnt_end; // 计数允许和停止计数标志 wire cnt_flag; // 消抖计数标志 wire release_flag; // 按键释放标志 // 允许消抖计数逻辑:按键按下时或者释放时,cnt_en有效。assign cnt_en = current_state == key_pressed || current_state == key_release; // 停止计数标志:cnt_en有效并且debounce_cnt达到最大值,则cnt_end有效。 assign cnt_end = cnt_en && ( debounce_cnt == debounce_time - 1 ); // 正在计数标志:cnt_en有效并且debounce_cnt未达到最大值,则cnt_flag有效。 assign cnt_flag = cnt_en && ( debounce_cnt < debounce_time ); // 按键已释放标志: cnt_end有效,并且keytmp[1]为高电平,则release_flag有效。 assign release_flag = cnt_end && keytmp[1]; // 按键输入两级同步寄存过程,以消除亚稳态。 always @(posedge clk_50 or negedge rst_n) if ( !rst_n ) keytmp <= 2'b00; // 清零 else keytmp[0:1] <= {key_in,keytmp[0]}; // 右移 // 时序逻辑过程,描述状态转换 always @(posedge clk_50 or negedge rst_n) if ( !rst_n ) current_state <= key_idle; else current_state <= next_state; // 组合逻辑过程,定义次态 always @(*) begin case ( current_state ) key_idle: if ( !keytmp[1] ) // 按键按下时,进入key_pressed next_state = key_pressed; else // 否则,保持key_idle next_state = current_state; key_pressed: if ( cnt_end && !keytmp[1] ) // 消抖时间到且keytmp[1]为0,确认按下有效 next_state = key_active; else if ( cnt_flag && keytmp[1] ) // 正在计数,但keytmp[1]为1,则为抖动 next_state = key_idle;else // 否则状态保持 next_state = current_state; key_active: if ( keytmp[1] ) // keytmp[1]跳变为1则进入释放状态 next_state = key_release; else next_state = current_state; // 否则状态保持 key_release: if ( release_flag ) // 按键已释放,返回 next_state = key_idle; else if ( cnt_flag && !keytmp[1] ) // 正在计数,但keytmp[1]为0,则为抖动 next_state = key_active; else // 否则状态保持 next_state = current_state; default: next_state = key_idle; endcase end // 时序逻辑过程,消抖计时 always @( posedge clk_50 or negedge rst_n ) if ( !rst_n ) debounce_cnt <= 20'b0; else if ( cnt_en ) // 计数允许信号有效 if ( cnt_end ) // 消抖时间到 debounce_cnt <= 20'b0; else // 消抖时间未到 debounce_cnt <= debounce_cnt + 1'b1; else // 计数允许信号无效 debounce_cnt <= 20'b0;// 时序逻辑过程,按键消抖后输出 always @( posedge clk_50 or negedge rst_n ) if ( !rst_n ) key_out <= 1'b1; else case ( current_state ) key_idle : key_out <= 1'b1;key_pressed: key_out <= 1'b1;key_active : key_out <= 1'b0; key_release: key_out <= 1'b0;default: key_out <= 1'b1; endcaseendmodule
对上述代码进行仿真验证时,需要建立testbench文件,应用系统函数$random产生随机数,以模拟不规则的抖动脉冲间隔。
应用系统函数$random产生随机整数的语法格式为
num = $random%b其中b为十进制整数,num为-(b-1) ~ (b-1)范围内的随机整数。 应用系统函数$random产生随机正整数的语法格式为num ={$random}%b其中b为十进制整数,num为0 ~ (b-1)范围内的随机整数。 测试按键消抖模块功能的testbench代码参考如下:`timescale 1ns/1psmodule key_debounce_vlg_tst(); reg clk; reg rst_n; reg key_in; wire key_out; // 模块参数重定义,减少计数容量,以缩短仿真时间 defparam key_debounce.debounce_time = 50000;// 内部变量定义 reg [15:0] rand_num; // 仿真参数定义 parameter reset_time = 2, step = 5; // 按键消抖模块例化 key_debounce i1 ( .clk (clk), .rst_n (rst_n), .key_in (key_in), .key_out (key_out)); // 设置复位信号波形 initial begin rst_n = 1; #1; rst_n = 0; #(step * reset_time); rst_n = 1; end // 设置按键输入 initial begin #1; key_in = 1; //按下前 #(step * 10); press_key; // 第1次按键过程 #10_000; press_key; // 第2次按键过程 end // 设置时钟信号 initial clk_50 = 0; always #(step/2) clk_50 = ~clk_50; // 监测任务 initial $monitor($time,clk_50=%b rst_n=%b key_in=%b key_out=%b,clk_50,rst_n,key_in,key_out); // 按键任务定义 task press_key; begin repeat (20) begin // 模拟前沿抖动过程 rand_num = {$random}%5000; #rand_num key_in = ~key_in; end key_in = 0; #300_000; repeat (20) begin // 模拟后沿抖动过程 rand_num = {$random}%5000; #rand_num key_in = ~key_in; end key_in = 1; #300_000; end endtask endmodule
上述代码中使用了defparam语句用于对key_debounce模块中的debounce_time参数进行重定义,在确保功能验证的前提下缩短消抖时间。启动modelsim进行仿真,结果如图5所示。
图5 按键消抖模块仿真波形
从波形图中可以看出,消抖电路对按键按下和释放产生的4次抖动都能实现有效消抖,因此验证基于状态机设计的按键消抖模块功能正确。
电子行业投资热:三星将向芯片市场大举投资
拆解发现三星Gear Live智能手表采用赛普拉斯触摸屏解决方案
物联网+互联网的B2C模式_互联网时代全新的转机和商机
第二代EPYC揭开神秘面纱 AMD率先迈入7nm时代
一文了解蒸发器的分类及工作原理
小编科普几种按键消抖电路的设计方案
智能汽车数据的全生命周期都存在风险
三星欲借5G重振中国市场,能否打开一道口子
智能听诊器可以通过倾听病人的呼吸来诊断肺炎
HD-SDI转HDMI转换器工作原理及使用方法
老外用5个理由说服你:苹果电视将会成功
注射器滑动性泄漏性测试仪的简单介绍
量化AI:真正解决客户问题是关键
微系统技术的现状和发展趋势
GAD-201G自动失真度测试仪的性能特点及应用
再也不用进口了!我国自研首台2万瓦光纤激光器明年投产使用
锂离子电池之三元电池相关知识介绍
苹果最近怎么了?更新速度好快,iOS10.3.2新版系统又更新了
非线性运算放大器的应用电路
网友吐槽:IC工程师有多苦逼