In this tutorial, we’ll look at how to configure two ESP32s to establish communication using the ESP-NOW protocol. The ESP32 is a development board integrating Bluetooth and WiFi. It can therefore connect and exchange data with devices connected to the same network.
Hardware
- Computer
- NodeMCU ESP32 x2
- USB A Male to Mini B Male cable x2
Description ESP-NOW
ESP-NOW is a communication protocol developed by Espressif, enabling wireless communication between several devices without the need for a specific network. It enables high-speed exchange of small data packets over 2.4GHz frequency bands, up to 200m away. It requires an initial pairing, but once this is done, communication is persistent and establishes itself when the ESP32 starts up. One of the great advantages of this system, apart from the fact that it uses a dedicated network, is that one or more ESP32 stations can connect to the Wifi in parallel.
EPS-NOW also enables communication between several ESP32 and ESP8266 cards. All you need to do is adapt the code to the type of card.
Code
To test ESP-NOW communication between two ESP32 boards, we’ll use the esp_now.h library, available when you install the board manager. We will define and send a data structure between the master and slave boards. This structure must be identical between the two boards for the data to be understood correctly.
Sender code
For the sending card to communicate with another ESP32 card, it needs its MAC address. You can retrieve this address from the messages sent by the setup() function when the receiving board is started. We’re going to define a function that runs after a message has been sent, to check whether the transmission was successful.
#include <esp_now.h>// https://github.com/espressif/esp-idf/blob/master/components/esp_wifi/include/esp_now.h #include <WiFi.h> uint8_t broadcastAddress[] = {0xA4, 0xCF, 0x12, 0x9A, 0x19, 0xAC};// REPLACE WITH RECEIVER MAC ADDRESS // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message myData; // callback when data is sent void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print(F("\r\n Master packet sent:\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(F("Error initializing ESP-NOW")); return; } Serial.print(F("Transmitter initialized : ")); Serial.println(WiFi.macAddress()); // Define Send function esp_now_register_send_cb(OnDataSent); // Register peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println(F("Failed to add peer")); return; } } void loop() { // Set values to send strcpy(myData.a, "data type char"); myData.b = random(1, 20); myData.c = 1.2; myData.d = "hello"; myData.e = 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(F("Sent with success")); } else { Serial.println(F("Error sending the data")); } delay(1000); }
N.B.: In some versions, this code does not work. Two possible solutions if you encounter a problem: or
- place memset(&peerInfo, 0, sizeof(peerInfo)); after the peerInfo declaration
- declare esp_now_peer_info_t peerInfo before loop()
Receiver code
In the receiver code, we’ll create a function that runs when a message is received. This function is used to process the information received. In this example, we display the data contained in the structure.
#include <esp_now.h>// https://github.com/espressif/esp-idf/blob/master/components/esp_wifi/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; String d; bool e; } struct_message; struct_message myData; // callback 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("String: "); Serial.println(myData.d); Serial.print("Bool: "); Serial.println(myData.e); 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; } Serial.print(F("Receiver initialized : ")); Serial.println(WiFi.macAddress()); // Define receive function esp_now_register_recv_cb(OnDataRecv); } void loop() { }
When you upload the code for the first time, pay close attention to the setup message containing the MAC address. You need to place it in the transmitter code to ensure successful pairing and communication.
Results
Once the MAC address of the receiving card has been defined and the codes uploaded to each card, communication is established and the structure is sent and decrypted correctly.
Bonus: Bidirectional communication between two ESP32s
We’ve seen how to send data from one card to another using the ESP-NOW protocol. Now we’ll see how, by slightly modifying the codes, we can obtain two-way communication.
Code Transceiver
#include <esp_now.h> #include <WiFi.h> uint8_t broadcastAddress[] = {0xA4, 0xCF, 0x12, 0x9A, 0x19, 0xAC};// REPLACE WITH OTHER TRANSCEIVER MAC ADDRESS // Structure example to send data // Must match the receiver structure typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; struct_message myData; char dataRcv[15]; // callbacks for sending and receiving data void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nMaster packet sent:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&dataRcv, incomingData, sizeof(dataRcv)); Serial.print("\r\nBytes received: "); Serial.println(len); Serial.print("From slave: "); Serial.println(dataRcv); Serial.println(); } 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(F("Error initializing ESP-NOW")); return; } Serial.print(F("Transceiver initialized : ")); Serial.println(WiFi.macAddress()); // Define callback functions esp_now_register_send_cb(OnDataSent); esp_now_register_recv_cb(OnDataRecv); // Register peer esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Add peer if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.println(F("Failed to add peer")); return; } } void loop() { // Set values to send strcpy(myData.a, "from Master"); myData.b = random(1, 20); myData.c = 1.2; myData.d = "hello"; myData.e = false; // Send message via ESP-NOW esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); delay(1000); }
N.B.: To obtain the same code for sender and receiver, we use the same data structure for sent and received messages. It is possible to define different structures depending on the origin of the message. Bear in mind, however, that the receiving card needs to know the structure of the received message to be able to decipher it.
Results
For station 0, we define the message
strcpy(dataSent.a, "Master"); dataSent.b = random(100, 200); dataSent.c = false;
And for station 1:
strcpy(dataSent.a, "Slave"); dataSent.b = random(10, 20); dataSent.c = false;