你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32单片机和FPGA按键消大全来了!

[复制链接]
gaosmile 发布时间:2020-3-20 14:10

写在前面:

微信图片_20200320140847.jpg
按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。
1、单片机中按键消抖程序
1.1  单片机中,比如STM32中,一般的方法(最简单的方法)

软件消抖程序:

  1. if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
  2.            {  
  3.                   delay_ms(20);//延时20ms再去检测按键值
  4.           if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相当于下降沿
  5. {
  6. KEY1 = 1;  //表示KEY1被按下
  7. }
  8. }
复制代码

1.2 比较全面的按键消抖程序及按键状态检测程序
第一步:初始化全局时间戳的定时器,一般采用SysTick定时器来产生,每ms一次tick即可。
第二步:初始化按键对应的IO,复用为边沿触发的外部中断。
第三步:在外部中断函数中添加按键事件处理函数。
代码部分:

  1. typedef struct _Key_t  
  2. {  
  3.   u32 last_time;  
  4.   enum  
  5.   {  
  6.     May_Press,  
  7.     Release,  
  8.   }private_state;  
  9.   enum  
  10.   {  
  11.     No_Press,  
  12.     Short_Press,  
  13.     Long_Press,  
  14.   }state;  
  15. }Key_t;

  16. #define Is_ShortPress_Threshold  1500
复制代码

简单定义一个按键状态的结构体,用于管理每个按键的状态。顺便再定义一个长短按的识别阈值,用于区分按键的长短按。

  1. if(key_state.private_state==Release)         
  2. {  
  3.   if(KEY==0)  
  4.   {  
  5.     key_state.private_state=May_Press;  
  6.     key_state.last_time=course_ms();  
  7.   }  
  8. }  
  9. else if(key_state.private_state==May_Press)  
  10. {  
  11.   if(KEY==1)  
  12.   {  
  13.     if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time
  14.     {  
  15.       key_state.state=Short_Press;  
  16.       key_state.private_state=Release;  
  17.     }  
  18.     else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)  
  19.     {  
  20.       key_state.state=Long_Press;  
  21.       key_state.private_state=Release;  
  22.     }  
  23.     else  
  24.       key_state.private_state=Release;  
  25.   }  
  26. }
复制代码




以上为需要添加到中断处理函数的按键事件处理函数,算法的核心是一个状态机。在本例中,按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。

思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。
效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。经实际测试,消抖效果可以达到其他两种消抖算法的水平。
2、FPGA按键消抖程序
首先,做两个假定,以方便后面的描述:

  • 假定按键的默认状态为0,被按下后为1
  • 假定按键抖动时长小于20ms,也即使用20ms的消抖时间

核心:方案

  • 最容易想到的方案

在按键电平稳定的情况下,当第一次检测到键位电平变化,开始20ms计时,计时时间到后将按键电平更新为当前电平。

  • 或许这才是最容易想的方案

在20ms计时的过程中,有任何的电平变化都立即复位计时

  • 消除按键反应延时抖方案

在有电平变化时立即改变按键输出电平,并开始20ms计时,忽略这其中抖动
测试平台设计(修改代码以仿真的1us代替实际1ms)

  • 无抖动 上升沿抖动5毫秒
  • 下降沿抖动15毫秒
  • 上升和下降沿均抖动19毫秒
  附加测试(可以不通过)
  • 抖动25毫秒

代码
方案1

  1. module debounce(    input wire clk, nrst,    input wire key_in,    output reg key_out
  2. );    // 20ms parameter//    localparam TIME_20MS = 1_000_000;
  3.     localparam TIME_20MS = 1_000;       // just for test    // variable
  4.     reg [20:0] cnt;    reg key_cnt;   
  5.     // debounce time passed, refresh key state
  6.     always @(posedge clk or negedge nrst) begin
  7.         if(nrst == 0)
  8.             key_out <= 0;        else if(cnt == TIME_20MS - 1)
  9.             key_out <= key_in;    end

  10.     // while in debounce state, count, otherwise 0
  11.     always @(posedge clk or negedge nrst) begin
  12.         if(nrst == 0)
  13.             cnt <= 0;        else if(key_cnt)
  14.             cnt <= cnt + 1'b1;
  15.         else
  16.             cnt <= 0;
  17.     end

  18.      //
  19.      always @(posedge clk or negedge nrst) begin
  20.             if(nrst == 0)
  21.                 key_cnt <= 0;            else if(key_cnt == 0 && key_in != key_out)
  22.                 key_cnt <= 1;            else if(cnt == TIME_20MS - 1)
  23.                 key_cnt <= 0;     endendmodule
复制代码

