0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

ESP32 電容式觸摸感測器完整教學:touchRead、觸摸中斷、Deep Sleep 喚醒與滑桿實作

2026 年 6 月 18 日 8點熱度 0人點贊 0條評論

ESP32 電容式觸摸感測器

ESP32 內建 10 通道電容式觸摸感測器(Capacitive Touch Sensor),透過測量 RC 震盪電路的放電次數來偵測觸摸。這項功能在 IoT 裝置中非常實用,可用於觸控按鈕、滑桿、近距離感測、水位偵測,以及 Deep Sleep 觸摸喚醒等場景。

與傳統機械按鈕比較

特性 電容式觸摸 機械按鈕
使用壽命 無限(無實體磨損) 約 5~50 萬次
防水防塵 可穿透塑膠/玻璃外殼 需開孔,易進水
電路設計 僅需一根訊號線 需上拉電阻 + 去彈跳電容
成本 內建,零成本 元件成本 0.1~1 元
功耗 最低約 10 uA(觸摸喚醒) 需輪詢,功耗較高

工作原理

ESP32 觸摸感測器採用 RC 震盪充放電法,原理如下:

  1. 觸摸腳位內部整合一個恆流源,對寄生電容(Cp)充電
  2. 當電壓達到閾值時,觸發放電(透過內部開關快速放電)
  3. 充放電次數由 RTC 計數器(RTC_CNT)計數
  4. 同樣時間內,觸摸(手指增加寄生電容 Cfinger)會使充電速度變慢,計數值降低
  5. 比對計數值與閾值,判斷是否觸摸

RC 充放電與脈衝計數

關鍵關係式:

充電時間 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):設定觸摸中斷(非阻塞)

觸摸腳位對應:

ESP32 觸摸感測器腳位對應

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。

觸摸喚醒 Deep Sleep 時序

// 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()
  • 隔空感測:大面積電極 + 適應性濾波 + 高靈敏度
標籤: 教學
最後更新:2026 年 6 月 18 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang