Files
STM32F4-Base/test/host/sb_proto/sb_proto_test.c
T
wandering1 b25d70e962 feat(屏板): 协议层 sb_proto(帧 A 解析 / 帧 B 组帧 / CRC-16)
批 1 的 1-1:新建 Drivers/BSP/ScreenLink/sb_proto.c/h,纯逻辑、无 HAL 依赖,
可主机侧单测。屏板侧的协议事实源是 DTU-LCD 的 CRC16.c / MODBUS.c / rs485.h。

- CRC-16/MODBUS 用一张 16 位合并表(512 B,与屏板两张 8 位表同量)。可逐项
  验证的关系:s_crc_table[i] == (auchCRCLo[i] << 8) | auchCRCHi[i]——屏板把
  低字节表叫 Hi、高字节表叫 Lo,这正是下面那个字节序陷阱的根源。
- 帧 A 解析:先校 CRC 再看字段。地址/功能码/命令类型不符报 BSP_ERR_PARAM,
  CRC 不符报 BSP_ERR_CRC(调用方据此决定"计数"还是"静默丢")。RT_CMD 带
  非 0 key 不报错、按 0 收——为这一项丢一帧等于丢一次刷新,代价不对等。
- 帧 B 组帧:普通图(掩码小端)+ LOGO(宽高大端 + 魔数 0x89 0x45),整帧
  4208 B 重写(998 B 填充固定 0x00 且参与 CRC),组完即自校验。
  LOGO 容量按字节数判(((w+7)/8)*h <= 3200)而不是像素数:161x159 只有
  25599 像素,却要 3339 B 才装得下。
- 主机单测 59/59 PASSED(test/host/sb_proto/),覆盖率 74/74 行、6/6 函数。
  其中两条向量是屏板自己算出来的线上字节(01 03 01 00 F0 48 与
  01 03 00 09 31 DE),并按屏板 Check_CRC 的口径反查"它会认这一帧"。
- 双端注册:uvprojx 加 File + IncludePath;模拟器侧走 check_registration.py
  的 MCU-only 豁免(纯逻辑,模拟器无消费路径)。
- Keil 构建 0 错误 0 警告;Code 不变——尚无调用者,链接器按未用节丢弃。

文档侧的 CRC 字节序订正见下一个提交。
2026-10-29 19:17:28 +08:00

491 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:屏板链路协议层(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 <stdint.h>
#include <stdio.h>
#include <string.h>
/* ===================== 断言 ===================== */
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;
}