今天我們來玩兒ESP-NOW。
相信每個玩電子的童鞋對無線通信都非常感興趣,從孩童時期的無線遙控賽車,到學(xué)生時期的收音機(jī),到長大后接觸的各種無線控制的家電,手機(jī)等電子產(chǎn)品,無線通信讓我們可以像神話小說中的某些絕技,可以做到隔空控制,每個人都都希望將那個遙控器掌握在自己手中。
無線遙控的本質(zhì)就是發(fā)射端發(fā)射某個頻率的電磁波出去,接收端接收并解碼這個信號。我們經(jīng)常接觸的空調(diào),電視,風(fēng)扇等遙控器很多都是紅外的,頻率大多是38Khz;窗簾,車庫門等大多是315MHz或者433MHz;國外有868MHz、915MHz等;低于1Ghz的泛稱為Sub 1Ghz,不同國家有不同的免費(fèi)頻率段;燈,手機(jī)等大多是用2.4Ghz的,2.4G這個頻段用的比較多,這個頻段在全球是免費(fèi)使用的,我們熟知的WiFi、藍(lán)牙、Zigbee的都是2.4Ghz的;當(dāng)然還有NBIOT,2G,4G,5G,長波,中波,短波等很多頻率的設(shè)備。理論來說,在相同功率下,低頻能獲得更好的傳送距離,高頻有更好的抗干擾性能,不同領(lǐng)域根據(jù)自身特性選擇最合適的通信頻率。
氣象站項(xiàng)目預(yù)覽
ESP-NOW概述
ESP-NOW 是由 Espressif 開發(fā)的一種協(xié)議,它使多個設(shè)備能夠在不使用 Wi-Fi 的情況下相互通信。該協(xié)議類似于無線鼠標(biāo)中用的2.4GHz無線連接。因此,設(shè)備之間的配對需要在它們通信之前進(jìn)行。配對完成后,連接是安全且點(diǎn)對點(diǎn)的,無需握手,也就是他不像TCP/IP等是長連接的,換句話說,它是無連接的,如果其中一個板子突然斷電,重新啟動后,會自動匹配它的連接設(shè)備繼續(xù)通信。
不同于傳統(tǒng)的OSI模型,ESP-NOW去掉了其中一些層,只保留最基本的傳輸層,減少了網(wǎng)絡(luò)擁堵造成的丟包延遲,實(shí)現(xiàn)快速響應(yīng)。簡單來說,ESP-NOW 是一種快速通信協(xié)議,可用于在 ESP32 板之間交換短消息(單次最多 250 字節(jié))。
ESP-NOW的優(yōu)勢
- 快速響應(yīng):開機(jī)后,設(shè)備無需任何無線連接即可直接傳輸數(shù)據(jù)和控制其他配對設(shè)備,
響應(yīng)速度以毫秒為單位
; - 遠(yuǎn)距離通信:ESP-NOW 支持遠(yuǎn)距離通信,板載天線戶外空曠距離能
達(dá)到200米+
; - 多跳控制:ESP-NOW可以實(shí)現(xiàn)設(shè)備的多跳控制,可通過單播、廣播和群控方式控制數(shù)百臺設(shè)備;
- 新配網(wǎng)方式:提供了除 Wi-Fi 和藍(lán)牙之外的新方式,通過藍(lán)牙為第一臺設(shè)備配置網(wǎng)絡(luò),其他設(shè)備不需要配置SSID/密碼等信息,
第一臺連接到網(wǎng)絡(luò)的設(shè)備可以直接將這些信息發(fā)送給其他設(shè)備
; - 升級:可用于
固件升級
或者大量數(shù)據(jù)升級的場景; - 調(diào)試:在一些高溫高壓等不太方便的場合,可以接收多個設(shè)備的數(shù)據(jù),
快速診斷設(shè)備故障
。 - 低成本:
可與WiFi,藍(lán)牙等共存
; - 安全:ESP-NOW 采用 CCMP 方法保護(hù)供應(yīng)商特定動作幀的安全,具體可參考 IEEE Std. 802.11-2012。
ESP-NOW通信
單向通信
一個從機(jī)向一個主機(jī)發(fā)送數(shù)據(jù)
這種情況適用于一個設(shè)備向另一個設(shè)備單向發(fā)送數(shù)據(jù),比如一個從機(jī)采集傳感器數(shù)據(jù)或?qū)?a class="article-link" target="_blank" href="/tag/%E5%BC%80%E5%85%B3/">開關(guān)量發(fā)送到主機(jī)。
一個主機(jī)向多個從機(jī)發(fā)送數(shù)據(jù)
一個從機(jī)從多個主機(jī)接收數(shù)據(jù)
雙向通信
主機(jī)與從機(jī)互相通信
多個設(shè)備之間互相通信
ESP-NOW非常適合組建一個小型網(wǎng)絡(luò),可以讓多個ESP32之間交換數(shù)據(jù)。
硬件
基本演示不需要加入其它外設(shè),板子接上串口助手觀察就好了。
軟件
獲取板子的MAC地址
ESP-NOW是通過MAC地址做為不同設(shè)備的唯一識別的,就像不同設(shè)備的ID碼一樣,當(dāng)然我們可以通過掃描配對的方式去自動配對,這里為了方便展示程序原理,我們就先采用最基本的方式,先通過下面的代碼獲取主機(jī)設(shè)備的MAC地址。
#include?"WiFi.h"
?
void?setup(){
??Serial.begin(115200);
??WiFi.mode(WIFI_MODE_STA);
??Serial.println(WiFi.macAddress());
}
?
void?loop(){
}
獲取到主機(jī)的MAC地址后,我們記下來。
初始化ESP-NOW
初始化ESP-NOW,在這個函數(shù)調(diào)用之前必須初始化WiFi。
esp_now_init();
添加配對設(shè)備
調(diào)用此函數(shù)配對設(shè)備,將MAC地址,通道,加密信息等進(jìn)行配置。
esp_now_add_peer();
發(fā)送數(shù)據(jù)
向配對設(shè)備發(fā)送數(shù)據(jù)
esp_now_send();
發(fā)送數(shù)據(jù)回調(diào)函數(shù)
注冊一個發(fā)送數(shù)據(jù)時調(diào)用的函數(shù),此函數(shù)會返回是否發(fā)送成功的消息。
esp_now_register_send_cb();
接收數(shù)據(jù)回調(diào)函數(shù)
注冊一個接收到數(shù)據(jù)時調(diào)用的函數(shù)。
esp_now_register_rcv_cb();
還有其它一些函數(shù),我們用到的時候再講。
完整發(fā)送程序
我們將之前打印的MAC地址保存下來,替換到broadcastAddress數(shù)組中。代碼中,首先定義了一個結(jié)構(gòu)體,包含幾種不同類型的數(shù)據(jù)變量,這個就是我們要發(fā)送的數(shù)據(jù),在setup()中先設(shè)置WiFi工作在STA模式
,然后調(diào)用esp_now_init()初始化,將配對設(shè)備的信息進(jìn)行添加
,簡單配置一下發(fā)送回調(diào)函數(shù),打印是否發(fā)送成功,主函數(shù)中,每2秒發(fā)送一次數(shù)據(jù)。esp_now_send返回是否發(fā)送出去,回調(diào)函數(shù)中展示是否成功發(fā)送給接收方。
#include?<esp_now.h>
#include?<WiFi.h>
//?REPLACE?WITH?YOUR?RECEIVER?MAC?Address
uint8_t?broadcastAddress[]?=?{0x8C,?0xCE,?0x4E,?0xA6,?0x73,?0x74};
//?Structure?example?to?send?data
//?Must?match?the?receiver?structure
typedef?struct?struct_message?{
??char?a[32];
??int?b;
??float?c;
??bool?d;
}?struct_message;
//?Create?a?struct_message?called?myData
struct_message?myData;
esp_now_peer_info_t?peerInfo;
//?callback?when?data?is?sent
void?OnDataSent(const?uint8_t?*mac_addr,?esp_now_send_status_t?status)?{
??Serial.print("rnLast?Packet?Send?Status:t");
??Serial.println(status?==?ESP_NOW_SEND_SUCCESS???"Delivery?Success"?:?"Delivery?Fail");
}
?
void?setup()?{
??//?Init?Serial?Monitor
??Serial.begin(115200);
?
??//?Set?device?as?a?Wi-Fi?Station
??WiFi.mode(WIFI_STA);
??//?Init?ESP-NOW
??if?(esp_now_init()?!=?ESP_OK)?{
????Serial.println("Error?initializing?ESP-NOW");
????return;
??}
??//?Once?ESPNow?is?successfully?Init,?we?will?register?for?Send?CB?to
??//?get?the?status?of?Trasnmitted?packet
??esp_now_register_send_cb(OnDataSent);
??
??//?Register?peer
??memcpy(peerInfo.peer_addr,?broadcastAddress,?6);
??peerInfo.channel?=?0;??
??peerInfo.encrypt?=?false;
??
??//?Add?peer????????
??if?(esp_now_add_peer(&peerInfo)?!=?ESP_OK){
????Serial.println("Failed?to?add?peer");
????return;
??}
}
?
void?loop()?{
??//?Set?values?to?send
??strcpy(myData.a,?"THIS?IS?A?CHAR");
??myData.b?=?random(1,20);
??myData.c?=?1.2;
??myData.d?=?false;
??
??//?Send?message?via?ESP-NOW
??esp_err_t?result?=?esp_now_send(broadcastAddress,?(uint8_t?*)?&myData,?sizeof(myData));
???
??if?(result?==?ESP_OK)?{
????Serial.println("Sent?with?success");
??}
??else?{
????Serial.println("Error?sending?the?data");
??}
??delay(2000);
}
完整接收程序
接收跟發(fā)送差不多,也是要定義一個跟發(fā)送方一樣的數(shù)據(jù)結(jié)構(gòu)體,用于保存接收到的數(shù)據(jù),創(chuàng)建一個接收回調(diào)函數(shù),當(dāng)接收到數(shù)據(jù)時,調(diào)用此函數(shù),將數(shù)據(jù)保存到一個結(jié)構(gòu)體變量中,然后打印出來。
#include?<esp_now.h>
#include?<WiFi.h>
//?Structure?example?to?receive?data
//?Must?match?the?sender?structure
typedef?struct?struct_message?{
????char?a[32];
????int?b;
????float?c;
????bool?d;
}?struct_message;
//?Create?a?struct_message?called?myData
struct_message?myData;
//?callback?function?that?will?be?executed?when?data?is?received
void?OnDataRecv(const?uint8_t?*?mac,?const?uint8_t?*incomingData,?int?len)?{
??memcpy(&myData,?incomingData,?sizeof(myData));
??Serial.print("Bytes?received:?");
??Serial.println(len);
??Serial.print("Char:?");
??Serial.println(myData.a);
??Serial.print("Int:?");
??Serial.println(myData.b);
??Serial.print("Float:?");
??Serial.println(myData.c);
??Serial.print("Bool:?");
??Serial.println(myData.d);
??Serial.println();
}
?
void?setup()?{
??//?Initialize?Serial?Monitor
??Serial.begin(115200);
??
??//?Set?device?as?a?Wi-Fi?Station
??WiFi.mode(WIFI_STA);
??//?Init?ESP-NOW
??if?(esp_now_init()?!=?ESP_OK)?{
????Serial.println("Error?initializing?ESP-NOW");
????return;
??}
??
??//?Once?ESPNow?is?successfully?Init,?we?will?register?for?recv?CB?to
??//?get?recv?packer?info
??esp_now_register_recv_cb(OnDataRecv);
}
?
void?loop()?{
}
將上面的發(fā)送與接收程序燒錄到兩塊板,就實(shí)現(xiàn)了數(shù)據(jù)的單向傳輸,我們看下接收方的串口打印數(shù)據(jù):
這是最簡單的一個案例,大家對ESP-NOW先有一個簡單的理解,關(guān)于ESP-NOW的管理設(shè)備,刪除設(shè)備,掃描從設(shè)備,自動配對等,下一期再講,布置個作業(yè),大家可以在發(fā)送端添加一個按鈕,接收端添加一個LED等,就可以實(shí)現(xiàn)一個遙控器的Demo了,快去嘗試一下吧。
下一節(jié),我們會實(shí)際做一個小項(xiàng)目,增加以下功能:
多個
主機(jī),多個
從機(jī);從機(jī)按鍵長按觸發(fā)下進(jìn)入廣播模式
,釋放出WIFI信號,主機(jī)在上電的時候,會掃描到WIFI信號并自動配對
;配對完成之后,主機(jī)從機(jī)都會將MAC地址自動寫入存儲空間
,下次啟動就不用重新配對;兩個從機(jī)可以通過按鍵控制主機(jī)上的兩個LED燈,主機(jī)也可以同時控制兩個從機(jī)上的LED燈;兩個從機(jī)分別采集溫度濕度氣壓
數(shù)據(jù),傳給主機(jī);主機(jī)將數(shù)據(jù)在本地顯示屏顯示出來,同時在連接WiFi之后,可以通過Web端實(shí)時查看傳感器數(shù)據(jù)
;指示燈顯示連接狀態(tài)。
感謝大家,關(guān)于ESP32的學(xué)習(xí),希望大家Enjoy!
參考資料:
1、https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/network/esp_now.html
2、https://randomnerdtutorials.com/esp-now-esp32-arduino-ide/
3、https://github.com/espressif/esp-now