[技术问答] 一种可靠的按键消抖方法

[复制链接]
 楼主| 余三水 发表于 2020-4-27 14:36 | 显示全部楼层 |阅读模式
方法介绍
按一定的间隔采样,连续多次都处于按下状态才判定为按下状态,可以有效的减少误操作。具体实现如下:

 楼主| 余三水 发表于 2020-4-27 14:38 | 显示全部楼层
按键头文件
  1. #ifndef        __KEY_H__
  2. #define __KEY_H__       

  3. #include <stdint.h>

  4. typedef struct Key_t {
  5.         uint8_t inited : 1;     //引脚是否已经初始化
  6.         uint8_t press : 1;                //按下表示,1表示发生了按下事件,等待用户处理
  7.         uint8_t release : 1;        //松开标志,1表示发送了松开事件,等待用户处理
  8.         uint8_t state : 1;                //按键的稳定状态
  9.         uint8_t states : 4;                //最近的4次采样
  10.         uint8_t pin;                        //按键的编号或者所在引脚
  11. } Key_t;

  12. #ifdef __cplusplus
  13. extern "C" {
  14. #endif

  15. //初始化一个按键
  16. void Key_Init(Key_t* key, uint8_t pin);

  17. //扫描一下按键,一般每隔5个毫秒扫描一次即可
  18. //连续4次扫描就需要20个毫秒了,如果4次检测都是低电平才判断为低电平。
  19. //由于一般情况下都是只处理按下事件,有按下事件返回1
  20. uint8_t Key_Scan(Key_t* key);

  21. //获取按键所在引脚的状态,高电平返回1,低电平返回0
  22. uint8_t Key_GetPinState(Key_t* key);

  23. //检查按键是否存在按下事件
  24. static inline uint8_t Key_HasPressEvent(Key_t* key)
  25. {
  26.         return key->press;
  27. }

  28. //清除按键的按下事件标志
  29. static inline void Key_ClearPressEvent(Key_t* key)
  30. {
  31.         key->press = 0;
  32. }

  33. //检查按键是否存在松开事件
  34. static inline uint8_t Key_HasReleaseEvent(Key_t* key)
  35. {
  36.         return key->release;
  37. }

  38. //清除按键松开事件标志
  39. static inline void Key_ClearReleaseEvent(Key_t* key)
  40. {
  41.         key->release = 0;
  42. }

  43. #ifdef __cplusplus
  44. }
  45. #endif

  46. #endif //__KEY_H__
 楼主| 余三水 发表于 2020-4-27 14:38 | 显示全部楼层
按键检测具体实现
  1. #include "key.h"
  2. #include <ioCC2530.h>

  3. uint8_t Key_GetPinState(Key_t* key)
  4. {
  5.     if (1 == key->pin) {
  6.         return P0_1;
  7.     }
  8.     else {
  9.         return 0;
  10.     }
  11. }

  12. //计算一个数字里面为1的位的个数
  13. static inline uint8_t CountOne(uint8_t num)
  14. {
  15.     uint8_t high = (num & 0xf0) >> 4;
  16.     uint8_t low = num & 0x0f;
  17.     const uint8_t table[] = {
  18.         0, 1, 1, 2,        //0000 0001 0010 0011
  19.         1, 2, 2, 3,        //0100 0101 0110 0111
  20.         1, 2, 2, 3, //1000 1001 1010 1011
  21.         2, 3, 3, 4,        //1100 1101 1110 1111
  22.     };

  23.     return table[high] + table[low];
  24. }

  25. void Key_Init(Key_t* key, uint8_t pin)
  26. {
  27.     key->pin = pin;
  28.     key->press = 0;
  29.     key->release = 0;
  30.     key->state = Key_GetPinState(key);
  31.     key->states = key->state ? 0x0f : 0x00;
  32.     key->inited = 1;
  33. }

  34. uint8_t Key_Scan(Key_t* key)
  35. {
  36.     uint8_t press = 0;
  37.     uint8_t state = 0;
  38.     uint8_t states = 0;

  39.     if (key && key->inited) {
  40.         //移出上一次的扫描状态并存入本次的扫描状态
  41.         states = key->states;
  42.         states <<= 1;
  43.         states |= Key_GetPinState(key);
  44.         key->states = states;

  45.         state = CountOne(states) ? 1 : 0;        //4次采样全部为0才判定为低电平
  46.         if (state != key->state) {          //引脚状态发生变化
  47.             key->state = state;
  48.             if (state) {
  49.                 key->release = 1;
  50.             }
  51.             else {
  52.                 press = 1;
  53.                 key->press = 1;
  54.             }
  55.         }
  56.     }

  57.     return press;
  58. }
 楼主| 余三水 发表于 2020-4-27 14:48 | 显示全部楼层
测试代码
  1. #include <ioCC2530.h>
  2. #include "key.h"

  3. #define LED1   P1_0     //定义LED1所在引脚
  4. #define KEY1   P0_1     //定义BTN1所在引脚

  5. void DelayMs(int ms)
  6. {
  7.     while (ms--) {
  8.         volatile int x = 500;//注意:这个数字是估计的,不准确
  9.         while (x--);
  10.     }
  11. }

  12. void main(void)
  13. {
  14.     Key_t key;
  15.    
  16.     //配置P0_1引脚的按键1
  17.     P0SEL &= ~0x02; //普通GPIO模式<0为IO模式,1为外设模式>
  18.     P0DIR &= ~0x02; //输入功能<0为输入,1为输出>
  19.     P0INP &= ~0x02; //上拉或下拉模式<0为上拉或下拉模式,1为三态模式>

  20.     //配置P1_0引脚的LED1
  21.     P1SEL &= ~0x01; //普通GPIO模式<0为IO模式,1为外设模式>
  22.     P1DIR |= 0x01;  //输出功能<0为输入,1为输出>
  23.     P1INP &= ~0x01; //上拉或下拉模式<0为上拉或下拉模式,1为三态模式>
  24.    
  25.     P2INP |= 0xe0;  //P0,P1,P2都设置为上拉模式
  26.    
  27.     Key_Init(&key, 1);
  28.    
  29.     while (1)
  30.     {
  31.         //一般使用定时器来扫描,这里使用延时函数用来测试一下。
  32.         DelayMs(5);     
  33.         Key_Scan(&key);
  34.         
  35.         if (Key_HasPressEvent(&key)) {
  36.             Key_ClearPressEvent(&key);
  37.             LED1 = !LED1;
  38.         }
  39.     }
  40. }
ayb_ice 发表于 2020-5-19 17:14 | 显示全部楼层
不需要多次,定时(10~100MS)调用,与上次值比较就可以了,不同就是按下或弹起
詹求实 发表于 2020-5-28 10:29 | 显示全部楼层
支持下,谢谢分享!
yzq13246068880 发表于 2020-7-9 10:17 | 显示全部楼层
顶下,顶下,顶下,
スモモ 发表于 2020-8-23 18:47 | 显示全部楼层
感谢分享
张さん 发表于 2020-8-23 19:11 | 显示全部楼层
学习了
weifeng90 发表于 2020-9-18 19:07 来自手机 | 显示全部楼层
复杂化了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

28

主题

356

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部

28

主题

356

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部