/* * 模块名称:屏板链路协议层(sb_proto)主机侧单元测试 * 模块功能:在 PC 上对 Drivers/BSP/ScreenLink/sb_proto.c 做离线验证。 * * 被测对象:真实源文件(以 #include 原样纳入,保证测的是生产代码而非副本): * ../../../Drivers/BSP/ScreenLink/sb_proto.c * 本层为**纯逻辑**(只依赖 stdint.h 与 string.h),故**无需任何桩**。 * * 为什么要主机侧单测(而不是只用台架): * 1. 协议层只有两种结局:屏板认 / 屏板不认。屏板不认时它的超时判定只看「收没收到一整包」、 * 不看 CRC,现场只表现为**画面不刷新**——在台架上这条现象与波特率错、时序错、线接反 * 完全同形,无法归因。主机侧把 CRC 与字节序钉死,才谈得上"发出去的就是对的"。 * 2. 帧 A 的坏帧分支(长度不对 / 地址不对 / 功能码不对 / CRC 错)在台架上要造噪声才可能出现, * 且一出现就被复位冲掉;主机侧是几行断言。 * 3. 帧 B 4208 B 的边界(填充段参与 CRC、LOGO 宽高装不下点阵)不占 MCU 的 RAM,毫秒级。 * * 覆盖点(对应 docs/产品需求/产品需求设计文档.md §5.16.2 帧结构与任务规划 1-1 判据): * TC-SBPROTO-001 CRC-16/MODBUS 教科书校验值:"123456789" -> 0x4B37 * TC-SBPROTO-002 CRC 与 Modbus RTU 教科书帧一致:01 03 00 00 00 0A -> 0xCDC5(线上 C5 CD) * TC-SBPROTO-003 查表法 == 逐位法(300 组伪随机长度/内容,钉住表被改错) * TC-SBPROTO-004 查表法边界:len 个字节与逐字节累积等价(前 1 字节 / 全 0 / 全 FF) * TC-SBPROTO-010 帧 A 实测向量:屏板发的周期召唤 01 03 01 00 F0 48 -> RT_CMD / key 0 * TC-SBPROTO-011 帧 A 实测向量:按键召唤 01 03 00 09 31 DE -> KEY_CMD / key 9 * TC-SBPROTO-012 帧 A 的 key:RT 命令下非 0 也按 0 收(不为此丢一帧) * TC-SBPROTO-013 帧 A 坏帧:CRC 错 / 长度 5 与 7 / 地址 0x02 / 功能码 0x04 / 命令类型 0x02 * TC-SBPROTO-014 帧 A 参数守卫:空指针两路;失败时不动 *p_out * TC-SBPROTO-020 帧 B 普通图:点阵逐字节、掩码小端、info[4..7] 为 0、填充段全 0、CRC 自校验 * TC-SBPROTO-021 帧 B 普通图:整帧重写(预填 0xAA 的缓冲被清干净)、重复组帧结果一致 * TC-SBPROTO-022 帧 B LOGO:宽高大端、魔数 0x89 0x45、info[6..7] 为 0、CRC 自校验 * TC-SBPROTO-023 帧 B LOGO 宽高守卫:161x159 与 160x161 被拒(按字节数判容量)、宽或高为 0 被拒 * TC-SBPROTO-024 帧 B 参数守卫:空指针三处 * TC-SBPROTO-030 帧 B 自校验:改点阵 / 改填充段 / 改 CRC 自身 / 长度不对 * TC-SBPROTO-040 屏板侧视角:用屏板 Check_CRC 的口径(高低互换后比对)复核本模块组的两种帧 * * 通过:全部 TC PASS(main 返回 0)。 * 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。 * * 创建日期:2026-10-29 */ #include #include #include /* ===================== 断言 ===================== */ static int s_total = 0, s_passed = 0, s_failed = 0; #define CHECK(cond, name) do { \ s_total++; \ if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \ else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \ } while (0) /* ===================== 被测代码 ===================== */ /* 原样纳入生产源文件(纯逻辑,无需桩) */ #include "sb_proto.c" /* ===================== 工具 ===================== */ /* 伪随机(自持实现,不依赖 rand() 的实现差异,换编译器也复现同一组向量) */ static uint32_t s_rng = 0x20261029U; static uint32_t rng_next(void) { s_rng = (s_rng * 1103515245U) + 12345U; return (s_rng >> 8) & 0x00FFFFFFU; } /* 逐位法 CRC-16/MODBUS:与被测查表法互为独立实现,用来抓表项被改错 */ static uint16_t crc16_bitwise(const uint8_t *p, uint16_t len) { uint16_t crc = 0xFFFFU; uint16_t i; uint8_t b; for (i = 0U; i < len; i++) { crc ^= (uint16_t)p[i]; for (b = 0U; b < 8U; b++) { if ((crc & 1U) != 0U) { crc = (uint16_t)((crc >> 1) ^ 0xA001U); } else { crc = (uint16_t)(crc >> 1); } } } return crc; } /* * 屏板 Check_CRC() 的等价物(照屏板固件 Middlewares/Modbus/CRC16.c 的口径): * 屏板的 Calculate_CRC() 返回的是高低字节互换后的「线上字」,所以它把帧尾两字节按 * 「前一个是高」拼起来再比。本函数只用 sb_crc16 的标准值复刻这一口径, * 用来回答"屏板会不会认这一帧"。 */ static int screen_board_accepts(const uint8_t *buf, uint16_t len) { uint16_t wire; uint16_t crc; if (len < 2U) { return 0; } wire = (uint16_t)(((uint16_t)buf[len - 2U] << 8) | (uint16_t)buf[len - 1U]); crc = sb_crc16(buf, (uint16_t)(len - 2U)); /* 标准值高低互换 == 屏板 Calculate_CRC() 的返回值 */ return ((uint16_t)((crc << 8) | (crc >> 8)) == wire) ? 1 : 0; } /* 按给定 4 字节组一帧帧 A(用生产代码自己的 CRC,再按线上顺序摆) */ static void make_frame_a(uint8_t *p_out, uint8_t addr, uint8_t func, uint8_t cmd, uint8_t key) { uint16_t crc; p_out[0] = addr; p_out[1] = func; p_out[2] = cmd; p_out[3] = key; crc = sb_crc16(p_out, (uint16_t)SB_FRAME_A_CRC_OFF); p_out[4] = (uint8_t)(crc & 0xFFU); p_out[5] = (uint8_t)((crc >> 8) & 0xFFU); } static uint8_t s_frame[SB_FRAME_B_LEN]; /* 4208 B,帧 B 输出缓冲 */ static uint8_t s_bmp[SB_FRAME_B_BMP_LEN]; /* 3200 B,点阵源 */ static void fill_bmp_pattern(void) { uint16_t i; for (i = 0U; i < SB_FRAME_B_BMP_LEN; i++) { s_bmp[i] = (uint8_t)((i * 37U) & 0xFFU); } } /* ===================== 用例 ===================== */ static void tc_crc_vectors(void) { static const uint8_t k_check[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9' }; static const uint8_t k_modbus[] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x0A }; uint16_t crc; crc = sb_crc16(k_check, (uint16_t)sizeof(k_check)); CHECK(crc == 0x4B37U, "TC-SBPROTO-001 CRC-16/MODBUS check value of '123456789' is 0x4B37"); crc = sb_crc16(k_modbus, (uint16_t)sizeof(k_modbus)); CHECK(crc == 0xCDC5U, "TC-SBPROTO-002 the textbook Modbus RTU frame yields 0xCDC5, so the wire bytes are C5 CD (low first)"); } static void tc_crc_table(void) { uint8_t buf[64]; uint16_t n; uint16_t i; int mismatch = 0; for (n = 0U; n < 300U; n++) { uint16_t len = (uint16_t)(rng_next() % 65U); for (i = 0U; i < len; i++) { buf[i] = (uint8_t)(rng_next() & 0xFFU); } if (sb_crc16(buf, len) != crc16_bitwise(buf, len)) { mismatch++; } } CHECK(mismatch == 0, "TC-SBPROTO-003 the table walk agrees with the bitwise reference on 300 pseudo-random buffers"); buf[0] = 0x00U; CHECK(sb_crc16(buf, 1U) == crc16_bitwise(buf, 1U), "TC-SBPROTO-004 a single 0x00 byte: table and bitwise agree (initial value is really 0xFFFF)"); for (i = 0U; i < 32U; i++) { buf[i] = 0xFFU; } CHECK(sb_crc16(buf, 32U) == crc16_bitwise(buf, 32U), "TC-SBPROTO-004b 32 bytes of 0xFF: table and bitwise agree"); } static void tc_frame_a_vectors(void) { /* 屏板 RS485_RefreshScreenCMD() 在 700000 8N1 上真实发出去的字节: * 周期召唤(type=RT_CMD=1, key=0)与按键召唤(type=KEY_CMD=0, key=9)。 * 这两条是拿屏板自己的算法算出来的线上字节,**不是**本模块算的。 */ static const uint8_t k_rt[] = { 0x01, 0x03, 0x01, 0x00, 0xF0, 0x48 }; static const uint8_t k_key[] = { 0x01, 0x03, 0x00, 0x09, 0x31, 0xDE }; sb_frame_a_t out; memset(&out, 0, sizeof(out)); CHECK(sb_frame_a_parse(k_rt, (uint16_t)sizeof(k_rt), &out) == BSP_OK, "TC-SBPROTO-010 the screen board periodic summon 01 03 01 00 F0 48 parses"); CHECK((out.cmd == SB_CMD_RT) && (out.key == 0U), "TC-SBPROTO-010b it reports RT_CMD with key 0"); memset(&out, 0, sizeof(out)); CHECK(sb_frame_a_parse(k_key, (uint16_t)sizeof(k_key), &out) == BSP_OK, "TC-SBPROTO-011 the screen board key summon 01 03 00 09 31 DE parses"); CHECK((out.cmd == SB_CMD_KEY) && (out.key == 9U), "TC-SBPROTO-011b it reports KEY_CMD with key 9"); /* 屏板会认本模块自己组出来的这两帧吗(同一口径反查) */ CHECK(screen_board_accepts(k_rt, (uint16_t)sizeof(k_rt)) == 1, "TC-SBPROTO-011c the board side accepts the periodic summon frame"); CHECK(screen_board_accepts(k_key, (uint16_t)sizeof(k_key)) == 1, "TC-SBPROTO-011d the board side accepts the key summon frame"); } static void tc_frame_a_lenient_key(void) { uint8_t buf[SB_FRAME_A_LEN]; sb_frame_a_t out; /* 屏板周期召唤本应带 key = 0;真带了非 0 也收下(丢一帧就等于丢一次刷新) */ make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0x2AU); memset(&out, 0, sizeof(out)); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_OK, "TC-SBPROTO-012 an RT frame carrying a non-zero key is still accepted"); CHECK(out.key == 0U, "TC-SBPROTO-012b its key is normalised to 0 (the key field only means something for KEY_CMD)"); /* KEY_CMD 的 key 原样透出,包括 0 与 0xFF */ make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_KEY, 0x00U); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_OK && out.key == 0x00U, "TC-SBPROTO-012c a KEY_CMD with key 0 is accepted and keeps 0"); make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_KEY, 0xFFU); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_OK && out.key == 0xFFU, "TC-SBPROTO-012d a KEY_CMD with key 0xFF is accepted and keeps 0xFF"); } static void tc_frame_a_reject(void) { uint8_t buf[SB_FRAME_A_LEN]; sb_frame_a_t out; sb_frame_a_t sentinel; /* CRC 错:改 CRC 低字节 / 改数据字节 */ make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); buf[4] ^= 0x01U; CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_CRC, "TC-SBPROTO-013 a flipped CRC byte is reported as CRC error"); make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); buf[2] ^= 0x02U; CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_CRC, "TC-SBPROTO-013b a flipped payload byte is reported as CRC error"); /* 长度不对 */ make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); CHECK(sb_frame_a_parse(buf, (uint16_t)(SB_FRAME_A_LEN - 1U), &out) == BSP_ERR_PARAM, "TC-SBPROTO-013c a 5-byte frame is rejected"); CHECK(sb_frame_a_parse(buf, (uint16_t)(SB_FRAME_A_LEN + 1U), &out) == BSP_ERR_PARAM, "TC-SBPROTO-013d a 7-byte frame is rejected (the idle interrupt may hand over a longer burst)"); /* 地址 / 功能码 / 命令类型不符(CRC 都是对的,纯粹是内容不对) */ make_frame_a(buf, 0x02U, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_PARAM, "TC-SBPROTO-013e slave address 0x02 is rejected even with a good CRC"); make_frame_a(buf, SB_ADDR, 0x04U, SB_CMD_RT, 0U); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_PARAM, "TC-SBPROTO-013f function code 0x04 is rejected even with a good CRC"); make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, 0x02U, 0U); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_PARAM, "TC-SBPROTO-013g command type 0x02 is rejected even with a good CRC"); /* 失败时不动 *p_out */ sentinel.cmd = 0x5AU; sentinel.key = 0xA5U; out = sentinel; make_frame_a(buf, 0x02U, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); (void)sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, &out); CHECK((out.cmd == 0x5AU) && (out.key == 0xA5U), "TC-SBPROTO-014 a failed parse leaves the caller result struct untouched"); } static void tc_frame_a_guard(void) { uint8_t buf[SB_FRAME_A_LEN]; sb_frame_a_t out; make_frame_a(buf, SB_ADDR, SB_FUNC_READ_HOLDING, SB_CMD_RT, 0U); CHECK(sb_frame_a_parse(0, (uint16_t)SB_FRAME_A_LEN, &out) == BSP_ERR_PARAM, "TC-SBPROTO-014b a null buffer is rejected"); CHECK(sb_frame_a_parse(buf, (uint16_t)SB_FRAME_A_LEN, 0) == BSP_ERR_PARAM, "TC-SBPROTO-014c a null result pointer is rejected"); } static void tc_frame_b_image(void) { uint16_t i; uint16_t crc; int bmp_ok = 1; int pad_ok = 1; fill_bmp_pattern(); CHECK(sb_frame_b_build_image(s_frame, s_bmp, 0xDEADBEEFU) == BSP_OK, "TC-SBPROTO-020 building an image frame succeeds"); for (i = 0U; i < SB_FRAME_B_BMP_LEN; i++) { if (s_frame[i] != s_bmp[i]) { bmp_ok = 0; } } CHECK(bmp_ok == 1, "TC-SBPROTO-020b the 3200-byte bitmap lands at offset 0 unchanged"); CHECK((s_frame[SB_FRAME_B_INFO_OFF + 0U] == 0xEFU) && (s_frame[SB_FRAME_B_INFO_OFF + 1U] == 0xBEU) && (s_frame[SB_FRAME_B_INFO_OFF + 2U] == 0xADU) && (s_frame[SB_FRAME_B_INFO_OFF + 3U] == 0xDEU), "TC-SBPROTO-020c the LED mask goes into info[0..3] little-endian (info[0] = bit0..7)"); CHECK((s_frame[SB_FRAME_B_INFO_OFF + 4U] == 0U) && (s_frame[SB_FRAME_B_INFO_OFF + 5U] == 0U) && (s_frame[SB_FRAME_B_INFO_OFF + 6U] == 0U) && (s_frame[SB_FRAME_B_INFO_OFF + 7U] == 0U), "TC-SBPROTO-020d info[4..7] stay 0 for a normal picture (the LOGO magic must not leak in)"); for (i = SB_FRAME_B_PAD_OFF; i < SB_FRAME_B_CRC_OFF; i++) { if (s_frame[i] != 0U) { pad_ok = 0; } } CHECK(pad_ok == 1, "TC-SBPROTO-020e the 998-byte padding block is all zero"); crc = sb_crc16(s_frame, (uint16_t)SB_FRAME_B_CRC_OFF); CHECK((s_frame[SB_FRAME_B_CRC_OFF] == (uint8_t)(crc & 0xFFU)) && (s_frame[SB_FRAME_B_CRC_OFF + 1U] == (uint8_t)((crc >> 8) & 0xFFU)), "TC-SBPROTO-020f the CRC sits at 4206/4207 low byte first"); CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_OK, "TC-SBPROTO-020g the built frame verifies"); CHECK(screen_board_accepts(s_frame, (uint16_t)SB_FRAME_B_LEN) == 1, "TC-SBPROTO-040 the board side Check_CRC accepts the image frame"); } static void tc_frame_b_rewrite(void) { static uint8_t first[SB_FRAME_B_LEN]; uint16_t i; int pad_ok = 1; /* 预填脏数据:整帧重写必须把填充段与 info 一起清干净 */ memset(s_frame, 0xAA, sizeof(s_frame)); fill_bmp_pattern(); CHECK(sb_frame_b_build_image(s_frame, s_bmp, 0x00000001U) == BSP_OK, "TC-SBPROTO-021 building over a dirty buffer succeeds"); for (i = SB_FRAME_B_PAD_OFF; i < SB_FRAME_B_CRC_OFF; i++) { if (s_frame[i] != 0U) { pad_ok = 0; } } CHECK(pad_ok == 1, "TC-SBPROTO-021b a 0xAA-pre-filled buffer is fully rewritten (padding zeroed)"); memcpy(first, s_frame, sizeof(first)); CHECK(sb_frame_b_build_image(s_frame, s_bmp, 0x00000001U) == BSP_OK, "TC-SBPROTO-021c building the same content twice succeeds"); CHECK(memcmp(first, s_frame, sizeof(first)) == 0, "TC-SBPROTO-021d the same inputs give a byte-identical frame (idempotent)"); } static void tc_frame_b_logo(void) { fill_bmp_pattern(); CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 160U, 160U) == BSP_OK, "TC-SBPROTO-022 building a 160x160 LOGO frame succeeds"); CHECK((s_frame[SB_FRAME_B_INFO_OFF + 0U] == 0x00U) && (s_frame[SB_FRAME_B_INFO_OFF + 1U] == 0xA0U) && (s_frame[SB_FRAME_B_INFO_OFF + 2U] == 0x00U) && (s_frame[SB_FRAME_B_INFO_OFF + 3U] == 0xA0U), "TC-SBPROTO-022b width and height go into info[0..3] big-endian"); CHECK((s_frame[SB_FRAME_B_INFO_OFF + 4U] == 0x89U) && (s_frame[SB_FRAME_B_INFO_OFF + 5U] == 0x45U), "TC-SBPROTO-022c the LOGO magic 0x89 0x45 sits in info[4..5]"); CHECK((s_frame[SB_FRAME_B_INFO_OFF + 6U] == 0U) && (s_frame[SB_FRAME_B_INFO_OFF + 7U] == 0U), "TC-SBPROTO-022d info[6..7] stay 0 (reserved)"); CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_OK, "TC-SBPROTO-022e the LOGO frame verifies"); CHECK(screen_board_accepts(s_frame, (uint16_t)SB_FRAME_B_LEN) == 1, "TC-SBPROTO-040b the board side Check_CRC accepts the LOGO frame"); /* 小图也要能组:1x1 只需 1 字节点阵 */ CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 1U, 1U) == BSP_OK, "TC-SBPROTO-022f a 1x1 LOGO is accepted (the capacity check is about bytes, not pixels)"); } static void tc_frame_b_logo_guard(void) { fill_bmp_pattern(); /* 161x159 = 25599 像素(看起来"装得下"),实际要 21x159 = 3339 B > 3200 B */ CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 161U, 159U) == BSP_ERR_PARAM, "TC-SBPROTO-023 161x159 is rejected: 3339 bytes of bitmap do not fit in 3200"); CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 160U, 161U) == BSP_ERR_PARAM, "TC-SBPROTO-023b 160x161 is rejected (3220 bytes)"); CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 168U, 152U) == BSP_OK, "TC-SBPROTO-023c 168x152 is accepted (exactly 3192 bytes, width not a multiple of 8 is fine)"); CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 0U, 160U) == BSP_ERR_PARAM, "TC-SBPROTO-023d a zero width is rejected"); CHECK(sb_frame_b_build_logo(s_frame, s_bmp, 160U, 0U) == BSP_ERR_PARAM, "TC-SBPROTO-023e a zero height is rejected"); } static void tc_frame_b_guard(void) { fill_bmp_pattern(); CHECK(sb_frame_b_build_image(0, s_bmp, 0U) == BSP_ERR_PARAM, "TC-SBPROTO-024 a null frame buffer is rejected (image)"); CHECK(sb_frame_b_build_image(s_frame, 0, 0U) == BSP_ERR_PARAM, "TC-SBPROTO-024b a null bitmap is rejected (image)"); CHECK(sb_frame_b_build_logo(0, s_bmp, 160U, 160U) == BSP_ERR_PARAM, "TC-SBPROTO-024c a null frame buffer is rejected (LOGO)"); CHECK(sb_frame_b_build_logo(s_frame, 0, 160U, 160U) == BSP_ERR_PARAM, "TC-SBPROTO-024d a null bitmap is rejected (LOGO)"); CHECK(sb_frame_b_verify(0, (uint16_t)SB_FRAME_B_LEN) == BSP_ERR_PARAM, "TC-SBPROTO-024e a null frame is rejected by verify"); } static void tc_frame_b_verify(void) { fill_bmp_pattern(); (void)sb_frame_b_build_image(s_frame, s_bmp, 0x00000000U); s_frame[100] ^= 0x01U; CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_ERR_CRC, "TC-SBPROTO-030 a flipped bitmap byte is caught"); s_frame[100] ^= 0x01U; CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_OK, "TC-SBPROTO-030b flipping it back restores the verdict"); /* 填充段屏板不读,但它参与 CRC:改了必须报错,否则"填充随便填"会悄悄破坏帧 */ s_frame[SB_FRAME_B_PAD_OFF + 500U] = 0x01U; CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_ERR_CRC, "TC-SBPROTO-030c a modified padding byte is caught (the padding really is inside the CRC range)"); s_frame[SB_FRAME_B_PAD_OFF + 500U] = 0x00U; s_frame[SB_FRAME_B_CRC_OFF] ^= 0xFFU; CHECK(sb_frame_b_verify(s_frame, (uint16_t)SB_FRAME_B_LEN) == BSP_ERR_CRC, "TC-SBPROTO-030d a corrupted CRC byte is caught"); (void)sb_frame_b_build_image(s_frame, s_bmp, 0U); CHECK(sb_frame_b_verify(s_frame, (uint16_t)(SB_FRAME_B_LEN - 1U)) == BSP_ERR_PARAM, "TC-SBPROTO-030e a length other than 4208 is rejected"); } int main(void) { printf("=== ScreenLink Proto Tests ===\n"); printf("-- CRC-16/MODBUS check values --\n"); tc_crc_vectors(); printf("-- CRC table vs bitwise reference --\n"); tc_crc_table(); printf("-- frame A: the board's real summon bytes --\n"); tc_frame_a_vectors(); printf("-- frame A: key handling --\n"); tc_frame_a_lenient_key(); printf("-- frame A: rejection paths --\n"); tc_frame_a_reject(); printf("-- frame A: argument guards --\n"); tc_frame_a_guard(); printf("-- frame B: image (bitmap + LED mask) --\n"); tc_frame_b_image(); printf("-- frame B: full-frame rewrite / idempotence --\n"); tc_frame_b_rewrite(); printf("-- frame B: LOGO (width/height + magic) --\n"); tc_frame_b_logo(); printf("-- frame B: LOGO capacity guard --\n"); tc_frame_b_logo_guard(); printf("-- frame B: argument guards --\n"); tc_frame_b_guard(); printf("-- frame B: self-verification --\n"); tc_frame_b_verify(); printf("\n=== ScreenLink Proto Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }