11. Networking and Communications
This week I implemented wireless communication between two ESP32 development boards using ESP-NOW — Espressif's peer-to-peer, connection-less radio protocol. Because our lab's Snapmaker CNC mill currently lacks a stable workholding fixture, I could not mill a custom PCB this week. Instead I used two off-the-shelf ESP32 DevKit boards and designed the supporting circuits in Cirkuit Designer.
The system works as follows: pressing a push-button on Board A (the sender) causes it to broadcast an ESP-NOW packet to Board B (the receiver), which then commands a SG90 servo to rotate 90 °. Releasing the button sends another packet and the servo returns to 0 °.
Student's checklist – 4 / 7
- ☐ Linked to the group assignment page
- ☐ Documented the project and what I learned from implementing networking / communication
- ☐ Explained the programming process(es) I used
- ☐ Ensured and documented that addressing for boards works
- ☐ Outlined problems and how I fixed them
- ✓ Included design files (Cirkuit Designer circuits)
- ☐ Included a hero shot of the network / communications setup
Group Assignment
As a group we surveyed multiple communication protocols and interfaces available in the lab — including UART, I²C, SPI, Wi-Fi (TCP/IP), and ESP-NOW — and compared their speed, range, power consumption, and ease of use. The full group documentation is on the lab page:
Week 11 Group Assignment – Networking and Communications (Chaihuo Lab)
Communication Protocol Comparison
Before choosing a protocol, I compared the main options available on the ESP32 platform. The table below summarises the key characteristics of each method and explains why I selected ESP-NOW for this project.
| Method | Common Pins | Wired / Wireless | Typical Use | Speed | Range | Good For | Main Limitation |
|---|---|---|---|---|---|---|---|
| UART / Serial | TX, RX | Wired | Board-to-board, PC debugging, modules like GPS / DFPlayer | Medium | Short | Simple communication between two devices | Usually one-to-one |
| I²C | SDA, SCL | Wired | Sensors, OLEDs, RTC, multiple devices | Medium | Short | Many devices on just 2 signal wires | Short distance, address conflicts possible |
| SPI | MOSI, MISO, SCK, CS | Wired | Displays, SD cards, fast sensors, radio modules | Fast | Short | High-speed communication | Uses more pins |
| Wi-Fi | Built into ESP32 / ESP8266 | Wireless | IoT, web server, remote control, sending data online | Fast | Medium | Internet, phone / web dashboard | Higher power use |
| Bluetooth / BLE | Built into ESP32 | Wireless | Phone control, apps, nearby device connection | Medium | Short | Easy phone-to-board communication | Shorter range than Wi-Fi |
| Radio (RF) | Often via SPI modules | Wireless | Remote control, board-to-board wireless | Varies | Medium to long | No Wi-Fi needed | Needs extra module |
| CAN Bus | CAN TX/RX + transceiver | Wired | Automotive, robotics, robust multi-device systems | Medium | Long | Reliable in noisy environments | Extra hardware needed |
| ESP-NOW ✓ | ESP32 / ESP8266 wireless | Wireless | Direct ESP-to-ESP communication | Fast | Medium | No router needed, low latency | Mostly for ESP family only |
Why I chose ESP-NOW — and ruled out the alternatives
My project needs two ESP32 boards to communicate in real time: a button press on Board A must move the servo on Board B with no noticeable delay. Here is why I rejected each alternative:
- Wi-Fi — requires a router (or one board acting as a software access point). Connection setup takes seconds, and even after connecting, the TCP/IP stack adds latency that makes servo responses feel sluggish. Wi-Fi is the right choice when you need internet access or a web dashboard, but not for fast local control.
- Bluetooth / BLE — designed primarily for phone-to-board communication. Setting up a board-to-board BLE connection requires a GATT server/client pairing process that is significantly more complex than ESP-NOW. Typical BLE round-trip latency is 15–50 ms, compared to <5 ms for ESP-NOW. For a robotic arm where smooth motion matters, this difference is noticeable.
- Radio (RF modules such as nRF24L01 or LoRa) — would require soldering an external SPI module to each board, increasing wiring complexity and cost. Since the ESP32 already has a built-in 2.4 GHz radio that ESP-NOW can use directly, adding external RF hardware would be redundant.
- UART / I²C / SPI (wired) — perfectly valid for short distances, but a physical cable between the two boards defeats the purpose of demonstrating networking. The assignment specifically asks for board-to-board communication, and a wireless link is more interesting to document and demonstrate.
ESP-NOW wins because: both boards are already ESP32s (no extra hardware),
the latency is <5 ms (servo feels instant), no router or infrastructure is needed, and
the API is straightforward — register a peer by MAC address and call
esp_now_send(). For a robotic arm lamp that needs real-time local control,
it is the right tool for the job.
Note: if the lamp later needs phone app control, I would add BLE on top of ESP-NOW. If it needs to log data to a server, I would add Wi-Fi. ESP-NOW and Wi-Fi can even run simultaneously on the same ESP32.
What is the first principle behind ESP-NOW?
At the most fundamental level, ESP-NOW works because two ESP32 boards use their built-in 2.4 GHz Wi-Fi radio hardware to send and receive specially formatted wireless frames directly, without needing a router or a normal Wi-Fi network connection.
So the core idea is:
- ESP-NOW is not a completely different radio system.
- It uses the same Wi-Fi hardware inside the ESP32, but instead of connecting to a router and using TCP/IP, it sends small direct packets from one ESP32 to another.
First-principles view: what is wireless communication?
Wireless communication is simply this:
- One device converts digital data (0s and 1s) into electromagnetic signals.
- Those signals are transmitted through the air using a radio frequency.
- Another device listening on the same frequency receives the signal.
- It converts the signal back into digital data.
For ESP32, this happens using its 2.4 GHz Wi-Fi radio. So when two ESP32 boards communicate with ESP-NOW, they are really doing this:
- one ESP32 uses its antenna and Wi-Fi hardware to radiate a signal
- the other ESP32 listens for that signal
- if the message format matches ESP-NOW and the address matches, it accepts the data
How do two ESP32 boards communicate without a router?
Normally, Wi-Fi communication looks like this:
Device → Router → Network → Another device
But ESP-NOW skips the router. Instead, it works more like this:
ESP32 A → direct wireless packet through the air → ESP32 B
So ESP-NOW is a peer-to-peer communication method. That is why it is fast and lightweight.
What happens step by step?
Here is the real process:
On ESP32 A:
- Wi-Fi hardware is turned on.
- ESP-NOW is initialized.
- ESP32 B is added as a peer using its MAC address.
- A program calls
esp_now_send(). - The ESP32 packages the data into a special wireless frame.
- Its antenna transmits that frame through the air.
On ESP32 B:
- Its Wi-Fi hardware is listening on the same channel.
- It receives the frame.
- It checks whether the address matches.
- If valid, the received data is passed to the receive callback function.
So from first principles: ESP-NOW is direct device-to-device wireless packet delivery using Wi-Fi radio hardware, but without traditional networking layers.
Why is ESP-NOW so useful?
Because it avoids a lot of extra overhead.
With normal Wi-Fi, you usually need:
- a router
- connection setup
- an IP address
- a full network protocol stack
With ESP-NOW, you can just send short data directly.
That makes it very useful for:
- sensor nodes
- remote controls
- robots
- smart lamps
- communication between multiple ESP32 boards
System Architecture
Push-button on GPIO 4
Built-in LED feedback
Peer MAC address
No router needed
SG90 servo on GPIO 18
Serial debug output
I chose ESP-NOW over standard Wi-Fi for three reasons:
- No router required — the two boards talk directly, making the setup fully standalone.
- Very low latency — packets are delivered in < 5 ms, so the servo feels instantaneous.
- Simple API — after registering a peer by MAC address, sending data is a single
esp_now_send()call.
Circuit Design (Cirkuit Designer)
Because I could not mill a custom PCB this week, I used Cirkuit Designer to design and simulate both circuits before wiring them on breadboards. The schematics are shown below.
Board A — Button Sender
A momentary push-button is wired between GPIO 4 and GND. GPIO 4 is configured with the ESP32's internal pull-up resistor, so it reads HIGH at rest and LOW when pressed. The built-in LED (GPIO 2) blinks to confirm each transmission.
Board B — Servo Receiver
The SG90 servo signal wire is connected to GPIO 18, which supports LEDC PWM on the ESP32. The servo is powered from the board's 5 V pin (USB-sourced), with a 100 µF capacitor across the supply to absorb the inrush current when the servo stalls.
Board Addressing
ESP-NOW uses the standard Wi-Fi MAC address of each ESP32 as its unique network identifier. There is no IP layer or DHCP — the sender simply registers the receiver's 6-byte MAC address as a "peer" and sends packets directly to it.
How I found the MAC addresses
I uploaded the small sketch below to each board and read the MAC address from the Serial Monitor at 115200 baud:
get_mac_address.ino
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
Serial.print("MAC Address: ");
Serial.println(WiFi.macAddress());
}
void loop() {}
The addresses I recorded are:
| Board | Role | MAC Address | GPIO used |
|---|---|---|---|
| Board A | Sender (button) | XX:XX:XX:XX:XX:XX (replace with your value) |
GPIO 4 (button), GPIO 2 (LED) |
| Board B | Receiver (servo) | XX:XX:XX:XX:XX:XX (replace with your value) |
GPIO 18 (servo PWM) |
Board B's MAC address is hard-coded into Board A's firmware as the
receiverMac[] array (see source code below). This ensures that Board A only
sends to the correct peer and no other ESP32 in range can accidentally receive the packets.
Programming Process
Both sketches were written in Arduino IDE 2 with the esp32 by Espressif Systems board package (v3.x). The ESP-NOW API is included in the package; no extra library is needed.
-
1
Find MAC addresses — Upload
get_mac_address.inoto each board and record the output in the Serial Monitor. -
2
Write the sender sketch (Board A) — Initialise Wi-Fi in station mode,
initialise ESP-NOW, register Board B as a peer using its MAC, then in
loop()poll the button and callesp_now_send()on state changes. -
3
Write the receiver sketch (Board B) — Initialise ESP-NOW and register
an
OnDataRecvcallback. Inside the callback parse the message and move the servo accordingly. - 4 Flash and test — Upload Board B's sketch first (receiver must be ready), then Board A. Open the Serial Monitor on Board B to confirm packets are arriving.
Board A – sender.ino (Button → ESP-NOW)
#include <esp_now.h>
#include <WiFi.h>
// ── Replace with Board B's actual MAC address ──────────────────────────────
uint8_t receiverMac[] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
// ──────────────────────────────────────────────────────────────────────────
const int BUTTON_PIN = 4;
const int LED_PIN = 2;
typedef struct Message {
bool buttonPressed;
} Message;
Message outgoing;
void onSent(const uint8_t *macAddr, esp_now_send_status_t status) {
Serial.print("Send status: ");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "OK" : "FAIL");
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_register_send_cb(onSent);
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, receiverMac, 6);
peer.channel = 0;
peer.encrypt = false;
esp_now_add_peer(&peer);
Serial.println("Board A ready — press button to trigger servo");
}
bool lastState = HIGH;
void loop() {
bool current = digitalRead(BUTTON_PIN);
if (current != lastState) {
lastState = current;
outgoing.buttonPressed = (current == LOW); // LOW = pressed (pull-up)
digitalWrite(LED_PIN, outgoing.buttonPressed ? HIGH : LOW);
esp_now_send(receiverMac, (uint8_t *)&outgoing, sizeof(outgoing));
Serial.print("Button ");
Serial.println(outgoing.buttonPressed ? "PRESSED → sending" : "RELEASED → sending");
}
delay(20); // simple debounce
}
Board B – receiver.ino (ESP-NOW → Servo)
#include <esp_now.h>
#include <WiFi.h>
#include <ESP32Servo.h> // install "ESP32Servo" library by Kevin Harrington
const int SERVO_PIN = 18;
Servo myServo;
typedef struct Message {
bool buttonPressed;
} Message;
void onDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
Message incoming;
memcpy(&incoming, data, sizeof(incoming));
Serial.print("Packet received. buttonPressed = ");
Serial.println(incoming.buttonPressed);
if (incoming.buttonPressed) {
myServo.write(90); // rotate to 90 °
Serial.println("Servo → 90°");
} else {
myServo.write(0); // return to 0 °
Serial.println("Servo → 0°");
}
}
void setup() {
Serial.begin(115200);
myServo.attach(SERVO_PIN);
myServo.write(0);
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed");
return;
}
esp_now_register_recv_cb(onDataRecv);
Serial.println("Board B ready — waiting for packets");
}
void loop() {
// All work is done in the onDataRecv callback
}
What I Learned
The biggest insight from this week was that networking does not require complex infrastructure. ESP-NOW's peer-to-peer model means the two boards can communicate across a room with no router, no DHCP, and no TCP/IP stack overhead. Once I understood that it is essentially just "send a byte array to this MAC address", the API became very approachable.
Hero Shot
The two ESP32 boards communicating: pressing the button on Board A (left) causes the servo on Board B (right) to rotate to 90 °.
Design Files
- Board A – Button Sender schematic (Cirkuit Designer)
- Board B – Servo Receiver schematic (Cirkuit Designer)
(Files will be uploaded once the Cirkuit Designer exports are finalised.)