ESP32电容触摸实操:touchRead做无接触按键

2026-11-13 20:18:38 35阅读 所属项目:ESP32 开发板

ESP32开发板接杜邦线做触摸演示

给按钮留位置是很多自制项目的老大难:外壳要开孔、机械按键用久了会磨损,偶尔还接触不良。其实ESP32芯片里就自带一套电容触摸外设,GPIO上接一根杜邦线,露出一小截金属头,就能当按键用——手指靠近甚至不用按下去,读数就有反应。这篇把touchRead的用法、阈值判断,再到中断和深睡唤醒一次理顺,API细节逐条对照过乐鑫官方文档和现行core源码,老教程里容易踩的坑也标了出来。

先认引脚:T0-T9和GPIO的对应关系

原版ESP32一共有10个电容触摸引脚,编号T0到T9。这些引脚能感知任何带电荷物体的靠近,比如人的皮肤,所以最适合替代机械按键,还能在深度睡眠时当唤醒源。代码里写T0还是写GPIO数字都可以,两者等价。对照表如下,建议存一份:

触摸编号GPIO触摸编号GPIO
T0GPIO4T5GPIO12
T1GPIO0T6GPIO14
T2GPIO2T7GPIO27
T3GPIO15T8GPIO33
T4GPIO13T9GPIO32

两个提醒。第一,T1对应GPIO0,常见的30脚DevKit板上没有把这个脚引出来,要用T1得选36脚的版本。第二,老教程里常提醒"GPIO32和GPIO33在Arduino IDE里标反了",那是早期版本的已知问题,想用GPIO32得写T8、想用GPIO33得写T9;对照现行core的引脚定义,T8已经是GPIO33、T9是GPIO32,按数据手册的正常映射走了,新装环境的读者不用再绕这个弯。

另外,ESP32-S2和ESP32-S3的触摸外设是第二代,数量增加到14个,编号T1到T14,一一对应GPIO1到GPIO14。数值方向上和原版ESP32不一样,后面细说。贴一张30脚DevKit的引脚总图,触摸引脚在图上有高亮标注,找脚方便:

ESP32 DOIT DEVKIT V1开发板引脚图,触摸引脚高亮标注

touchRead:一个函数读出触摸值

读触摸引脚只要一个函数:touchRead(),参数传GPIO号或T编号都行。它的工作原理是统计一段固定时间里的充放电周期数:手指碰到引脚,人体电容并入电路,这个计数就会变化。下面是乐鑫官方文档里的示例,读T2(GPIO2):

// ESP32 Touch Test
// Just test touch pin - Touch2 is T2 which is on GPIO 2.

#include "Arduino.h"

void setup() {
  Serial.begin(115200);
  delay(1000);  // give me time to bring up serial monitor
  Serial.println("ESP32 Touch Test");
}

void loop() {
  Serial.println(touchRead(T2));  // get value using T2
  delay(1000);
}

烧录后打开串口监视器,每秒打印一个数。原版ESP32上有个关键特性:不碰的时候数值高,手指一碰数值明显下降——电容变大,充放电周期变少了。所以判断触摸的逻辑是"读数低于阈值就算碰到"。这和很多新手的直觉相反,老代码抄错了方向,现象就是"怎么碰都没反应"。

ESP32-S2/S3正好反过来:触摸时数值上升,判断条件是"高于阈值触发",而且返回值类型从16位变成32位,数值量级大得多。乐鑫文档里写明touchRead在ESP32上返回uint16_t、在S2/S3上返回uint32_t,两代芯片硬件测量电路不同。拿着为原版ESP32写的阈值直接搬到S3上,几乎必然失灵,这是新版教程评论区最常见的翻车点。

动手测试:从串口监视器到绘图器

