繼上文介紹了ESP-NOW的拓樸架構後,在本章節中將會透過幾個範例為讀者示範如何去實現及實作前面一節中不同拓樸組態的配置,此外在我們的範例程式中,不需要特別去區分所是用的模組是ESP8266或是ESP32,單一程式可以無落差的直接套用在兩種模組上,這也是我們這個範例程式最大的特點之一!
除此之外,為了避免在傳輸發送前的配對還必須先得知接收模組的MAC位址,在我們的程式中都會採用廣播式的方式發送資料,因此便可省略了必須先知悉接收裝置MAC位址碼的問題!
單向一對一/點對點(peer to peer)架構
在這篇文章中將示範『一對一單向傳輸』這個ESP-NOW最基本拓樸結構的通信方式,而在介紹範例程式之前,先讓我們來看一下在ESP-NOW常會使用到的一些指令函數,以下便是 ESP-NOW 最基本功能的幾個指令函數摘要:
函數名稱和說明:
⬛ esp_now_init():初始化 ESP-NOW。但是你必須在初始化 ESP-NOW 之前先初始化 Wi-Fi功能,如果成功,則返回 0。
⬛ esp_now_set_self_role(role):自我腳色設定函式,role這個參數可以是:ESP_NOW_ROLE_IDLE=0、ESP_NOW_ROLE_CONTROLLER、ESP_NOW_ROLE_SLAVE、ESP_NOW_ROLE_COMBO、ESP_NOW_ROLE_MAX
⬛ esp_now_add_peer(uint8 mac_addr, uint8 role, uint8 channel, uint8 key, uint8 key_len):呼叫此函數可用來配對設備。
⬛ esp_now_send(uint8 mac_address,uint8 data,int len):資料數據發送函式,可用來發送data這部分的數據。
⬛ esp_now_register_send_cb(onDataSend):註冊一個在發送數據時觸發的回應函數(即onDataSend),發送訊息時會呼叫這個函數,而此函數可用以返回發送是否成功的訊息。
⬛ esp_now_register_rcv_cb(onDataRecv):註冊一個接收數據時觸發的回應函數(即onDataRecv)。當通過 ESP-NOW 接收到數據時,會呼叫這一個函數。
發送端程式列表與說明:
本次範例所使用的開發工具還是採用最常見的Arduino IDE,在撰寫ESP-NOW程式時有兩個問題最讓人頭痛,一是要先查出接收端的MAC位址,二是在使用ESP8266或是ESP32模組時,不管程式或引用的函式庫都不一樣;雖然現在大家都漸漸改用ESP32了,可是如果我們只是單純要控制一個開關,這時假如使用ESP32加上一個繼電器或是模組,在成本上有些說不過去;這時如果採用一個由ESP-01(核心晶片為ES8266)構成繼電器模組,就會是一個不錯的選擇。
可是使用過Arduino IDE的朋友就會有個感覺,就是晶片種類切換來切換去還真有些麻煩,更不要說因為模組的不同所需改換的函式庫及指令函數等問題更是煩人;為了讓程式更容易使用,筆者花了不少時間去調整程式,終於一次把前述兩個問題給解決了!
以下是發送端的完整程式列表:
#ifdef ESP32
#include
#include
esp_now_peer_info_t peerInfo;
int ledOn=1;
int ledOff=0;
#else
#include
#include
int ledOn=0;
int ledOff=1;
#endif
uint8_t macAddr,broadCastMacAddr={0xff,0xFf,0xff,0xff,0xff,0xff};
String ESPmac="";
typedef struct message{
int ID;
char a;
int b;
unsigned int c;
float d;
bool e;
} message;
message myData;
byte myId=1,LED=2;
unsigned int sendTimes=0;
unsigned long lastTime=0;
unsigned long timerDelay=5000;
void setup() {
Serial.begin(115200);
Serial.println();
WiFi.macAddress(macAddr);
for(int i=0;i<6;i++) {
if (macAddr<16)
ESPmac+="0";
ESPmac+=String(macAddr,HEX);
}
Serial.print("ESP mac Addrres = ");
Serial.println(ESPmac);
// WiFi.softAP mac Address(macAddr);
WiFi.mode(WIFI_STA);
if(esp_now_init() !=0) {
Serial.println("Error initializing ESP-NOW!");
while(1) {
digitalWrite(LED,0);
delay(100);
digitalWrite(LED,1);
delay(100);
}
}
#ifdef ESP32
esp_now_register_send_cb(onDataSent);
memcpy(peerInfo.peer_addr, broadCastMacAddr, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
//Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
#else
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_add_peer(broadCastMacAddr, ESP_NOW_ROLE_SLAVE,1, NULL, 0);
esp_now_register_send_cb(OnDataSent);
#endif
lastTime=millis();
}
void loop() {
if((millis()-lastTime) > timerDelay)
{
lastTime=millis();
sendTimes++;
Serial.print("目前為第 ");Serial.print(sendTimes);Serial.println(" 筆資料,資料已發送完成!");
myData.ID=myId;
strcpy(myData.a,"This is a Char");
myData.b=random(1,100);
myData.c=sendTimes;
myData.d=1.23;
myData.e=true;
esp_now_send(broadCastMacAddr,(uint8_t *)&myData,sizeof(myData));
}
}
// ESP 使用的資料發送callback 副程式:
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus)
{
Serial.print("最後一筆資料傳送狀況: ");
if (sendStatus == 0)
Serial.println("傳送成功!\n");
else
Serial.println("傳送失敗!\n");
}
// ESP32 使用的資料發送callback 副程式:
#ifdef ESP32
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("最後一筆資料傳送狀況: ");
if (status == 0)
Serial.println("傳送成功!\n");
else
Serial.println("傳送失敗!\n");
}
#endif
對於一個發送端的裝置而言必須執行下面的動作:
- 初始化 ESP-NOW(記得要先初始化WiFi功能)。
- 在發送數據時註冊一個回應函數 — 『OnDataSent』,以便在發送消息時執行此函數,而此函數可以告訴我們是否成功發送資料數據。
- 添加對應的設備(接收方),為此您需要知道接收方的 MAC 地址。
- 向對應設備發送消息。
為了讓程式能同時使用在ESP8266與ESP32兩種模組上,因此在程式一開始的引入(include)部份我們就必須先處理所使用的相關函式庫及相關變數,下面的程式碼便是我們這個發送範例程式的引入部分。
由於啟動ESP-NOW的初始化功能前必須先啟動ESPxx的WiFi功能,在此我們用”#ifdef … #esle … #endif”的編譯前置指令,讓我們的Arduino IDE自動依所選用的ESPxx模組不同,去引入不同的函式庫;對ESP32而言,必須用到””和””這兩個函式庫,而對ESP8266來說,則是””及””。
在我們的程式中為了確定ESP-NOW的初始化功能是否成功﹖使用了ESP8266郵票板上內建的LED(接在GPIO 2上)做為指示燈,對ESP32而言,它的郵票板並沒有這顆LED,一般是作在擴充模組板(例如NodeMCU)上,而且亮滅的電壓位準和ESP8266相反,為了相容起見,我們用「ledOn」、「ledOff」這兩種個標記來代表LED的亮滅。
#ifdef ESP32
#include
#include
esp_now_peer_info_t peerInfo;
int ledOn=1;
int ledOff=0;
#else
#include
#include
int ledOn=0;
int ledOff=1;
#endif
為了避開必須先知道接收端的MAC位址才能發出資料的困擾,在此我們是使用廣播(Broadcast)的方式來傳送資料,也就是說發射端傳送的資料所有的接收板(假如還有其他的話)都會接收到,如果接收端要知道是誰發送,除了可以由發送者的MAC位址得知之外,也可以在發送的資料中包含一個發送板所賦予的ID編號去辨識。
下面所定義的變數中,這個六位元組內容都為0xff的「boardCastMacAddr」,便是ESP-NOW中用來標示為廣播用的MAC位址變數。
uint8_t macAddr,broadCastMacAddr={0xff,0xFf,0xff,0xff,0xff,0xff};
為了方便展示所能傳送的資料種類,在此我們定義了一可包括各種資料型態的結構變數「message」;在其中包括了整數(int)、長整數(long)、浮點數(float)、布林數(bool)及字串(char )等變數型態,其內容如下面程式所示。其中的「ID」用來傳送發送板所賦予的ID編號,而「c」則是代表目前發送的資料筆數流水編號。
至於後面的「myData」則是我們實際會傳送的型態為「message」的結構變數,而「MyId」是這塊送發送板所賦予的ID編號變數,在此為1,使用者可依自己的需要改成其他的號碼。
typedef struct message{
int ID;
char a;
int b;
unsigned long c;
float d;
bool e;
} message;
message myData;
byte myId=1,LED=2;
在前面常用指令函數介紹時說過,當要初始化ESP-NOW之前必須先初始化Wi-Fi功能,因此在下面屬於初始化(setup())部分的程式中,我們先用『WiFi.mode(WIFI_STA)』這行指令將ESPxx晶片進行Wi-Fi功能的初始化,而且設定為STA模式。然後呼叫『esp_now_init()』這個ESP-NOW初始化用的指令函數,如果傳回來的結果是錯誤的,則會在Arduino IDE的監控視窗中顯示"Error initializing ESP-NOW!"這樣的錯誤提示訊息,而且會令發送板進入一個讓接在GPIO 2的LED快速閃爍的無窮迴圈,因為如果初始化失敗,系統就不應該再動作下去!
WiFi.mode(WIFI_STA);
if(esp_now_init() !=0) {
Serial.println("Error initializing ESP-NOW!");
while(1) {
digitalWrite(LED,0);
delay(100);
digitalWrite(LED,1);
delay(100);
}
}
在初始化完ESP-NOW之後接下來的動作就是註冊一個回應函數,及添加對應的接收方設備的 MAC 地址,由於ESP32、ESP8266兩者對這些動作使用的指令及方法並不相同,所以在我們的範例程式中一樣的採用”#ifdef … #esle … #endif”的編譯前置指令去把它區分出來,以便可以相容使用在兩種晶片上。
下面這段程式是針對ESP32而來的,首先用『esp_now_register_send_cb』這個指令函數去註冊一個回應函數「onDataSent」,然後再用『esp_now_add_peer』這個指令函數指定接收端的MAC位址「peefInfo」,不過我們必須先將廣播用的MAC位址「broadCastMacAddr」複製到「peefInfo」上。
#ifdef ESP32
esp_now_register_send_cb(onDataSent);
memcpy(peerInfo.peer_addr, broadCastMacAddr, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
//Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
對於ESP8266來說必須先定義自己的腳色,也就是發送端,在此使用『esp_now_set_self_role』這個指令函數去設定為發送者,其參數值為「ESP_NOW_ROLE_CONTROLLER」;至於指定接收端MAC位址的指令函數『esp_now_add_peer』,它引用參數的方式與內容跟ESP32是不一樣的,這點還請讀者多注意!至於去註冊一個回應函數『onDataSent』則是一樣使用『esp_now_register_send_cb』這個指令函數。
#else
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_add_peer(broadCastMacAddr, ESP_NOW_ROLE_SLAVE,1, NULL, 0);
esp_now_register_send_cb(OnDataSent);
#endif
下面部分則是主迴圈(loop())部分的所有程式碼,內容很簡單,就是在固定的時間(timerDelay🡺5秒)發送一次資料(myData),每發送一次「sendTimes」這個變數會加一,用以代表資料的筆數;在設定好「myData」這個資料結構變數的內容之後,最後呼叫『esp_now_send』這個指令函數把資料發送出去,這樣便完成一次ESP-NOW的資料傳輸動作。
void loop() {
if((millis()-lastTime) > timerDelay)
{
lastTime=millis();
sendTimes++;
Serial.print("目前為第 ");Serial.print(sendTimes);Serial.println(" 筆資料,資料已發送完成!");
myData.ID=myId;
strcpy(myData.a,"This is a Char");
myData.b=random(1,100);
myData.c=sendTimes;
myData.d=1.23;
myData.e=true;
esp_now_send(broadCastMacAddr,(uint8_t *)&myData,sizeof(myData));
}
}
下面的程式是發送回應函數『onDataSent』的主體內容,同樣的因為ESP32、ESP8266兩者使用的程式指令及方法並不相同,所以在此我們一樣的採用”#ifdef … #esle … #endif”的編譯前置指令去把它區分出來,以便可以相容使用在兩種晶片上。
在這個資料發送callback副程式中,同樣的都會在Arduino IDE的監控視窗中先顯示"第 ?? 筆資料傳送狀況:"的提示訊息,其中的”??”就是「sendTimes」這個變數的內容,如果發送成功,會顯示"傳送成功!"的提示訊息,否則將顯示"傳送失敗!"的錯誤提示訊息。
// ESP 使用的資料發送callback 副程式:
#ifdef ESP32
// ESP32 使用的資料發送callback 副程式:
void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("第 ");Serial.print(sendTimes);Serial.print(" 筆資料傳送狀況: ");
if (status == 0)
Serial.println("傳送成功!\n");
else
Serial.println("傳送失敗!\n");
}
#else
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus)
{
Serial.print("第 ");Serial.print(sendTimes);Serial.print(" 筆資料傳送狀況: ");
if (sendStatus == 0)
Serial.println("傳送成功!\n");
else
Serial.println("傳送失敗!\n");
}
#endif
執行結果:
為測試程式是否能同時相容於ESP32及ESP8266,發送端程式是燒錄到ESP32上;下圖為發送端裝置(即ESP32)傳送到Arduino IDE監控視窗中的提示訊息,在其中會顯示所傳送資料的筆數及是否傳送成功。
只需不到短短一分鐘...
輸入您的信箱與ID註冊即可享有一切福利!
會員福利
免費電子報
會員搶先看
主題訂閱
好文收藏