ESP32定时中断实现单、双击、长按等功能的按键状态机Arduino代码

下载:ESP32定时中断实现单击、双击、长按等功能的按键状态机Arduino代码。-嵌入式文档类资源-CSDN下载

一、问题的提出。

按键处理一直都是嵌入系统必须要做的事情,而在很多实时要求较高的系统里面,采用传统延时防抖读取按键的过程容易产生阻塞。会使系统来不及处理其他事务。如显示扫描,串行接收,WIFI通信等,都是需要及时获得CPU控制权的,如果按键长时间阻塞,就会影响到这些事务的处理。

二、实现原理。

本人使用定时中断按键状态机原理。封装一个CLASS,用于ESP32控制 VS1053解码的播放器器中,以实现单击(切换下一曲);双击(切换上一曲);三连击(功能切换);长按(启动WIFI配网过程)。现将此按键状态机处理代码分享给大家,以期对大家有所帮助。

其基本原理是定时器每间隔一定时间(1毫秒)查询按键状态,并采用计数方法判断按键在指定时间内按下和弹起的次数,存入键值缓冲序列。需要按键处理的过程查询键值序列,依次处理按键输入。

下面以图形方式讲解这一处理过程的原理:

首先设置按键计时变量,当按键首次按下时(t0时刻)开始计时(计数,也就是中断次数)。此时计时初值为0,键值为0。然后每次中断读取按键状态,同时判断计时值。中断过程在每次按键弹起时键值加1,如果计时t1时刻没有到来,此值表示连续按键次数。计时超过t1,如果按键处于弹起状态,则将键值存入缓冲序列,处理过程结束,等待下一次按键处理,如图1所示。

如果t1时刻后按键还处于按下状态,如图2,则一至等待按键弹起(t2时刻到来之前),按键弹起,则将键值存入缓冲序列,处理过程结束,等待下一次按键处理。

如果t2时刻到来,按键还没有弹起,如图3,则当作“长按”状态处理(至到按键弹起)。缓冲序列最后一个键值被赋予值0xFF(255)。

需要按键处理的过程调用键值读取过程,此过程依次读取缓冲序列,返回每一个键值。如果所有缓冲序列都读取完毕,则判断当前是否处于长按状态,长按状态返回0xFF,否则返回0。即返回值为0表示没有按键,返回0xFF表示正处于长按过程,返回其他值表示一次连续按压的次数(单击、双击、三连击等)。

三、程序代码。

本程序采用Arduino软件编写完成,共两个文件,加了详细中文注释,方便阅读。文件KEYS.C为模块文件,按键处理机封装为一个Class对象,供主程序调用。KEYS.ino为Arduino主程序文件。(完整代码文后提供,如果你懒得复制,也可文前链接下载)

下面说一下KEYS.C文件:

begin()用于初始化,指定按键引脚GPIO编号,指定使用的硬件定时器,然后SetSystemTimer()设置并启动定时器。定时器会在指定的时间内(这里暂定为1毫秒)调用中断程序Timer_CallBack().在中断处理程序中完成按键状态机的读取和保存功能。相应的处理结果保存于缓冲序列Buffer[]中。模块提供一个getKey()函数,外部过程每次调用该过程获取按键序列用于处理。这里要特别说明的是,中断处理程序必须是static申明,其中使用到的外部变量必须是全局变量,为了增强程序的可读性和可移植性,将全部变量放入一个结构体中。这样,可以最大限度避免在复杂程序中发生变量重复申明。

主程序#include “KEYS.C”,再申明操作对象:ESP32_KEYS keyobj,然后在setup()过程里初始化对象: keyobj.begin(0,true);这里提供两个参数,第一个参数表示按键引脚IO编号,这里使用的是GPIO0,第二个参数表示使用哪一个硬件定时器,这里true表示使用定时器1。在一个具体的应用系统中,可根据实际情况选用相应的按键引脚和硬件定时器。

作为实验,本程序只是在loop()过程里不断调用对象的getKey()函数获取键值,从串口输出按键信息。调试信息类似下图:

完整代码如下:

主程序文件KEYS.ino:

#include "KEYS.C"
ESP32_KEYS keyobj;
void setup(){
  Serial.begin(115200);
  keyobj.begin(0,true);//使用GPIO0,定时器1,初始化按键状态机。
}
void loop() {
  uint8_t key;
  key=keyobj.getKey();
  if(key){Serial.printf("\r\n键值:【%d】----",key);}
  switch(key){
  case 0://没有按键按下。
      break;
    case 1://单击。
      Serial.print("单击");
      break;
    case 2://双击。
      Serial.print("双击");
      break;
    case 3://三连击。
      Serial.print("三连击");
      break;
    case 255://长按。
      Serial.print("长按");
      break;
    default://表示在一次按键过程中连续点按了多少次。
      Serial.print("多次连按");
      break;
  }
  delay(50);
}

模块文件KEYS.C:

#ifndef ESP32_TIMER_KEYS_H  //避免重复定义.
  #define ESP32_TIMER_KEYS_H 
  typedef struct ESP32_TIMER_KEYS_RAM{
    uint8_t KeyIO=0;//按键IO。
    boolean TimerIndex=0;//定时器选择。(false--定时器0;true--定时器1)
    boolean PreLevel=0;//上次引脚电平。
    boolean PreKey=0;//上次按键状态。(true--按下;false--弹起)
    uint8_t KeysNow=0;//当前按键键值。
    uint32_t TIMERONE=500;//单次按键时长上限。t1
    uint32_t TIMERLONG=1500;//长按状态时长下限。t2
    uint32_t TimerNow=0;//本次按键计时。
    uint8_t BufferCount=20;//缓冲大小。
    uint8_t BufferWriteIndex=0;//缓冲保存指针。
    uint8_t BufferReadIndex=0;//缓冲读取指针。
    uint8_t Buffer[20];//按键缓冲。
  };
  static ESP32_TIMER_KEYS_RAM KeysObj;
class ESP32_KEYS{
private:
protected:
  static void Timer_CallBack(void){
    boolean IO_State_Now;
    KeysObj.TimerNow++;//计时(每一次中断1毫秒。)
    IO_State_Now=!digitalRead(KeysObj.KeyIO);//按下为0,弹起为1。取反适应习惯用法。
    if(IO_State_Now!=KeysObj.PreLevel){KeysObj.PreLevel=IO_State_Now;return;}//按键防抖。
    if(IO_State_Now==KeysObj.PreKey){//按键状态没有改变。
      if(IO_State_Now==0){
        if(KeysObj.TimerNow>=KeysObj.TIMERONE){
          if(KeysObj.KeysNow>0){
            KeysObj.Buffer[KeysObj.BufferWriteIndex]=KeysObj.KeysNow;
            KeysObj.BufferWriteIndex=(KeysObj.BufferWriteIndex+1)%KeysObj.BufferCount;
            if(KeysObj.BufferWriteIndex==KeysObj.BufferReadIndex){
              KeysObj.Buffer[KeysObj.BufferWriteIndex]=0;KeysObj.BufferReadIndex=(KeysObj.BufferReadIndex+1)%KeysObj.BufferCount;}//缓冲区溢出。
            }
            KeysObj.KeysNow=0;
            KeysObj.TimerNow=KeysObj.TIMERONE;
          }
          return;
        }//按键弹起状态。
        if(KeysObj.TimerNow<KeysObj.TIMERLONG){return;}//非超长按。
        //这里处理长按。
        KeysObj.TimerNow=KeysObj.TIMERLONG;
        KeysObj.KeysNow=255;
        return;
      }//按键没有变化。
      //下面代码处理按键变化情况。
      KeysObj.PreKey=IO_State_Now;
      if(IO_State_Now==1){
        //按键按下。
        if(KeysObj.KeysNow==0){KeysObj.TimerNow=0;}
      }else{
        //按键弹起。
        if(KeysObj.TimerNow<KeysObj.TIMERONE){
          KeysObj.KeysNow++;
        }else{
          if(KeysObj.TimerNow<KeysObj.TIMERLONG){
            KeysObj.KeysNow++;
            KeysObj.Buffer[KeysObj.BufferWriteIndex]=KeysObj.KeysNow;
            KeysObj.BufferWriteIndex=(KeysObj.BufferWriteIndex+1)%KeysObj.BufferCount;
            if(KeysObj.BufferWriteIndex==KeysObj.BufferReadIndex){KeysObj.Buffer[KeysObj.BufferWriteIndex]=0;KeysObj.BufferReadIndex=(KeysObj.BufferReadIndex+1)%KeysObj.BufferCount;}//缓冲区溢出。
          }
          KeysObj.KeysNow=0;
        }
      }
    }//定时中断程序。
    void SetSystemTimer(boolean TimerIndex){
      //设置硬件定时器中断。
      if(TimerIndex>0){TimerIndex=1;}//false使用硬件定时器0;true使用硬件定时器1。
      hw_timer_t *timerObj=NULL;
      timerObj=timerBegin(TimerIndex,80,true);//使用指定硬件定时器,预分频80,向上计数。
      timerAttachInterrupt(timerObj,Timer_CallBack,true);//设置回调函数,边延触发。
      timerAlarmWrite(timerObj,1000,true);//设定中断计数器值,自动重启计数(循环定时)。
      timerAlarmEnable(timerObj);//开启定时器1。
    }
  public:
    ESP32_KEYS(){KeysObj.KeyIO=0;KeysObj.TimerIndex=0;}
    void begin(void){pinMode(KeysObj.KeyIO, INPUT);SetSystemTimer(KeysObj.TimerIndex);}
    void begin(uint8_t key_io){KeysObj.KeyIO=key_io;begin();}
    void begin(boolean TimerIndex){KeysObj.TimerIndex=TimerIndex;begin();}
    void begin(uint8_t key_io,boolean TimerIndex){KeysObj.KeyIO=key_io;KeysObj.TimerIndex=TimerIndex;begin();}
    void begin(boolean TimerIndex,uint8_t key_io){KeysObj.KeyIO=key_io;KeysObj.TimerIndex=TimerIndex;begin();}
    uint8_t getKey(void){
      uint8_t TemKey;
      if(KeysObj.BufferWriteIndex==KeysObj.BufferReadIndex){
        if(KeysObj.KeysNow==255){
          return 255;
        }else{
            return 0;
        }
      }/*返回0表示所有按键序列已经全部处理;返回255表示当前按键处于”长按“状态。*/
      TemKey=KeysObj.Buffer[KeysObj.BufferReadIndex];
      KeysObj.BufferReadIndex=(KeysObj.BufferReadIndex+1)%KeysObj.BufferCount;
      return TemKey;
    }
};
#endif

谢谢阅读,欢迎建议。

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟渔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值