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

2021-12-14  本文已影响0人  Lengff

2021-12-18 更新

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


#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】。
所支持的标准中断类型有:

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

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

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

引脚对应的中断号:

代码

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


#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 中断

上一篇下一篇

猜你喜欢

热点阅读