Arduino ESP32 电容触摸传感器 API 完全指南:touchRead、触摸中断与深度睡眠唤醒
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
本指南以 Arduino-ESP32 核心的 TOUCH 官方文档(docs/en/api/touch.rst)为骨架,系统讲解 ESP32 系列 SoC 电容触摸传感器的工作原理、touchRead()、touchSetCycles()、触摸中断(touchAttachInterrupt系列)与深度睡眠触摸唤醒等全部 API,并结合 esp32-hal-touch.c 源码与 Touch 示例 给出可直接复制的实战代码。阅读完本文,你将掌握如何用触摸引脚替代机械按键、如何校准阈值、如何在触摸时触发中断或唤醒芯片,并理解 ESP32(TOUCH_V1)与 ESP32-S2/S3(TOUCH_V2)两代触摸外设在 API 行为上的差异。
触摸传感器的工作原理
触摸传感器是一种外设(peripheral),它内置振荡器电路,在固定时间窗口内对指定 GPIO 引脚上的充放电循环次数进行测量。正因如此,这类传感器也被称为电容式传感器(capacitive sensors)。
其物理机理是:当手指触碰这些引脚时,手指的电荷会改变与触摸传感器相连的 RC 电路的等效电容,从而使单位时间内的充放电循环次数发生变化。touchRead()返回的就是某个测量周期(meas)内的循环次数。这个计数值的变化量被用来判断"是否发生了触摸"。基于此,这些引脚可以很方便地接入电容触摸焊盘(capacitive pads),用来替代机械按键。
注意:触摸外设并非存在于每一款 SoC 中。例如 ESP32-P4(TOUCH_V3)等芯片具有不同的触摸实现,具体请参考各芯片的数据手册(datasheet)。本仓库的触摸 HAL 还根据 IDF 版本做了新旧两套 API 的区分,详见下文"源码级实现"一节。
通用 TOUCH API
以下 API 在所有支持触摸外设的芯片上(ESP32 / ESP32-S2 / ESP32-S3)均可用,声明位于 esp32-hal-touch.h,实现在 esp32-hal-touch.c。
touchRead():读取触摸传感器数据
每个触摸传感器都有一个计数器,用来统计充放电循环次数。当焊盘被"触摸"时,由于等效电容变大,计数器的值会随之改变,据此即可判断焊盘是否被触摸。
touch_value_t touchRead(uint8_t pin);pin:要读取触摸值的 GPIO 引脚。
函数返回值类型因芯片而异:在 ESP32 上返回uint16_t,在 ESP32-S2/S3 上返回uint32_t。这一点在源码中由SOC_TOUCH_SENSOR_VERSION宏决定:
#if SOC_TOUCH_SENSOR_VERSION == 1 // ESP32 typedef uint16_t touch_value_t; #elif SOC_TOUCH_SENSOR_VERSION == 2 // ESP32S2 ESP32S3 typedef uint32_t touch_value_t; #endif读数方向性:ESP32 上触摸时读数接近 0(值下降),而 ESP32-S2/S3 上触摸时读数升高。这一点决定了中断阈值的判断方向,也是 V1/V2 两代 API 行为差异的根源。
以经典 ESP32 DevKit 为例,触摸引脚与 GPIO 的对应关系定义在 variants/esp32/pins_arduino.h:
static const uint8_t T0 = 4; static const uint8_t T1 = 0; static const uint8_t T2 = 2; static const uint8_t T3 = 15; static const uint8_t T4 = 13; static const uint8_t T5 = 12; static const uint8_t T6 = 14; static const uint8_t T7 = 27; static const uint8_t T8 = 33; static const uint8_t T9 = 32;即 ESP32 的 10 个触摸通道(TOUCH0~TOUCH9)依次映射到 GPIO 4、0、2、15、13、12、14、27、33、32。不同开发板的映射可能不同,请以对应 variant 目录下的pins_arduino.h为准。
touchSetCycles():设置测量与休眠周期
该函数用于设置一次测量操作所花费的周期数。touchRead的结果、阈值判定以及检测精度都依赖于这两个值。默认配置下touchRead大约耗时 0.5 ms。
void touchSetCycles(uint16_t measure, uint16_t sleep);measure:设置测量触摸传感器数值所花费的时间。sleep:设置下一次测量周期开始前的等待时间。
源码中 ESP32 的默认值为0x1000(measure)与0x1000(sleep),ESP32-S2/S3 则使用 IDF 的TOUCH_PAD_MEASURE_CYCLE_DEFAULT与TOUCH_PAD_SLEEP_CYCLE_DEFAULT:
#if SOC_TOUCH_SENSOR_VERSION == 1 // ESP32 static uint16_t __touchSleepCycles = 0x1000; static uint16_t __touchMeasureCycles = 0x1000; #elif SOC_TOUCH_SENSOR_VERSION == 2 // ESP32S2, ESP32S3 static uint16_t __touchSleepCycles = TOUCH_PAD_SLEEP_CYCLE_DEFAULT; static uint16_t __touchMeasureCycles = TOUCH_PAD_MEASURE_CYCLE_DEFAULT; #endif底层实现分别调用 IDF 的touch_pad_set_measurement_clock_cycles()(ESP32,设置测量时钟周期)或touch_pad_set_charge_discharge_times()(S2/S3,设置充放电次数),再统一调用touch_pad_set_measurement_interval()设置测量间隔(见 esp32-hal-touch.c)。
触摸中断:touchAttachInterrupt / touchAttachInterruptArg
这两个函数用于为触摸焊盘挂载中断回调。回调函数会在以下条件下被调用:ESP32 上触摸值低于给定阈值时,或ESP32-S2/S3 上触摸值高于给定阈值时。要确定"触摸"与"未触摸"状态之间的合适阈值,请先使用touchRead()实测读数。
void touchAttachInterrupt(uint8_t pin, void (*userFunc)(void), touch_value_t threshold); void touchAttachInterruptArg(uint8_t pin, void (*userFunc)(void*), void *arg, touch_value_t threshold);参数说明:
pin:GPIO 触摸焊盘引脚。userFunc:中断触发时要调用的函数。touchAttachInterruptArg版本中,ISR 回调内可以访问传入的参数。arg(仅touchAttachInterruptArg):传递给中断回调的参数。threshold:触发中断的阈值。
在源码实现中,两个函数最终都汇聚到__touchConfigInterrupt():若userFunc为NULL则视为解绑(将阈值设为TOUCH_PAD_THRESHOLD_MAX以停用 ISR),否则先完成触摸模块与通道初始化,再记录回调指针、callWithArgs标志和参数,最后调用touch_pad_set_thresh()写入阈值(见 esp32-hal-touch.c)。内部 ISR__touchISR在触发后会逐位检查触摸通道状态并分发到对应的用户回调(见 esp32-hal-touch.c)。
touchDetachInterrupt():解绑触摸中断
用于将触摸中断从触摸焊盘上解绑:
void touchDetachInterrupt(uint8_t pin);pin:GPIO 触摸焊盘引脚。
实现上等价于调用touchAttachInterrupt(pin, NULL, 0),通过把回调置空、阈值置为TOUCH_PAD_THRESHOLD_MAX来停用该通道的 ISR(见 esp32-hal-touch.c)。
touchSleepWakeUpEnable():深度睡眠触摸唤醒
该函数用于将触摸焊盘配置为深度睡眠唤醒源:
void touchSleepWakeUpEnable(uint8_t pin, touch_value_t threshold);pin:GPIO 触摸焊盘引脚。threshold:触发唤醒的阈值。
注意:ESP32-S2 与 ESP32-S3只支持一个用于睡眠唤醒的触摸焊盘。
源码中,该函数会通过 periman 完成引脚总线注册与触摸模块初始化,然后按芯片分支处理(见 esp32-hal-touch.c):
- ESP32:调用
touch_pad_set_thresh()设置阈值; - ESP32-S2/S3:调用
touch_pad_sleep_channel_enable(pad, true)使能睡眠通道,再调用touch_pad_sleep_set_threshold()设置唤醒阈值;
最后统一调用esp_sleep_enable_touchpad_wakeup()使能触摸唤醒。在新版 API(IDF ≥ 5.5)中,该函数同时支持深度睡眠与浅睡眠(light sleep),且浅睡眠下所有已使用的触摸焊盘都能唤醒芯片。
芯片专属 API
ESP32 专属(TOUCH_V1):touchInterruptSetThresholdDirection()
该函数用于告诉驱动:当传感器值低于还是高于阈值时激活中断。默认是"低于"(lower)。
void touchInterruptSetThresholdDirection(bool mustbeLower);mustbeLower:为true时在传感器值低于阈值时触发,为false时在高于阈值时触发。
底层分别映射到 IDF 的touch_pad_set_trigger_mode(TOUCH_TRIGGER_BELOW)与TOUCH_TRIGGER_ABOVE(见 esp32-hal-touch.c)。由于 ESP32 触摸读数在触摸时下降,默认的"低于阈值触发"正好匹配触摸事件。
ESP32-S2 / ESP32-S3 专属(TOUCH_V2):touchInterruptGetLastStatus()
该函数用于获取触摸焊盘最新的 ISR 状态:
bool touchInterruptGetLastStatus(uint8_t pin);如果触摸焊盘已被按下且持续处于按下状态则返回true,否则返回false。
该函数可以配合 ISR 用户回调使用,以便在触摸焊盘被按下和释放的瞬间分别采取动作——例如把触摸模拟成"按下 / 松开"的按钮。源码在 V2 的 ISR 中根据TOUCH_PAD_INTR_MASK_ACTIVE(按下)与TOUCH_PAD_INTR_MASK_INACTIVE(释放)两种中断掩码实时维护lastStatusIsPressed标志(见 esp32-hal-touch.c),touchInterruptGetLastStatus()只是读取该标志并返回。
阈值校准与基准值:touchSetDefaultThreshold()
在示例中可以看到另一个与阈值相关的 API——touchSetDefaultThreshold(float percentage)。当向touchAttachInterrupt传入的threshold为0时,驱动会使用基准值(benchmark)的百分比作为默认阈值,默认百分比为 1.5%,可通过该函数修改:
// 将默认阈值设为基准值的 5%;仅在 threshold = 0 时生效 touchSetDefaultThreshold(5);其头文件声明位于 esp32-hal-touch-ng.h(新版 API 中),适合在不知道绝对读数、希望以相对变化量判断触摸时使用。
源码级实现细节
理解 HAL 层实现有助于在真实项目中规避坑点:
- periman 引脚管理:每次
touchRead()、中断挂载或唤醒配置时,HAL 都会先通过perimanGetPinBus()检查引脚是否已被占用;若未注册,则调用perimanSetPinBus()以ESP32_BUS_TYPE_TOUCH类型登记该引脚,并在全部触摸通道释放后自动touch_pad_deinit()(见 esp32-hal-touch.c)。这意味着触摸引脚与 GPIO 其他功能(如 ADC、PWM)的互斥是自动管理的。 - 模块初始化:
__touchInit()完成touch_pad_init()、电压设置(ESP32 为TOUCH_HVOLT_2V7 / TOUCH_LVOLT_0V5 / TOUCH_HVOLT_ATTEN_0V,S2/S3 使用TOUCH_PAD_HIGH_VOLTAGE_THRESHOLD等宏)、FSM 定时模式启动,以及 ESP32 上的touch_pad_filter_start(10)滤波启动(见 esp32-hal-touch.c)。 - 弱符号别名:公开 API 通过
__attribute__((weak, alias(...)))导出,允许用户代码覆盖默认实现(见 esp32-hal-touch.c)。 - 新老两套 API:当 IDF 版本 ≥ 5.5 或触摸外设版本为 3(如 ESP32-P4)时,编译走 esp32-hal-touch-ng.c 与 esp32-hal-touch-ng.h 这条路径(
touchSetCycles变为touchSetTiming(float measure, uint32_t sleep),并新增touchSetConfig()调节充放电电压/次数),否则使用本指南所述的经典 API。
完整示例应用
仓库 libraries/ESP32/examples/Touch 下提供了三个可直接编译运行的示例,下面完整列出。
示例一:TouchRead —— 读取触摸值
最简单的触摸测试程序,每秒打印一次 T2(GPIO 2)的读数。触摸该引脚时观察数值变化,即可判断触摸行为:
// 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); }运行要点:先在不触摸的状态下记录读数,再用手触摸 T2 引脚对比读数变化;在 ESP32 上触摸时读数会明显下降(接近 0),据此可为中断示例挑选合适的阈值。
示例二:TouchInterrupt —— 触摸中断
演示如何为两个触摸焊盘(T2、T3)挂载中断回调,并利用touchSetDefaultThreshold(5)把默认阈值设为基准值的 5%:
/* 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"); } }注意示例头部注释的要点:阈值越大,触摸越灵敏(指 ESP32-S2/S3 方向;ESP32 上读数方向相反)。ISR 回调中只置标志位,实际处理放在loop()中,避免在中断上下文做耗时操作。该示例的 CI 配置见 TouchInterrupt/ci.yml。
示例三:TouchButton —— 模拟按键按下/释放
该示例基于 V2 专属 APItouchInterruptGetLastStatus(),把触摸行为模拟成"按下 / 松开"的按键,仅在 ESP32-S2 / ESP32-S3 上适用:
/* This is an example how to use Touch Intrrerupts The sketch will tell when it is touched and then released as like a push-button This method based on touchInterruptGetLastStatus() */ #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); //Set the touch pads Serial.println("\n ESP32 Touch Interrupt Test\n"); touchAttachInterrupt(T1, gotTouch1, threshold); touchAttachInterrupt(T2, gotTouch2, threshold); } void loop() { if (touch1detected) { touch1detected = false; if (touchInterruptGetLastStatus(T1)) { Serial.println(" --- T1 Touched"); } else { Serial.println(" --- T1 Released"); } } if (touch2detected) { touch2detected = false; if (touchInterruptGetLastStatus(T2)) { Serial.println(" --- T2 Touched"); } else { Serial.println(" --- T2 Released"); } } delay(80); }使用注意事项与最佳实践
- 先校准再设阈值:
touchRead()是挑选阈值的第一手依据。分别记录"未触摸"与"触摸"状态下的读数,取中间值作为touchAttachInterrupt的threshold;或直接传入0使用touchSetDefaultThreshold()的基准值百分比方案。 - 注意读数的方向性:ESP32 触摸读数下降、ESP32-S2/S3 读数上升,判断"阈值是否触发"时方向相反;跨芯片移植代码时务必重新校准。
- 睡眠唤醒限制:ESP32-S2/S3 仅支持一个触摸唤醒焊盘;新版 API 中浅睡眠(light sleep)下所有已启用焊盘均可唤醒。
- 引脚资源互斥:触摸引脚由 periman 统一管理,与 ADC、GPIO 等总线互斥;若引脚已被其他外设占用,触摸初始化会失败或返回 0,可通过
log_e输出排查。 - ISR 保持轻量:中断回调内只置标志位或做极简操作,复杂逻辑放到
loop();需要向回调传参时使用touchAttachInterruptArg。 - 版本差异:IDF ≥ 5.5 或 TOUCH_V3(如 ESP32-P4)芯片走新的 esp32-hal-touch-ng API,
touchSetCycles已更名为touchSetTiming,请以实际编译使用的核心版本为准。
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考