数码之家

 找回密码
 立即注册
搜索
查看: 7530|回复: 4

[Arduino] [分享代码]Arduino中按键处理的类,支持短按和长按两种事件回调方式

[复制链接]
发表于 2020-2-18 11:27:18 | 显示全部楼层 |阅读模式

爱科技、爱创意、爱折腾、爱极致,我们都是技术控

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 jjbboox 于 2020-2-19 13:27 编辑

gpio_button.h文件修改了一下,原来的版本定义多个按钮的时候事件响应会混乱

处理按键其实很简单,Arduino可以用C++,所以我们可以定义一个类来处理按键。
关键就是我们把按键定义在哪个GPIO口,以及按键按下后需要进行哪些处理。
简单的类定义不需要cpp,直接一个.h文件就可以了。

将该文件放在.ino同一个目录中就可以了。
gpio_button.h
  1. #ifndef _GPIO_BUTTON_H_
  2. #define _GPIO_BUTTON_H_
  3. #include <Arduino.h>

  4. #define        DEF_ELIMINATING_JITTER_MS        20                // 默认消抖延时
  5. #define DEF_LONG_PRESS_WAIT_MS  1000                // 默认长按延时

  6. class GpioButton {
  7.     public:
  8.                     // 构造函数,定义端口号,回调函数,初始化默认值
  9.         GpioButton(uint8_t gpio_pin, void(*btn_press_event)()=nullptr) :
  10.             GpioPin(gpio_pin),
  11.             ButtonPressEvent(btn_press_event),
  12.             LongPressWaitMS(DEF_LONG_PRESS_WAIT_MS),
  13.             ButtonLongPressEvent(nullptr),
  14.             key_down_millis(0),
  15.             action_done(false) {
  16.                 pinMode(GpioPin, INPUT_PULLUP);
  17.                 digitalWrite(GpioPin, HIGH);
  18.         };
  19.         // 绑定按键回调函数
  20.         void BindBtnPress(void(*btn_press_event)()) {ButtonPressEvent = btn_press_event;};
  21.         // 绑定长按事件回调函数和长按的判定时长
  22.         bool BindBtnLongPress(void(*btn_long_press_event)(), uint16_t wait_ms=DEF_LONG_PRESS_WAIT_MS) {
  23.             if(wait_ms < DEF_LONG_PRESS_WAIT_MS) return false;
  24.             ButtonLongPressEvent = btn_long_press_event;
  25.             LongPressWaitMS = wait_ms;
  26.             return true;
  27.         };
  28.         // 轮询函数
  29.         void loop(){
  30.             // 读取端口状态
  31.             uint8_t pin_val = digitalRead(GpioPin);
  32.             // 获取当前系统时间
  33.             uint32_t now_millis = millis();
  34.             // 计算按下时点到当前经过的毫秒数
  35.             uint32_t pass_millis = now_millis - key_down_millis;
  36.             // 记录按键按下时点的系统时间,清楚动作执行标志
  37.             if(pin_val == LOW && key_down_millis == 0) {
  38.                 key_down_millis = now_millis;
  39.                 action_done = false;
  40.             }
  41.             // 按键按下状态如果超过长按判定时长时,调用长按事件回调(如果已绑定长按事件回调)
  42.             else if(pin_val == LOW && ButtonLongPressEvent != nullptr && pass_millis > LongPressWaitMS && !action_done) {
  43.                             // 防止重复调用,设置执行标志
  44.                 action_done = true;
  45.                 // 调用回调方法
  46.                 ButtonLongPressEvent();
  47.             }
  48.             // 按键释放状态
  49.             else if(pin_val == HIGH) {
  50.                             // 如果是按键释放瞬间,按下时长超过消抖间隔时长,且尚未执行过回调函数
  51.                 if(!action_done && key_down_millis > 0 && pass_millis > DEF_ELIMINATING_JITTER_MS && ButtonPressEvent != nullptr) {
  52.                                 // 设置执行标志
  53.                     action_done = true;
  54.                     // 调用回调方法
  55.                     ButtonPressEvent();
  56.                 }
  57.                 // 清空按下时点的值
  58.                 key_down_millis = 0;
  59.             }
  60.         };
  61.     protected:
  62.         uint8_t GpioPin;
  63.         void (*ButtonPressEvent)();
  64.         uint16_t LongPressWaitMS;
  65.         void (*ButtonLongPressEvent)();
  66.         uint32_t key_down_millis;
  67.         bool action_done;
  68. };

  69. #endif
