2026 別再用 Delay!用 C++ 封裝「事件驅動 Event Driven」 | ESP32-S3 C++ Tutorial
Event Driven(事件驅動) 是 2026 年嵌入式開發者的必修課。在 ESP32-S3 效能過剩的時代,寫出「會動」的程式不難,但寫出「不阻塞、高效能、易維護」的架構才是真正的挑戰。
回想我們初學嵌入式開發時,總習慣用線性的思考方式:開燈、等待一秒、關燈、再等待一秒。這種 delay(1000) 的思維雖然直觀,卻嚴重限制了 ESP32-S3 這顆雙核、240MHz 強大處理器的潛能。當系統需要同時監控多個感測器、處理網路請求、更新顯示器時,傳統的阻塞式程式設計很快就會陷入「顧此失彼」的混亂。
如果你還在程式碼裡寫 delay(),那這篇文章就是為你準備的。我們將從 C++ 的封裝出發,結合 std::function 與現代 C++ 特性,帶你徹底消滅阻塞邏輯,讓你的硬體控制從「死板的指令」進化為「靈活的事件」,進入專業開發者的全新層次。

內容
為何用封裝事件驅動 Event Driven 控制?
在嵌入式專案中,隨著功能增加,直接在程式裡操作 GPIO、塞滿 delay()、混雜硬體細節,程式很快就會變得混亂且難以維護。
透過 C++ 封裝(Encapsulation) 結合 事件驅動(Event Driven),我們可以把硬體資源轉換成 具備清楚責任、自主行為的物件,讓程式結構清晰,同時徹底消除阻塞。
使用 C++ 事件驅動封裝硬體資源,有以下幾個明顯優點:
- 消除阻塞,釋放 CPU
不再使用delay()空轉,改用硬體計時器(esp_timer)與中斷,CPU 可以同時處理 Wi-Fi、藍牙、感測器等多重任務。 - 降低耦合度
硬體腳位、時序控制與事件回呼都被封裝在 class 內部,外部程式只需訂閱事件,不必關心底層實作細節。 - 提高可讀性與反應性
語意化呼叫,如led.on()與onButtonPress([]{ ... }),比digitalWrite(2, HIGH)與delay(100)更容易理解,且系統能即時回應硬體變化。 - 易於維護與擴充
當硬體腳位變更、加入新感測器,或調整時序時,只需修改 class 內部實作,主程式完全不用動。新增功能只需訂閱新事件即可。 - 符合現代嵌入式 C++ 最佳實務
ESP-IDF 原生支援 FreeRTOS 與事件循環,透過 C++ 物件導向方式管理硬體事件,是成熟且廣泛採用的專業開發模式。
透過這種設計方式,即使專案規模成長,程式碼依然 乾淨、有結構、非阻塞,且容易長期維護。
什麼是 ESP32-S3
ESP32-S3 是 Espressif 發布的最新一代微控制器 MCU,具有以下特點:
- Xtensa LX7 雙核心 CPU
- SIMD 向量指令支援(針對 AI/ML 加速)
- 支援 Wi-Fi、藍牙 LE,適合 Edge AIoT 裝置
- 可執行 ESP-DL / ESP-SR / TensorFlow Lite Micro 等 AI 框架
開發環境
在開始編程之前,請確保已完成以下準備工作:
- 安裝 ESP-IDF 開發環境 (至少版本 v5.x 或更高)。
- ESP32-S3 開發板。
專案結構
假設建立一個 led_abstraction 專案,Arduino ESP32 目錄結構如下:
led_abstraction/
├── src/
│ ├── main.cpp
│ ├── LedController.h
│ └── LedController.cpp
└── platformio.ini
在實際專案中,這類硬體封裝通常屬於 HAL(Hardware Abstraction Layer),可獨立於應用層,形成清晰分層架構。
從「控制腳位」到「控制物件」
新手寫 Arduino/ESP32 常見的寫法是這樣的:
// 傳統阻塞寫法 (Bad Smell)
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(1000); // CPU 停擺,無法處理網路請求
digitalWrite(LED_PIN, LOW);
delay(1000);
}
問題:
- 阻塞 CPU,無法同時處理其他事件
- 每個硬體操作都分散在
loop(),程式混亂
ESP32-S3 C++ 思維:把硬體視為 物件,具有 狀態與行為。
封裝一個非阻塞 LED 控制器
我們來寫一個簡單的 LedController 類別。這個類別將負責處理所有與 LED 相關的底層細節。
在這裡,我們定義介面。使用 constexpr 和 enum class 是現代 C++ 的好習慣。
Header File (LedController.h)
#pragma once
#include <Arduino.h>
class AsyncLed {
public:
AsyncLed(uint8_t pin, bool activeHigh = true);
void begin();
void on();
void off();
void toggle();
void blink(uint32_t interval); // 非阻塞閃爍介面
void update(); // 核心:驅動事件更新
bool isOn() const { return _state; }
private:
uint8_t _pin;
bool _activeHigh;
bool _state;
bool _isBlinking;
uint32_t _interval;
uint32_t _lastTick;
};
這裡實作具體的邏輯。注意我們如何將硬體細節(如 digitalWrite 邏輯)隱藏起來。
Implementation File (LedController.cpp)
#include "LedController.h"
AsyncLed::AsyncLed(uint8_t pin, bool activeHigh)
: _pin(pin), _activeHigh(activeHigh), _state(false), _isBlinking(false), _interval(0), _lastTick(0) {}
void AsyncLed::begin() {
pinMode(_pin, OUTPUT);
off();
}
void AsyncLed::on() {
_isBlinking = false;
_state = true;
digitalWrite(_pin, _activeHigh ? HIGH : LOW);
}
void AsyncLed::off() {
_isBlinking = false;
_state = false;
digitalWrite(_pin, _activeHigh ? LOW : HIGH);
}
void AsyncLed::blink(uint32_t interval) {
_interval = interval;
_isBlinking = true;
}
void AsyncLed::update() {
if (!_isBlinking) return;
if (millis() - _lastTick >= _interval) {
_lastTick = millis();
_state = !_state;
digitalWrite(_pin, _activeHigh ? (_state ? HIGH : LOW) : (_state ? LOW : HIGH));
}
}
優雅的 Main Loop
現在,看看我們的 main.cpp 變得多麼強大。我們不再操作「腳位」,而是在操作「行為」。
#include <Arduino.h>
#include "LedController.h"
AsyncLed statusLed(1); // 狀態燈
AsyncLed alertLed(2); // 警告燈
void setup() {
Serial.begin(115200);
statusLed.begin();
alertLed.begin();
statusLed.blink(1000); // 每秒閃爍
alertLed.blink(100); // 快速閃爍
}
void loop() {
// 這裡完全沒有 delay()!
// 所有的硬體物件透過 update() 自行處理時間事件
statusLed.update();
alertLed.update();
// CPU 現在有充裕的時間處理高優先權任務,例如 Wi-Fi 或 AI 辨識
}
結論
在 2026 年,硬體效能已不是限制,程式碼的可維護性與非阻塞設計才是專業的關鍵。透過 ESP32-S3 C++ 類別封裝事件驅動控制:
- 隔離變化: 更改閃爍邏輯只需動 .cpp 檔,主應用邏輯完全不變。
- 釋放潛能: 徹底消滅
delay(),讓雙核心發揮應有的並行處理能力。 - 優雅成長: 從「指令控制」進化到「事件訂閱」,這是邁向嵌入式架構師的必經之路。
下次開始專案時,試著先設計你的類別介面。當你開始思考「物件的責任與事件」,你就已經從 Maker 踏入專業嵌入式軟體工程的領域。









