当前位置:网站首页>ESP32定时中断实现单、双击、长按等功能的按键状态机Arduino代码
ESP32定时中断实现单、双击、长按等功能的按键状态机Arduino代码
2022-07-25 09:24:00 【悟渔】
下载: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谢谢阅读,欢迎建议。
边栏推荐
- 无线振弦采集仪应用工程安全监测
- Swift creates weather app
- Customize dialog to realize the pop-up box of privacy clause statement imitating Netease cloud music
- First knowledge of opencv4.x ---- mean filtering
- 1094--谷歌的招聘
- Mlx90640 infrared thermal imager temperature measurement module development notes (4)
- 单目深度估计自监督模型Featdepth解读(上)——论文理解和核心源码分析
- ARMV8 datasheet学习
- 【深度学习模型部署】使用TensorFlow Serving + Tornado部署深度学习模型
- ECO简介
猜你喜欢

Mixed supervision for surface-defect detection: from weakly to fully supervised learning:表面缺陷检测的混合监督

Connection and data reading of hand-held vibrating wire vh501tc collector sensor

Mlx90640 infrared thermal imager temperature measurement module development instructions

Swift creates weather app

First knowledge of opencv4.x --- drawing shapes on images

Minkowskiengine installation

How to import a large amount of data in MATLAB

工程监测无线中继采集仪和无线网络的优势

matlab如何导入大量数据

Matlab drawing | some common settings of axis
随机推荐
SOC芯片内部结构
ARM预备知识
About student management system (registration, login, student side)
matlab的find()函数的一些用法(快速查找符合条件的值)
CDA Level1知识点总结之业务数据分析
Chmod and chown invalidate the files of the mounted partition
【深度学习模型部署】使用TensorFlow Serving + Tornado部署深度学习模型
VS无线振弦采集仪蓝牙功能的使用
Use kotlin use to simplify file reading and writing
Binary Cross Entropy真的适合多标签分类吗?
Solve the Chinese garbled code error of qtcreator compiling with vs
C语言基础
Creation of adjacency matrix of undirected connected graph output breadth depth traversal
File -- first acquaintance
TensorFlow2 安装快速避坑汇总
FPGA基础进阶
手持振弦VH501TC采集仪传感器的连接与数据读取
Introducing MLOps 解读(一)
FLASH read / write operation and flash upload file of esp8266
十进制整数转换为其它进制的数