- Nano: B data is drawn fast enough for the serial line. A hex digit arrives every 87 us; with a division and four drawPixel() calls per digit a lit digit took about 105 us, so dense frames (all yellow, the panda bitmap) overflowed the 64 byte receive buffer: frames not shown or wrong, "Line too long", frame check repairs that failed the same way. Now the parser counts columns and rows and BROSE9323::drawDots() sets the 4 dots (about 20 us). - ESP32: Power off and the start of the night draw the off image also when slow mode had the power off already (it powers on for it, once) - Tests: drawDots() against drawPixel(), off image in slow mode - Version 2.19.3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.8 KiB
Arduino Nano: flipdot driver
PlatformIO environment: nano
The Nano is connected directly to the Brose panel electronics. It keeps an 84 × 19 frame buffer and takes drawing commands over serial (115200 baud). It has no clock or network logic.
Files
| File | Contents |
|---|---|
main.cpp |
setup() / loop(), feeds serial bytes to the parser, periodic full redraw |
config.h |
All settings: debug options, display size, flip time limits, timings |
command_parser.h/.cpp |
CommandParser: the serial protocol, one method per command |
settings.h/.cpp |
Flip time stored in EEPROM |
The display driver itself is in lib/BROSE9323.
Tests
pio test -e test_nano runs the unit tests in test/test_nano
on the PC. They feed serial data to the firmware and check what it asks the display to do:
every command, bitmaps, bad input, flip time storage and the full-redraw timing. The
Arduino core, EEPROM and the display driver are replaced by fakes in
test/mocks.
Behaviour
- Boot: the Nano loads the flip settings from EEPROM, sets up the pins and sends
READY. It doesn't flip any dots at boot. The relay powers the Nano, so it boots every time the display is switched on. - First
display()after boot redraws every dot, because the Nano doesn't know which state the dots were left in. display(): compares the new buffer with the last one that was shown and strobes only the dots that changed, each withNpulses (default 2: the second one catches dots that missed the first). Every pulse is followed by a pause (G, default 200 µs) so the coil supply can recover.- Refresh effect (
E): with0(default) a frame appears column by column as fast as possible. The other effects (sparkle, wipe, rain, from the middle out, diagonal, spiral, flip all then reveal, push, random) spread the flips over the effect time. Push shifts the whole picture one column per step, so it flips many dots and can take longer than the effect time. With flag 1 only small changes (at most 1/10 of the dots, like the clock's minutes) get the effect. After boot the dot state is unknown, so push falls back to a wipe. They run in small steps fromloop(), so serial data keeps being read; while a new frame is being drawn into the buffer the effect pauses, and the nextDstarts it again. The orders come fromDotSequencein lib/BROSE9323/dot_sequence.h. - Nothing blocks. Every frame (also instant ones,
D1) is flipped fromupdate()in slices of about 1 ms, soloop()keeps reading the serial port. A blocking redraw used to overflow the 64-byte receive buffer; the lost bytes shifted the rest of a bitmap (the picture moved up and left) and nothing repaired it. Bitmap data is drawn as fast as it arrives too (one hex digit every 87 µs); dense images used to be slower than that. - Every 2 minutes (
Icommand, stored in EEPROM, 0 = never) it flips every dot once more, with one pulse, to fix dots that got stuck. This also runs in slices, only while no frame is being received or shown, and continues where it was after a frame. - Flip pulse length (µs, 50–2000) is set with the
Tcommand and stored in EEPROM, so it is kept after a reboot. The default is 200 µs.G,NandEare stored the same way (addresses insettings.cpp; values that were never written fall back to the defaults inconfig.h).
Debug options
Uncomment these in config.h:
| Define | Effect |
|---|---|
DEBUG |
Print unknown command bytes on the serial port |
MEASURE_REFRESH |
At boot, time a full redraw and print the duration in ms |
Serial protocol
Each command is one ASCII line that ends with \n (\r is ignored). The Nano
collects a whole line before it runs the command. If a line is broken or unknown, it is
skipped and the next line starts clean. Coordinates are 2-digit hex. Color is 0
(black) or 1 (yellow). The ESP32 driver (ESPSERIAL) only uses s, S, F, D, T,
G, N and E.
| Cmd | Format | Meaning |
|---|---|---|
s |
sXXYYc |
Set pixel in the buffer |
S |
SXXYYc |
Set pixel and flip it right away (direct mode) |
F |
Fc |
Fill the whole buffer |
D |
D or D1 |
Push the buffer to the display (changed dots only). D1: a frame of moving content (scrolling text, seconds), shown without the refresh effect. |
P |
PXXYYcsTEXT |
Print text at (XX,YY), color c, size s. A literal \n (backslash, n) in TEXT starts a new line. The whole line can be at most 64 characters. |
B |
BXXYYWWHHcHEX... |
Draw a bitmap. WW must be a multiple of 4. Pixel data is one hex digit per 4 pixels, row by row. The data is drawn while it arrives, so it is not limited to 64 characters. |
R |
R |
Print the buffer as ASCII art on the serial port |
T |
Tnnn |
Set the flip pulse length in µs (decimal, 50–2000), stored in EEPROM |
G |
Gnnn |
Pause after each pulse in µs (decimal, 0–5000), stored in EEPROM |
N |
Nn |
Pulses for each changed dot (1–3), stored in EEPROM |
I |
Innn |
Minutes between full redraws (decimal, 0–1440, 0 = never), stored in EEPROM |
C |
C |
Reply C xxxx: checksum of the image received so far (CRC-16/CCITT of the buffer, only the dots, lib/BROSE9323/buffer_crc.h), 4 hex digits |
E |
Ee,nnnn[,f] |
Refresh effect e (0 instant, 1 sparkle, 2 wipe, 3 rain, 4 from the middle out, 5 diagonal, 6 spiral, 7 flip all then reveal, 8 push, 9 random), its time in ms (100–10000) and flags f (1 = only small changes, default 0), stored in EEPROM |
Replies on the Nano's TX pin:
READYafter booting. The ESP waits for it before sending the first frame. Later (while it is on) it means the Nano restarted, e.g. after a power dip.C xxxxforC. The ESP sendsCafter frames (at most every 3 s) and compares the answer with its own copy; a wrong or missing answer makes it send the whole image again.ERR: ...for invalid commands. The ESP counts them (web UI, System).
Example (what the ESP32 sends for one pixel and a refresh):
s0a051
D
Wiring (default pinout)
The pins are defined in lib/BROSE9323/BROSE9323.h.
If you use the PLCC adapter board, build with -D FLIPDOT_PLCC_ADAPTER to switch to its
pinout.
| Signal | Nano pin |
|---|---|
| ENABLE | D7 |
| ADDR_0 / 1 / 2 (panel select) | D3 / D5 / D6 |
| COL_DATA | D4 |
| COL_0 … COL_4 | D8, D10, D9, D11, D12 |
| ROW_0 … ROW_4 | A3, A4, A2, A0, A1 |
| ROW_RESET | D13 |
| ROW_SET | A5 |
| 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. |