复制代码

使用方法(例)
GPIOButtonTest.ino
  1. #include <Arduino.h>
  2. #include <gpio_button.h>

  3. #define MY_BUTTON_PIN   8

  4. // 定义按钮对象,指定按钮的GPIO口
  5. GpioButton myButton(MY_BUTTON_PIN);

  6. void setup() {
  7.   Serial.begin(9600);
  8.   Serial.println("Start.");
  9.   
  10.   // 初始化板载LED信号灯
  11.   pinMode(LED_BUILTIN, OUTPUT);
  12.   
  13.   // 绑定按钮事件处理
  14.   myButton.BindBtnPress([](){
  15.     Serial.println("Led Button Press Event\r\n\tSetup Bind Event.");
  16.     digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  17.   });

  18.   // 绑定长按事件处理(长按判定为1500ms)
  19.   myButton.BindBtnLongPress([](){
  20.     Serial.println("Led Button Long Press Event\r\n\tSetup Bind Event.");
  21.     digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  22.   }, 1500);
  23. }

  24. void loop() {
  25.   // 调用按键轮询处理
  26.   myButton.loop();
  27. }
复制代码
同样可以先在代码中写好事件处理函数(void onButtonClick()),在setup中用myButton.BindBtnPress(onButtonClick)的形式绑定回调函数。
绑定长按事件的时候可以指定时间,上例中持续按下1.5s后执行长按事件。
不绑定任何回调事件的时候按键不会执行任何处理,也不会导致系统崩溃。

有兴趣的兄弟可以完善一下,让这个类能够实现双击功能。

已做消抖处理,使用的时候无需考虑硬件消抖,按键一只脚接地,另一只脚直接接GPIO口。
对象初始化时会自动将该端口设定为内部上拉方式。

打赏

参与人数 2家元 +30 收起 理由
家睦 + 20
人艰不拆了 + 10 謝謝分享

查看全部打赏

 楼主| 发表于 2020-2-19 13:27:58 | 显示全部楼层
头文件修改了一下,见顶楼红字
回复 支持 反对

使用道具 举报

发表于 2020-2-19 16:32:57 | 显示全部楼层
:victory::handshake:很棒,可以写类了。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-6-30 13:56:22 | 显示全部楼层
已实现单击,双击,长按3种事件同时处理
已经放到GitHub上共享
https://github.com/jjbboox/GpioKeyEvent

