ESP32 電容式觸摸感測器
ESP32 內建 10 通道電容式觸摸感測器(Capacitive Touch Sensor),透過測量 RC 震盪電路的放電次數來偵測觸摸。這項功能在 IoT 裝置中非常實用,可用於觸控按鈕、滑桿、近距離感測、水位偵測,以及 Deep Sleep 觸摸喚醒等場景。
與傳統機械按鈕比較
| 特性 | 電容式觸摸 | 機械按鈕 |
|---|---|---|
| 使用壽命 | 無限(無實體磨損) | 約 5~50 萬次 |
| 防水防塵 | 可穿透塑膠/玻璃外殼 | 需開孔,易進水 |
| 電路設計 | 僅需一根訊號線 | 需上拉電阻 + 去彈跳電容 |
| 成本 | 內建,零成本 | 元件成本 0.1~1 元 |
| 功耗 | 最低約 10 uA(觸摸喚醒) | 需輪詢,功耗較高 |
工作原理
ESP32 觸摸感測器採用 RC 震盪充放電法,原理如下:
- 觸摸腳位內部整合一個恆流源,對寄生電容(Cp)充電
- 當電壓達到閾值時,觸發放電(透過內部開關快速放電)
- 充放電次數由 RTC 計數器(RTC_CNT)計數
- 同樣時間內,觸摸(手指增加寄生電容 Cfinger)會使充電速度變慢,計數值降低
- 比對計數值與閾值,判斷是否觸摸
關鍵關係式:
充電時間 T_charge = R_eq * C_total
C_total = Cp + Cfinger(手指接近時)
無觸摸 → C_total = Cp → 充電快 → 放電次數多 → 數值高(約 500~800)
有觸摸 → C_total = Cp + Cfinger → 充電慢 → 放電次數少 → 數值低(約 200~400)
觸摸感測器 API
ESP32 Arduino 提供了兩組觸摸感測器 API:
- touchRead(pin):直接讀取觸摸感測器原始計數值(阻塞式)
- touchAttachInterrupt(pin, callback, threshold):設定觸摸中斷(非阻塞)
觸摸腳位對應:

