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

為何用封裝事件驅動 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 框架

開發環境

在開始編程之前,請確保已完成以下準備工作:

專案結構

假設建立一個 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 相關的底層細節。

在這裡,我們定義介面。使用 constexprenum 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 踏入專業嵌入式軟體工程的領域。