ESP32 实战教程:电容触摸按键与深度睡眠唤醒

机械按键需要开孔、会磨损,还会发出“啪”的一声;电容触摸片只需要一块铜箔,就能做出隐藏式按键、门板灯和桌面控制器。更有意思的是,ESP32 可以在深度睡眠时等待触摸,触碰之后再醒来。本文不止讲 touchRead(),还会讲基线、阈值、湿度变化和“为什么没人碰它却自己醒了”。

电容触摸与深度睡眠唤醒

一、电容触摸到底测的是什么

触摸引脚和人体、铜箔之间形成一个很小的电容。Touch 外设通过充放电计数来估算这个电容,手指靠近后,电场发生变化,计数值也会改变。不同芯片的数值方向可能不同,经典 ESP32 常见“触摸后读数下降”,但不能把这个经验硬套到所有 SoC。

铜箔越大,灵敏度通常越高;线越长,越容易收集环境噪声。想做一个稳定按键,铜箔下面不要铺完整地平面,触摸线尽量短,远离高频时钟和 Wi-Fi 天线。面包板上用一根 30 cm 杜邦线也能实验,但它更像一根会收听广播的天线。

二、不同 ESP32 的 Touch 能力不完全一样

Touch 外设并非每个 ESP32 变体都有,Touch V1 与 Touch V2 的 API 和唤醒限制也不同。ESP32-S2/S3 的深睡触摸唤醒通常只支持一个触摸焊盘;经典 ESP32 的可用触摸 GPIO 则不同。选板子时先查数据手册和 Arduino-ESP32 Touch 文档,再决定 GPIO,不要只看网上一张通用引脚表。

常见测试代码如下:

#include <Arduino.h>

const uint8_t TOUCH_PIN = T7; // 具体编号请按芯片/开发板修改
uint16_t baseline = 0;
uint16_t threshold = 0;

uint16_t readAverage(uint8_t pin, uint8_t times = 32) {
  uint32_t sum = 0;
  for (uint8_t i = 0; i < times; ++i) {
    sum += touchRead(pin);
    delay(3);
  }
  return sum / times;
}

void setup() {
  Serial.begin(115200);
  delay(500);
  baseline = readAverage(TOUCH_PIN);
  threshold = baseline * 70 / 100; // 经典 ESP32 的示例方向
  Serial.printf("baseline=%u threshold=%u\\n", baseline, threshold);
}

void loop() {
  uint16_t value = touchRead(TOUCH_PIN);
  bool pressed = value < threshold;
  Serial.printf("touch=%u pressed=%d\\n", value, pressed);
  delay(100);
}

这里的 70% 只是演示,不是万能参数。实际阈值应该来自多次采样,并留出温度、湿度和安装外壳的余量。阈值过近,手指稍微偏一点就失效;阈值过宽,空气湿度变化都可能被当成触摸。

三、基线校准比阈值更重要

稳定的触摸产品会持续维护一个缓慢变化的基线。没有触摸时,使用低通滤波更新基线;检测到触摸时暂停基线更新,否则手指按住几秒,系统会慢慢把“手指”学习成新的空气状态。

float base = 0;
bool touched = false;

bool readTouchState(uint8_t pin) {
  float now = touchRead(pin);
  if (base == 0) base = now;
  float threshold = base * 0.78f;
  bool next = now < threshold;
  if (!next) base = base * 0.995f + now * 0.005f;
  if (next && !touched) Serial.println("TOUCH DOWN");
  if (!next && touched) Serial.println("TOUCH UP");
  touched = next;
  return next;
}

对于 Touch V2 或某些板卡,触摸后数值方向可能相反,应把比较符号和比例改成现场测量结果。建议开机打印 20 秒原始值,记录没人碰、手指轻触、手指按住三个区间,再决定阈值。

四、深度睡眠唤醒

触摸设备的启动、校准与唤醒流程

设备做成电池供电时,最理想的行为是:平时几乎不耗电,触摸后亮灯或执行一次动作,完成后再次睡眠。Arduino-ESP32 提供 touchSleepWakeUpEnable(),但可用引脚和阈值形式与芯片有关。

#include "esp_sleep.h"

