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
+113
-38
@@ -8,6 +8,7 @@
|
||||
#include "Arduino.h"
|
||||
#include "BROSE9323.h"
|
||||
#include "EEPROM.h"
|
||||
#include "buffer_crc.h" // the real one, from lib/BROSE9323
|
||||
#include "frame_commands.h" // the real one, from lib/BROSE9323
|
||||
|
||||
#include <cstdlib>
|
||||
@@ -117,16 +118,15 @@ void test_display_instant_frame()
|
||||
TEST_ASSERT_EQUAL(1, display.last("display")->b); // without the effect
|
||||
}
|
||||
|
||||
void test_refresh_waits_for_idle_after_instant_frames()
|
||||
void test_refresh_starts_between_instant_frames()
|
||||
{
|
||||
// The driver pauses the refresh while frames arrive, so it may start at any time
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
send("D1\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display")); // no full redraw: more frames follow
|
||||
fake_millis += 3000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.calls.back().a);
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(0, display.last("display")->a); // the frame is not forced
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh"));
|
||||
}
|
||||
|
||||
void test_lowercase_and_uppercase_commands()
|
||||
@@ -455,19 +455,6 @@ void test_invalid_effect_is_rejected()
|
||||
TEST_ASSERT_TRUE(replied("ERR"));
|
||||
}
|
||||
|
||||
void test_no_full_redraw_while_an_effect_runs()
|
||||
{
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
display.effect_running = true;
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display")); // only the frame itself
|
||||
display.effect_running = false;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.last("display")->a);
|
||||
}
|
||||
|
||||
// Every effect order visits each dot exactly once
|
||||
static void checkSequence(uint8_t effect, uint8_t w, uint8_t h, uint16_t seed)
|
||||
{
|
||||
@@ -560,15 +547,16 @@ void test_sparkle_order_is_scrambled()
|
||||
// Periodic full redraw
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_refresh_waits_for_end_of_frame()
|
||||
void test_refresh_when_due()
|
||||
{
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
send("s000001\n");
|
||||
TEST_ASSERT_EQUAL(0, display.count("display"));
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.calls.back().a); // forced full redraw after the frame
|
||||
fake_millis += 119999;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
fake_millis += 1;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh"));
|
||||
TEST_ASSERT_EQUAL(0, display.count("display")); // no frame, the driver does it
|
||||
}
|
||||
|
||||
void test_refresh_only_once_per_interval()
|
||||
@@ -576,18 +564,19 @@ void test_refresh_only_once_per_interval()
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
send("D\n");
|
||||
display.calls.clear();
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh"));
|
||||
}
|
||||
|
||||
void test_refresh_when_serial_is_idle()
|
||||
void test_running_refresh_is_not_restarted()
|
||||
{
|
||||
last_refresh = fake_millis;
|
||||
fake_millis += 120000;
|
||||
display.refresh_running = true;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.last("display")->a);
|
||||
display.refresh_running = false;
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
}
|
||||
|
||||
void test_no_refresh_storm_after_millis_wraps()
|
||||
@@ -597,12 +586,92 @@ void test_no_refresh_storm_after_millis_wraps()
|
||||
fake_millis += 5000; // wraps past 0
|
||||
for (int i = 0; i < 5; i++)
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(5, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
|
||||
fake_millis += 120000;
|
||||
display.calls.clear();
|
||||
send("D\n");
|
||||
TEST_ASSERT_EQUAL(2, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh"));
|
||||
}
|
||||
|
||||
void test_refresh_interval_is_applied_and_stored()
|
||||
{
|
||||
send("I5\n");
|
||||
TEST_ASSERT_FALSE(replied("ERR"));
|
||||
fake_millis += 120000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
fake_millis += 180000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh"));
|
||||
|
||||
// Kept over a power cycle
|
||||
setup();
|
||||
last_refresh = fake_millis;
|
||||
display.calls.clear();
|
||||
fake_millis += 120000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
send("I2\n");
|
||||
}
|
||||
|
||||
void test_refresh_interval_zero_never_refreshes()
|
||||
{
|
||||
send("I0\n");
|
||||
TEST_ASSERT_FALSE(replied("ERR"));
|
||||
fake_millis += 24UL * 3600 * 1000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(0, display.count("refresh"));
|
||||
send("I2\n");
|
||||
}
|
||||
|
||||
void test_refresh_interval_out_of_range_is_rejected()
|
||||
{
|
||||
send("I1441\n");
|
||||
send("I-1\n");
|
||||
send("I\n");
|
||||
TEST_ASSERT_TRUE(replied("ERR"));
|
||||
fake_millis += 120000;
|
||||
loop();
|
||||
TEST_ASSERT_EQUAL(1, display.count("refresh")); // still every 2 minutes
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Checksum
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void test_checksum_reply()
|
||||
{
|
||||
display.crc_value = 0x0A1F;
|
||||
send("C\n");
|
||||
TEST_ASSERT_EQUAL_STRING("C 0A1F\n", Serial.out.c_str());
|
||||
display.crc_value = 0xFFFF;
|
||||
send("c\n");
|
||||
TEST_ASSERT_EQUAL_STRING("C 0A1F\nC FFFF\n", Serial.out.c_str());
|
||||
}
|
||||
|
||||
void test_checksum_follows_the_frame()
|
||||
{
|
||||
// The reply comes after the frame's commands ran (they are handled in order)
|
||||
display.crc_value = 0x1234;
|
||||
send("B0000040118\nD\nC\n");
|
||||
TEST_ASSERT_EQUAL(1, display.count("display"));
|
||||
TEST_ASSERT_EQUAL(1, display.count("pixel"));
|
||||
TEST_ASSERT_EQUAL_STRING("C 1234\n", Serial.out.c_str());
|
||||
}
|
||||
|
||||
void test_crc_counts_only_the_dots()
|
||||
{
|
||||
// 84 x 19: 11 bytes per row, the top 4 bits of each 11th byte are not dots
|
||||
uint8_t a[11 * 19] = {}, b[11 * 19] = {};
|
||||
TEST_ASSERT_EQUAL_HEX16(bufferCrc(a, 84, 19), bufferCrc(b, 84, 19));
|
||||
for (int y = 0; y < 19; y++)
|
||||
b[y * 11 + 10] = 0xF0; // what fillScreen(1) leaves there
|
||||
TEST_ASSERT_EQUAL_HEX16(bufferCrc(a, 84, 19), bufferCrc(b, 84, 19));
|
||||
b[5] = 0x01; // one dot
|
||||
TEST_ASSERT_NOT_EQUAL(bufferCrc(a, 84, 19), bufferCrc(b, 84, 19));
|
||||
// CRC-16/CCITT-FALSE check value
|
||||
const uint8_t check[9] = {'1', '2', '3', '4', '5', '6', '7', '8', '9'};
|
||||
TEST_ASSERT_EQUAL_HEX16(0x29B1, bufferCrc(check, 72, 1));
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -618,7 +687,7 @@ int main()
|
||||
RUN_TEST(test_fill);
|
||||
RUN_TEST(test_display);
|
||||
RUN_TEST(test_display_instant_frame);
|
||||
RUN_TEST(test_refresh_waits_for_idle_after_instant_frames);
|
||||
RUN_TEST(test_refresh_starts_between_instant_frames);
|
||||
RUN_TEST(test_lowercase_and_uppercase_commands);
|
||||
RUN_TEST(test_command_split_over_several_reads_with_crlf);
|
||||
RUN_TEST(test_print_text_with_newline_escape);
|
||||
@@ -648,15 +717,21 @@ int main()
|
||||
RUN_TEST(test_pulses_out_of_range_are_rejected);
|
||||
RUN_TEST(test_effect_is_applied_and_stored);
|
||||
RUN_TEST(test_invalid_effect_is_rejected);
|
||||
RUN_TEST(test_no_full_redraw_while_an_effect_runs);
|
||||
RUN_TEST(test_every_effect_visits_each_dot_once);
|
||||
RUN_TEST(test_wipe_goes_left_to_right_and_center_starts_in_the_middle);
|
||||
RUN_TEST(test_sparkle_order_is_scrambled);
|
||||
|
||||
RUN_TEST(test_refresh_waits_for_end_of_frame);
|
||||
RUN_TEST(test_refresh_when_due);
|
||||
RUN_TEST(test_refresh_only_once_per_interval);
|
||||
RUN_TEST(test_refresh_when_serial_is_idle);
|
||||
RUN_TEST(test_running_refresh_is_not_restarted);
|
||||
RUN_TEST(test_no_refresh_storm_after_millis_wraps);
|
||||
RUN_TEST(test_refresh_interval_is_applied_and_stored);
|
||||
RUN_TEST(test_refresh_interval_zero_never_refreshes);
|
||||
RUN_TEST(test_refresh_interval_out_of_range_is_rejected);
|
||||
|
||||
RUN_TEST(test_checksum_reply);
|
||||
RUN_TEST(test_checksum_follows_the_frame);
|
||||
RUN_TEST(test_crc_counts_only_the_dots);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in new issue
Block a user