ESP-NOW pair: two boards talking
Two ESP32-C3 boards share the virtual air and ping-pong ESP-NOW messages, each showing its own TX/RX tally and the last message received on its OLED. Watch the glowing packets fly between the chips, follow every frame in the 📡 Sniffer, and click either board for its radio state. One script runs on both; each derives its peer from its own MAC.

How it works
ESP-NOW is Espressif's connectionless radio protocol: no access point, no DHCP, just frames between MACs. One symmetric script runs on both C3 boards; each derives the other's address by flipping bits in its own MAC (the bench hands out consecutive MACs ending :c1 and :c2) and self-assigns the name A or B. Both loop: send a ping, poll for a reply, count TX and RX on their OLEDs, and print every received message. Watch the canvas: the frames visibly fly between the boards.
What's on the bench
- 2× Battery
- 2× ESP32-C3
- 2× OLED 128×64
How it's wired
- Battery · pos→ESP32-C3 · vin
- Battery · neg→ESP32-C3 · gnd
- ESP32-C3 · 3v3→OLED 128×64 · vcc
- ESP32-C3 · gnd2→OLED 128×64 · gnd
- ESP32-C3 · g9→OLED 128×64 · scl
- ESP32-C3 · g8→OLED 128×64 · sda
- Battery · pos→ESP32-C3 · vin
- Battery · neg→ESP32-C3 · gnd
- ESP32-C3 · 3v3→OLED 128×64 · vcc
- ESP32-C3 · gnd2→OLED 128×64 · gnd
- ESP32-C3 · g9→OLED 128×64 · scl
- ESP32-C3 · g8→OLED 128×64 · sda
The code
This MicroPython script runs on the emulated board every boot; edit it in the Code tab.
from machine import I2C, Pin import framebuf, time i = I2C(0, scl=Pin(9), sda=Pin(8)) buf = bytearray(1024) fb = framebuf.FrameBuffer(buf, 128, 64, framebuf.MONO_VLSB) for c in b'\xae\xd5\x80\xa8\x3f\xd3\x00\x40\x8d\x14\x20\x00\xa1\xc8\xda\x12\x81\xcf\xd9\xf1\xdb\x40\xa4\xa6\xaf': i.writeto(0x3c, bytes([0, c])) def show(): i.writeto(0x3c, b'\x00\x21\x00\x7f\x22\x00\x07') i.writeto(0x3c, b'\x40' + buf) import network, espnow w = network.WLAN(network.STA_IF) w.active(True) e = espnow.ESPNow() e.active(True) mac = w.config('mac') peer = bytearray(mac) peer[5] ^= 0x03 # …:c1 <-> …:c2 — the other board on the bench e.add_peer(bytes(peer)) me = 'A' if mac[5] == 0xc1 else 'B' print('board', me, 'ready, sending to', bytes(peer)) tx = 0; rx = 0; last = "" while True: e.send(bytes(peer), 'ping %d from %s' % (tx, me)); tx += 1 host, msg = e.recv(500) if msg: rx += 1; last = msg.decode() print('board', me, 'received:', last) fb.fill(0) fb.text('ESP-NOW board ' + me, 0, 0) fb.hline(0, 10, 128, 1) fb.text('TX sent: %d' % tx, 0, 16) fb.text('RX recv: %d' % rx, 0, 30) fb.text(last[:16], 0, 46) show() time.sleep_ms(500)
Try this
- Watch both OLEDs: TX and RX counters climb together, half a second apart.
- Open both serial tabs: 'board A received: ping 3 from B' and vice versa.
- Open the 📡 Sniffer: ESP-NOW frames are 802.11 action frames, addressed MAC to MAC.
- Edit the message text in the Code tab; the change appears on the other board’s screen.


