Send only the changed dots of a frame to the Nano

Frames were sent as F0 plus one s command per yellow dot (thousands of
characters). During scrolling that overran the Nano's serial buffer, and
half-received frames redrew the whole screen. showBitmap() now sends two B
commands over the changed rectangle (dots off, dots on), or one B command for
a full frame, and the pacing counts the characters sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-15 21:14:48 +02:00
1 parent 89001ddbc6
commit 47ef65ee7d
8 files changed
+262 -25

No files matched your search

+4 -3
View File
@@ -49,9 +49,10 @@ const uint32_t POWER_OFF_DELAY = 30000;
// timeout (a read blocks loop() for at most about twice this)
const uint32_t HA_POLL_INTERVAL = 60000;
const uint32_t HA_HTTP_TIMEOUT = 1500;
// Moving dynamic pages (scrolling, seconds): sending a frame to the Nano takes about this
// long (a bitmap at 115200 baud), plus the flip time of the changed dots
const uint32_t FRAME_SEND_TIME = 60;
// Moving dynamic pages (scrolling, seconds): the next frame waits until the Nano received
// this one (characters per ms at NANO_BAUD) and flipped its changed dots, plus a margin
const uint32_t NANO_CHARS_PER_MS = NANO_BAUD / 10000;
const uint32_t FRAME_MARGIN = 20;
// ---------------------------------------------------------------------------
// Flashing the Nano over WiFi (see README.md, pio run -e nano_ota -t upload)
+13 -15
View File
@@ -10,18 +10,15 @@ FlipdotFrames::FlipdotFrames(BROSE9323 &display, PageResources &resources) : _di
{
}
// Only the dots that changed are sent (unless redraw: the Nano's image is unknown)
uint32_t FlipdotFrames::dynamicPage(const LocalTime &time, uint32_t now, bool redraw)
{
uint8_t bitmap[BITMAP_BYTES];
uint32_t wait = renderPage(_dynamic, time, now, _resources, bitmap);
bool moving = wait != 0;
if (!redraw && memcmp(bitmap, _last_dynamic, BITMAP_BYTES) == 0)
return wait;
// The Nano has no acknowledgement: wait until it has flipped the dots of this frame,
// or the next frame overflows its serial buffer
uint32_t busy = _flipMs(bitmap, redraw);
_drawFullScreen(bitmap, moving);
memcpy(_last_dynamic, bitmap, BITMAP_BYTES);
// The Nano has no acknowledgement: the next frame waits until it has received this one
// and flipped its dots, or the next frame overflows its serial buffer
uint32_t busy = _show(bitmap, !redraw, moving);
return moving && wait < busy ? busy : wait;
}
@@ -30,14 +27,17 @@ void FlipdotFrames::setDrive(uint16_t flip_time, const DriveSettings &drive)
_dot_us = (uint32_t)drive.pulses * (flip_time + drive.flip_gap);
}
// About how long the Nano takes to receive and show the bitmap (all: every dot flips)
uint32_t FlipdotFrames::_flipMs(const uint8_t *bitmap, bool all) const
// Sends the bitmap; returns about how long the Nano takes to receive and show it in ms
uint32_t FlipdotFrames::_show(const uint8_t *bitmap, bool changes_only, bool instant)
{
uint32_t dots = 0;
for (int y = 0; y < BITMAP_HEIGHT; y++)
for (int x = 0; x < BITMAP_WIDTH; x++)
dots += all || bitmapPixel(bitmap, x, y) != _display.shownPixel(x, y);
return FRAME_SEND_TIME + dots * _dot_us / 1000;
dots += bitmapPixel(bitmap, x, y) != _display.shownPixel(x, y);
size_t chars = _display.showBitmap(bitmap, changes_only, instant);
if (chars == 0)
return 0;
return chars / NANO_CHARS_PER_MS + dots * _dot_us / 1000 + FRAME_MARGIN;
}
void FlipdotFrames::noWifi(int hour, int minute)
@@ -86,9 +86,7 @@ void FlipdotFrames::setOffBitmap(const uint8_t *bitmap)
memset(_off, 0, BITMAP_BYTES);
}
void FlipdotFrames::_drawFullScreen(const uint8_t *bitmap, bool instant)
void FlipdotFrames::_drawFullScreen(const uint8_t *bitmap)
{
_display.fillScreen(0);
_display.drawBitmap(0, 0, bitmap, BITMAP_WIDTH, BITMAP_HEIGHT, 1);
_display.display(false, instant);
_show(bitmap, false, false);
}
+5 -6
View File
@@ -12,9 +12,9 @@ class FlipdotFrames : public Frames
public:
FlipdotFrames(BROSE9323 &display, PageResources &resources);
// The page set with setDynamicPage(), drawn for this time. Not sent when it looks the
// same as the last frame (unless redraw). Moving pages are sent without the refresh
// effect, and not faster than the Nano can flip the dots.
// The page set with setDynamicPage(), drawn for this time. Only the changed dots are
// sent (nothing when it looks the same), unless redraw. Moving pages are sent without
// the refresh effect, and not faster than the Nano can receive and flip them.
uint32_t dynamicPage(const LocalTime &time, uint32_t now, bool redraw) override;
// "No WiFi" with the time below it
void noWifi(int hour, int minute) override;
@@ -37,12 +37,11 @@ private:
BROSE9323 &_display;
PageResources &_resources;
DynamicPage _dynamic;
uint8_t _last_dynamic[BITMAP_BYTES] = {};
uint32_t _dot_us = 1000; // per changed dot: pulses x (flip time + gap)
uint8_t _page[BITMAP_BYTES] = {};
uint8_t _live[BITMAP_BYTES] = {};
uint8_t _off[BITMAP_BYTES] = {}; // all zero: blank
void _drawFullScreen(const uint8_t *bitmap, bool instant = false);
uint32_t _flipMs(const uint8_t *bitmap, bool all) const;
void _drawFullScreen(const uint8_t *bitmap);
uint32_t _show(const uint8_t *bitmap, bool changes_only, bool instant);
};