// 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 #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(); }