自己试的话,准备一根杜邦线插到GPIO4上,金属端露在外面当触摸面。板子型号和COM口选对,串口监视器波特率设115200,就能看到数值刷屏。第一次测建议什么都别接,先看裸读数:数值在某个区间稳稳跳动,说明外设工作正常,再去折腾触摸面。线越长、触摸面越大,数值变化越明显,但也越容易受环境干扰——USB供电不稳、桌面材质、旁边有没有金属物,都会让基线漂移,所以阈值宁可取得保守一点。下面是读GPIO4的实测输出,手指碰上线的那几行数值肉眼可见地往下掉:

GPIO4触摸读数的串口监视器输出,触摸时数值下降

光看数字不够直观的话,换串口绘图器:关掉监视器,在工具菜单里打开Serial Plotter,触摸值会画成一条曲线,手指一碰就是一个向下的坑,一眼就能看出稳定区间:

串口绘图器画出的触摸值曲线,触摸时出现向下的低谷

想让触摸面更大更好碰,剪一小块铝箔包在杜邦线头上就行,接触面积大了数值变化也更明显。铝箔别贪大,两三厘米见方足够按键用;面积越大,基线漂移和误触发越难伺候,先用小块把逻辑跑通,再按项目外壳的形状慢慢加大。铝箔和线头之间建议用胶带压紧,虚接会让读数时不时跳一下,看起来就像有人乱碰:

杜邦线头部包裹铝箔做成触摸 pad

阈值的确定没有玄学,就靠实测:记下不碰时的正常读数和碰住时的读数,在两者之间取一个值。一篇英文教程里给出的参考是,不碰时读数在70以上,按住铝箔会掉到10以下,于是阈值取20。你的板子、你的线长、你的环境都会影响这两个数,别直接照抄,用绘图器看一眼再定。

实战:触摸点亮LED

读数会看了,接一个输出就成完整项目。要用的东西:ESP32 DOIT DEVKIT V1板、5mm LED一颗、330欧电阻一颗、面包板和几根杜邦线。LED正极经电阻接GPIO16,负极接GND,接线照这张原理图:

触摸点亮LED电路的接线原理图,LED经330欧电阻接GPIO16

完整代码如下,逻辑就是循环里读GPIO4,读数低于阈值20就点亮LED,否则熄灭。阈值那行按自己的实测改:

// set pin numbers
const int touchPin = 4; 
const int ledPin = 16;

// change with your threshold value
const int threshold = 20;
// variable for storing the touch pin value 
int touchValue;

void setup(){
  Serial.begin(115200);
  delay(1000); // give me time to bring up serial monitor
  // initialize the LED pin as an output:
  pinMode (ledPin, OUTPUT);
}

void loop(){
  // read the state of the pushbutton value:
  touchValue = touchRead(touchPin);
  Serial.print(touchValue);
  // check if the touchValue is below the threshold
  // if it is, set ledPin to HIGH
  if(touchValue < threshold){
    // turn LED on
    digitalWrite(ledPin, HIGH);
    Serial.println(" - LED on");
  }
  else{
    // turn LED off
    digitalWrite(ledPin, LOW);
    Serial.println(" - LED off");
  }
  delay(500);
}

烧录后用手按住铝箔,LED亮;松开就灭。这套"读数加阈值"的判断再往前走一步就是无接触触发:不摸引脚,手指隔着薄塑料片甚至靠近到几毫米,读数就会往下掉,灵敏度用阈值微调。做非接触开关、湿度检测(贴片受潮读数变化)、或者面板底下藏一块铝箔当隐藏按键,都是同一个原理:

手指触摸铝箔时LED点亮的实物演示

新手常遇到的三种异常也说一下。读数纹丝不动、恒为0或者恒为满值,多半是引脚号写错了,或者板上那个脚已经被别的外设占用,换一个T编号再试。数值乱跳、阈值形同虚设,先缩短杜邦线,再换一个供电稳定的USB口,最后才怀疑板子本身。LED不亮但读数正常,那是LED极性或者330欧电阻插错了行,触摸部分没问题就别乱动代码。排查顺序永远是从读数到接线,别一上来就重烧固件。

进阶:中断和深睡唤醒,新core的几个变化