方案2

  1. module debounce(    input wire clk, nrst,    input wire key_in,    output reg key_out
  2.     );//    localparam TIME_20MS = 1_000_000;
  3.     localparam TIME_20MS = 1_000;    reg key_cnt;    reg [20:0] cnt;    always @(posedge clk or negedge nrst) begin
  4.         if(nrst == 0)
  5.             key_cnt <= 0;        else if(cnt == TIME_20MS - 1)
  6.             key_cnt <= 0;        else if(key_cnt == 0 && key_out != key_in)
  7.             key_cnt <= 1;    end

  8.     always @(posedge clk or negedge nrst) begin
  9.         if(nrst == 0)
  10.             cnt <= 0;        else if(key_cnt) begin
  11.             if(key_out == key_in)
  12.                 cnt <= 0;            else
  13.                 cnt <= cnt + 1'b1;
  14.         end
  15.         else
  16.             cnt <= 0;    end

  17.      always @(posedge clk or negedge nrst) begin
  18.             if(nrst == 0)
  19.                 key_out <= 0;            else if(cnt == TIME_20MS - 1)
  20.                 key_out <= key_in;     endendmodule
复制代码

方案3

  1. module debounce(    input wire clk, nrst,    input wire key_in,    output reg key_out
  2.     );//    localparam TIME_20MS = 1_000_000;
  3.     localparam TIME_20MS = 1_000;       // just for test

  4.     reg key_cnt;    reg [20:0] cnt;    always @(posedge clk or negedge nrst) begin
  5.         if(nrst == 0)
  6.             key_cnt <= 0;        else if(key_cnt == 0 && key_out != key_in)
  7.             key_cnt <= 1;        else if(cnt == TIME_20MS - 1)
  8.             key_cnt <= 0;    end

  9.     always @(posedge clk or negedge nrst) begin
  10.         if(nrst == 0)
  11.             cnt <= 0;        else if(key_cnt)
  12.             cnt <= cnt + 1'b1;
  13.         else
  14.             cnt <= 0;    end

  15.     always @(posedge clk or negedge nrst) begin
  16.         if(nrst == 0)
  17.             key_out <= 0;        else if(key_cnt == 0 && key_out != key_in)
  18.             key_out <= key_in;    endendmodule
复制代码
测试代码

  1. // 按键消抖测试电路// 时间单位`timescale 1ns/10ps// modulemodule  debounce_tb;    // time period parameter
  2.     localparam T = 20;    // variable
  3.     reg clk, nrst;    reg key_in;    wire key_out;    // instantiate    debounce uut(
  4.         .clk    (clk    ),
  5.         .nrst   (nrst   ),
  6.         .key_in (key_in ),
  7.         .key_out(key_out)
  8.     );    // clock
  9.     initial begin
  10.         clk = 1;        forever #(T/2) clk = ~clk;    end

  11.     // reset
  12.     initial begin
  13.         nrst = 1;
  14.         @(negedge clk) nrst = 0;
  15.         @(negedge clk) nrst = 1;    end

  16.     // key_in
  17.     initial begin
  18.         // initial value
  19.         key_in = 0;        
  20.         // wait reset
  21.         repeat(3) @(negedge clk);        
  22.         // no bounce        // key down
  23.         key_in = 1;        // last 60ms
  24.         repeat(3000) @(negedge clk);        // key up
  25.         key_in = 0;        // wait 50ms
  26.         repeat(2500) @(negedge clk);        // down 5ms, up 15ms        // key down, bounce 5ms
  27.         repeat(251) @(negedge clk) key_in = ~key_in;        // last 60ms
  28.         repeat(3000) @(negedge clk);        // key up, bounce 15ms
  29.         repeat(751) @(negedge clk) key_in = ~key_in;        // wait 50ms
  30.         repeat(2500) @(negedge clk);        // down 19ms, up 19ms        // key down, bounce 19ms
  31.         repeat(951) @(negedge clk) key_in = ~key_in;        // last 60ms
  32.         repeat(3000) @(negedge clk);        // key up, bounce 19ms
  33.         repeat(951) @(negedge clk) key_in = ~key_in;        // wait 50ms
  34.         repeat(2500) @(negedge clk);        
  35.         // additional, this situation shoud not ever happen        // down 25ms, up 25ms        // key down, bounce 25ms
  36.         repeat(1251) @(negedge clk) key_in = ~key_in;        // last 60ms
  37.         repeat(3000) @(negedge clk);        // key up, bounce 25ms
  38.         repeat(1251) @(negedge clk) key_in = ~key_in;        // wait 50ms
  39.         repeat(2500) @(negedge clk);        // stop        $stop;    endendmodule
复制代码

放在最后的,并不一定是最不重要的

对于上面的三种方案,我比较喜欢第三种方案,它更贴合实际的按键状态,以上的代码我都做过modelsim仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。
收藏 评论0 发布时间:2020-3-20 14:10

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似分享

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版