Move the Nano link to Serial2, update READMEs

- ESP32 talks to the Nano on UART2 (GPIO 17 TX / GPIO 16 RX); Serial (USB)
  is now free for debug output and boot messages no longer reach the Nano
- README: wiring table, actual schedule (clock from 08:00), TODO section
  for the rewiring

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 00:16:24 +02:00
1 parent c67a90ba09
commit 75f677a2eb
6 files changed
+58 -29

No files matched your search

+5 -5
View File
@@ -23,17 +23,17 @@ See README.md for the overview.
- **Nano RAM is tight** (ATmega328P, 2 KB). The frame buffers are allocated with
`calloc`. Avoid `String`, large stack buffers and new globals in Nano code. Put constant
data in `PROGMEM`.
- The Nano and the ESP32 share one UART. On the ESP32, `Serial` is the display link, so
**don't print debug output on the ESP32's `Serial`**: the Nano would read it as
commands. The Nano ignores unknown command bytes, but text that starts with
s/S/F/D/P/B/R/T will be run as a command.
- The ESP32 talks to the Nano on `Serial2` (`nanoSerial` in `libs.h`, GPIO 17 TX / GPIO 16
RX). `Serial` (UART0/USB) is free for debug output. Never write debug text to
`nanoSerial`: the Nano runs lines that start with s/S/F/D/P/B/R/T as commands.
- Open hardware TODOs are listed in the README `TODO` section. Update it when one is done.
- `src/esp32/secrets.h` holds the real credentials and is gitignored. Never commit it.
Add new secrets to `secrets.example.h` as placeholders.
- The relay (ESP32 GPIO 13, `LOW` = on) powers both the panels and the Nano. Each time it
switches on, the Nano cold-boots and then sends `READY`. The ESP's `relayOn()` waits for
that message, at most `NANO_BOOT_TIMEOUT`. The timeout also covers the case where the
Nano's TX isn't wired to the ESP's RX.
- `relayOff()` ends `Serial` and sets the TX pin to input. Otherwise the idle-high TX line
- `relayOff()` ends `nanoSerial` and sets its TX pin to input. Otherwise the idle-high TX line
powers the Nano through its RX pin. Never send display commands while `relay_on` is
false.
- The Nano keeps no state over a power cycle except the flip time in EEPROM. The driver
+33 -8
View File
@@ -68,21 +68,34 @@ pio device monitor -e nano # serial monitor
In VS Code you can also pick the environment in the PlatformIO status bar.
> **Tip:** The Nano and the ESP32 share a serial line. Disconnect the ESP32 TX → Nano RX
> wire before you flash the Nano over USB, or the upload may fail.
> **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.
## 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. Use a 5 V → 3.3 V voltage divider (e.g. 1 kΩ / 2 kΩ), because the ESP32 is not 5 V tolerant. Optional: without it, the ESP waits a fixed 3 s after power-on. |
| GND | GND | Common ground |
| GPIO 13 | Relay input | Relay powers the panels and the Nano (`LOW` = on) |
## How it works
1. **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, starts NTP
and connects to MQTT.
fix before the main code runs again. It then switches the display relay on, waits for
the Nano's `READY` message (at most 3 s), starts NTP and connects to MQTT.
2. **Clock loop (every second).**
- **07:00–22:00:** Pikachu with a thought bubble showing `HH:MM`. A yawning Pikachu is
- **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 drawn on the quarter hour. In between, the relay
turns the display off.
- **Night:** the screen is cleared and power is cut after 30 seconds.
- **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.
3. **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.
@@ -91,3 +104,15 @@ In VS Code you can also pick the environment in the PlatformIO status bar.
to fix dots that did not flip.
The serial protocol is described in [src/nano/README.md](src/nano/README.md).
## TODO
- [ ] **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](#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.
- [ ] **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.
+6 -5
View File
@@ -3,8 +3,9 @@
PlatformIO environments: `esp32` (OTA) and `esp32_usb` (USB)
The ESP32 decides what is shown on the display. It never touches the panel itself. The
BROSE9323 driver is built with `-D ESPSERIAL`, so every GFX call is sent over `Serial`
(115200 baud) to the Nano.
BROSE9323 driver is built with `-D ESPSERIAL`, so every GFX call is sent to the Nano over
`Serial2` (UART2, GPIO 17 TX / GPIO 16 RX, 115200 baud). `Serial` (USB) is only used for
debug output. Wiring is described in the [main README](../../README.md#wiring-between-esp32-and-nano).
## Files
@@ -40,10 +41,10 @@ BROSE9323 driver is built with `-D ESPSERIAL`, so every GFX call is sent over `S
| Time | Display |
|------|---------|
| 07:00 – 17:00 | Pikachu and the clock, updated every minute |
| 08:00 – 17:00 | Pikachu and the clock, updated every minute |
| 17:00 – 22:00 | Yawning Pikachu and the clock |
| 22:00 – 23:30 | Clock drawn only on the quarter hour; power is off in between |
| night | Screen cleared, power off |
| 22:00 – 23:30 | Clock shown only during the minutes :00, :15, :30 and :45; power is off in between |
| 23:31 – 08:00 | Screen cleared, power off |
## Adding bitmaps
+4 -1
View File
@@ -45,7 +45,10 @@ bool power = true;
const int led1 = BUILTIN_LED; // Pin of the LED
const int relay = 13; // Pin of the relay, powers the panels and the Nano (LOW = on)
const int nano_tx = 1; // UART0 TX, wired to the Nano RX
// Link to the Nano on UART2. Serial (UART0) stays free for USB / debug output.
HardwareSerial &nanoSerial = Serial2;
const int nano_rx = 16; // UART2 RX, wired to the Nano TX (through a 5V -> 3.3V divider)
const int nano_tx = 17; // UART2 TX, wired to the Nano RX
bool relay_on = false;
// Max wait for the Nano's READY after power on (also the wait when Nano TX is not wired to our RX)
const uint32_t NANO_BOOT_TIMEOUT = 3000;
+8 -8
View File
@@ -14,9 +14,9 @@ void waitForNano()
uint32_t start = millis();
while (millis() - start < NANO_BOOT_TIMEOUT)
{
while (Serial.available())
while (nanoSerial.available())
{
char c = Serial.read();
char c = nanoSerial.read();
if (c == '\n')
{
line[len] = '\0';
@@ -39,10 +39,10 @@ void relayOn()
if (relay_on)
return;
Serial.begin(115200);
nanoSerial.begin(115200, SERIAL_8N1, nano_rx, nano_tx);
// drop old data (e.g. ERR replies) so only a new READY counts
while (Serial.available())
Serial.read();
while (nanoSerial.available())
nanoSerial.read();
digitalWrite(relay, LOW);
waitForNano();
@@ -55,8 +55,8 @@ void relayOff()
relay_on = false;
// An idle UART TX line is high and would power the Nano through its RX pin
Serial.flush();
Serial.end();
nanoSerial.flush();
nanoSerial.end();
pinMode(nano_tx, INPUT);
}
void turnPowerOff(uint32_t delay = 30000, bool _turn_hassio_power_off = false)
@@ -289,7 +289,7 @@ void setup()
pinMode(relay, OUTPUT);
relayOn();
display.begin(&Serial);
display.begin(&nanoSerial);
// display.setTiming(200);
// display.setTiming(180);
// display.setTiming(220); // works best so far
+2 -2
View File
@@ -77,5 +77,5 @@ pinout.
| ROW_0 … ROW_4 | A3, A4, A2, A0, A1 |
| ROW_RESET | D13 |
| ROW_SET | A5 |
| Serial RX | D0 (from ESP32 TX) |
| Serial TX | D1 (to ESP32 RX, through a 5 V → 3.3 V voltage divider). Optional: needed for `READY`. Without it, the ESP waits the full 3 s timeout. |
| Serial RX | D0 (from ESP32 GPIO 17, TX2) |
| Serial TX | D1 (to ESP32 GPIO 16, RX2, through a 5 V → 3.3 V voltage divider). Optional: needed for `READY`. Without it, the ESP waits the full 3 s timeout. |