轮询要一直占着loop,更优雅的做法是中断:touchAttachInterrupt(引脚, 回调函数, 阈值),读数越过阈值就调用回调。触发方向跟读数方向一致——原版ESP32是低于阈值触发,S2/S3是高于阈值触发。这里顺带澄清一个流传的说法:有文章提到新版core把touchAttachInterrupt弃用了。对照现行core(包括3.x各版本)的头文件esp32-hal-touch.h和官方迁移指南,这个函数没有被弃用,仍然是标准用法;3.x真正的变化是S2/S3读数方向相反、返回类型变宽,以及按芯片代际拆出了两个专属函数:touchInterruptSetThresholdDirection()只有原版ESP32能用,用来指定低于还是高于阈值触发(默认低于);touchInterruptGetLastStatus()只有S2/S3能用,用来在中断后查是哪个脚、是否仍在按住。3.x还支持阈值传0,让驱动用基准值自动判断,默认变化1.5%算触发,可以用touchSetDefaultThreshold()调百分比。官方中断示例:

/*
This is an example how to use Touch Intrrerupts
The bigger the threshold, the more sensible is the touch
*/

#include "Arduino.h"

int threshold = 0;  // if 0 is used, benchmark value is used. Its by default 1,5% change, can be changed by touchSetDefaultThreshold(float percentage)

bool touch1detected = false;
bool touch2detected = false;

void gotTouch1() {
  touch1detected = true;
}

void gotTouch2() {
  touch2detected = true;
}

void setup() {
  Serial.begin(115200);
  delay(1000);  // give me time to bring up serial monitor

  //Optional: Set the threshold to 5% of the benchmark value. Only effective if threshold = 0.
  touchSetDefaultThreshold(5);

  Serial.println("ESP32 Touch Interrupt Test");
  touchAttachInterrupt(T2, gotTouch1, threshold);
  touchAttachInterrupt(T3, gotTouch2, threshold);
}

void loop() {
  if (touch1detected) {
    touch1detected = false;
    Serial.println("Touch 1 detected");
  }
  if (touch2detected) {
    touch2detected = false;
    Serial.println("Touch 2 detected");
  }
}

最后是触摸引脚的招牌用法:深度睡眠唤醒。用touchSleepWakeUpEnable(引脚, 阈值)把某个触摸脚设成唤醒源,设备深睡后手指一碰就醒,做电池供电的低功耗项目很好用。想象一个贴在门边的门铃或者床头小夜灯,平时深睡几乎不耗电,手一伸过去就被唤醒、干活、再睡回去,不用留一个一直待机的机械按键。注意S2/S3只支持一个触摸唤醒脚,设多个会报错。

总结一下:原版ESP32有T0到T9共10个触摸引脚,touchRead读数、不碰值高碰了值降、低于阈值判触摸;S2/S3是T1到T14、方向相反、返回32位。阈值靠实测定,中断用touchAttachInterrupt,深睡唤醒用touchSleepWakeUpEnable。一根线加一块铝箔的成本,就能把项目里的机械按键换掉,值得在下一块板子上试一试。


作者:菜鸟也想玩
链接:https://www.aiddithome.com/p/1619881a1ef3a7.html
本作版权归作者所有,未经允许请勿转载。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
4条评论
板砖搬不动
1楼 · 11小时前

阈值必须现场实测,抄别人的必翻车,我那块板子基线跟教程差了快一倍。

调参到凌晨
2楼 · 11小时前

S3方向反过来这点太真实了,老代码直接搬过去,怎么碰都没反应,还以为板子坏了,后来打印数值才发现是越碰越大。

周五下班搞一搞
3楼 · 11小时前

无接触那玩法可以啊,隔塑料片都能触发,门铃有思路了。

触底反弹的Tony
4楼 · 11小时前

这玩意我去年就玩过,一根杜邦线加锡纸做的门铃,差点忘了T8和T9在老版core里标反的坑,那会儿对着GPIO32写了半天T9死活没反应,查了一晚上才知道是T8。文里把新旧版的映射都列出来了,现在新装环境的人少走弯路,属实功德一件。