Introduction: ESP32 With ESP-Now Protocol

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

The ESP-Now is a very special, high-speed network, making it perfect for residential and industrial automation. It is another protocol developed by Espressif. We’ll be talking about this network today, which allows several devices to communicate without using a WiFi network made by a router. I'll show you an introduction to the subject and make several ESPs32 communicate through this scheme. Therefore, an ESP32 will read the pins and transmit their values, while the other devices will receive these values and change the output of the pins according to those numbers.

This network is reliable and is 2.4GHz. This network also is with the same frequency and channels as your WiFi router. The highlight, however, is that it goes away from WiFi, as it is instant.

Step 1: ESP32 Pinout

I show here the pinning of the ESP32.

Step 2: About ESP-Now

• Communication protocol created by Espressif.

• You don’t need a WiFi network.

• Similar to the low-power protocol used on a 2.4GHz wireless mouse.

• Initial pairing required.

• After pairing, the connection is persistent peer-to-peer.

Step 3: Tutorial Example

1. Master reads pins

2. Broadcast of the values read

3. Slaves change the output of their pins to equal the values received from the Master

Step 4: Demonstration

In our assembly, we have an ESP32 isolated, which is configured as Master. It is important to remember that there is no Master device, and that they all function as Stations. However, to facilitate the identification, I point to this first ESP as Master, in which I set up a button in the GPIO02.

When this button is pressed, a LED lights up in this microcontroller, and all four other ESPs32 instantly restart the action. Why does this occur? Because the moment the Master sends the information to the station, it is sending a MAC address of Broadcast, which means that everyone on the network receives the data at the same time.

In this example, I compiled the same receiver for all microcontrollers. I copied the code that sends to the Master, and this one also sends the Broadcast to the others. In this scheme, it is still possible to see that we practically don’t have Boot time, because when the ESP is switched off and reconnected, the operation resumes immediately.

In the serial print of Setup, both the sending and the receiving code contain the MAC address values of each of the chips involved. See the example in the pictures.

Step 5: Distance

I will show here that in the tests we did with the ESPs32, they managed to communicate up to 165.47 meters in a straight line, using only the internal antennas of the devices. See on the map.

Step 6: Code - ​ESPNowMaster.ino

Here, we will work with the libraries ESP_NOW.h and WiFi.h. Let's define the channel for connection and the pins that will be read, as well as the data that will be sent to Slaves. It is important to highlight that the source code of Slaves has this same array with the same GPIOs. In the Setup part, we will calculate the number of pins and put in this variable, so we won’t need to change every time we change the number of pins. We still work with the MAC Address of the slaves to which we will send the reading.

//Libs for espnow and wifi
#include <esp_now.h>
#include <WiFi.h>

//Channel used in the connection
#define CHANNEL 1

//Gpios that we are going to read (digitalRead) and send to the Slaves
//It's important that the Slave source code has this same array
//with the same gpios in the same order
uint8_t gpios[] = {23, 2};

//In the setup function we'll calculate the gpio count and put in this variable,
//so we don't need to change this variable everytime we change
//the gpios array total size, everything will be calculated automatically
//on setup function
int gpioCount;