void enterTouchSleep(uint8_t pin, touch_value_t wakeThreshold) {
  touchSleepWakeUpEnable(pin, wakeThreshold);
  Serial.flush();
  esp_deep_sleep_start();
}

void setup() {
  Serial.begin(115200);
  esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
  if (cause == ESP_SLEEP_WAKEUP_TOUCHPAD) {
    Serial.println("wakeup by touch");
    // 在这里执行灯光、继电器或一次网络请求
    delay(800);
  }
  uint16_t base = readAverage(T7);
  enterTouchSleep(T7, (touch_value_t)(base * 0.70f));
}

void loop() {}

进入深睡前要先完成阈值设置和必要的外设关闭。唤醒后程序会从 setup() 重新开始,不是从 loop() 接着执行,所以要通过 esp_sleep_get_wakeup_cause() 判断这次启动的原因。把它理解成“电脑从关机启动”,而不是“暂停后继续”,很多逻辑就不会写反。

五、为什么会误触发

误触发来源很多:电源噪声、手指靠近但没有碰到、外壳吸湿、温度变化、触摸线旁边的 PWM 和 LCD 时钟。可以采取几种策略:

  1. 连续多次超过阈值才确认触摸;
  2. 触摸确认后设置冷却时间,避免一次触摸重复执行;
  3. 通过长时间采样更新基线,但触摸期间暂停;
  4. 让铜箔和地之间保持合适距离,必要时用屏蔽环;
  5. 电池设备把触摸唤醒和 RTC 定时唤醒结合,定期重新校准。

机械按键可以靠硬件上拉和 RC 滤波消抖,电容触摸更多依赖环境建模。好的触摸按键不是“越灵敏越好”,而是无触摸时安静,有意触摸时明确。

六、触摸滑条和多按键

多个触摸焊盘可以组成音量加减、菜单导航或简单滑条。每个通道都要有自己的基线和阈值,不能用一个全局百分比硬套。滑条还需要比较相邻通道的相对变化,计算手指中心位置;如果只看绝对值,手指大小和环境湿度会让位置漂移。

多按键同时触摸时,人体会影响整个电场,可能出现“按 A,B 也略微变化”。这时使用相对变化最大的通道,并增加最小差值;不要把所有超过阈值的通道都当作独立按下。

七、低功耗设计清单

  • 深睡前关闭 Wi-Fi、BLE、显示和不需要的传感器;
  • 让外接 LED、分压电阻和 USB 转串口不再持续耗电;
  • 用万用表或电流计实测,而不是只看芯片数据手册的典型值;
  • 唤醒后先做本地动作,再决定是否开启 Wi-Fi,避免每次触摸都等待联网;
  • 记录唤醒原因和耗时,发现设备频繁自醒时能找到线索。

电池设备最常见的失败不是睡不下去,而是“看起来进了深睡,旁边的模块还在吃电”。把外围供电、指示灯和开发板稳压器一起算进去,结果才接近用户真正拿到手的续航。

八、总结

电容触摸把一块铜箔变成了输入设备,深度睡眠又把它变成低功耗产品。这个项目练到的不只是一个 API,而是校准、阈值、状态机和唤醒原因这几类通用思路。下一步可以做门板灯、桌面控制器、无机械按键的音乐盒,或者把触摸片贴到 3D 打印外壳里,做一个“看不见按钮但知道该按哪里”的小装置。

如果外壳使用木材、亚克力或 3D 打印材料,触摸片可以藏在材料后面,但厚度会影响灵敏度。建议先做三块不同面积的铜箔,分别测量裸露、覆盖 1 mm 和覆盖 3 mm 的数值区间,再决定最终尺寸。这个过程没有一张万能尺寸表,因为外壳、线长、接地和使用者都会参与到电容系统里。

调试时最好把原始值、基线和阈值同时画成串口曲线。看到三条曲线后,很多偶尔失灵都会变得具体:可能是基线缓慢漂移,也可能是 Wi-Fi 发射瞬间造成尖峰,还可能是手指离开后恢复太慢。

参考资料

打赏

如果这篇学习记录对你有帮助,欢迎扫码支持后续整理。

打赏码

打赏支持:王庆岩

posted on 2026-09-30 10:18  潇洒石头  阅读(20)  评论(0)    收藏  举报

导航