0x6A Logbook

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

ESP-NOW 通訊協定完整教學:ESP32 無需 Router 的點對點通訊

2026 年 6 月 7 日 38點熱度 0人點贊 0條評論

什麼是 ESP-NOW?

ESP-NOW 是 Espressif 開發的專有無線通訊協定,基於 IEEE 802.11 PHY 層(即 WiFi 的底層),但不需要傳統的 WiFi 路由器或 AP。它讓 ESP32 裝置之間可以直接點對點(P2P)通訊,延遲極低(~2 ms),非常適合 IoT 感測器網路和遙控器應用。

傳統 WiFi 通訊需要先連線到 Router/AP 才能交換資料,而 ESP-NOW 的運作方式更像「無線 UART」——發送端直接把資料「丟」給接收端,不需要建立連線。

ESP-NOW vs 其他通訊方式

ESP-NOW vs MQTT vs HTTP 比較

從上表可以看出,ESP-NOW 在延遲和功耗上有顯著優勢,但 Payload 限制(最大 250 bytes)和節點數量限制(最多 20 個)是其主要限制。

ESP-NOW 封包格式

ESP-NOW 基於 WiFi 802.11 的底層封包,但進行了簡化:

ESP-NOW 封包格式

  • Preamble:802.11 標準前導碼(8 bytes)
  • MAC DA:目標裝置 MAC 位址(6 bytes)
  • MAC SA:來源裝置 MAC 位址(6 bytes)
  • Type:封包類型(2 bytes,標記為 ESP-NOW)
  • Payload:自訂資料內容,最大 250 bytes
  • FCS:Frame Check Sequence,CRC 校驗(4 bytes)

Payload 雖然只有 250 bytes,但對於感測器資料(溫度、濕度、氣壓等 float 值通常 4~8 bytes)來說已經綽綽有餘。

配對與資料傳輸流程

ESP-NOW 配對與資料傳輸流程

ESP-NOW 的配對流程很簡單:

  1. 廣播:發送端廣播自己的存在(或直接指定目標 MAC)
  2. 配對:接收端回應配對請求,建立 Peer 關係
  3. 傳輸:配對完成後,雙方可直接雙向傳送資料
  4. ACK:每個封包都會回傳 ACK 確認

重傳機制

ESP-NOW 重傳機制

ESP-NOW 內建重傳機制(最多 7 次),若發送失敗會自動重傳。如果 WiFi 通道干擾嚴重,還可以切換通道(Channel)重試。

WiFi + ESP-NOW 共存

ESP-NOW 底層使用的是 WiFi 的 PHY,因此理論上不能同時運作。但 ESP32 可以在 WiFi Station/AP 模式和 ESP-NOW 之間快速切換,達到「分時共存」的效果:

WiFi + ESP-NOW 分時共存

典型的做法是:

  • 大部分時間 ESP-NOW 用於低延遲資料傳輸
  • 每隔一段時間切換到 WiFi 模式,將感測器資料上傳到雲端
  • 切換完成後回到 ESP-NOW 模式

ESP-NOW 實作:一對一通訊

// ESP-NOW 一對一通訊 - 發送端 (Sender)
#include 
#include 

// 接收端 MAC 位址 (請換成你的接收端 MAC)
uint8_t receiverMac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// 要傳送的資料結構
typedef struct sensor_data {
    float temperature;
    float humidity;
    float pressure;
    uint32_t reading_id;
} sensor_data_t;

sensor_data_t myData;

// 發送完成回呼
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
    Serial.printf("發送狀態: %s\n",
                  status == ESP_NOW_SEND_SUCCESS ? "✅ 成功" : "❌ 失敗");
}

void setup() {
    Serial.begin(115200);

    // 設定 WiFi 為 Station 模式(必要)
    WiFi.mode(WIFI_STA);

    // 初始化 ESP-NOW
    if (esp_now_init() != ESP_OK) {
        Serial.println("ESP-NOW 初始化失敗");
        return;
    }

    // 註冊發送回呼
    esp_now_register_send_cb(onDataSent);

    // 註冊 Peer
    esp_now_peer_info_t peerInfo = {};
    memcpy(peerInfo.peer_addr, receiverMac, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("新增 Peer 失敗");
        return;
    }

    myData.reading_id = 0;
}

void loop() {
    // 模擬感測器資料
    myData.temperature = 25.0 + random(-50, 50) / 10.0;
    myData.humidity    = 60.0 + random(-100, 100) / 10.0;
    myData.pressure    = 1013.25 + random(-50, 50) / 10.0;
    myData.reading_id++;

    // 發送資料
    esp_err_t result = esp_now_send(receiverMac, (uint8_t*) &myData, sizeof(myData));

    Serial.printf("發送 #%lu: %.1f°C %.1f%% %.1fhPa\n",
                  myData.reading_id,
                  myData.temperature, myData.humidity, myData.pressure);

    delay(5000);  // 每 5 秒發送一次
}
// ESP-NOW 一對一通訊 - 接收端 (Receiver)
#include 
#include 

typedef struct sensor_data {
    float temperature;
    float humidity;
    float pressure;
    uint32_t reading_id;
} sensor_data_t;

sensor_data_t incomingData;

