diff --git a/CLAUDE.md b/CLAUDE.md index b3b740e..b83ef65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/README.md b/README.md index 5618cde..60bcc3d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/esp32/README.md b/src/esp32/README.md index a2ea866..1b68a46 100644 --- a/src/esp32/README.md +++ b/src/esp32/README.md @@ -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 diff --git a/src/esp32/libs.h b/src/esp32/libs.h index f4bc75f..a2e9765 100644 --- a/src/esp32/libs.h +++ b/src/esp32/libs.h @@ -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; diff --git a/src/esp32/main.cpp b/src/esp32/main.cpp index 1ba7236..05d17e4 100644 --- a/src/esp32/main.cpp +++ b/src/esp32/main.cpp @@ -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 diff --git a/src/nano/README.md b/src/nano/README.md index 9cf7643..a5b7026 100644 --- a/src/nano/README.md +++ b/src/nano/README.md @@ -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. |