Public Access
Fix shifted pictures: non-blocking Nano drawing, frame check, refresh settings
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>
This commit is contained in:
1 parent
58a7cb03c2
commit
090c2bf8ae
45 files changed
+1265
-157
No files matched your search
@@ -0,0 +1,241 @@
|
||||
// Tests for the real display driver (lib/BROSE9323, Nano build) on the PC, with the
|
||||
// simulated panel of test/driver_mocks: pio test -e test_driver
|
||||
//
|
||||
// What matters most: drawing never blocks loop() (the Nano must keep reading the serial
|
||||
// port, or bytes are lost and the picture ends up shifted), and the dots end up right.
|
||||
|
||||
#include <unity.h>
|
||||
|
||||
#include "../../lib/BROSE9323/BROSE9323.cpp"
|
||||
|
||||
static const int W = 84, H = 19;
|
||||
|
||||
// Like the user's settings: 3 pulses of 200 us with a 300 us gap
|
||||
static BROSE9323 *make()
|
||||
{
|
||||
BROSE9323 *d = new BROSE9323(W, H, 28, 200);
|
||||
d->setGap(300);
|
||||
d->setPulses(3);
|
||||
panel = FakePanel(); // before begin(): it sets the pins
|
||||
d->begin();
|
||||
return d;
|
||||
}
|
||||
|
||||
// Runs update() until the frame and the refresh are done. Fails when one call takes
|
||||
// longer than max_us (fake time): then loop() would not read the serial port in time.
|
||||
static int finish(BROSE9323 &d, uint32_t max_us = 3000)
|
||||
{
|
||||
int calls = 0;
|
||||
while (d.busy() || d.refreshing())
|
||||
{
|
||||
uint32_t start = fake_micros;
|
||||
d.update();
|
||||
uint32_t took = fake_micros - start;
|
||||
if (took > max_us)
|
||||
{
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "update() took %u us", (unsigned)took);
|
||||
TEST_FAIL_MESSAGE(msg);
|
||||
}
|
||||
TEST_ASSERT_LESS_THAN(1000000, ++calls);
|
||||
}
|
||||
return calls;
|
||||
}
|
||||
|
||||
static void assertPanelShows(BROSE9323 &d)
|
||||
{
|
||||
for (int x = 0; x < W; x++)
|
||||
for (int y = 0; y < H; y++)
|
||||
if (panel.dot[x][y] != d.shownPixel(x, y))
|
||||
{
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "dot %d,%d differs", x, y);
|
||||
TEST_FAIL_MESSAGE(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawPattern(BROSE9323 &d, int seed)
|
||||
{
|
||||
for (int x = 0; x < W; x++)
|
||||
for (int y = 0; y < H; y++)
|
||||
d.drawPixel(x, y, ((x * 7 + y * 3 + seed) % 5) == 0);
|
||||
}
|
||||
|
||||
static int dotsFlippedAtLeast(int n)
|
||||
{
|
||||
int count = 0;
|
||||
for (int x = 0; x < W; x++)
|
||||
for (int y = 0; y < H; y++)
|
||||
count += panel.flips[x][y] >= n;
|
||||
return count;
|
||||
}
|
||||
|
||||
void setUp() {}
|
||||
void tearDown() {}
|
||||
|
||||
void test_first_frame_flips_every_dot_without_blocking()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
drawPattern(*d, 0);
|
||||
uint32_t start = fake_micros;
|
||||
d->display();
|
||||
TEST_ASSERT_LESS_THAN(3000, fake_micros - start); // display() itself returns at once
|
||||
TEST_ASSERT_TRUE(d->busy());
|
||||
int calls = finish(*d);
|
||||
TEST_ASSERT_GREATER_THAN(100, calls); // spread over many loop() passes
|
||||
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_instant_frame_flips_only_the_changed_dots_with_all_pulses()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
drawPattern(*d, 0);
|
||||
d->display();
|
||||
finish(*d);
|
||||
panel.clearCounts();
|
||||
|
||||
d->drawPixel(10, 5, !d->shownPixel(10, 5));
|
||||
d->drawPixel(70, 18, !d->shownPixel(70, 18));
|
||||
d->display(false, true);
|
||||
finish(*d);
|
||||
TEST_ASSERT_EQUAL(6, panel.strobes); // 2 dots, 3 pulses each
|
||||
TEST_ASSERT_EQUAL(3, panel.flips[10][5]);
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_effects_end_with_the_right_image()
|
||||
{
|
||||
for (uint8_t effect = EFFECT_SPARKLE; effect < EFFECT_COUNT; effect++)
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
d->setEffect(effect, 500);
|
||||
drawPattern(*d, 0);
|
||||
d->display();
|
||||
finish(*d);
|
||||
assertPanelShows(*d);
|
||||
drawPattern(*d, 2);
|
||||
d->display();
|
||||
finish(*d, 5000);
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
}
|
||||
|
||||
void test_refresh_flips_every_dot_once_without_blocking()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
drawPattern(*d, 0);
|
||||
d->display();
|
||||
finish(*d);
|
||||
panel.clearCounts();
|
||||
|
||||
d->startRefresh();
|
||||
TEST_ASSERT_TRUE(d->refreshing());
|
||||
TEST_ASSERT_FALSE(d->busy()); // not a frame: loop() may take new frames meanwhile
|
||||
int calls = finish(*d);
|
||||
TEST_ASSERT_GREATER_THAN(100, calls);
|
||||
TEST_ASSERT_EQUAL(W * H, panel.strobes); // one pulse each, nothing changed
|
||||
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_refresh_pauses_while_a_frame_arrives_and_then_finishes()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
drawPattern(*d, 0);
|
||||
d->display();
|
||||
finish(*d);
|
||||
panel.clearCounts();
|
||||
|
||||
d->startRefresh();
|
||||
d->update(); // a part of the dots
|
||||
int before = panel.strobes;
|
||||
TEST_ASSERT_GREATER_THAN(0, before);
|
||||
TEST_ASSERT_LESS_THAN(W * H, before);
|
||||
|
||||
// A new frame is being received: the refresh must not show half of it
|
||||
bool old = d->shownPixel(83, 18);
|
||||
d->drawPixel(83, 18, !old);
|
||||
for (int i = 0; i < 50; i++)
|
||||
d->update();
|
||||
TEST_ASSERT_EQUAL(before, panel.strobes);
|
||||
TEST_ASSERT_EQUAL(old, panel.dot[83][18]);
|
||||
|
||||
d->display(false, true);
|
||||
finish(*d);
|
||||
assertPanelShows(*d);
|
||||
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1)); // the refresh got to the end
|
||||
TEST_ASSERT_FALSE(d->refreshing());
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_refresh_waits_for_a_running_effect()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
d->setEffect(EFFECT_WIPE, 1000);
|
||||
drawPattern(*d, 0);
|
||||
d->display();
|
||||
finish(*d);
|
||||
drawPattern(*d, 1);
|
||||
d->display();
|
||||
d->startRefresh();
|
||||
panel.clearCounts();
|
||||
// The wipe goes left to right; the refresh would start at the left too and finish
|
||||
// before the wipe reaches the right side if it ran at the same time
|
||||
while (d->busy())
|
||||
d->update();
|
||||
TEST_ASSERT_TRUE(d->refreshing());
|
||||
finish(*d);
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_interrupted_first_frame_still_reaches_every_dot()
|
||||
{
|
||||
BROSE9323 *d = make();
|
||||
drawPattern(*d, 0);
|
||||
d->display(); // after boot: every dot
|
||||
for (int i = 0; i < 20; i++)
|
||||
d->update();
|
||||
TEST_ASSERT_LESS_THAN(W * H, dotsFlippedAtLeast(1));
|
||||
|
||||
// The next frame (scrolling text) arrives before it is done
|
||||
d->drawPixel(0, 0, !d->shownPixel(0, 0));
|
||||
d->display(false, true);
|
||||
finish(*d);
|
||||
TEST_ASSERT_EQUAL(W * H, dotsFlippedAtLeast(1));
|
||||
assertPanelShows(*d);
|
||||
delete d;
|
||||
}
|
||||
|
||||
void test_crc_of_equal_images_is_equal()
|
||||
{
|
||||
BROSE9323 *a = make(), *b = make();
|
||||
a->fillScreen(1); // also sets the unused bits at the end of each row
|
||||
for (int x = 0; x < W; x++)
|
||||
for (int y = 0; y < H; y++)
|
||||
b->drawPixel(x, y, 1);
|
||||
TEST_ASSERT_EQUAL_HEX16(a->crc(), b->crc());
|
||||
b->drawPixel(40, 9, 0);
|
||||
TEST_ASSERT_NOT_EQUAL(a->crc(), b->crc());
|
||||
delete a;
|
||||
delete b;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_first_frame_flips_every_dot_without_blocking);
|
||||
RUN_TEST(test_instant_frame_flips_only_the_changed_dots_with_all_pulses);
|
||||
RUN_TEST(test_effects_end_with_the_right_image);
|
||||
RUN_TEST(test_refresh_flips_every_dot_once_without_blocking);
|
||||
RUN_TEST(test_refresh_pauses_while_a_frame_arrives_and_then_finishes);
|
||||
RUN_TEST(test_refresh_waits_for_a_running_effect);
|
||||
RUN_TEST(test_interrupted_first_frame_still_reaches_every_dot);
|
||||
RUN_TEST(test_crc_of_equal_images_is_equal);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user