// 接收回呼
void onDataRecv(const uint8_t *mac, const uint8_t *incomingData_bytes, int len) {
    memcpy(&incomingData, incomingData_bytes, sizeof(incomingData));
    Serial.printf("收到 #%lu: %.1f°C %.1f%% %.1fhPa (len=%d)\n",
                  incomingData.reading_id,
                  incomingData.temperature,
                  incomingData.humidity,
                  incomingData.pressure, len);
}

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);

    if (esp_now_init() != ESP_OK) {
        Serial.println("ESP-NOW 初始化失敗");
        return;
    }

    esp_now_register_recv_cb(onDataRecv);
}

void loop() {
    // 接收端不需要做任何事,靠回呼驅動
    delay(1000);
}

ESP-NOW 實作:一對多廣播

如果需要同時與多個 ESP32 通訊,可以註冊多個 Peer:

// ESP-NOW 一對多廣播
// 註冊多個接收端
esp_now_peer_info_t peerInfo;

// Peer 1: 感測器節點 A
uint8_t macA[] = {0x24, 0x62, 0xAB, 0xF4, 0x12, 0x34};
memcpy(peerInfo.peer_addr, macA, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);

// Peer 2: 感測器節點 B
uint8_t macB[] = {0x24, 0x62, 0xAB, 0xF4, 0x56, 0x78};
memcpy(peerInfo.peer_addr, macB, 6);
esp_now_add_peer(&peerInfo);

// 發送給特定節點
esp_now_send(macA, (uint8_t*) &data, sizeof(data));

// 或廣播給所有 Peer(MAC 全 FF)
esp_now_send(broadcastMac, (uint8_t*) &data, sizeof(data));

ESP-NOW + Deep Sleep 低功耗感測器

ESP-NOW 最強大的應用之一是低功耗感測器節點。配合 Deep Sleep 模式,一顆 18650 鋰電池可以運行數月:

// ESP-NOW + Deep Sleep 低功耗感測器節點
#include 
#include 
#include 

// RTC 記憶體(Deep Sleep 後仍保留)
RTC_DATA_ATTR int bootCount = 0;

void setup() {
    Serial.begin(115200);
    bootCount++;
    Serial.printf("開機次數: %d\n", bootCount);

    // 初始化 WiFi(短暫喚醒)
    WiFi.mode(WIFI_STA);
    esp_now_init();

    // 註冊 Peer(閘道器 MAC)
    uint8_t gatewayMac[] = {0x24, 0x62, 0xAB, 0xF4, 0xAA, 0xBB};
    esp_now_peer_info_t peerInfo = {};
    memcpy(peerInfo.peer_addr, gatewayMac, 6);
    esp_now_add_peer(&peerInfo);

    // 讀取感測器 + 發送資料
    float temp = 25.3;  // 實際應讀取 DS18B20 等
    float hum  = 60.5;
    esp_now_send(gatewayMac, (uint8_t*) &temp, sizeof(temp));

    // 進入 Deep Sleep(10 分鐘後喚醒)
    Serial.println("進入 Deep Sleep...");
    esp_deep_sleep(600 * 1000000ULL);  // 10 分鐘 (µs)
}

void loop() {
    // 不會執行到這裡
}

獲取 MAC 位址

要使用 ESP-NOW,需要知道對方的 MAC 位址。每塊 ESP32 的 MAC 位址都不同:

// 獲取本機 MAC 位址
#include 

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    Serial.print("本機 MAC: ");
    Serial.println(WiFi.macAddress());
}

void loop() {}

網路拓樸

ESP-NOW 網路拓樸比較

ESP-NOW 支援多種網路拓樸:

  • 一對一:遙控器 → 裝置
  • 一對多:閘道器收集多個感測器資料
  • 多對一:多個控制器控制單一裝置
  • 網狀:透過中繼節點擴展範圍(需自行實作路由)

ESP-NOW 限制與注意事項

限制 說明 解決方案
最大 20 個 Peer 最多只能註冊 20 個裝置 分層網狀拓樸
Payload 250 bytes 單次傳送最多 250 bytes 分包傳送
無重傳保證 預設重傳 7 次,仍可能遺失 應用層 ACK + 序號檢查
無多跳路由 不支援自動中繼 自行實作 Store-and-Forward
WiFi 共存 ESP-NOW 和 WiFi 不能同時收發 分時切換模式

結合其他通訊協定

ESP-NOW 常與其他通訊協定搭配,形成完整的 IoT 架構:

// 感測器節點 → ESP-NOW → 閘道器 → MQTT → 雲端
// 區域網路:ESP-NOW(低延遲、低功耗)
// 廣域網路:MQTT(透過閘道器轉發到 Internet)

// 閘道器程式碼片段
void onEspNowRecv(const uint8_t *mac, const uint8_t *data, int len) {
    // 收到感測器資料
    float temp;
    memcpy(&temp, data, sizeof(temp));

    // 透過 MQTT 轉發到雲端
    client.publish("sensor/temperature", String(temp).c_str());

    // 記錄到 LittleFS(可參考 SPIFFS/LittleFS 文章)
    logToFile(mac, temp);
}

更多關於 MQTT 的細節,請參考 ESP32 MQTT 實戰教學。

總結

ESP-NOW 是 ESP32 生態中最被低估的通訊協定之一。它提供:

  • 超低延遲:~2 ms,比 MQTT 快兩個數量級
  • 無需基礎設施:不依賴 WiFi Router
  • 低功耗:搭配 Deep Sleep 可運行數月
  • 簡單直觀:API 只有 init / add_peer / send / 回呼

對於不需要連上 Internet 的區域 IoT 網路(如智慧農業、感測器陣列、遙控系統),ESP-NOW 是最佳選擇。

標籤: 教學
最後更新:2026 年 6 月 7 日

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