ESP-NOW Communication Between ESP32 Master and ESP8266 Slave Using a Custom MAC Address

(Universal method — works with any ESP8266 board)

ESP-NOW is one of the fastest and most reliable ways to send data wirelessly between ESP32/ESP8266 devices.
Unlike Wi-Fi or MQTT, it requires no router, no pairing, and has almost zero latency (1–3 ms).

In this guide we build a complete Master/Slave setup:

  • ESP32 → Master (Transmitter)
  • ESP8266 → Slave (Receiver + GC9A01 round display)

But we use a special technique that makes this system universal:


⭐ Key Concept: Using a Custom MAC Address

Normally ESP-NOW requires the sender to target the physical MAC address of the receiver.
This means every slave board has to be discovered and paired manually.

But in this project:

✔ The ESP32 does not use the real MAC of the ESP8266

✔ Instead, it sends to a fixed custom MAC address

✔ The ESP8266 changes its Wi-Fi MAC address to match this custom MAC

This line is the magic:

wifi_set_macaddr(STATION_IF, &newMACAddress[0]);

You are overriding the ESP8266’s real MAC address, so it “pretends” to be the MAC the ESP32 expects.

✔ No MAC lookup

✔ No board-specific configuration

✔ Any ESP8266 you flash becomes a valid receiver

This is the cleanest, fastest way to build universal ESP-NOW systems.


1. ESP32 MASTER CODE (Sender)

This ESP32 sends a struct of 5 variables to the custom MAC address.

#include <esp_now.h>
#include <WiFi.h>

uint8_t broadcastAddress[] = {0x18, 0xFE, 0x34, 0xDA, 0x82, 0x31};

typedef struct struct_message {
  int a;
  long b;
  float c;
  float d;
  bool e;
} struct_message;

struct_message myData;
int a;
esp_now_peer_info_t peerInfo;
void OnDataSent(const wifi_tx_info_t *info, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_register_send_cb(OnDataSent);

  
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
}

void loop() {
  myData.a = a++;
  myData.b = 44868;
  myData.c = 12.8;
  myData.d = 14.6;
  myData.e = false;

 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 data");

  delay(100);
}

2. ESP8266 SLAVE CODE (Receiver with GC9A01 Display Or Serial Port)

It:

  • Changes its MAC address
  • Initializes ESP-NOW
  • Receives the struct
  • Displays live data on the round GC9A01 display

Comment out // the Serial port lines for GC9A01 to work

#include <ESP8266WiFi.h>
#include <espnow.h>

// --- Struct Definition ---
typedef struct struct_message {
    int a;
    long b;
    float c;
    float d;
    bool e;
} struct_message;

struct_message myData;

// Custom MAC – same as ESP32 target MAC
uint8_t newMACAddress[] = {0x18, 0xFE, 0x34, 0xDA, 0x82, 0x31};

// --- Display Setup ---
#include <Arduino_GFX_Library.h>
#if defined(DISPLAY_DEV_KIT)
Arduino_GFX *gfx = create_default_Arduino_GFX();
#else /* !defined(DISPLAY_DEV_KIT) */
/* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */
//Arduino_DataBus *bus = new Arduino_ESP32SPI(27 /* DC */, 5 /* CS */, 18 /* SCK */, 23 /* MOSI */, -1 /* MISO */, VSPI /* spi_num */);
Arduino_DataBus *bus = new Arduino_ESP8266SPI(1 /* DC */, 16 /* CS */);
/* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */
Arduino_GFX *gfx = new Arduino_GC9A01(bus, 1 /* RST */, 0 /* rotation */, true /* IPS */);
#endif /* !defined(DISPLAY_DEV_KIT) */

// --- Receive Callback ---
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
  digitalWrite(LED_BUILTIN, LOW);
  memcpy(&myData, incomingData, sizeof(myData));

 Serial.printf("Bytes received: %d\n", len);
 Serial.printf("Int: %d\n", myData.a);
 Serial.printf("Long: %ld\n", myData.b);
 Serial.printf("Float: %.2f\n", myData.c);
 Serial.printf("Float: %.2f\n", myData.d);
 Serial.printf("Bool: %d\n\n", myData.e);
}

void setup() {
  gfx->begin();
  gfx->fillScreen(RGB565_BLACK);

  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  // Override the ESP8266 MAC address!
  wifi_set_macaddr(STATION_IF, &newMACAddress[0]);

  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  Serial.println("ESP-NOW Ready");
  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);
  screen1();
  delay(100);
}

void screen1() {
 
   gfx->fillScreen(RGB565_BLACK); 
   gfx->setTextColor(RGB565_WHITE,RGB565_BLACK);
    gfx->setTextSize(4 , 4 , 0 /* pixel_margin */);
  gfx->setCursor(80, 185);
    gfx->println(myData.a);
     
 
}

3. How the Custom-MAC Method Works (Simple Explanation)

✔ ESP32 → Sends to 18:FE:34:DA:82:31

✔ ESP8266 → Changes its MAC to 18:FE:34:DA:82:31

✔ Therefore the ESP32 always sends to the correct receiver

The boards no longer need:

  • MAC lookup
  • Unique pairing
  • Per-device configuration
  • Setup steps per board

You flash any ESP8266 → it instantly becomes the correct slave.

This is perfect for:

  • Dashboards
  • Instrument clusters
  • Remote sensor nodes
  • Cloned or replicated devices

4. Summary

FeatureBenefit
Custom MAC addressUniversal compatibility
MAC override on ESP8266No pairing or discovery
Struct transmissionFast, organized data
ESP-NOWUltra-low latency
GC9A01 displayPerfect for gauges & dashboards

Newsletter Updates

Enter your email address below and subscribe to our newsletter

Leave a Reply

Your email address will not be published. Required fields are marked *