tag 标签: 消抖

相关帖子
相关博文
  • 热度 1
    2022-7-20 06:09
    2592 次阅读|
    8 个评论
    1.简介 按键为何消抖,原因是当按键按下和松开时,由于弹片回弹抖动,而导致按键对应管脚误判为多次按下和松开。 从以下动图对比可以看出: 2.原理 如图,我们认为的按键波形是理想的,然而其实正真的实际波形是每次按下和弹起都是由回弹的,如图实际波形其实也是理想化的,为了方便理解。 既然有回弹,那么怎么避免呢?一般采用方式为延时法。C语言最直接直接一个delay就完事了,简单粗暴。在FPGA里,使用延时+判断的方式,既延时完后再次判断是否已经稳定。 3.源代码 3.1状态机转移图 3.2实现 3.2.1延时模块 //计数延迟5ms always @( posedge clk or negedge rst_n ) begin if ( rst_n == 1'b0 ) delay_cnt <= 20'd0 ; else if ( cnt_start == 1'b1 ) delay_cnt <= delay_cnt + 20'd1 ; else delay_cnt <= 20'd0 ; end always @( posedge clk or negedge rst_n ) begin if ( rst_n == 1'b0 ) cnt_finish <= 1'b0 ; else if ( delay_cnt == 20'd499999 ) cnt_finish <= 1'b1 ; else cnt_finish <= 1'b0 ; end //FSM for the key always @( posedge clk or negedge rst_n ) begin if ( rst_n == 1'b0 ) begin state <= IDLE ; cnt_start <= 1'b0 ; key_o_r <= 1'b1 ; end else case ( state ) IDLE : begin if ( key_neg == 1'b1 ) begin cnt_start <= 1'b1 ; state <= FILTER0 ; end else state <= IDLE ; end FILTER0 : begin if ( cnt_finish == 1'b1 ) begin cnt_start <= 1'b0 ; key_o_r <= 1'b0 ; state <= DOWN ; end else if ( key_pos == 1'b1 ) begin state <= IDLE ; cnt_start <= 1'b0 ; end else begin state <= FILTER0 ; end end DOWN : begin if ( key_pos == 1'b1 ) begin state <= FILTER1 ; cnt_start <= 1'b1 ; end else begin state <= DOWN ; end end FILTER1 : begin if ( cnt_finish == 1'b1 ) begin cnt_start <= 1'b0 ; state <= IDLE ; key_o_r <= 1'b1 ; end else if ( key_neg == 1'b1 ) begin state <= DOWN ; cnt_start <= 1'b0 ; end else begin state <= FILTER1 ; end end default : begin state <= IDLE ; key_o_r <= 1'b1 ; cnt_start <= 1'b0 ; end endcase end
  • 热度 10
    2016-3-4 15:52
    758 次阅读|
    0 个评论
    图1 KEY原理图   图1 KEY原理图       图2 LED原理图   图2 LED原理图   key_led.v 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 module key_led(   input            CLOCK_50,   input            Q_KEY,   input       KEY,   output reg LED );     //++++++++++++++++++++++++++++++++++++++ // 获取键值 开始 //++++++++++++++++++++++++++++++++++++++ wire key_val;                     // 键值   key_debounce u0(   .i_clk            (CLOCK_50),   .i_rst_n          (Q_KEY),   .i_key            (KEY),   .o_key_val        (key_val)           // 按下为0,松开为1 );   //-------------------------------------- // 获取键值 结束 //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ // 按下键后开关LED 开始 //++++++++++++++++++++++++++++++++++++++ always @ (posedge CLOCK_50, negedge Q_KEY)   if (!Q_KEY)     LED = 4'hF;                        // 0灭1亮   else     case (1'b0)       key_val : LED = ~LED ;       key_val : LED = ~LED ;       key_val : LED = ~LED ;       key_val : LED = ~LED ;       default    : LED    =  LED   ;   // 缺省亮灭情况不变     endcase  //-------------------------------------- // 按下键后开关LED 结束 //--------------------------------------   endmodule key_debounce.v 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 module key_debounce(   input            i_clk,   input            i_rst_n,   input       i_key,               // 按下为0,松开为1   output reg o_key_val            // 键值 );     //++++++++++++++++++++++++++++++++++++++ reg key_samp1, key_samp1_locked;   // 将i_key采集至key_samp1 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)      key_samp1 = 4'hF;   else             key_samp1 = i_key;   // 将key_samp1锁存至key_samp1_locked always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)      key_samp1_locked = 4'hF;   else             key_samp1_locked = key_samp1; //--------------------------------------   //++++++++++++++++++++++++++++++++++++++ wire key_changed1;   // 当key_samp1由1变为0时 // key_changed1由0变为1,只维持一个时钟周期 assign key_changed1 = key_samp1_locked (~key_samp1);  //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ reg cnt;   // 一旦有按键按下,cnt立即被清零 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     cnt = 20'h0;   else if(key_changed1)     cnt = 20'h0;   else     cnt = cnt + 1'b1; //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ reg key_samp2, key_samp2_locked;   // 只有当按键不变化(不抖动),且维持20ms以上时 // 才将i_key采集至key_samp2 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     key_samp2 = 4'hF;   else if(cnt == 20'hF_FFFF)            // 0xFFFFF/50M = 20.9715ms     key_samp2 = i_key;   // 将key_samp2锁存至key_samp2_locked always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     key_samp2_locked = 4'hF;   else     key_samp2_locked = key_samp2; //--------------------------------------   //++++++++++++++++++++++++++++++++++++++ wire key_changed2;   // 当key_samp2由1变为0时 // key_changed2由0变为1,只维持一个时钟周期 assign key_changed2 = key_samp2_locked (~key_samp2);  //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ // 每次按键稳定后,输出键值 // 按下为0,松开为1 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     o_key_val = 4'hF;   else     o_key_val = ~key_changed2; //--------------------------------------   endmodule HDL思路 3cdd09ff-062d-4e68-85e7-697eab9c0e22   图3 按键抖动   如图3所示,按键在按下和释放的时候都会有一定的抖动。本代码仅消除了按下抖动,没有对释放抖动进行处理。   代码中使用了一个计数器来实现延时。只要这个计数器正常计数到20'hF_FFFF,我们就采样一个值key_samp2,并将其锁存进key_samp2_locked。一旦有下降沿来临,就输出按键改变值key_changed2。同时,该模块也在使用高频时钟i_clk不停地采集键值到key_samp1,并锁存进key_samp1_locked;一旦下降沿存在,输出按键变化值key_changed1,这样计数器就会被清零。假设没有key_changed1,我们将0xFFFFF/50M = 20.9715ms取样一次键值,并检测其下降沿,输出键值。但是由于抖动的原因,采用高频时钟i_clk采样的key_samp1就有可能导致key_changed1为一,进而使得低频的20.9715ms采样延时执行。也就是说,只有按键处于稳定的状态时,key_samp2才被正常采样,才有可能检测到key_changed2。   参考资料 1. 特权.基于verilog按键消抖设计   http://group.ednchina.com/1375/21132.aspx
  • 热度 9
    2016-3-4 13:59
    627 次阅读|
    0 个评论
    图1 KEY原理图   图1 KEY原理图       图2 LED原理图   图2 LED原理图   key_led.v 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 module key_led(   input            CLOCK_50,   input            Q_KEY,   input       KEY,   output reg LED );     //++++++++++++++++++++++++++++++++++++++ // 获取键值 开始 //++++++++++++++++++++++++++++++++++++++ wire key_val;                     // 键值   key_debounce u0(   .i_clk            (CLOCK_50),   .i_rst_n          (Q_KEY),   .i_key            (KEY),   .o_key_val        (key_val)           // 按下为0,松开为1 );   //-------------------------------------- // 获取键值 结束 //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ // 按下键后开关LED 开始 //++++++++++++++++++++++++++++++++++++++ always @ (posedge CLOCK_50, negedge Q_KEY)   if (!Q_KEY)     LED = 4'hF;                        // 0灭1亮   else     case (1'b0)       key_val : LED = ~LED ;       key_val : LED = ~LED ;       key_val : LED = ~LED ;       key_val : LED = ~LED ;       default    : LED    =  LED   ;   // 缺省亮灭情况不变     endcase  //-------------------------------------- // 按下键后开关LED 结束 //--------------------------------------   endmodule key_debounce.v 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 module key_debounce(   input            i_clk,   input            i_rst_n,   input       i_key,               // 按下为0,松开为1   output reg o_key_val            // 键值 );     //++++++++++++++++++++++++++++++++++++++ reg key_samp1, key_samp1_locked;   // 将i_key采集至key_samp1 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)      key_samp1 = 4'hF;   else             key_samp1 = i_key;   // 将key_samp1锁存至key_samp1_locked always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)      key_samp1_locked = 4'hF;   else             key_samp1_locked = key_samp1; //--------------------------------------   //++++++++++++++++++++++++++++++++++++++ wire key_changed1;   // 当key_samp1由1变为0时 // key_changed1由0变为1,只维持一个时钟周期 assign key_changed1 = key_samp1_locked (~key_samp1);  //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ reg cnt;   // 一旦有按键按下,cnt立即被清零 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     cnt = 20'h0;   else if(key_changed1)     cnt = 20'h0;   else     cnt = cnt + 1'b1; //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ reg key_samp2, key_samp2_locked;   // 只有当按键不变化(不抖动),且维持20ms以上时 // 才将i_key采集至key_samp2 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     key_samp2 = 4'hF;   else if(cnt == 20'hF_FFFF)            // 0xFFFFF/50M = 20.9715ms     key_samp2 = i_key;   // 将key_samp2锁存至key_samp2_locked always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     key_samp2_locked = 4'hF;   else     key_samp2_locked = key_samp2; //--------------------------------------   //++++++++++++++++++++++++++++++++++++++ wire key_changed2;   // 当key_samp2由1变为0时 // key_changed2由0变为1,只维持一个时钟周期 assign key_changed2 = key_samp2_locked (~key_samp2);  //--------------------------------------     //++++++++++++++++++++++++++++++++++++++ // 每次按键稳定后,输出键值 // 按下为0,松开为1 always @ (posedge i_clk, negedge i_rst_n)   if(!i_rst_n)     o_key_val = 4'hF;   else     o_key_val = ~key_changed2; //--------------------------------------   endmodule HDL思路 3cdd09ff-062d-4e68-85e7-697eab9c0e22   图3 按键抖动   如图3所示,按键在按下和释放的时候都会有一定的抖动。本代码仅消除了按下抖动,没有对释放抖动进行处理。   代码中使用了一个计数器来实现延时。只要这个计数器正常计数到20'hF_FFFF,我们就采样一个值key_samp2,并将其锁存进key_samp2_locked。一旦有下降沿来临,就输出按键改变值key_changed2。同时,该模块也在使用高频时钟i_clk不停地采集键值到key_samp1,并锁存进key_samp1_locked;一旦下降沿存在,输出按键变化值key_changed1,这样计数器就会被清零。假设没有key_changed1,我们将0xFFFFF/50M = 20.9715ms取样一次键值,并检测其下降沿,输出键值。但是由于抖动的原因,采用高频时钟i_clk采样的key_samp1就有可能导致key_changed1为一,进而使得低频的20.9715ms采样延时执行。也就是说,只有按键处于稳定的状态时,key_samp2才被正常采样,才有可能检测到key_changed2。   参考资料 1. 特权.基于verilog按键消抖设计   http://group.ednchina.com/1375/21132.aspx
  • 热度 22
    2014-10-27 14:17
    1721 次阅读|
    2 个评论
    八、 仿真分析   由上图仿真结果可知,当有按键按下时,需要较长一段时间后, Key_Flag 会有一个高电平脉冲,同时 Key_Value 更新为输入按键的反码。 为了确定消抖是成功的,这里再附上按键松开时的抖动细节图:   由图可知,松开按键时,该按键 IO 不断的检测到高电平和低电平,直到一段时间和,抖动方停止,稳定为按键没有按下时的状态 下图为整个工程的仿真结果,由图可知,每按下一次按键 0 ( key_in ) ,led 的状态便翻转一次。     九、 下板验证 手头暂无开发板,板级验证略。   十、 总结 本文档对按键消抖的原理进行了分析,并对消抖核心模块的设计进行了仿真,通过 modelsim 仿真验证了消抖模块设计的正确性。 具体的控制模块这里因为篇幅和时间关系暂不介绍,也因为没有开发板,暂时无法录制演示视频,等录制视频时,我会对整个系统的架构设计,代码设计进行详细的分析和讲解。以前没有做过不知道,写了两三次后才发现,原来文档的编写和整理比编写代码要的时间要多的多。不过,我总还是会坚持做下去的,希望我能有足够的时间来做这些事。
  • 热度 15
    2011-4-2 14:57
    1717 次阅读|
    2 个评论
    题目想表达的一个意思是“把门打开以后,别忘了随手关上门”。记得特权同学在第一个单位工作时,每天早晨上班都能提前个半小时,每次将电梯升上7楼以后,都不忘按上1以后才出电梯。原因很简单,只是希望能给后面来的同事省一点时间,多一些便利。印象中好像也有那么一句话“……,别忘了把电梯降回一楼”。当然,开篇那句话和这件事其实想表达的也都是一个意思。 之所以想起这码事,和近来在一个软件上遇到的几个问题解决过程中的所感所悟不无关系。这是一个与中断相关的问题,如图1所示,这是一个常见的带有中断的简单软件流程图。通常只是在中断函数中置位中断标志位,然后回到主程序中检查到该中断标志位置位的时候才去执行相应的处理。 图1 光看流程图可能意思表达的不是很明白,简单的一段代码可能更贴切一些。 假定中断函数如下: Void 中断函数名(void) { ① 读状态寄存器,确认当前中断已经产生 ② 清除中断寄存器相关标志位 ③ 置位中断标志位(这是一个用户自定义的系统变量,有别于中断寄存器) } 而通常情况下,我们会在主函数中做如下操作: Void main(void) { If(中断标志位置位) { ① 清除中断标志位 ② 确认是否满足中断处理条件?是,则进入中断处理函数,否,则继续。 } } 一般而言,这样的处理方式也没太有问题。当然对于多个中断的程序,并且还掺杂着优先级因素在里面的程序,就应另当别论了。而一般情况下,所谓的“把门关上”其实指的可能就是main函数中的“清除中断标志位”这一步了。 那,特权同学遇到的情况比较不一般,所以“关门”也就稍微费点劲。说白了,这是一个PIO下降沿捕获中断,而它的触发波形很不规则,或者你可以理解为“抖动”比较厉害。这里不直接点名说的是什么情况,就当这个PIO输入源就是一个按键,那么这个按键在按下和拿起的时候都是有抖动的,如果没有任何消抖的措施,恐怕用户想要的一次触发会演变为N次触发(N大于1),那用户肯定要抗议。那么问题就延伸到如何“消抖”上了。当然,为了有效滤除“按下”的抖动,我们可能只需要在判断中断标志位置位与否后再加一个延时(按键的消抖通常取20ms),这个延时过后如果键值仍然处于按下状态,那么就认为确实按键被按下了,而如果程序就此为止,其实也没有问题。有人可能会质疑按键释放的抖动如何滤除?其实在按下滤波的时候已经一并滤除了。 而特权同学遇到的问题如果仅仅停留于此,也就没有更多文章可作。如图2所示,在实际探索过程中发现这个PIO信号除了触发和释放过程有“抖动”,在中断处理过程中也受影响有类似的“抖动”状况(产生中断源和处理中断的对象是一个芯片,至于为什么恐怕只有厂商知道了,经过多方测试验证,特权同学觉得这个不像是干扰的问题,应该是由于芯片内部处理机制的问题)。 图2 这就产生了一个新的问题,在主函数还没有退出处理函数时,会再次产生一个新的中断。而如果在系统退出当前处理过程后,如果PIO本身的低电平状态还未退出,那么有可能接着引发一次“误动作”,这个“误动作”还有可能随着PIO低电平的保持一致的连续下去,这是很可怕的。 在发现的问题之后,特权同学第一反应是在进入中断处理后直接关掉中断,直到完成一次中断处理后再次开启。而问题其实好像没有这么简单,因为如此处理后状况依旧。为什么?因为中断虽然被关上了,但它本身其实依然在运转着,在判断到触发条件时仍然会改变当前的中断寄存器状态,而一旦重新开启中断后,立刻回进入中断函数。那么这也导致了“噩梦”会周而复始的继续下去。寻根究底,问题还出在中断寄存器这“扇门”还未彻底的关上。于是,如果能在重新开启中断后执行的第一条命令里关上这“扇门”,问题其实就能迎刃而解。我们习惯上并不会在意这个情况,因为我们总以为中断关上以后就万事大吉了,而实际上我们关上的只是中断而已,并没有完全将外设的工作停止,所以在重新开启中断后势必会出现一些意料之外的状况。 另外,关于这滤除抖动的延时对系统是一个不小的时间开销,在多个中断源的系统中,如果包含了其他实时性处理要求较高的外设,那么很可能导致系统的性能大打折扣。那么如何巧妙的处理这个不能不执行的“消抖”过程呢?呵呵,卖个关子,且待下回特权同学有空了再慢慢分析。
相关资源