找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 935|回复: 2
收起左侧

嵌入式设备中按键的硬件防抖, 软件防抖和按键消息处理

[复制链接]
ID:912806 发表于 2021-12-8 13:38 | 显示全部楼层 |阅读模式
按键

按钮就是一种配备了弹性装置的双状态开关: 连通和断开. 由于弹性部件的作用, 大部分时间按钮是断开的. 从电路角度看, 按钮扮演的角色就是开路和短路. 按钮在嵌入式设备中是常见组件, 通常情况下, 一个按钮需要有一个弱上拉或下拉电阻, 对于STM32而言, GPIO口已经自带了弱上拉电阻, 可以在程序中设置是否使用, STC系列的MCU, 要看具体型号和具体的IO口, 例如经典的stc89c51/stc89c52, P0口就是漏极开路的双向IO口, 使用时当电流流出需外接上拉电阻.

将按钮连接到MCU通常有两种方式, 一种是低电平有效, 另一种是高电平有效, 在低电平有效的电路中, 当按钮按下时, 将在引脚上读取到逻辑0, 按钮释放后读取的是1; 在高电平有效电路中则正好相反.

上拉/下拉电阻阻值选取

如果电阻太小, 电流过大可能会损坏元件, 一般这个阻值在几K到几十K欧. 阻值的大小受GPIO的逻辑转换时间限制, 对于STM32, IO口电容为5pF, 上拉电阻可以为10K欧.

按键抖动效应 The bounce effect

按钮在按下和释放时都有可能产生抖动效应, 会导致过程中产生多次短路与开路之间的切换, 对于这个问题, 需要从硬件和软件方面来解决:
* 硬件上, 低通滤除抖动
* 软件上, 增加第一次检测到动作后的 dead time

硬件处理

硬件防抖动(debouncing)是需要优先考虑的方法, 比软件方式更稳定和高效. 可以通过在GPIO口和按键之间添加一个低通滤波电路实现.

实现低通滤波最简单的电路就是 RC滤波. 其阻值和容值怎么计算呢? 取决于抖动的容忍频率. 可以使用以下计算式

$f_{LP} = \frac{1}{2 \pi RC} = 0.1 \cdot f_{bounce}$

低通频率不能太低, 否则会滤除正常的操作, 在正常情况下, 一个人不太可能以100赫兹的频率去按按键, 所以
* 将低通频率设为10KHz, 对应的就是160欧的电阻和100nF的电容, 或1K欧电阻和16nF电容
* 将低通频率设为1KHz, 对应的就是1K欧电阻和160nF电容
* 将低通频率设为100Hz, 对应的就是10K欧电阻和160nF电容

下面的电路中, 使用了10KR电阻和100nF(104)电容作为硬件防抖处理
1682739309.jpg


软件处理

软件处理分两种情况, 如果仅仅需要检测短按, 是比较简单的, 声明一个volatile static a变量用于表示按键状态, 声明一个static uint8_t b变量用于计数, 每个循环的检测中, 低电平(假定按下为低电平)b加1, 当b值计数到达一个阈值时表示按钮按下, 将a置位, 当循环中检测到高电平时将a和b都清零.

如果需要检测短按和长按, 就需要三个变量, 除了上面的a和b以外, 再增加一个循环计数c. 检测的每个循环中, 先按检测短按的方式, 做短按判断, 另外再通过第三个变量记录短按的次数, 当达到预设的长按判断的次数阈值时, 判断为长按. 要注意的是
1. 短按的置位要由按钮释放触发
1. 长按的置位由按钮按下触发
1. 长按释放时, 要避免判断为短按

下面是一段实际应用中的代码, 会在一个间隔10ms的定时器中调用, 其中
* KEY1 为按键对应的IO口, 例如P01
* debounce[0] 为按键1对应的防抖延时计数器
* k1_pressed 当判断按键1为按下时置位, 全局使用
* switchcount[0] 按键1对应的长按键计数器, SW_CNTMAX为判断阈值
* k1_long_pressed 当判断按键1为长按时置位, 全局使用
* event 按键事件, 全局使用

  1. void read_key1(void)

  2. {

  3.     //未按下时, KEY1处于高电平, 因此debounce为0xFF

  4.     debounce[0] = (debounce[0] << 1) | KEY1;

  5.     if (debounce[0] == 0x00) { // 8次检测都为0, 按下置位

  6.         k1_pressed = 1;

  7.         if (!k1_long_pressed) { // 如果长按未置位, 计数加1

  8.             switchcount[0]++;

  9.         }

  10.     } else { // 按键已松开或未按下

  11.         if (k1_pressed) {

  12.             if (!k1_long_pressed) {

  13.                 // 如果短按已置位, 但是长按未置位, 按短按发出系统消息

  14.                 event = EV_K1_SHORT;

  15.             }

  16.             // 清理状态和计数器

  17.             k1_pressed = 0;

  18.             k1_long_pressed = 0;

  19.             switchcount[0] = 0;

  20.         }

  21.     }

  22.     if (switchcount[0] > SW_CNTMAX) {

  23.         // 如果长按计数器已经达到阈值, 长按置位(避免松开时发出短按消息), 发出长按系统消息

  24.         k1_long_pressed = 1;

  25.         switchcount[0] = 0;

  26.         event = EV_K1_LONG;

  27.     }

  28. }
复制代码

按键消息处理

按键的系统消息是通过状态机模型进行处理的, 在每个按键处理循环中,
1. 清除全局消息
1. 根据当前的按键状态, 判断长按和短按对应的下一个状态
1. 下一个循环, 会跳到对应的按键状态, 再去判断下一个状态
1. 根据按键状态决定当前的显示模式

  1. void main(void)

  2. {

  3.     //...

  4.     while (true)

  5.     {

  6.         while (!loop_gate); // wait for open every 100ms

  7.         loop_gate = 0; // close gate



  8.         ev = event;

  9.         event = EV_NONE;

  10.         switch (kmode)

  11.         {

  12.             case K_DISP_SEC:

  13.                 dmode = D_DISP_SEC;

  14.                 if (ev == EV_K2_SHORT) {

  15.                     kmode = K_DISP_ALARM;

  16.                     m_timeout = TIMEOUT_SHORT;

  17.                 }

  18.                 break;

  19.             //...

  20.             case K_NORMAL:

  21.             default:

  22.                 dmode = D_NORMAL;

  23.                

  24.                 if (ev == EV_K1_SHORT) {

  25.                     kmode = K_DISP_DATE;

  26.                     m_timeout = TIMEOUT_SHORT;

  27.                 } else if (ev == EV_ALARM) {

  28.                     kmode = K_BUZZ_ALARM;

  29.                     m_timeout = TIMEOUT_LONG;

  30.                 }

  31.                 else if (ev == EV_K1_LONG)

  32.                     kmode = K_SET_MINUTE;

  33.                 else if (ev == EV_K2_SHORT)

  34.                     kmode = K_DISP_SEC;

  35.                 else if (ev == EV_K2_LONG)

  36.                     kmode = K_SET_ALARM_MINUTE;

  37.         }

  38. //...

  39.     }

  40. }
复制代码

参考
* 按键防抖的硬件和软件处理:可以去百度这篇文章:Dealing with push-buttons using an STM32
* 例子中的完整代码 https://github.com/IOsetting/HML ... 2c887/alarm_clock.c

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:514317 发表于 2021-12-10 08:31 | 显示全部楼层
好贴    主要是10KR电阻和100nF(104)电容
回复

使用道具 举报

ID:941265 发表于 2021-12-10 08:39 | 显示全部楼层
好资料,51黑有你更精彩!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表