前言:不用按鍵,也能讓 ESP32 感應手指
前面的 ESP32 GPIO Input 教學中,如果要讓使用者操作 ESP32,我們通常需要一顆:
Push Button
接到 GPIO:
GPIO
│
Button
│
GND
然後使用:
digitalRead();
判斷按鍵是否按下。
但 ESP32 有一項很值得單獨學習的硬體特色:
Capacitive Touch Sensor
也就是:
電容式觸控感應
使用者甚至不需要真正的機械按鍵,只要準備:
銅箔
金屬片
PCB Touch Pad
導電貼片
金屬螺絲
連接到 ESP32 支援 Touch 的 GPIO,就可以讓手指觸摸產生輸入。
Espressif 官方說明,Touch Sensor 會利用 GPIO Touch Pad 的充放電行為量測等效電容變化;手指接近或碰觸 Pad 時會改變電容,進而改變充放電計數結果。
因此可以做:
觸控式檯燈
觸控式電源開關
Touch Button
RGB Touch Controller
電容式選單
Touch Slider
Touch Wheel
智慧家電面板
隱藏式控制面板
這是傳統 Arduino UNO 本身沒有內建專用 Touch Peripheral 的功能,也因此非常值得作為 ESP32 的獨立一章。
本章將完成:
Capacitive Touch
↓
Touch GPIO
↓
touchRead()
↓
Baseline
↓
Threshold
↓
Touch Detection
↓
抗干擾
↓
短按 / 長按
↓
觸控燈光開關
↓
三鍵 Touch
↓
RGB LED
最後完成兩個專題:
ESP32 觸控式燈光開關
以及進階:
三鍵觸控控制 RGB LED
一、什麼是 Capacitive Touch?
Capacitive Touch:
Capacitive Touch Sensing
中文就是:
電容式觸控感應
它不是像普通按鍵一樣利用:
接點導通
判斷使用者是否按下。
它偵測的是:
電容量改變
基本概念:
Touch Pad
↓
原本具有一定電容
↓
手指接近
↓
人體增加等效電容
↓
充放電特性改變
↓
ESP32 偵測變化
↓
判斷 Touch
Espressif 官方說明,Touch Channel 在量測期間會反覆充電與放電;手指靠近或觸摸時,Touch Channel 的等效電容增加,使充放電行為發生變化。
二、ESP32 Touch Sensor 怎麼知道手指碰到了?
ESP32 Touch Sensor 內部具有振盪與電容量測電路。
簡化理解:
ESP32
↓
Touch Pad 充電
↓
放電
↓
再充電
↓
再放電
↓
計算固定時間內的週期數
Arduino-ESP32 官方說明,touchRead() 所讀取的就是一定量測時間內的 Touch 充放電 Cycle Count;手指造成等效電容變化後,這個數值就會改變。
因此程式並不是單純得到:
HIGH
LOW
而是得到一個:
Touch Value
例如:
未觸摸:
72
74
73
71
74
觸摸:
31
28
26
30
然後程式自己決定:
低於多少
=
Touch
這個判斷值就是:
Touch Threshold
三、ESP32 Touch 和 digitalRead() 有什麼不同?
普通按鍵:
digitalRead(BUTTON_PIN);
通常得到:
HIGH
LOW
Touch:
touchRead(TOUCH_PIN);
取得的是一個量測值。
因此架構不同:
普通按鍵
GPIO
↓
HIGH / LOW
↓
直接判斷
Touch Sensor
Touch GPIO
↓
touchRead()
↓
Touch Value
↓
Threshold
↓
Touched / Released
所以使用 Touch Sensor 最重要的不是只有學會:
touchRead();
而是學會:
如何建立可靠的 Threshold。
四、ESP32 有哪些 Touch GPIO?
本章主要以:
原始 ESP32
例如常見:
ESP32-WROOM-32
ESP32 DevKit
為例。
原始 ESP32 提供 10 個 Capacitive Touch Channel。Espressif 最新 ESP-IDF 文件列出的對應如下:
| Touch Channel | GPIO |
|---|---|
| T0 / CH0 | GPIO4 |
| T1 / CH1 | GPIO0 |
| T2 / CH2 | GPIO2 |
| T3 / CH3 | GPIO15 |
| T4 / CH4 | GPIO13 |
| T5 / CH5 | GPIO12 |
| T6 / CH6 | GPIO14 |
| T7 / CH7 | GPIO27 |
| T8 / CH8 | GPIO33 |
| T9 / CH9 | GPIO32 |
也就是:
GPIO4
GPIO0
GPIO2
GPIO15
GPIO13
GPIO12
GPIO14
GPIO27
GPIO33
GPIO32
都具有原始 ESP32 的 Touch Sensor 功能。
五、不要把所有 ESP32 型號當成一樣
ESP32 現在已經是一整個系列:
ESP32
ESP32-S2
ESP32-S3
ESP32-C3
ESP32-C6
ESP32-H2
ESP32-P4
...
它們的 Touch Peripheral 並不完全相同。
Arduino-ESP32 官方文件特別提醒:
Touch Peripheral 並不是存在於每一種 ESP32 SoC。
所以在使用:
touchRead();
之前,應先確認自己使用的:
ESP32 Chip
+
Development Board
是否具有 Touch 功能。
本章正式實驗以:
原始 ESP32 / ESP32-WROOM-32
為主要平台。
六、ESP32、ESP32-S2、ESP32-S3 的 Touch Value 方向不同
這是非常重要的一點。
Espressif 官方最新 Touch Sensor 文件明確區分:
原始 ESP32:Touch V1
未觸摸
→ 數值較高
觸摸
→ 數值下降
ESP32-S2 / ESP32-S3:Touch V2
未觸摸
→ 數值較低
觸摸
→ 數值上升
所以本章如果看到:
if (touchValue < threshold)
這是針對:
原始 ESP32(補充說明)
的邏輯。
不要直接把完全相同的 Threshold 判斷套到 ESP32-S2 / S3。
七、ESP32 touchRead()
Arduino-ESP32 最基本的 Touch API:
touchRead();
格式:
touch_value_t value =
touchRead(pin);
例如:
touch_value_t touchValue =
touchRead(27);
Arduino-ESP32 官方定義:
touch_value_t touchRead(
uint8_t pin
);
而原始 ESP32 的 Touch Value 使用 16-bit 類型;ESP32-S2 / S3 則使用較大的 32-bit Touch Value。
八、第一個 ESP32 Touch 程式
本章第一個實驗使用:
GPIO27
也就是原始 ESP32:
T7
程式:
const uint8_t TOUCH_PIN = 27;
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println(
"ESP32 Touch Test"
);
}
void loop()
{
touch_value_t touchValue =
touchRead(
TOUCH_PIN
);
Serial.print(
"Touch Value = "
);
Serial.println(
touchValue
);
delay(100);
}
打開:
Serial Monitor
設定:
115200 baud
先不要碰 GPIO27 Touch Pad。
記錄數值。
接著用手指觸摸。
九、觀察 Touch Value
例如可能看到:
沒有觸摸
72
73
71
74
72
73
手指觸摸
35
31
27
29
33
這只是示意。
你的實際數字可能完全不同。
原因包括:
ESP32 個體
Touch Pad 大小
導線長度
手指面積
PCB
桌面材質
電源
USB
環境濕度
外殼
接地
附近電子設備
都可能影響 Touch Reading。
因此:
不要直接複製別人的 Threshold。
十、什麼是 Touch Threshold?
假設:
沒有 Touch ≈ 70
而:
Touch ≈ 30
可以設定:
const int TOUCH_THRESHOLD = 50;
然後:
if (
touchRead(TOUCH_PIN)
< TOUCH_THRESHOLD
)
{
// Touched
}
對原始 ESP32 而言,因為 Touch 時數值下降,因此:
touchValue < threshold
可以視為觸摸。
Arduino-ESP32 官方的 Touch Interrupt API 也是以 Threshold 判斷;官方特別建議先利用 touchRead() 找出 Touched 與 Untouched 的讀值,再建立合適的 Threshold。
十一、Threshold 應該怎麼決定?
不要猜。
比較正確的流程是:
ESP32 開機
↓
先不碰 Touch Pad
↓
讀取 Baseline
↓
用手觸摸
↓
觀察 Touch Value
↓
找出安全分界
↓
設定 Threshold
例如:
Baseline = 75
Touch =
25~40
可以設定:
Threshold = 50
留下一段安全距離。
十二、使用百分比 Threshold 更方便
與其固定:
const int TOUCH_THRESHOLD = 50;
可以根據 Baseline 計算。
例如:
Threshold
=
Baseline × 70%
如果:
Baseline = 80
則:
Threshold =
80 × 0.70
=
56
程式:
touch_value_t threshold =
baseline * 0.70;
對不同硬體會比較容易調整。
但是 70% 只是:
教材中的起始值。
實際產品仍需校準。
十三、自動建立 Touch Baseline
可以在 ESP32 開機時:
讀取 50 次
↓
取平均
↓
建立 Baseline
例如:
touch_value_t getTouchBaseline(
uint8_t pin
)
{
uint32_t total = 0;
const int samples = 50;
for (
int i = 0;
i < samples;
i++
)
{
total += touchRead(pin);
delay(10);
}
return total / samples;
}
然後:
baseline =
getTouchBaseline(
TOUCH_PIN
);
十四、Touch Baseline 完整測試程式
const uint8_t TOUCH_PIN = 27;
touch_value_t baseline;
touch_value_t threshold;
touch_value_t readBaseline(
uint8_t pin
)
{
uint32_t total = 0;
const int samples = 50;
for (
int i = 0;
i < samples;
i++
)
{
total += touchRead(pin);
delay(10);
}
return total / samples;
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println();
Serial.println(
"Do not touch the sensor..."
);
baseline =
readBaseline(
TOUCH_PIN
);
threshold =
baseline * 70 / 100;
Serial.print(
"Baseline = "
);
Serial.println(
baseline
);
Serial.print(
"Threshold = "
);
Serial.println(
threshold
);
}
void loop()
{
touch_value_t value =
touchRead(
TOUCH_PIN
);
Serial.print(
"Touch = "
);
Serial.print(
value
);
if (
value < threshold
)
{
Serial.println(
" TOUCHED"
);
}
else
{
Serial.println(
" RELEASED"
);
}
delay(100);
}
這個版本已經比:
if (touchRead(27) < 40)
更適合作為實際教材。
十五、為什麼開機時不要碰 Touch Pad?
因為:
Baseline
代表:
沒有觸摸時的正常值。
如果開機校準時手正好放在 Touch Pad:
Touch 狀態
↓
被當成 Baseline
後面的 Threshold 就會錯。
因此可以在開機時顯示:
Touch Calibration
Do Not Touch
延遲約 1 秒後再量測。
十六、Touch Sensor 要接什麼?
最簡單的測試甚至可以:
GPIO27
│
杜邦線
│
金屬片
金屬片可以使用:
銅箔膠帶
鋁箔
銅片
PCB Pad
金屬螺絲
導電片
使用者:
手指
↓
Touch Pad
即可改變電容量。
ESP32 Touch Sensor 可以與不同形狀的 Electrode 結合,Espressif 官方也指出多個 Touch Pad 可以組成按鍵、矩陣或 Slider 等不同形式。
十七、Touch Pad 為什麼可以隔著塑膠?
因為 Capacitive Touch 並不是靠:
金屬直接導通
工作。
而是偵測:
Capacitance Change
因此適當設計時,可以把 Electrode 放在:
塑膠板
壓克力
玻璃
外殼
面板
後面。
使用者觸摸表面時仍可能造成足夠的電容量變化。
這也是 Capacitive Touch 很適合:
封閉式外殼
防塵面板
智慧家電
平面控制器
的重要原因。
實際可感應厚度與可靠度仍然取決於 Touch Pad、材料、厚度與完整電極設計。
十八、實作一:ESP32 觸控式燈光開關
現在開始第一個正式專題。
功能:
第一次 Touch
↓
LED ON
第二次 Touch
↓
LED OFF
第三次 Touch
↓
LED ON
相當於:
Touch Toggle Switch
硬體:
Touch Pad → GPIO27
LED → GPIO18
十九、ESP32 觸控燈接線
Touch:
GPIO27
│
│
Copper Pad
LED:
GPIO18
│
220Ω
│
LED +
│
LED -
│
GND
接線表:
| ESP32 | 元件 |
|---|---|
| GPIO27 | Touch Pad / 銅箔 |
| GPIO18 | 220Ω~330Ω → LED |
| GND | LED 負極 |
Touch Electrode 不需要像傳統按鍵那樣:
Touch Pad → GND
Touch GPIO 本身就是感應端。
二十、不能只用「目前正在 Touch」切換 LED
下面這種寫法有問題:
if (
touchRead(TOUCH_PIN)
< threshold
)
{
ledState =
!ledState;
}
因為人的手指一次可能停留:
100 ms
300 ms
500 ms
而 loop() 每秒可能跑很多次。
結果:
Touch
↓
LED ON
↓
LED OFF
↓
LED ON
↓
LED OFF
...
所以我們需要偵測:
Touch Edge
也就是:
Released
↓
第一次變成 Touched
時才執行一次。
二十一、Touch State Detection
先建立:
bool wasTouched = false;
目前:
bool isTouched =
touchValue < threshold;
只有:
isTouched == true
&&
wasTouched == false
代表:
新的一次 Touch
也就是:
Released
↓
Touch Down
這時才切換 LED。
最後:
wasTouched =
isTouched;
更新狀態。
二十二、ESP32 觸控式燈光開關完整程式
/*
第 7 章
ESP32 Touch Sensor
實作:
觸控式燈光開關
Touch:
GPIO27
LED:
GPIO18
適用:
原始 ESP32 / ESP32-WROOM-32
*/
const uint8_t TOUCH_PIN = 27;
const uint8_t LED_PIN = 18;
touch_value_t touchBaseline;
touch_value_t touchThreshold;
bool wasTouched = false;
bool ledState = false;
// -----------------------------
// Baseline Calibration
// -----------------------------
touch_value_t calibrateTouch(
uint8_t pin
)
{
uint32_t total = 0;
const uint8_t samples = 50;
for (
uint8_t i = 0;
i < samples;
i++
)
{
total += touchRead(pin);
delay(10);
}
return total / samples;
}
// -----------------------------
// setup()
// -----------------------------
void setup()
{
Serial.begin(115200);
pinMode(
LED_PIN,
OUTPUT
);
digitalWrite(
LED_PIN,
LOW
);
delay(1000);
Serial.println();
Serial.println(
"ESP32 Touch Light"
);
Serial.println(
"Do not touch the sensor"
);
touchBaseline =
calibrateTouch(
TOUCH_PIN
);
// 70% of baseline
touchThreshold =
touchBaseline
* 70
/ 100;
Serial.print(
"Baseline = "
);
Serial.println(
touchBaseline
);
Serial.print(
"Threshold = "
);
Serial.println(
touchThreshold
);
Serial.println(
"Touch Ready"
);
}
// -----------------------------
// loop()
// -----------------------------
void loop()
{
touch_value_t touchValue =
touchRead(
TOUCH_PIN
);
bool isTouched =
touchValue
< touchThreshold;
// New Touch
if (
isTouched
&&
!wasTouched
)
{
ledState =
!ledState;
digitalWrite(
LED_PIN,
ledState
);
Serial.print(
"Touch -> LED "
);
Serial.println(
ledState
? "ON"
: "OFF"
);
}
wasTouched =
isTouched;
delay(20);
}
本程式使用原始 ESP32 的 Touch V1 行為,也就是 Touch 時數值下降;官方 Arduino Touch API 也建議利用 touchRead() 取得 Touched / Untouched 數值後決定 Threshold。
二十三、為什麼 Touch Sensor 也會誤觸?
Touch 並不是完美的:
Touch / No Touch
數位開關。
它本身是一種:
Analog-like Measurement
所以讀值可能變成:
Baseline = 70
69
71
68
72
66
70
65
如果 Threshold 設得太接近 Baseline:
Threshold = 68
就很容易:
Noise
↓
誤判 Touch
因此 Threshold 必須留下安全 Margin。
二十四、Touch Sensor 會受到哪些因素影響?
實際 Touch 系統可能受到:
電源雜訊
USB 電源
Wi-Fi / RF
Touch Pad 尺寸
Touch Pad 走線
附近金屬
人體接地狀況
外殼材料
濕度
溫度
水滴
PCB Layout
附近訊號線
影響。
Espressif 官方 Touch 文件建議,在設定 Touch Threshold 前實際量測 Touched 與 Released 狀態;如果量測有 Noise,應使用 Filtering,並在實際應用環境中測試溫度與供電電壓變化的影響。
二十五、ESP32 Touch 抗干擾第一招:平均取樣
最簡單的方法就是不要只讀:
touchRead();
一次。
而是讀:
5 次
取平均。
touch_value_t readTouchAverage(
uint8_t pin
)
{
uint32_t total = 0;
const uint8_t samples = 5;
for (
uint8_t i = 0;
i < samples;
i++
)
{
total += touchRead(pin);
}
return total / samples;
}
然後:
touch_value_t value =
readTouchAverage(
TOUCH_PIN
);
這樣可以降低單次異常值對判斷的影響。
Espressif 的底層 Touch Driver 本身也提供 Filtering 機制,並建議當 Touch Reading Noise 較大時進行濾波。
二十六、第二招:不要把 Threshold 設太敏感
例如:
Baseline = 80
Touch = 30
比較合理的 Threshold 可以放在:
50~60
而不是:
78
Threshold 太靠近 Baseline:
Baseline
80
│
78 Threshold
│
Noise
↓
False Touch
而留出 Margin:
Baseline
80
│
│
55 Threshold
│
│
Touch
30
會可靠很多。
二十七、第三招:使用 Hysteresis
這是 Touch 系統非常實用的技巧。
假設:
Touch Threshold = 55
不要 Touch 和 Release 都用同一個數值。
可以設定:
Touch Threshold = 55
Release Threshold = 65
變成:
Value < 55
→ Touch
Value > 65
→ Release
中間:
55~65
保持原本狀態。
這稱為:
Hysteresis
也就是遲滯。
它可以避免:
54
56
55
57
54
在 Threshold 附近來回切換狀態。
二十八、Touch Hysteresis 範例
const int TOUCH_THRESHOLD =
50;
const int RELEASE_THRESHOLD =
60;
bool touched = false;
void loop()
{
int value =
touchRead(
TOUCH_PIN
);
if (!touched)
{
if (
value <
TOUCH_THRESHOLD
)
{
touched = true;
Serial.println(
"Touch"
);
}
}
else
{
if (
value >
RELEASE_THRESHOLD
)
{
touched = false;
Serial.println(
"Release"
);
}
}
}
這種設計會比單純:
value < threshold
更適合實際 Touch Button。
二十九、第四招:Touch Debounce
Touch Sensor 雖然不像機械按鍵有真正金屬 Contact Bounce,但感應值仍可能在臨界點附近波動。
因此也可以加入:
20~50 ms
的穩定確認時間。
例如:
Touch Value 低於 Threshold
↓
持續 30 ms
↓
才判定真正 Touch
這能進一步降低:
瞬間 Noise
↓
False Trigger
三十、第五招:定期更新 Baseline
實際環境可能慢慢變化:
溫度
濕度
電源
外殼
人體環境
如果開機 Baseline:
80
幾小時後自然變成:
70
固定 Threshold 可能開始變得不合理。
進階產品可以:
沒有 Touch 時
↓
緩慢更新 Baseline
↓
Threshold 跟著調整
例如:
baseline =
baseline * 0.99
+
current * 0.01;
但是:
Touch 狀態時不要快速更新 Baseline。
否則手指可能被系統慢慢當成新的「正常狀態」。
三十一、touchSetCycles()
Arduino-ESP32 還提供:
touchSetCycles();
可以設定 Touch Sensor 的量測時間與兩次量測之間的等待時間。
官方說明指出,這些設定會影響:
touchRead()
Threshold
Detection Accuracy
而 Arduino-ESP32 預設的 Touch Reading 大約需要 0.5 ms。
API:
touchSetCycles(
measure,
sleep
);
一般入門實驗不需要修改,
但它代表:
ESP32 Touch Sensor 並不是一個只有 touchRead() 的黑盒子,量測行為本身也可以調整。
三十二、如何判斷短按與長按?
Touch Button 很適合做:
Short Touch
Long Touch
例如:
短按
→ LED ON / OFF
長按
→ 切換模式
基本流程:
Touch Down
↓
記錄開始時間
↓
手指持續 Touch
↓
Release
↓
計算 Touch Duration
程式使用:
millis();
即可。
三十三、短按 / 長按的時間定義
例如:
const unsigned long
LONG_PRESS_TIME = 1000;
代表:
小於 1000 ms
→ Short Touch
大於等於 1000 ms
→ Long Touch
也就是:
Touch Down
↓
0 ms
↓
500 ms
↓
Release
↓
SHORT
Touch Down
↓
0 ms
↓
1000 ms
↓
1500 ms
↓
Release
↓
LONG
三十四、ESP32 Touch 長按 / 短按完整程式
const uint8_t TOUCH_PIN = 27;
touch_value_t baseline;
touch_value_t threshold;
bool wasTouched = false;
unsigned long touchStartTime = 0;
const unsigned long
LONG_PRESS_TIME = 1000;
touch_value_t calibrateTouch()
{
uint32_t total = 0;
for (
int i = 0;
i < 50;
i++
)
{
total +=
touchRead(
TOUCH_PIN
);
delay(10);
}
return total / 50;
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println(
"Do not touch..."
);
baseline =
calibrateTouch();
threshold =
baseline * 70 / 100;
Serial.print(
"Baseline = "
);
Serial.println(
baseline
);
Serial.print(
"Threshold = "
);
Serial.println(
threshold
);
}
void loop()
{
touch_value_t value =
touchRead(
TOUCH_PIN
);
bool isTouched =
value < threshold;
// Touch Down
if (
isTouched
&&
!wasTouched
)
{
touchStartTime =
millis();
}
// Touch Release
if (
!isTouched
&&
wasTouched
)
{
unsigned long
duration =
millis()
- touchStartTime;
if (
duration
>= LONG_PRESS_TIME
)
{
Serial.println(
"LONG TOUCH"
);
}
else
{
Serial.println(
"SHORT TOUCH"
);
}
Serial.print(
"Duration = "
);
Serial.print(
duration
);
Serial.println(
" ms"
);
}
wasTouched =
isTouched;
delay(20);
}
三十五、長按可以拿來做什麼?
例如設計智慧燈:
短按
↓
LED ON / OFF
長按:
LED Brightness
↓
25%
50%
75%
100%
或者:
短按
→ 下一個 RGB 顏色
長按
→ 關閉燈光
因此 Touch Sensor 不只可以取代普通 Button,
還可以建立:
User Interface
三十六、Touch Sensor + PWM
前面已經學過:
第 5 章
ESP32 PWM
因此現在可以組合:
Touch
↓
User Input
↓
ESP32
↓
LEDC PWM
↓
LED Brightness
例如:
Short Touch
→ ON / OFF
Long Touch
→ Brightness
這就是一個真正的:
Touch Dimmer
架構。
三十七、進階實作:三鍵 Touch 控制 RGB LED
接下來做本章第二個重點實作。
使用三個 Touch Sensor:
Touch 1
Touch 2
Touch 3
控制:
Red
Green
Blue
例如:
Touch 1
→ Red ON / OFF
Touch 2
→ Green ON / OFF
Touch 3
→ Blue ON / OFF
三個顏色還可以混合。
例如:
Red + Green
→ Yellow
Green + Blue
→ Cyan
Red + Blue
→ Magenta
Red + Green + Blue
→ White
這樣可以一次練習:
Touch
+
Multiple Input
+
LEDC
+
RGB LED
三十八、三鍵 Touch GPIO 規劃
使用:
Touch Red
GPIO32
Touch Green
GPIO33
Touch Blue
GPIO27
這三個 GPIO 都是原始 ESP32 Touch Channel:
GPIO32 → CH9
GPIO33 → CH8
GPIO27 → CH7
RGB LED:
Red
→ GPIO18
Green
→ GPIO19
Blue
→ GPIO21
三十九、完整接線表
Touch
| 功能 | ESP32 |
|---|---|
| Red Touch | GPIO32 |
| Green Touch | GPIO33 |
| Blue Touch | GPIO27 |
每一個 Touch GPIO:
GPIO
│
銅箔 / 金屬片
RGB LED
本例使用:
Common Cathode RGB LED
| RGB | ESP32 |
|---|---|
| Red | GPIO18 → 電阻 |
| Green | GPIO19 → 電阻 |
| Blue | GPIO21 → 電阻 |
| Common Cathode | GND |
每個 RGB LED Channel 建議各自使用限流電阻。
四十、RGB LED PWM 初始化
使用 Arduino-ESP32 3.x:
ledcAttach(
RED_PIN,
5000,
8
);
ledcAttach(
GREEN_PIN,
5000,
8
);
ledcAttach(
BLUE_PIN,
5000,
8
);
然後:
ledcWrite(
RED_PIN,
red
);
控制亮度。
目前 Arduino-ESP32 LEDC API 的 ledcAttach() 會自動配置 Channel,而且 LEDC 可提供多個獨立 PWM Channel,適合 RGB LED 控制。
四十一、每個 Touch Key 都需要自己的 Baseline
不能假設:
GPIO32 Baseline
=
GPIO33 Baseline
=
GPIO27 Baseline
因為:
Pad 大小
線長
附近導體
GPIO
可能不同。
所以必須分別校準:
Red Baseline
Green Baseline
Blue Baseline
然後各自建立:
Red Threshold
Green Threshold
Blue Threshold
四十二、建立 TouchKey 結構
為了讓程式更整齊,可以建立:
struct TouchKey
{
uint8_t pin;
touch_value_t baseline;
touch_value_t threshold;
bool previousState;
};
建立:
TouchKey redKey;
TouchKey greenKey;
TouchKey blueKey;
這樣未來增加更多 Touch Key 也比較容易管理。
四十三、三鍵 Touch RGB 完整正式版
/*
第 7 章
ESP32 Touch Sensor
進階實作:
三鍵 Touch 控制 RGB LED
適用:
原始 ESP32 / ESP32-WROOM-32
Touch:
Red -> GPIO32
Green -> GPIO33
Blue -> GPIO27
RGB LED:
Red -> GPIO18
Green -> GPIO19
Blue -> GPIO21
RGB Type:
Common Cathode
Arduino-ESP32:
3.x LEDC API
*/
// =====================================
// Touch Key Structure
// =====================================
struct TouchKey
{
uint8_t pin;
touch_value_t baseline;
touch_value_t threshold;
bool previousState;
};
// =====================================
// Touch GPIO
// =====================================
TouchKey redKey =
{
32,
0,
0,
false
};
TouchKey greenKey =
{
33,
0,
0,
false
};
TouchKey blueKey =
{
27,
0,
0,
false
};
// =====================================
// RGB GPIO
// =====================================
const uint8_t RED_PIN = 18;
const uint8_t GREEN_PIN = 19;
const uint8_t BLUE_PIN = 21;
// =====================================
// PWM
// =====================================
const uint32_t PWM_FREQ =
5000;
const uint8_t PWM_RESOLUTION =
8;
const uint8_t PWM_MAX =
255;
// =====================================
// RGB State
// =====================================
bool redState = false;
bool greenState = false;
bool blueState = false;
// =====================================
// Touch Parameters
// =====================================
const uint8_t
THRESHOLD_PERCENT =
70;
const uint8_t
CALIBRATION_SAMPLES =
50;
// =====================================
// Read Touch Average
// =====================================
touch_value_t readTouchAverage(
uint8_t pin,
uint8_t samples = 5
)
{
uint32_t total = 0;
for (
uint8_t i = 0;
i < samples;
i++
)
{
total +=
touchRead(
pin
);
}
return
total / samples;
}
// =====================================
// Calibrate One Touch Key
// =====================================
void calibrateKey(
TouchKey &key
)
{
uint32_t total = 0;
for (
uint8_t i = 0;
i < CALIBRATION_SAMPLES;
i++
)
{
total +=
touchRead(
key.pin
);
delay(10);
}
key.baseline =
total
/ CALIBRATION_SAMPLES;
key.threshold =
key.baseline
* THRESHOLD_PERCENT
/ 100;
key.previousState =
false;
}
// =====================================
// Detect New Touch
// =====================================
bool newTouch(
TouchKey &key
)
{
touch_value_t value =
readTouchAverage(
key.pin
);
bool currentState =
value
< key.threshold;
bool pressed =
currentState
&&
!key.previousState;
key.previousState =
currentState;
return pressed;
}
// =====================================
// Update RGB
// =====================================
void updateRGB()
{
ledcWrite(
RED_PIN,
redState
? PWM_MAX
: 0
);
ledcWrite(
GREEN_PIN,
greenState
? PWM_MAX
: 0
);
ledcWrite(
BLUE_PIN,
blueState
? PWM_MAX
: 0
);
}
// =====================================
// Print RGB State
// =====================================
void printRGBState()
{
Serial.print(
"RGB = "
);
Serial.print(
redState
? "R"
: "-"
);
Serial.print(
greenState
? "G"
: "-"
);
Serial.println(
blueState
? "B"
: "-"
);
}
// =====================================
// setup()
// =====================================
void setup()
{
Serial.begin(
115200
);
delay(
1000
);
Serial.println();
Serial.println(
"ESP32 3-Key Touch RGB"
);
Serial.println(
"Do NOT touch the pads"
);
// -----------------------------
// RGB PWM
// -----------------------------
if (!ledcAttach(
RED_PIN,
PWM_FREQ,
PWM_RESOLUTION))
{
Serial.println(
"RED LEDC failed"
);
}
if (!ledcAttach(
GREEN_PIN,
PWM_FREQ,
PWM_RESOLUTION))
{
Serial.println(
"GREEN LEDC failed"
);
}
if (!ledcAttach(
BLUE_PIN,
PWM_FREQ,
PWM_RESOLUTION))
{
Serial.println(
"BLUE LEDC failed"
);
}
updateRGB();
// -----------------------------
// Touch Calibration
// -----------------------------
calibrateKey(
redKey
);
calibrateKey(
greenKey
);
calibrateKey(
blueKey
);
// -----------------------------
// Display Calibration
// -----------------------------
Serial.print(
"RED Baseline = "
);
Serial.print(
redKey.baseline
);
Serial.print(
" Threshold = "
);
Serial.println(
redKey.threshold
);
Serial.print(
"GREEN Baseline = "
);
Serial.print(
greenKey.baseline
);
Serial.print(
" Threshold = "
);
Serial.println(
greenKey.threshold
);
Serial.print(
"BLUE Baseline = "
);
Serial.print(
blueKey.baseline
);
Serial.print(
" Threshold = "
);
Serial.println(
blueKey.threshold
);
Serial.println(
"Touch Ready"
);
}
// =====================================
// loop()
// =====================================
void loop()
{
// -----------------------------
// RED Touch
// -----------------------------
if (
newTouch(
redKey
)
)
{
redState =
!redState;
updateRGB();
Serial.println(
"RED Touch"
);
printRGBState();
}
// -----------------------------
// GREEN Touch
// -----------------------------
if (
newTouch(
greenKey
)
)
{
greenState =
!greenState;
updateRGB();
Serial.println(
"GREEN Touch"
);
printRGBState();
}
// -----------------------------
// BLUE Touch
// -----------------------------
if (
newTouch(
blueKey
)
)
{
blueState =
!blueState;
updateRGB();
Serial.println(
"BLUE Touch"
);
printRGBState();
}
delay(
20
);
}
四十四、這支 RGB Touch 程式可以產生哪些顏色?
初始:
R = OFF
G = OFF
B = OFF
→ OFF
按 Red:
R = ON
G = OFF
B = OFF
→ RED
再按 Green:
R = ON
G = ON
B = OFF
→ YELLOW
再按 Blue:
R = ON
G = ON
B = ON
→ WHITE
把 Red 關掉:
R = OFF
G = ON
B = ON
→ CYAN
因此三個 Touch Button 可以產生:
OFF
RED
GREEN
BLUE
YELLOW
CYAN
MAGENTA
WHITE
八種組合。
四十五、進階:短按開關、長按調光
三鍵版本還可以再進一步設計:
Touch Red 短按
→ Red ON / OFF
Touch Red 長按
→ Red Brightness 增加
例如:
0
↓
64
↓
128
↓
192
↓
255
↓
0
也就是:
0%
25%
50%
75%
100%
這樣一顆 Touch Button 就同時具有:
Switch
+
Dimmer
功能。
四十六、進階:RGB Touch 控制器的人機介面設計
可以定義:
Touch 1
短按 → 下一種顏色
長按 → Red 調整
Touch 2
短按 → Brightness
長按 → Auto Fade
Touch 3
短按 → ON / OFF
長按 → Rainbow Mode
這樣三顆 Touch Button 就能建立完整:
RGB Lighting HMI
不需要:
機械按鍵
Keypad
旋鈕
四十七、ESP32 Touch Interrupt
除了不斷:
touchRead();
Arduino-ESP32 還提供:
touchAttachInterrupt();
格式:
touchAttachInterrupt(
pin,
userFunc,
threshold
);
當 Touch Reading 符合設定 Threshold 條件時,可以呼叫指定函式。
對原始 ESP32,預設是數值下降低於 Threshold 時觸發;ESP32-S2 / S3 則是 Touch Value 上升超過 Threshold。
四十八、touchAttachInterrupt() 範例
例如:
const uint8_t TOUCH_PIN =
27;
volatile bool
touchEvent =
false;
void ARDUINO_ISR_ATTR
onTouch()
{
touchEvent =
true;
}
void setup()
{
Serial.begin(
115200
);
touchAttachInterrupt(
TOUCH_PIN,
onTouch,
40
);
}
void loop()
{
if (
touchEvent
)
{
touchEvent =
false;
Serial.println(
"Touch Interrupt"
);
}
}
這裡同樣建議:
Touch ISR
↓
只設定 Event Flag
↓
loop()
↓
完成真正工作
不要把大量:
Serial
Wi-Fi
LCD
RGB Animation
全部塞進 ISR。
四十九、Touch Interrupt 的 Threshold 更需要校準
不建議看到別人的:
touchAttachInterrupt(
27,
onTouch,
40
);
就直接把:
40
當標準值。
正確流程:
touchRead()
↓
量測 Released
↓
量測 Touched
↓
決定 Threshold
↓
再設定 Interrupt
Arduino-ESP32 官方 Touch API 本身也明確建議利用 touchRead() 找到 Touched 與 Untouched 之間適合的 Threshold。
五十、touchAttachInterruptArg()
Arduino-ESP32 另外提供:
touchAttachInterruptArg();
格式:
touchAttachInterruptArg(
pin,
userFunc,
arg,
threshold
);
它與普通:
touchAttachInterrupt();
最大的不同是:
可以傳入自訂 Argument。
這對:
多顆 Touch Button
特別方便。
五十一、Touch Sensor 還可以喚醒 ESP32
Arduino-ESP32 目前還提供:
touchSleepWakeUpEnable();
可以設定 Touch Pad 作為 Sleep Wake-up Source。
也就是:
ESP32 Deep Sleep
↓
低功耗等待
↓
使用者 Touch
↓
ESP32 Wake Up
這對:
電池裝置
無線遙控器
智慧家電
Touch Panel
低功耗 IoT
非常有價值。
Arduino-ESP32 官方 Touch API 目前也特別指出,ESP32-S2 / S3 的 Touch Sleep Wake-up 支援限制與原始 ESP32 不完全相同。
五十二、Touch Sensor 適合做 Slider 嗎?
可以。
Espressif 官方說明,多個 Touch Sensing Pad 可以排列成:
Matrix
Slider
等不同形式。
例如:
T1 T2 T3 T4 T5
[ ] [ ] [ ] [ ] [ ]
手指由左向右移動:
T1
↓
T2
↓
T3
↓
T4
↓
T5
就能估算:
Finger Position
進一步做:
Volume Slider
Brightness Slider
Color Slider
Menu Slider
Motor Speed
五十三、Touch Sensor 和 TTP223 有什麼差別?
如果以前使用 Arduino UNO,可能接觸過:
TTP223
這是一顆外部 Capacitive Touch Sensor IC / Module。
它通常把 Touch 事件轉成:
HIGH
LOW
所以 Arduino 只需要:
digitalRead();
ESP32 原生 Touch 則是:
Touch Electrode
↓
ESP32 Touch Peripheral
↓
touchRead()
因此不一定需要額外 TTP223。
簡化比較:
| 功能 | ESP32 Native Touch | TTP223 |
|---|---|---|
| 外部 Touch IC | 不需要 | 需要 |
| ESP32 原生支援 | 是 | 當一般數位輸入模組 |
| 讀值 | Touch Value | 通常 HIGH / LOW |
| Threshold 控制 | 軟體可自行設計 | 多由模組處理 |
| 多點設計 | ESP32 可使用多個 Touch Channel | 通常一模組一鍵 |
| Arduino UNO | 無內建同等 Touch Peripheral | 可使用 |
所以對 ESP32 而言:
原生 Touch Sensor 是一項非常值得利用的特色周邊。
五十四、為什麼某些 Touch GPIO 數值特別不穩?
原因可能包括:
導線太長
Pad 太大
附近有 AC 電源
旁邊有高速數位訊號
USB 電源品質差
Wi-Fi / RF
PCB Layout 不理想
Threshold 太敏感
因此開發時不要只測:
桌上裸板
還應測試最終:
PCB
外殼
供電方式
Wi-Fi 開啟
實際使用者觸摸
環境。
Espressif 官方同樣建議 Threshold 應在實際應用條件下評估 Noise、電源與環境變化。
五十五、抗干擾的實用設計原則
對 Arduino-ESP32 入門專案,可以先採取:
1. Touch Pad 不要過度巨大
2. Touch 線不要不必要地拉很長
3. Touch 線避免貼著高頻訊號
4. 開機建立 Baseline
5. 多次取樣平均
6. Threshold 保留 Margin
7. 使用 Hysteresis
8. 加入 Touch Debounce
9. 實際外殼中重新校準
10. Wi-Fi 開啟狀態也要測試
如果進一步做到商用品質,
則應研究 Espressif 的:
Touch Sensor Driver
Filtering
Denoise
Shield
Waterproof
PCB Layout
等硬體與底層功能。
例如 ESP32-S2 / S3 的新一代 Touch Peripheral 提供更進階的 Filter、Denoise 與防水相關能力。
五十六、常見問題:touchRead() 完全沒反應
第一個要確認:
你的 ESP32 型號有沒有 Touch Peripheral?
Arduino-ESP32 官方明確提醒:
Touch Peripheral 並不存在於所有 ESP32 SoC。
第二個確認:
使用的 GPIO
是不是該 SoC 真正的 Touch GPIO。
不要拿任意 GPIO:
touchRead(18);
就期待一定可以工作。
五十七、常見問題:ESP32 Touch 時數值為什麼變小?
如果使用:
原始 ESP32(補充說明 2)
這是正常的。
官方 Touch Sensor Version 表顯示:
ESP32 Touch V1
↓
Touched
↓
Channel Value Decreases
所以:
value < threshold
代表 Touch。
五十八、常見問題:ESP32-S3 為什麼 Touch 時數值反而變大?
ESP32-S2 / ESP32-S3 使用:
Touch V2
官方說明:
Touch
↓
Channel Value Increases
所以判斷方向與原始 ESP32 相反。
因此:
value < threshold
這種原始 ESP32 程式不能不修改就直接套到 S3。
五十九、常見問題:Threshold 到底應該設定多少?
沒有通用數字。
不要認為:
Threshold = 30
或:
Threshold = 40
適合所有 ESP32。
正確方法:
量 Baseline
↓
量 Touch Value
↓
觀察 Noise
↓
設定安全 Threshold
官方文件也是建議先利用實際 Touch Measurement 找出 Touched 與 Untouched 狀態後再建立 Threshold。
六十、常見問題:為什麼 Touch 開關一次會切換很多次?
通常不是 Touch Sensor 被「按很多次」。
而是程式寫成:
if (touched)
{
ledState =
!ledState;
}
手指停留期間:
loop()
會一直執行。
解決方法是使用:
Edge Detection
只在:
Released
↓
Touched
那一刻處理一次。
也就是:
if (
isTouched
&&
!wasTouched
)
六十一、常見問題:Touch 誤觸很多
依序檢查:
Threshold
↓
Touch Pad
↓
線長
↓
Noise
↓
電源
↓
Filtering
↓
Hysteresis
↓
PCB / 外殼
通常第一個要做的不是:
換 ESP32
而是先把:
Touch Value
連續印到 Serial Monitor,
觀察真正的:
Baseline
Noise Range
Touched Range
再決定 Threshold。
六十二、常見問題:Touch Pad 一定要直接碰金屬嗎?
不一定。
Capacitive Touch 的原理是:
Capacitance Change
因此適當設計下可以隔著:
塑膠
玻璃
壓克力
面板
感應。
但材料厚度增加時,
Touch Signal 可能變小,
所以需要重新調整:
Pad Size
Threshold
Sensitivity
六十三、常見問題:Touch Pad 可以做成很大的金屬片嗎?
可以增大感應面積,但:
越大
不代表:
一定越好
因為更大的 Pad 也會改變:
Base Capacitance
Noise
Sensitivity
Environmental Coupling
因此 Touch Pad 應配合實際:
外殼
PCB
使用者手指
環境
一起設計。
六十四、ESP32 Touch 可以應用在哪些專題?
學完本章後可以做:
1. Touch Light
手指 Touch
↓
LED ON / OFF
2. Touch Dimmer
短按
→ ON / OFF
長按
→ Brightness
3. RGB Touch Controller
Touch R
Touch G
Touch B
↓
RGB Mixing
4. Touch Doorbell
Touch
↓
Buzzer
↓
Wi-Fi Notification
5. Touch Menu
UP
DOWN
ENTER
6. Touch Slider
手指位置
↓
Brightness / Volume
7. Hidden Control Panel
塑膠外殼
↓
隱藏 Touch Electrode
8. Smart Home Panel
Touch
↓
ESP32
↓
Wi-Fi / MQTT
↓
Lighting / Relay
六十五、Touch + PWM + Wi-Fi 可以形成完整智慧燈
目前已經學過:
PWM
Touch
後續加入:
Wi-Fi
就可以變成:
手指 Touch
↓
ESP32 Touch
↓
燈光控制
↓
PWM Brightness
同時
Wi-Fi
↓
Web / MQTT
↓
遠端控制
也就是:
Local Control + Remote Control
這是實際智慧家庭裝置非常常見的架構。
六十六、本章正式實作一覽
基礎實作
ESP32 觸控式燈光開關(補充說明)
硬體:
GPIO27
↓
Touch Pad
GPIO18
↓
LED
流程:
touchRead()
↓
Threshold
↓
New Touch
↓
Toggle
↓
LED ON / OFF
進階實作
三鍵觸控控制 RGB LED(補充說明)
硬體:
GPIO32
→ RED Touch
GPIO33
→ GREEN Touch
GPIO27
→ BLUE Touch
輸出:
GPIO18
→ RGB Red
GPIO19
→ RGB Green
GPIO21
→ RGB Blue
架構:
Touch R ──┐
Touch G ──┼── ESP32 ── LEDC ── RGB LED
Touch B ──┘
六十七、FAQ:ESP32 Touch 常見問題
Q1:ESP32 有內建 Touch Sensor 嗎?
部分 ESP32 SoC 有。
原始 ESP32 具有 10 個 Capacitive Touch Channel,但 Arduino-ESP32 官方提醒 Touch Peripheral 並不是所有 ESP32 SoC 都具備,因此應確認實際晶片 Datasheet。
Q2:ESP32 Touch 使用什麼函式?
最基本:
touchRead(pin);
例如:
touchRead(27);
會取得該 Touch GPIO 的量測值。
Q3:原始 ESP32 有哪些 Touch GPIO?
原始 ESP32 包括:
GPIO4
GPIO0
GPIO2
GPIO15
GPIO13
GPIO12
GPIO14
GPIO27
GPIO33
GPIO32
共 10 個 Touch Channel。
Q4:Touch 時數值會增加還是下降?
這要看 ESP32 型號。
原始 ESP32
→ Touch 時下降
ESP32-S2 / S3
→ Touch 時上升
Q5:ESP32 Touch Threshold 要設多少?
沒有固定答案。
應先:
touchRead();
取得:
Untouched Value
Touched Value
再根據實際硬體設定 Threshold。Arduino-ESP32 官方也建議使用這種方式建立 Threshold。
Q6:Touch Sensor 可以隔著塑膠嗎?
Capacitive Touch 是利用電容量變化,不需要像機械開關一樣金屬直接導通,因此經過適當電極、外殼與 Threshold 設計後,可以製作隔著非導電面板操作的 Touch Interface。
Q7:Touch Sensor 為什麼會誤觸?
常見原因包括:
Noise
Threshold 太敏感
線路太長
Touch Pad 設計
Power Noise
外部環境
Espressif 官方建議在 Noise 較大時使用 Filtering,並根據實際環境重新評估 Touch Threshold。
Q8:ESP32 Touch 可以判斷長按嗎?
可以。
利用:
millis();
記錄:
Touch Down
到:
Release
的 Duration,
就可以區分:
Short Touch
Long Touch
Q9:ESP32 Touch 可以使用 Interrupt 嗎?
可以。
Arduino-ESP32 提供:
touchAttachInterrupt();
以及:
touchAttachInterruptArg();
Q10:Touch 可以喚醒 Deep Sleep 嗎?
支援 Touch Peripheral 的相關 ESP32 SoC 可利用 Touch Wake-up 功能;Arduino-ESP32 提供:
touchSleepWakeUpEnable();
但不同 SoC 的 Wake-up 能力與限制不同,應依所用晶片確認。
六十八、本章學習重點整理
第一個重點:
Capacitive Touch
並不是:
接點導通
而是:
手指
↓
Capacitance Change
↓
Touch Peripheral
↓
Touch Value Change
第二個重點:
最基本 API:
touchRead(
pin
);
第三個重點:
對原始 ESP32:
未 Touch
→ Value 較大
Touch
→ Value 下降
對 ESP32-S2 / S3 則方向相反。
第四個重點:
Threshold 不要抄別人的數字。
應該:
Measure Baseline
↓
Measure Touch
↓
Observe Noise
↓
Choose Threshold
第五個重點:
正式 Touch 系統最好加入:
Average
+
Threshold Margin
+
Edge Detection
+
Hysteresis
+
Debounce
來降低 False Touch。
第六個重點:
Touch 不只是:
ON / OFF
還可以判斷:
Touch Down
Release
Short Touch
Long Touch
Multiple Touch Keys
第七個重點:
多個 Touch GPIO:
Touch R
Touch G
Touch B
搭配:
LEDC PWM
就可以製作:
三鍵 RGB Touch Controller
六十九、本章總結
在第 7 章中,我們正式學習 ESP32 很有代表性的硬體特色:
Capacitive Touch Sensor(補充說明)
它不需要傳統:
Push Button
而可以利用:
Copper Pad
Metal Plate
PCB Electrode
建立觸控式人機介面。
整個工作流程可以整理成:
Finger
↓
Capacitance Change
↓
ESP32 Touch GPIO
↓
touchRead()
↓
Touch Value
↓
Threshold
↓
Touch / Release
↓
Application
Espressif 官方說明,Touch Peripheral 會量測 Touch Channel 在固定時間中的充放電週期,手指所造成的等效電容變化會改變這個測量結果。
本章首先學會:
touchRead();
接著建立:
Baseline
Threshold
再加入:
Average
Hysteresis
Debounce
Edge Detection
提高 Touch 判斷的可靠度。
之後進一步利用:
millis();
區分:
Short Touch
Long Touch
並完成第一個正式實作:
ESP32 觸控式燈光開關(補充說明 2)
架構:
Touch Pad
↓
GPIO27
↓
touchRead()
↓
Threshold
↓
Touch Event
↓
LED ON / OFF
進階則完成:
三鍵觸控控制 RGB LED(補充說明 2)
架構:
GPIO32 Touch ──────┐
GPIO33 Touch ──────┼── ESP32
GPIO27 Touch ──────┘
│
▼
LEDC
│
┌─────────┼─────────┐
▼ ▼ ▼
Red Green Blue
│ │ │
└──────── RGB LED ──┘
因此從前面幾章開始,現在已經逐漸形成一套完整 ESP32 控制架構:
GPIO Output
+
GPIO Input
+
PWM
+
ADC
+
Touch
而 Touch Sensor 最重要的價值,就是讓 ESP32 能從一般:
機械式按鍵控制器
進一步變成:
無機械接點
平面式
隱藏式
智慧家電式
的人機控制介面。
接下來只要再加入:
OLED / LCD
Wi-Fi
Bluetooth
MQTT
Web Server
就可以把本章的三鍵 Touch Controller 發展成真正完整的: