什麼是 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 在延遲和功耗上有顯著優勢,但 Payload 限制(最大 250 bytes)和節點數量限制(最多 20 個)是其主要限制。
ESP-NOW 封包格式
ESP-NOW 基於 WiFi 802.11 的底層封包,但進行了簡化:
- 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 的配對流程很簡單:
- 廣播:發送端廣播自己的存在(或直接指定目標 MAC)
- 配對:接收端回應配對請求,建立 Peer 關係
- 傳輸:配對完成後,雙方可直接雙向傳送資料
- ACK:每個封包都會回傳 ACK 確認
重傳機制
ESP-NOW 內建重傳機制(最多 7 次),若發送失敗會自動重傳。如果 WiFi 通道干擾嚴重,還可以切換通道(Channel)重試。
WiFi + ESP-NOW 共存
ESP-NOW 底層使用的是 WiFi 的 PHY,因此理論上不能同時運作。但 ESP32 可以在 WiFi Station/AP 模式和 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 限制與注意事項
| 限制 | 說明 | 解決方案 |
|---|---|---|
| 最大 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 是最佳選擇。
文章評論