- ESP32: NanoFlashBridge on TCP port 2323. A connection power cycles the Nano into its bootloader and bridges TCP <-> Serial2 at 57600 baud; the clock pauses during the session, 10 s idle timeout - tools/nano_net_upload.py: STK500v1 uploader over TCP (standard library only), with sync retries, write and read-back verification. avrdude 6.3 net: ports are broken on Windows, so avrdude is not used - New nano_ota environment: pio run -e nano_ota -t upload - README: usage, requirements, troubleshooting; TODO for hardware test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Flipdot Display
Firmware for an 84 × 19 Brose flipdot display (three 28 × 19 panels) that runs as a Home Assistant–controlled clock.
The system uses two microcontrollers:
┌──────────────┐ WiFi / MQTT ┌────────────────┐ UART 115200 ┌──────────────┐ GPIO ┌──────────────┐
│ Home │ ◄─────────────► │ ESP32 │ ──────────────► │ Arduino Nano │ ───────► │ Brose │
│ Assistant │ │ (controller) │ text commands │ (driver) │ │ flipdot │
└──────────────┘ └───────┬────────┘ └──────▲───────┘ │ panels 84x19 │
│ GPIO 13 │ └──────▲───────┘
▼ │ power │
┌─────────────┐ │ │
│ Relay │ ─────────────────────────┴─────────────────────────┘
└─────────────┘
The relay powers both the panels and the Nano. When the relay is off, the Nano is off too, and it boots again each time the relay switches on. Only the ESP32 runs all the time.
| Part | Folder | What it does |
|---|---|---|
| Arduino Nano | src/nano | Drives the panel's row and column electronics directly. Keeps a frame buffer and flips only the dots that changed. Takes simple text commands over serial. |
| ESP32 | src/esp32 | Connects to WiFi, gets the time over NTP, shows a clock with Pikachu bitmaps, exposes switches to Home Assistant over MQTT, switches display power with a relay and supports OTA updates. |
| BROSE9323 driver | lib/BROSE9323 | Shared Adafruit_GFX display driver. On the Nano it toggles GPIO pins. On the ESP32 (-D ESPSERIAL) it turns every drawing call into a serial command for the Nano. |
Because the driver is an Adafruit_GFX subclass on both boards, the ESP32 can use the
normal GFX API (print, drawBitmap, fillScreen, ...). Each call is sent to the
Nano and drawn there.
Repository layout
platformio.ini one PlatformIO project, multiple environments
lib/BROSE9323/ shared flipdot driver (used by both targets)
src/nano/ Arduino Nano firmware
src/esp32/ ESP32 firmware
secrets.example.h template for WiFi / MQTT credentials
test/test_nano/ unit tests for the Nano firmware (run on the PC)
test/mocks/ fake Arduino, EEPROM and display driver for the tests
tools/nano_net_upload.py uploader for flashing the Nano over WiFi
archive/ old, unused sketches kept for reference
CLAUDE.md project notes for Claude Code
Building and flashing
Install PlatformIO (the VS Code extension is easiest) and open this folder.
- Copy
src/esp32/secrets.example.htosrc/esp32/secrets.hand fill in your WiFi and MQTT details.secrets.his gitignored. - Check the COM ports and the OTA IP in
platformio.ini. - Build or upload:
| Environment | Board | Upload method |
|---|---|---|
nano |
Arduino Nano (ATmega328P) | USB (COM10) |
nano_ota |
Arduino Nano (ATmega328P) | Over WiFi through the ESP32 (10.1.1.198). See Flashing the Nano over WiFi. |
esp32 |
ESP32 DevKit v1 | OTA over WiFi (10.1.1.198) |
esp32_usb |
ESP32 DevKit v1 | USB (COM11). Use it for the first flash or for recovery. |
pio run # build nano + esp32
pio run -e nano -t upload # flash the Nano over USB
pio run -e nano_ota -t upload # flash the Nano over WiFi, through the ESP32
pio run -e esp32 -t upload # flash the ESP32 over the air
pio run -e esp32_usb -t upload # flash the ESP32 over USB
pio device monitor -e nano # serial monitor
pio test -e native # run the Nano unit tests on the PC
The unit tests need gcc/g++ on your PATH. On Windows you can get it from
MSYS2 or Strawberry Perl.
In VS Code you can also pick the environment in the PlatformIO status bar.
Tip: The ESP32 → Nano wire goes to the Nano's RX pin, which the Nano's USB chip also uses. Disconnect that wire before you flash the Nano over USB, or the upload may fail. Flashing over WiFi (
nano_ota) doesn't have this problem.
Flashing the Nano over WiFi
The ESP32 can flash the Nano for you, so you don't need a USB cable at the display:
pio run -e nano_ota -t upload
What happens:
- The uploader (tools/nano_net_upload.py) connects to the ESP32 on TCP port 2323.
- The ESP32 pauses the clock and switches the relay off for 1 second, then on again. This restarts the Nano, and its bootloader starts.
- The ESP32 switches its link to the bootloader's 57600 baud and passes bytes between WiFi and the Nano.
- The uploader writes the new firmware and reads it back to verify it.
- When the uploader disconnects, the ESP32 switches the relay off. The clock powers the Nano on again with the new firmware and continues.
The display goes dark for the duration of the upload, which takes a few seconds.
Requirements
- The reply wire must be connected: Nano D1 (TX) → voltage divider → ESP32 GPIO 16. The uploader needs the bootloader's answers.
- The ESP32 must be running firmware with the flash bridge (this version or newer).
- The Nano must have the old bootloader (the
nanoatmega328board, 57600 baud), which also runs at power-on. The newer Optiboot bootloader only runs after a reset-pin reset, so the relay power cycle would not start it.
Settings
- ESP32 IP address:
upload_portin thenano_otaenvironment inplatformio.ini. You can add a port number:10.1.1.198:2323. For a one-time different address, usepio run -e nano_ota -t upload --upload-port 10.1.1.50. - Port, baud rate and power-off time:
NANO_FLASH_PORT,NANO_BOOTLOADER_BAUDandNANO_POWER_OFF_TIMEinsrc/esp32/libs.h.
If it fails
| Message | Cause |
|---|---|
can't connect to ... |
Wrong IP address, or the ESP32 is offline or running older firmware |
no answer from the Nano bootloader |
Reply wire missing, or the Nano has Optiboot |
bad reply to command ... / verify failed |
Connection problem. Try again. A failed upload can leave the Nano without working firmware, but the bootloader itself is never overwritten, so flashing again (over WiFi or USB) always fixes it. |
The ESP32 ends the session after 10 seconds without data, so the clock always resumes, even if the uploader is interrupted.
Security: anyone on your network can connect to port 2323 and flash the Nano, just like the ESP32's own OTA has no password. Keep the display on a trusted network.
Wiring between ESP32 and Nano
The ESP32 talks to the Nano on UART2 (Serial2, 115200 baud). The ESP32's own
Serial (UART0, USB) is free for debug output.
| ESP32 | Nano | Notes |
|---|---|---|
| GPIO 17 (TX2) | D0 (RX) | Display commands |
| GPIO 16 (RX2) | D1 (TX) | READY and ERR replies, and bootloader answers when flashing over WiFi. Use a 5 V → 3.3 V voltage divider (e.g. 1 kΩ / 2 kΩ), because the ESP32 is not 5 V tolerant. Without it, the ESP waits a fixed 3 s after power-on, and nano_ota doesn't work. |
| GND | GND | Common ground |
| GPIO 13 | Relay input | Relay powers the panels and the Nano (LOW = on) |
How it works
- ESP32 boot. The ESP32 connects to WiFi and starts ArduinoOTA. After a crash or
unexpected reset it stays in an OTA panic window for 15 seconds, so you can flash a
fix before the main code runs again. It then switches the display relay on, waits for
the Nano's
READYmessage (at most 3 s), starts NTP and connects to MQTT. - Clock loop (every second).
- 08:00–22:00: Pikachu with a thought bubble showing
HH:MM. A yawning Pikachu is shown from 17:00. - 22:00–23:30: the clock is only shown during the minutes :00, :15, :30 and :45. 30 seconds after each of those minutes, the relay turns the display off.
- 23:31–08:00: the screen is cleared and power is cut after 30 seconds.
- 08:00–22:00: Pikachu with a thought bubble showing
- Home Assistant switches (MQTT discovery):
FlipDot Display Clock: off shows a Nintendo logo instead of the clock.FlipDot Display Power: off clears the display and cuts power through the relay.
- Nano. The Nano reads commands from the serial line and updates its frame buffer.
On
Dit flips only the dots that changed. Every 2 minutes it redraws the whole screen to fix dots that did not flip.
The serial protocol is described in src/nano/README.md.
TODO
- Rewire the Nano link to UART2. The firmware now uses
Serial2(GPIO 16/17) instead ofSerial(GPIO 1/3). Move the ESP32 TX wire from GPIO 1 to GPIO 17 before you flash this firmware, or the display gets no data. See Wiring between ESP32 and Nano. - Add the reply wire: Nano D1 (TX) → voltage divider → ESP32 GPIO 16 (RX2). With it,
the ESP sends the first frame as soon as the Nano is ready, instead of after 3 s, and
the Nano can be flashed over WiFi (
nano_ota). - Test flashing over WiFi on the real hardware. The uploader is tested against a simulated bootloader, not yet against the real Nano. Check especially that the old bootloader starts after the relay power cycle.
- Check the boot timing. Build the Nano with
MEASURE_REFRESHto see how long a full redraw takes. If the reply wire isn't fitted, check that 3 s (NANO_BOOT_TIMEOUT) is enough for the Nano to boot.