Arduino 程式設計
基礎讀取:touchRead()
// ESP32 觸摸感測器基礎讀取
#define TOUCH_PIN T0 // GPIO 4
void setup() {
Serial.begin(115200);
Serial.println("ESP32 Touch Sensor Demo");
Serial.println("將手指碰觸 GPIO 4 觀察數值變化");
}
void loop() {
int touchVal = touchRead(TOUCH_PIN);
Serial.printf("Touch Value: %d
", touchVal);
if (touchVal < 300) {
Serial.println(">> Touch detected! <<");
} else if (touchVal < 450) {
Serial.println("> Proximity detected <");
}
delay(100);
}
觸摸中斷(非阻塞)
// ESP32 觸摸中斷範例
#define TOUCH_PIN T0 // GPIO 4
#define THRESHOLD 300 // 觸摸閾值(小於此值表示觸摸)
#define LED_PIN 2 // 內建 LED
volatile bool touched = false;
void IRAM_ATTR touchISR() {
touched = true;
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// 附加中斷:數值低於 threshold 時觸發
touchAttachInterrupt(TOUCH_PIN, touchISR, THRESHOLD);
Serial.println("Touch interrupt enabled, touch GPIO 4...");
}
void loop() {
if (touched) {
touched = false;
Serial.printf("Touch! Value: %d
", touchRead(TOUCH_PIN));
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // 切換 LED
delay(50); // 簡單去彈跳
}
// 主程式可以做其他工作
delay(10);
}
多重觸摸按鈕
// ESP32 多重觸摸按鈕:T0 (GPIO4), T2 (GPIO2), T3 (GPIO15)
#define TOUCH_BTN1 T0
#define TOUCH_BTN2 T2
#define TOUCH_BTN3 T3
#define THRESHOLD 350
struct TouchButton {
uint8_t pin;
uint8_t touchChannel;
bool state; // 目前是否被觸摸
bool lastState;
unsigned long lastDebounce;
};
TouchButton buttons[3] = {
{TOUCH_BTN1, 0, false, false, 0},
{TOUCH_BTN2, 2, false, false, 0},
{TOUCH_BTN3, 3, false, false, 0},
};
void processTouch(TouchButton* btn) {
int val = touchRead(btn->pin);
bool currentlyTouched = (val < THRESHOLD);
// 去彈跳
if (currentlyTouched != btn->lastState) {
btn->lastDebounce = millis();
}
if ((millis() - btn->lastDebounce) > 50) {
if (currentlyTouched != btn->state) {
btn->state = currentlyTouched;
if (btn->state) {
Serial.printf("Button on T%d pressed! (value=%d)
",
btn->touchChannel, val);
}
}
}
btn->lastState = currentlyTouched;
}
void setup() {
Serial.begin(115200);
Serial.println("Multi-touch button demo");
}
void loop() {
for (int i = 0; i < 3; i++) {
processTouch(&buttons[i]);
}
delay(10);
}
觸摸感測器濾波與適應性基準線
// 適應性觸摸閾值(自動校準基準線)
class AdaptiveTouch {
private:
uint8_t _pin;
int _baseline;
int _threshold;
int _smoothVal;
float _alpha; // 濾波係數 0.0~1.0
public:
AdaptiveTouch(uint8_t pin, int thresholdDelta = 100, float alpha = 0.1) {
_pin = pin;
_thresholdDelta = thresholdDelta;
_alpha = alpha;
_baseline = 0;
_smoothVal = 0;
}
void calibrate(int samples = 20) {
long sum = 0;
for (int i = 0; i < samples; i++) {
sum += touchRead(_pin);
delay(10);
}
_baseline = sum / samples;
_smoothVal = _baseline;
_threshold = _baseline - _thresholdDelta;
Serial.printf("Calibrated: baseline=%d, threshold=%d
",
_baseline, _threshold);
}
bool isTouched() {
int raw = touchRead(_pin);
// 指數移動平均濾波
_smoothVal = _alpha * raw + (1 - _alpha) * _smoothVal;
// 如果不處於觸摸狀態,緩慢更新基準線(適應環境變化)
if (_smoothVal > _baseline - _thresholdDelta/2) {
_baseline = _alpha * _smoothVal + (1 - _alpha) * _baseline;
_threshold = _baseline - _thresholdDelta;
}
return (_smoothVal < _threshold);
}
int getSmooth() { return _smoothVal; }
int getBaseline() { return _baseline; }
};

觸摸中斷時序與狀態機
Deep Sleep 觸摸喚醒
ESP32 最大的優勢之一是在 Deep Sleep 模式下,觸摸感測器仍可以喚醒主 CPU,功耗僅 10~50 uA。
// ESP32 Deep Sleep 觸摸喚醒範例
// 觸摸 GPIO 4 (T0) 喚醒 ESP32
#define TOUCH_WAKE_PIN T0 // GPIO 4
#define WAKE_THRESHOLD 300
void setup() {
Serial.begin(115200);
delay(100);
// 檢查是什麼原因喚醒
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_TOUCH) {
Serial.println("=== 觸摸喚醒!===");
Serial.printf("Touch value: %d
", touchRead(TOUCH_WAKE_PIN));
} else {
Serial.println("=== 正常開機 ===");
// 首次開機,校準觸摸閾值
int baseline = 0;
for (int i = 0; i < 10; i++) {
baseline += touchRead(TOUCH_WAKE_PIN);
delay(10);
}
baseline /= 10;
Serial.printf("Baseline: %d
", baseline);
// 設定觸摸喚醒閾值
touchSleepWakeUpEnable(TOUCH_WAKE_PIN, WAKE_THRESHOLD);
}
// 正常工作 5 秒後進入 Deep Sleep
Serial.println("Active for 5 seconds then sleep...");
delay(5000);
Serial.println("Entering Deep Sleep. Touch GPIO 4 to wake!");
// 進入 Deep Sleep
esp_deep_sleep_start();
}
void loop() {
// 永不執行
}
觸摸喚醒 + 定時喚醒(雙重喚醒源)
// ESP32 雙重喚醒源:觸摸按鈕 或 定時器
#define TOUCH_WAKE_PIN T0
#define WAKE_THRESHOLD 350
#define SLEEP_SECONDS 3600 // 1 小時
void setup() {
Serial.begin(115200);
// 設定觸摸喚醒
touchSleepWakeUpEnable(TOUCH_WAKE_PIN, WAKE_THRESHOLD);
// 設定定時喚醒
esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * 1000000ULL);
// 檢查喚醒源
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_TOUCH) {
Serial.println("觸摸按鈕喚醒 — 執行互動工作");
// 例如:發送 HTTP 請求、顯示畫面
} else if (cause == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("定時喚醒 — 執行定期上報");
// 例如:上傳感測器資料
}
// 工作完成後再次睡眠
Serial.println("Work done, sleeping again...");
esp_deep_sleep_start();
}
觸摸滑桿(Slider)實作
// ESP32 觸摸滑桿:使用 3 個觸摸通道實現線性滑桿
#define TCH_A T0 // GPIO 4
#define TCH_B T2 // GPIO 2
#define TCH_C T3 // GPIO 15
// 滑桿位置插值
float calculateSliderPos(int valA, int valB, int valC) {
// 將觸摸值反轉為「接近度」(越小 = 越接近)
float proxA = max(0, 500 - valA);
float proxB = max(0, 500 - valB);
float proxC = max(0, 500 - valC);
float total = proxA + proxB + proxC;
if (total < 10) return -1.0; // 沒有觸摸
// 加權平均計算位置 (0.0 ~ 1.0)
float pos = (proxA * 0.0 + proxB * 0.5 + proxC * 1.0) / total;
return pos;
}
void setup() {
Serial.begin(115200);
}
void loop() {
int va = touchRead(TCH_A);
int vb = touchRead(TCH_B);
int vc = touchRead(TCH_C);
float pos = calculateSliderPos(va, vb, vc);
if (pos >= 0) {
Serial.printf("Slider: %.2f [A=%d B=%d C=%d]
", pos, va, vb, vc);
}
delay(50);
}
近距離感測(Proximity)
// ESP32 近距離感測:大面積電極(如銅箔)可感測 5~15 cm 內的人手
#define PROX_PIN T0
void setup() {
Serial.begin(115200);
// Proximity 模式不需要特殊設定,只需降低閾值靈敏度
}
void loop() {
int val = touchRead(PROX_PIN);
Serial.printf("Proximity: %d
", val);
if (val < 350) {
Serial.println(">> Hands detected! <<");
} else if (val < 450) {
Serial.println("-> Approaching...");
}
delay(200);
}
ESP-IDF 直接操作
// ESP-IDF 觸摸感測器底層 API
#include "driver/touch_pad.h"
#define TOUCH_PAD T0
void setup() {
// 初始化觸摸 pad
touch_pad_init();
// 設定觸摸通道
touch_pad_config(TOUCH_PAD);
// 設定濾波參數
touch_pad_filter_start(10); // 取樣視窗大小
// 設定中斷閾值
touch_pad_set_thresh(TOUCH_PAD, 300);
touch_pad_isr_register(touchISR, NULL);
touch_pad_intr_enable();
}
void loop() {
uint16_t touchVal;
touch_pad_read_raw_data(TOUCH_PAD, &touchVal);
printf("Touch raw: %u
", touchVal);
uint16_t filteredVal;
touch_pad_read_filtered(TOUCH_PAD, &filteredVal);
printf("Touch filtered: %u
", filteredVal);
}
實務注意事項
- 參考電容:觸摸腳位與 GND 之間可並聯 10pF 電容,減少外部雜訊干擾
- 絕緣層:可穿透 1~5mm 塑膠或玻璃面板(不需要開孔),建議 PCB 銅箔面積 10x10mm 以上
- 防水:表面使用防水塗層(如三防漆),或使用密封外殼
- 環境漂移:溫度、濕度、灰塵會改變基準線,建議使用適應性濾波器定期校準
- 電源雜訊:Wi-Fi 發射時可能干擾觸摸感測器(高功率射頻耦合),可在觸摸腳位加 RC 濾波
- 多通道串擾:相鄰觸摸通道之間建議保留至少 2mm 間距,減少寄生電容耦合
- ESP32-S2/S3:可使用任何 GPIO 作為觸摸通道(最多 14 通道)
應用案例
| 應用 | 電極設計 | 觸摸通道數 | 特殊考量 |
|---|---|---|---|
| 觸控按鈕 | 圓形/方形銅箔 10x10mm | 1 | 去彈跳 + 適應性閾值 |
| 線性滑桿 | 3~5 個梯形電極陣列 | 3~5 | 加權插值計算位置 |
| 圓形滾輪 | 4~8 片扇形電極 | 4~8 | 相位差檢測旋轉方向 |
| 近距離感測 | 大面積銅箔 (30x30mm) | 1 | 靈敏度閾值需調低 |
| 水位偵測 | PCB 平行條狀電極 | 1 | 液體導電性差異 |
| 金屬觸控 (隔空) | 金屬螺絲/彈簧 | 1 | 需透過 10k 電阻隔離 ESD |
總結
ESP32 內建的電容式觸摸感測器是 IoT 互動設計的利器,無需額外 IC 即可實現觸控按鈕、滑桿、近距離感測和超低功耗喚醒。正確的電極設計和適應性濾波演算法是穩定運作的關鍵。
選型參考:
- 簡單觸控開關:1 通道 + touchRead() + 閾值判斷(最簡)
- 多按鈕面板:3~5 通道 + touchAttachInterrupt() + 去彈跳
- 類比式滑桿/滾輪:3~8 通道 + 加權插值
- 低功耗喚醒:1 通道 + Deep Sleep + touchSleepWakeUpEnable()
- 隔空感測:大面積電極 + 適應性濾波 + 高靈敏度
文章評論