GpioKeyEvent.h
  1. #ifndef _GPIO_BUTTON_H_
  2. #define _GPIO_BUTTON_H_
  3. #include <Arduino.h>

  4. #define        DEF_ELIMINATING_JITTER_MS        20                // default eliminating jitter ms
  5. #define DEF_LONG_PRESS_WAIT_MS  1000                // default long press wait ms
  6. #define DEF_DB_PRESS_MS 300

  7. class GpioButton {
  8.     public:
  9.         GpioButton(uint8_t gpio_pin, void(*btn_press_event)()=nullptr) :
  10.             GpioPin(gpio_pin),
  11.             ButtonPressEvent(btn_press_event),
  12.             LongPressWaitMS(DEF_LONG_PRESS_WAIT_MS),
  13.             ButtonLongPressEvent(nullptr),
  14.             first_key_down_millis(0),
  15.             first_key_up_millis(0),
  16.             action_done(false),
  17.             last_gpio_state(HIGH) {
  18.                 pinMode(GpioPin, INPUT_PULLUP);
  19.                 digitalWrite(GpioPin, HIGH);
  20.         };
  21.         // bind click event CB function
  22.         void BindBtnPress(void(*btn_press_event)()) {
  23.                 ButtonPressEvent = btn_press_event;
  24.         };
  25.         // bind long key press CB function
  26.         bool BindBtnLongPress(void(*btn_long_press_event)(), uint16_t wait_ms=DEF_LONG_PRESS_WAIT_MS) {
  27.             if(wait_ms < DEF_LONG_PRESS_WAIT_MS) return false;
  28.             ButtonLongPressEvent = btn_long_press_event;
  29.             LongPressWaitMS = wait_ms;
  30.             return true;
  31.         };
  32.         // bind double click CB function
  33.         void BindBtnDblPress(void(*btn_dbl_press_event)()) {
  34.             ButtonDblPressEvent = btn_dbl_press_event;
  35.         };
  36.         // loop function
  37.         void loop(){
  38.             
  39.             uint8_t current_gpio_state = digitalRead(GpioPin);
  40.             uint32_t current_millis = millis();
  41.             
  42.             // gpio status no change
  43.             if(current_gpio_state == last_gpio_state) {
  44.                 if(current_gpio_state == LOW) {
  45.                     if(first_key_down_millis && !first_key_up_millis && (current_millis - first_key_down_millis > LongPressWaitMS)) {
  46.                         if(!action_done && ButtonLongPressEvent != nullptr) {
  47.                             ButtonLongPressEvent();
  48.                             action_done = true;
  49.                         }
  50.                     }
  51.                 }
  52.                 else {
  53.                     if(first_key_up_millis && (current_millis - first_key_up_millis > DEF_DB_PRESS_MS)) {
  54.                         if(!action_done && ButtonPressEvent != nullptr) {
  55.                             // Serial.println("Debug:Press Event.");
  56.                             ButtonPressEvent();
  57.                             action_done = true;
  58.                         }
  59.                     }
  60.                 }
  61.             }
  62.             // gpio status changed
  63.             else {
  64.                 if(current_millis - last_jitter_millis > DEF_ELIMINATING_JITTER_MS) {
  65.                     // key down
  66.                     if(current_gpio_state == LOW) {
  67.                         // is first keydown in cycle
  68.                         if(0 == first_key_down_millis) {
  69.                             first_key_down_millis = current_millis;
  70.                             first_key_up_millis = 0;
  71.                             action_done = false;
  72.                         }
  73.                         // is not first key down in cycle
  74.                         else {
  75.                             // has define double click CB function
  76.                             if(nullptr != ButtonDblPressEvent){
  77.                                 // key down mill - last key up mill > elimination jitter interval
  78.                                 if(        0 != first_key_up_millis // is release key in event cycle
  79.                                     && (current_millis - first_key_up_millis) > DEF_ELIMINATING_JITTER_MS) {        // skip eliminating jitter
  80.                                     // is double click?
  81.                                     if(        false == action_done // did in event cycle?
  82.                                         && current_millis - first_key_up_millis < DEF_DB_PRESS_MS) {        // and 2nd click is in interval
  83.                                         // call double click event function
  84.                                         // Serial.println("Debug:Double Press Event.");
  85.                                         ButtonDblPressEvent();
  86.                                         action_done = true;
  87.                                     }
  88.                                     
  89.                                 }
  90.                             }
  91.                         }
  92.                     }
  93.                     // key up
  94.                     else {
  95.                         if(!action_done && first_key_down_millis && first_key_up_millis == 0) {
  96.                             first_key_up_millis = current_millis;
  97.                         }
  98.                     }
  99.                     // Keep gpio status
  100.                     last_gpio_state = current_gpio_state;
  101.                     last_jitter_millis = current_millis;
  102.                 }
  103.             }
  104.             
  105.             if(action_done && current_gpio_state == HIGH) {
  106.                 // Serial.println("Event Reset.");
  107.                 first_key_down_millis = 0;
  108.                 first_key_up_millis = 0;
  109.                 action_done = false;
  110.             }
  111.         };
  112.     protected:
  113.         uint8_t GpioPin;                                                // gpio pin of key
  114.         void (*ButtonPressEvent)();       // Click Event CB function
  115.         uint16_t LongPressWaitMS;                            // Long press ms
  116.         void (*ButtonLongPressEvent)();                // Long press Event CB function
  117.         void (*ButtonDblPressEvent)();                // Double click Event CB function
  118.         uint32_t first_key_down_millis;
  119.         uint32_t first_key_up_millis;
  120.         bool action_done;
  121.         uint8_t last_gpio_state;
  122.         uint32_t last_jitter_millis;
  123. };

  124. #endif
复制代码

打赏

参与人数 1家元 +10 收起 理由
cao57508 + 10 優秀文章

查看全部打赏

回复 支持 1 反对 0

使用道具 举报

发表于 2020-7-3 13:02:28 | 显示全部楼层
感谢分享

ARDUINO 早就有了模拟鼠标、键盘、摇杆的扩展产品
可能和LZ的思路不谋而合?


回复 支持 反对

使用道具 举报

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

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-10 16:52 , Processed in 0.670801 second(s), 15 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

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