//Slaves Mac Addresses that will receive data from the Master
//If you want to send data to all Slaves, use only the broadcast address {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
//If you want to send data to specific Slaves, put their Mac Addresses separeted with comma (use WiFi.macAddress())
//to find out the Mac Address of the ESPs while in STATION MODE)
uint8_t macSlaves[][6] = {
  //To send to specific Slaves
  //{0x24, 0x0A, 0xC4, 0x0E, 0x3F, 0xD1}, {0x24, 0x0A, 0xC4, 0x0E, 0x4E, 0xC3}
  //Or to send to all Slaves
  {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
};

Step 7: ESPNowMaster.ino - Setup

In this part, we will deal with the Master, remembering that we have a Master and four Slaves. Let's calculate the array size of GPIOs that will be read with digitalRead. We will still put ESP in station mode, and display on the Serial Monitor of the MAC Address of this ESP when in station mode, and we’ll call the function that initializes ESPNow.

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

//Calculation of gpio array size: //sizeof(gpios) returns how many bytes "gpios" array points to. //Elements in this array are of type uint8_t. //sizeof(uint8_t) return how many bytes uint8_t type has. //Therefore if we want to know how many gpios there are, //we divide the total byte count of the array by how many bytes //each element has. gpioCount = sizeof(gpios)/sizeof(uint8_t);

//Puts ESP in STATION MODE WiFi.mode(WIFI_STA);

//Shows on the Serial Monitor the STATION MODE Mac Address of this ESP Serial.print("Mac Address in Station: "); Serial.println(WiFi.macAddress());

//Calls the function that will initialize the ESP-NOW protocol InitESPNow();

We also calculate the size of the array with the MAC addresses of the slaves and create a variable that will save the information of each slave. We inform the channel and add the slave.

//Calculation of the size of the slaves array:
//sizeof(macSlaves) returns how many bytes the macSlaves array points to. //Each Slave Mac Address is an array with 6 elements. //If each element is sizeof(uint8_t) bytes //then the total of slaves is the division of the total amount of bytes //by how many elements each MAc Address has //by how much bytes each element has. int slavesCount = sizeof(macSlaves)/6/sizeof(uint8_t);

//For each Slave for(int i=0; i

In this step, we record the callback that will inform us about the status of the shipment. The function that will be executed is OnDataSent and is declared below. We put the slave in read mode and call the send function, which deals with sending.

//Registers the callback that will give us feedback about the sent data
//The function that will be executed is called OnDataSent esp_now_register_send_cb(OnDataSent); //For each gpio for(int i=0; i

//Calls the send function send(); }

Step 8: ESPNowMaster.ino - InitESPNow

The InitESPNow function is simple and works here with the possibilities of successful boot, as well as possibilities of error during boot time.

void InitESPNow() {
//If the initialization was successful if (esp_now_init() == ESP_OK) { Serial.println("ESPNow Init Success"); } //If there was an error else { Serial.println("ESPNow Init Failed"); ESP.restart(); } }

Step 9: ESPNowMaster.ino - Send

Here, we have the function that will read the pins that are in the array GPIOs and send the read values to the other ESPs. This array will store the read values.

//Function that will read the gpios and send
//the read values to the others ESPs void send(){ //Array that will store the read values uint8_t values[gpioCount];

//For each gpio for(int i=0; i<gpioCount; i++){

//Reads the value (HIGH or LOW) of the gpio //and stores the value on the array values[i] = digitalRead(gpios[i]); }

The broadcast address will send the information to all ESPs. If you want the information to go to specific ESPs, you should call the esp_now_send function for each MAC Address, passing the information that the MAC Address is the first parameter in place of the Broadcast.

//In this example we are going to use the broadcast address {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF}
//to send the values to all Slaves. //If you want to send to a specific Slave, you have to put its Mac Address on macAddr. //If you want to send to more then one specific Slave you will need to create //a "for loop" and call esp_now_send for each mac address on the macSlaves array uint8_t macAddr[] = {0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF}; esp_err_t result = esp_now_send(macAddr, (uint8_t*) &values, sizeof(values)); Serial.print("Send Status: "); //If it was successful if (result == ESP_OK) { Serial.println("Success"); } //if it failed else { Serial.println("Error"); } }

Step 10: ESPNowMaster.ino - OnDataSent

Let's now define the function that serves as a callback to let us know about the sending situation that we make. We copy the destination MAC address to a string and show the MAC address that served as the destination of the message. We've also shown whether the status of the upload was successful or not.

//Callback function that gives us feedback about the sent data
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { char macStr[18]; //Copies the receiver Mac Address to a string snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); //Prints it on Serial Monitor Serial.print("Sent to: "); Serial.println(macStr); //Prints if it was successful or not Serial.print("Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail"); //Sends again send(); }

Step 11: SPNowMaster.ino - Loop

We don’t have to do anything in the Loop, because whenever we receive feedback from the Send via OnDataSent function, we send the data again. This causes the data to always be sent in sequence.

//We don't do anything on the loop.
//Every time we receive feedback about the last sent data, //we'll be calling the send function again, //therefore the data is always being sent void loop() { }

Step 12: ESPNowSlave.ino

Now we're off to the Slaves. We work with the libraries ESP_NOW.h and WiFi.h. The pins that we will write (digitalWrite) will have the values received from the Master. Remember that it is important that the source code of the Master has this same array with the same GPIOs in the same order. In the setup, we will calculate the amount of pins and put in this variable. This way, we don’t have to change here every time we change the number of pins, because everything is calculated in the setup.

//Libs for espnow e wifi
#include <esp_now.h>
#include <WiFi.h>
 

//Gpios we'll write the values received from the Master
//It's important that the Master source code has this same array //with the same gpios in the same order uint8_t gpios[] = {23, 2};

//In the setup function we'll calculate the gpio count and put in this variable, //so we don't need to change this variable everytime we change //the gpios array total size, everything will be calculated automatically //on setup function int gpioCount;

Step 13: ESPNowSlave.ino - Setup

We have in this step the calculation of the array size of GPIOs. We put the ESP in station mode, and we show on the Serial Monitor the Mac Address of this ESP when in station mode. If you want the Master to send to ESPs in specific, make changes in the array of slaves (in the source code of the Master) so that it has only the Mac Addresses printed there. Finally, we call the function that initializes ESPNow.

void setup() {
Serial.begin(115200); //Calculation of gpio array size: //sizeof(gpios) returns how many bytes "gpios" array points to. //Elements in this array are of type uint8_t. //sizeof(uint8_t) return how many bytes uint8_t type has. //Therefore if we want to know how many gpios there are, //we divide the total byte count of the array by how many bytes //each element has. gpioCount = sizeof(gpios)/sizeof(uint8_t);

//Puts ESP in STATION MODE WiFi.mode(WIFI_STA);

//Shows on the Serial Monitor the STATION MODE Mac Address of this ESP Serial.print("Mac Address in Station: "); Serial.println(WiFi.macAddress());

//Calls the function that will initialize the ESP-NOW protocol InitESPNow();

We register the callback that will inform us when the Master sent something. The function that will be executed is OnDataRecv and is declared below. We put it in output mode.

//Registers the callback function that will be executed when
//this Slave receives data from the Master. //The function in this case is called OnDataRecv esp_now_register_recv_cb(OnDataRecv);

//For each gpio on gpios array for(int i=0; i

Step 14: ESPNowSlave.ino - InitESPNow

Once again we work with the InitESPNow function, this time on the Slave. Again, if all is correct, it will print "Success." Otherwise, it will print "Error.”

void InitESPNow() {
//If the initialization was successful if (esp_now_init() == ESP_OK) { Serial.println("ESPNow Init Success"); } //If there was an error else { Serial.println("ESPNow Init Failed"); ESP.restart(); } }

Step 15: ESPNowSlave.ino - OnDataRecv

We also resumed the function that serves as a callback there at Slave to let us know when something came from the Master. We copy the source MAC address to a string and show the MAC address that was the origin of the message. For each pin, we put the value received from the Master in the respective output.

//Callback function that tells us when data from Master is received
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) { char macStr[18]; //Copies the sender Mac Address to a string snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); //Prints it on Serial Monitor Serial.print("Received from: "); Serial.println(macStr); Serial.println("");

//For each gpio for(int i=0; i<gpioCount; i++){

//Sets its output to match the received value digitalWrite(gpios[i], data[i]); } }

Step 16: ESPNowSlave.ino - Loop

Here, we don’t have to do anything on the Loop. Whenever something comes from the Master, the OnDataRecv function runs automatically, since we added it as callback using the esp_now_register_recv_cb function.

//We don't do anything on the loop.
//Everytime something comes from Master //the OnDataRecv function is executed automatically //because we added it as callback using esp_now_register_recv_cb void loop() { }