NodeMCU(ESP8266)外部中断实现按键单击-双击-长按功能

2021-12-18 更新

B站网友 柳桥风起 分享了一个开源库使用效果更佳,OneButton 这个库功能更齐全,可直接使用,我个人分享的还存在bug,看看就好了,这里也贴出一段个人写的demo代码,当然更推荐的是到github上看原作者代码说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

#include <OneButton.h>

OneButton btn = OneButton(D3, false, false);

// 记录按键按下时间
uint32_t clicktime = 0;

/**
* 处理单击
*/
static void singleClick() {
Serial.println("按键单击");
}

/**
* 处理双击
*/
static void doubleClick() {
Serial.println("按键双击");
}

/**
* 按键长按开始做的事情
*/
static void longClickStart() {
Serial.println("按键长按开始");
clicktime = millis();
}

/**
* 处理按键长按
*/
static void longClick() {
Serial.println("按键长按结束");
Serial.println("按键按下时间:");
// 这里为啥加1000? 因为按键长按开始时间时按下一秒后开始计算的,所以就要加上我们原本已经按下的一秒种
Serial.print(millis()-clicktime+1000);
// 重置按下时间
clicktime = 0;
}

void setup() {
Serial.begin(115200);
// 添加单击事件函数
btn.attachClick(singleClick);
// 添加双击事件函数
btn.attachDoubleClick(doubleClick);
// 添加长按事件函数
btn.attachLongPressStop(longClick);
// 添加按下事件函数
btn.attachLongPressStart(longClickStart);
}

void loop() {
btn.tick();
}

背景

很多时候我们的设备就只有一个按键,但是我们需要的功能却比较多,所以就会围绕一个按键实现多种交互功能,单击,双击,长按可能就是最常见的几种交互了,所以我就想着用nodeMcu(esp8266)也搞一个出来,中途遇到很多的问题,为此便写下这篇笔记记录下来,分享给大家!

效果演示

实现方式

如果只是要实现按键单击功能是比较简单的,只需要读取对应的GPIO的电平信号即可,但是如果我们要实现案件双击,长按此时单纯靠读取电平信号则无法解决此问题。需要使用外部中断来处理按键的状态值。 大概思路就是根据按键按下的时间,和按键回弹的时间,来判断按键是否是第一次按压和按压两次或者长按(这里我也不想不出好的文字来描述此过程,大家看代码就懂了的)!

实现功能

  1. 单击切换LED显示状态
  2. 双击切换LED显示模式 (模式1:亮/灭 、模式2:闪烁/常亮)
  3. 长按超过3秒重启系统

电路图及原理

电路图和原理部分我直接搬运此文章的 ESP8266-12F 中断 ,有兴趣欢迎到原作者处查阅,我只是一个搬运工记录一下。

电路图

这里的电阻10K左右即可,大到20多K也没问题,切勿放一个小电阻,形成短路主板无法正常工作

外部中断

基于ESP8266的NodeMcu的数字IO的中断功能是通过attachInterrupt,detachInterrupt函数所支持的。
除了D0/GPIO16,中断可以绑定到任意GPIO的引脚上【D0-D10】。
所支持的标准中断类型有:

  • CHANGE(改变沿,电平从低到高或者从高到低)
  • RISING(上升沿,电平从低到高)
  • FALLING(下降沿,电平从高到低)

attachInterrupt(pin, function, mode); 设置触发中断的引脚

  • pin:要设置中断编号,注意,这里不是引脚编号
  • function:中断发生时运行的函数, 这个函数不带任何参数,不返回任何内容
  • Interrupt type/mode:它定义中断被触发的条件方式
    • CHANGE:改变沿,引脚电平从低变为高或者从高变为低时触发中断。
    • RISING:上升沿,引脚电平从低变为高时触发中断。
    • FALLING:下降沿,引脚电平从高变为低时触发中断
  • 返回值: 无

detachInterrupt(pin); 取消指定引脚的中断

  • pin:中断号
  • 返回值: 无

digitalPinToInterrupt(pin);获取指定引脚的中断号

  • pin:要获取中断号的GPIO引脚
  • 返回值: 中断号

引脚对应的中断号:

  • D1 -> 5
  • D2 -> 4
  • D4 -> 2
  • D5 -> 14
  • D6 -> 12
  • D7 -> 13
  • D8 -> 15

代码

代码部分参考自CSDN文章Arduino 触摸按键:实现单击,双击,长按功能,稳定无抖动。
感觉作者的代码分享,欢迎查阅原作者代码分享,我只是搬运了一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

#include <ESP8266WiFi.h>

// 按键设置在D8(gpio15)引脚
int touchPin = D2;
// 模式:0:LED亮和灭 1. LED常亮或闪烁 2. 重启系统
int mode = 0;
// 模式0:亮/灭 模式1: 常亮/闪烁
bool isshow = false;
// 按键按下去的时间 按键按起来的时间 第一次按按键的时间
long touchDownTime = 0, touchUpTime = 0, firstTouchTime = 0;
// 是否单击 是否双击
bool isOne = 0, isDouble = 0;
// 按键状态 0:无任何操作,1:单机 2: 双击 3:长按
int touchStatus = 0;

/**
* 获取功能菜单
*/
void powerMode()
{
// 单击判断逻辑
if (isOne && millis() - firstTouchTime > 150)
{
isDouble = 0;
isOne = 0;
touchStatus = 1;
}
if (touchStatus == 1)
{
isshow = (isshow == 0) ? 1 : 0;
}
else if (touchStatus == 2)
{
mode = (mode == 0) ? 1 : 0;
}
// 如果是长按,且按下到按起时间超过五秒,则重启系统
else if (touchStatus == 3 && (touchUpTime - touchDownTime) >= 3000)
{
mode = 3;
}
// 重置按键状态
touchStatus = 0;
}

/**
* LED闪烁
*/
void lightning()
{
// 每三百毫秒闪一次LED
digitalWrite(LED_BUILTIN, HIGH);
delay(300);
digitalWrite(LED_BUILTIN, LOW);
delay(300);
}

/**
* 显示模式0
*/
void showMode0()
{
digitalWrite(LED_BUILTIN, isshow ? LOW : HIGH);
}

/**
* 显示模式1
*/
void showMode1()
{
if (isshow == 1)
{
lightning();
}
else
{
digitalWrite(LED_BUILTIN, LOW);
}
}

void setup()
{
//打开串口
Serial.begin(119200);
// 使用板载的LED灯来显示
pinMode(LED_BUILTIN, OUTPUT);
// 设置按键中断(上升沿,引脚电平从低变为高时触发中断。)
attachInterrupt(digitalPinToInterrupt(touchPin), touchDownInterrupt, RISING);
Serial.println("system is start");
}

void loop()
{
powerMode();
if (mode == 0)
{
// 模式0
showMode0();
}
else if (mode == 1)
{
// 模式1
showMode1();
}
else if (mode == 3)
{
// 重启系统
ESP.restart();
}
}

/**
* 按键按下去的中断
*
*/
ICACHE_RAM_ATTR void touchDownInterrupt()
{
// 如果已经按下过一次,且第二次按下的时间不超过150ms则表示双击
if (isOne && millis() - firstTouchTime <= 150)
{
isOne = 0;
isDouble = 1;
touchStatus = 2;
}
// 记录按下的时间
touchDownTime = millis();
attachInterrupt(digitalPinToInterrupt(touchPin), touchUpInterrupt, FALLING);
}

/**
* 按键弹起来的中断
*
*/
ICACHE_RAM_ATTR void touchUpInterrupt()
{
// 记录按键按起时间
touchUpTime = millis();
// 按键按下到按起时间超过700毫秒则表示处于长按状态
if ((touchUpTime - touchDownTime) > 700)
{
touchStatus = 3;
}
else if (isDouble)
{
isDouble = 0;
}
else
{
isOne = 1;
firstTouchTime = millis();
}
attachInterrupt(digitalPinToInterrupt(touchPin), touchDownInterrupt, RISING);
}

参考文章

Arduino 触摸按键:实现单击,双击,长按功能,稳定无抖动。
ESP8266-12F 中断