Public Access
The Nano's periodic full redraw blocked for ~0.8 s. A frame that arrived meanwhile overflowed its 64-byte serial buffer; lost bytes in a bitmap shift the rest of the image (picture moved up), and diff frames never repaired it. - Nano: all drawing (frames, effects, the dot refresh) runs from update() in ~1 ms slices; the refresh pauses while a frame is received or shown - New commands: C (reply with the image checksum) and I (refresh minutes, EEPROM) - ESP32: NanoMonitor asks for the checksum after frames and sends the whole image again on a mismatch, missing answer or Nano restart; counts ERR replies. Periodic whole-image resync. - Settings: "Dot refresh" and "Image resync" intervals (settings v5, v4 records are migrated) - System tab: loop timing, free memory, frame check counts - test_driver: the real driver against a simulated panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
9.2 KiB
Markdown
143 lines
9.2 KiB
Markdown
# 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](src/nano/README.md) | 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](src/esp32/README.md) | Connects to WiFi, gets the time over NTP, shows dynamic pages (a background per time of day with a clock, the date, Home Assistant values or text) or your own bitmaps, has a [web UI](docs/web-ui.md), exposes switches to Home Assistant over MQTT, switches display power with a relay and supports OTA updates. |
|
||
| **ESP32 recovery app** | [src/recovery](src/recovery/README.md) | Small second app: WiFi setup page and OTA, so the ESP32 stays reachable when the clock firmware fails. |
|
||
| **BROSE9323 driver** | [lib/BROSE9323](lib/BROSE9323/README.md) | 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.
|
||
|
||
## Documentation
|
||
|
||
| Page | About |
|
||
|------|-------|
|
||
| [Web UI](docs/web-ui.md) | The web page of the display, with screenshots of every tab |
|
||
| [Pages](docs/pages.md) | Pictures of the dynamic pages |
|
||
| [Home Assistant](docs/home-assistant.md) | Entities, values on pages, rules |
|
||
| [How it works](docs/how-it-works.md) | Boot, the clock and the schedule, the Nano |
|
||
| [Wiring](docs/wiring.md) | Between the ESP32, the Nano and the relay |
|
||
| [Flashing the Nano over WiFi](docs/nano-ota.md) | `nano_ota`, bootloaders, troubleshooting |
|
||
| [Recovery app](docs/recovery.md) | The ESP32's second app: WiFi setup and OTA, one-time USB installation |
|
||
| [Nano firmware](src/nano/README.md) | The serial protocol and the Nano's behaviour |
|
||
| [ESP32 firmware](src/esp32/README.md) | The files, keeping OTA alive, tests |
|
||
|
||
## 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 clock firmware
|
||
secrets.example.h template for WiFi / MQTT credentials
|
||
web/ the web UI
|
||
src/recovery/ ESP32 recovery app: WiFi setup page + OTA
|
||
partitions.csv ESP32 flash layout (recovery app + two clock firmware slots)
|
||
test/test_nano/ unit tests for the Nano firmware (run on the PC)
|
||
test/test_esp32/ unit tests for the ESP32 logic (run on the PC)
|
||
test/test_driver/ unit tests for the display driver with a simulated panel
|
||
test/mocks/ fake Arduino, EEPROM and display driver for the Nano tests
|
||
test/driver_mocks/ fake Arduino and Adafruit GFX for the driver tests
|
||
tools/ uploader for flashing the Nano over WiFi, font and picture scripts
|
||
docs/ documentation (see below)
|
||
Pictures/ photos, pictures of the pages and screenshots of the web UI
|
||
examples/ example pages to import in the web UI
|
||
archive/ old, unused sketches kept for reference
|
||
CLAUDE.md project notes for Claude Code
|
||
```
|
||
|
||
## Building and flashing
|
||
|
||
Install [PlatformIO](https://platformio.org/) (the VS Code extension is easiest) and open
|
||
this folder.
|
||
|
||
1. Copy `src/esp32/secrets.example.h` to `src/esp32/secrets.h` and fill in your WiFi and
|
||
MQTT details. `secrets.h` is gitignored.
|
||
2. Check the COM ports and the OTA IP in `platformio.ini`.
|
||
3. 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](docs/nano-ota.md). |
|
||
| `esp32` | ESP32 DevKit v1 | OTA over WiFi (`10.1.1.198`) |
|
||
| `esp32_usb` | ESP32 DevKit v1 | USB (`COM11`), into `ota_0`. Normally not needed. |
|
||
| `recovery` | ESP32 DevKit v1 | USB (`COM11`), one-time install of the [recovery app](docs/recovery.md) |
|
||
|
||
```sh
|
||
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 test_nano -e test_esp32 -e test_driver # run the unit tests on the PC
|
||
```
|
||
|
||
The unit tests need `gcc`/`g++` on your PATH. On Windows you can get it from
|
||
[MSYS2](https://www.msys2.org/) 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.
|
||
|
||
## TODO
|
||
|
||
- [X] **Install the recovery app** (one-time USB flash, see
|
||
[One-time installation](docs/recovery.md#one-time-installation-usb)). Until then the ESP32 runs the old
|
||
flash layout: the clock firmware works, but without the recovery app; the Home
|
||
Assistant sensor shows "no recovery app".
|
||
- [X] **Test the web UI on the real hardware**: live view, uploading and drawing a bitmap,
|
||
live mode, choosing a page, settings, Home Assistant settings (restart).
|
||
- [ ] **Test the dynamic pages on the real hardware**: the Clock page after the update
|
||
(same as before, yawning from 17:00), a new page with date and Full height clock, an
|
||
uploaded font, a Home Assistant value (token in the Home Assistant tab).
|
||
- [ ] **Test the recovery app on the real hardware**: the Home Assistant button, the setup
|
||
WiFi with a wrong WiFi password, 3 quick power cycles, and an OTA upload into it.
|
||
|
||
- [X] **Rewire the Nano link to UART2.** The firmware now uses `Serial2` (GPIO 16/17)
|
||
instead of `Serial` (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](docs/wiring.md).
|
||
- [X] **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`).
|
||
- [X] **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.
|
||
- [ ] **Tune the flip settings on the real hardware.** Flash both the Nano and the ESP32
|
||
(the old Nano ignores the new `G`/`N`/`E` commands). With the test patterns (Settings),
|
||
try flip gap 0/200/500 µs and 1 or 2 pulses, and check that the refresh effects look
|
||
right and that the off image is complete before the power is cut.
|
||
- [ ] **Check the boot timing.** Build the Nano with `MEASURE_REFRESH` to 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.
|
||
- [ ] **Watch the frame check.** After a few days, look at System, "Frame check": any
|
||
"wrong" or "no answer" counts mean bytes still get lost somewhere; "Nano restarted"
|
||
means power dips (e.g. while many dots flip).
|