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 字节序订正见下一个提交。
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 模块名称:屏板链路协议层实现(帧 A 解析 / 帧 B 组帧 / CRC-16)
|
||||
* 模块功能:见 sb_proto.h 的模块头。本文件只做字节级编解码,不碰寄存器、DMA、任务与时间。
|
||||
* 适用平台:STM32F4 系列(ARMCC v5);仅依赖 stdint.h 与 string.h,主机侧可编译。
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* 2026-10-29 王建锋 创建:按屏板固件 CRC16.c / MODBUS.c 对齐,并订正 CRC 字节序口径
|
||||
*
|
||||
* ============================================================================
|
||||
* CRC 的字节序:这里有一个会让人白查一天的口径陷阱,先看这段
|
||||
* ============================================================================
|
||||
* 屏板固件 Middlewares/Modbus/CRC16.c 的 Calculate_CRC() 返回的**不是**教科书上那个
|
||||
* CRC-16/MODBUS 值,而是它高低字节互换后的「线上字」。举实测值:
|
||||
*
|
||||
* 数据 "123456789" 屏板返回 0x374B CRC-16/MODBUS 标准值 0x4B37
|
||||
* 数据 01 03 00 00 00 0A 屏板返回 0xC5CD 标准值 0xCDC5
|
||||
*
|
||||
* 于是屏板 RS485_RefreshScreenCMD() 里那句「cmdbuf[4] = crc >> 8」(注释写"CRC校验高字节")
|
||||
* 实际写出去的是**标准 CRC 的低字节**——两处命名都是反的,但**线上字节是对的**:
|
||||
* 上面第二帧的线上 CRC 就是 C5 CD(Modbus RTU 的低字节在前)。
|
||||
*
|
||||
* 结论(本模块采用的写法):
|
||||
* · sb_crc16() 返回**标准 CRC-16/MODBUS 值**(可用教科书校验值 0x4B37 直接对);
|
||||
* · 写帧时一律 **低字节在前**:帧 A 偏移 4 = 低、偏移 5 = 高;帧 B 偏移 4206 = 低、4207 = 高。
|
||||
* 若照屏板注释的字面意思写成「高字节在前」,两侧算出的值差一次字节交换,屏板会把每一帧都
|
||||
* 判成 CRC 错(它的超时判定只看"收没收到一整包"、不看 CRC,所以现场只表现为画面不刷新),
|
||||
* 极易误判成波特率或时序问题。主机单测里钉了屏板算法的实测向量,改这两行会立刻红。
|
||||
*
|
||||
* 表:屏板用两张 8 位表(auchCRCHi / auchCRCLo),本模块用一张 16 位表(同样 512 B 常量)。
|
||||
* 两者关系是可逐项验证的恒等式:s_crc_table[i] == (auchCRCLo[i] 左移 8) 或 auchCRCHi[i]
|
||||
* ——即屏板把**低字节表叫 Hi、高字节表叫 Lo**,这也是上面那个反相的根源。
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sb_proto.h"
|
||||
|
||||
/* ============================================================================
|
||||
* CRC-16/MODBUS 查表(多项式 0xA001 反转式,初值 0xFFFF)
|
||||
* 表的生成方式:对 i = 0..255,取 crc = i,做 8 次「最低位为 1 则右移后异或 0xA001,
|
||||
* 否则只右移」,所得 crc 即表项。主机单测逐项复核前 8 项与末项,并复算屏板的实测向量。
|
||||
* ==========================================================================*/
|
||||
static const uint16_t s_crc_table[256] =
|
||||
{
|
||||
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
||||
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
||||
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
||||
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
||||
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
||||
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
||||
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
||||
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
||||
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
||||
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
||||
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
||||
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
||||
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
||||
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
||||
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
||||
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
||||
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
||||
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
||||
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
||||
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
||||
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
||||
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
||||
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
||||
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
||||
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
||||
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
||||
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
||||
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
||||
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
||||
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
||||
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
||||
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* 私有工具
|
||||
* ==========================================================================*/
|
||||
|
||||
/*
|
||||
* 函数功能:把 CRC 写到帧尾(低字节在前,见文件头那段说明)
|
||||
* 入口参数:p_frame - 帧首地址
|
||||
* off - CRC 域起始偏移
|
||||
* crc - sb_crc16() 返回的标准 CRC 值
|
||||
* 返回值:无
|
||||
* 限定条件:p_frame 至少长 off + 2。
|
||||
* 函数说明:帧 A 与帧 B 共用,两处字节序必须一致——写反了屏板会把每一帧都判成 CRC 错。
|
||||
*/
|
||||
static void sb_put_crc(uint8_t *p_frame, uint16_t off, uint16_t crc)
|
||||
{
|
||||
p_frame[off] = (uint8_t)(crc & 0xFFU); /* 低字节在前 */
|
||||
p_frame[off + 1U] = (uint8_t)((crc >> 8) & 0xFFU);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* 公开接口实现
|
||||
* ==========================================================================*/
|
||||
|
||||
uint16_t sb_crc16(const uint8_t *p_data, uint16_t len)
|
||||
{
|
||||
uint16_t crc = 0xFFFFU;
|
||||
|
||||
while (len > 0U)
|
||||
{
|
||||
crc = (uint16_t)((crc >> 8) ^ s_crc_table[(uint8_t)(crc ^ *p_data)]);
|
||||
p_data++;
|
||||
len--;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析一帧帧 A(屏板发来的 6 B 召唤 / 按键帧)
|
||||
* 入口参数:p_buf - 收到的字节首地址
|
||||
* len - 收到的字节数
|
||||
* p_out - 解析结果输出(成功时写 cmd / key,失败时不改)
|
||||
* 返回值:BSP_OK / BSP_ERR_CRC(帧被破坏,计数后丢弃)/ BSP_ERR_PARAM(不是合法召唤帧,直接丢)
|
||||
* 限定条件:只认固定 6 B;先校 CRC 再看字段。
|
||||
* 函数说明:细节与"为什么地址/功能码不符也报 PARAM"见 sb_proto.h 的同名声明。
|
||||
*/
|
||||
bsp_err_t sb_frame_a_parse(const uint8_t *p_buf, uint16_t len, sb_frame_a_t *p_out)
|
||||
{
|
||||
uint16_t crc;
|
||||
uint8_t cmd;
|
||||
|
||||
if ((p_buf == 0) || (p_out == 0))
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
if (len != SB_FRAME_A_LEN)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
/* 先校 CRC:字节已经乱了的话,再去解释地址/功能码没有意义 */
|
||||
crc = sb_crc16(p_buf, (uint16_t)SB_FRAME_A_CRC_OFF);
|
||||
if ((p_buf[SB_FRAME_A_CRC_OFF] != (uint8_t)(crc & 0xFFU)) ||
|
||||
(p_buf[SB_FRAME_A_CRC_OFF + 1U] != (uint8_t)((crc >> 8) & 0xFFU)))
|
||||
{
|
||||
return BSP_ERR_CRC;
|
||||
}
|
||||
|
||||
if (p_buf[0] != SB_ADDR)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
if (p_buf[1] != SB_FUNC_READ_HOLDING)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
cmd = p_buf[2];
|
||||
if ((cmd != SB_CMD_KEY) && (cmd != SB_CMD_RT))
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
p_out->cmd = cmd;
|
||||
/* 周期召唤的 key 字段按协议恒为 0;真收到非 0 也按 0 处理(不为此丢一帧) */
|
||||
p_out->key = (cmd == SB_CMD_KEY) ? p_buf[3] : (uint8_t)0U;
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:组一帧帧 B「普通图」(点阵 + 32 位 LED 掩码)
|
||||
* 入口参数:p_frame - 输出缓冲(>= SB_FRAME_B_LEN)
|
||||
* p_bmp - 点阵源(>= SB_FRAME_B_BMP_LEN)
|
||||
* led_mask - 状态灯位掩码,小端填进 info[0..3]
|
||||
* 返回值:BSP_OK / BSP_ERR_PARAM(指针为空)
|
||||
* 限定条件:整帧重写(含 998 B 填充与 CRC),调用方不必预先清零。
|
||||
* 函数说明:掩码的位语义不归本模块管(见任务规划批 3)。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_build_image(uint8_t *p_frame, const uint8_t *p_bmp, uint32_t led_mask)
|
||||
{
|
||||
if ((p_frame == 0) || (p_bmp == 0))
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
/* 整帧重写:998 B 填充与 info[4..7] 一并归零,调用方不必预先清零 */
|
||||
(void)memset(p_frame, 0, (size_t)SB_FRAME_B_LEN);
|
||||
(void)memcpy(p_frame, p_bmp, (size_t)SB_FRAME_B_BMP_LEN);
|
||||
|
||||
/* info[0..3]:LED 位掩码,小端(info[0] = bit0..7) */
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 0U] = (uint8_t)(led_mask & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 1U] = (uint8_t)((led_mask >> 8) & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 2U] = (uint8_t)((led_mask >> 16) & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 3U] = (uint8_t)((led_mask >> 24) & 0xFFU);
|
||||
|
||||
sb_put_crc(p_frame, (uint16_t)SB_FRAME_B_CRC_OFF, sb_crc16(p_frame, (uint16_t)SB_FRAME_B_CRC_OFF));
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:组一帧帧 B「LOGO」(点阵 + 宽高 + 魔数 0x89 0x45)
|
||||
* 入口参数:p_frame - 输出缓冲(>= SB_FRAME_B_LEN)
|
||||
* p_bmp - 点阵源(>= SB_FRAME_B_BMP_LEN)
|
||||
* width - 图片宽度(像素),大端填进 info[0..1]
|
||||
* height - 图片高度(像素),大端填进 info[2..3]
|
||||
* 返回值:BSP_OK / BSP_ERR_PARAM(指针空 / 宽高为 0 / 点阵装不下)
|
||||
* 限定条件:按字节数判容量(((width + 7) / 8) * height <= 3200),不是按像素数。
|
||||
* 函数说明:本帧会覆盖屏板 Flash 扇区 126,发送时机由调用方决定。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_build_logo(uint8_t *p_frame, const uint8_t *p_bmp, uint16_t width, uint16_t height)
|
||||
{
|
||||
uint32_t need_bytes;
|
||||
|
||||
if ((p_frame == 0) || (p_bmp == 0))
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
if ((width == 0U) || (height == 0U))
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
/* 按字节数判容量而不是像素数:161x159 只有 25599 像素,却要 21x159 = 3339 B 才装得下 */
|
||||
need_bytes = ((uint32_t)width + 7U) / 8U;
|
||||
need_bytes *= (uint32_t)height;
|
||||
if (need_bytes > (uint32_t)SB_FRAME_B_BMP_LEN)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
(void)memset(p_frame, 0, (size_t)SB_FRAME_B_LEN);
|
||||
(void)memcpy(p_frame, p_bmp, (size_t)SB_FRAME_B_BMP_LEN);
|
||||
|
||||
/* info[0..1] 宽、info[2..3] 高,均大端 */
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 0U] = (uint8_t)((width >> 8) & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 1U] = (uint8_t)(width & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 2U] = (uint8_t)((height >> 8) & 0xFFU);
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 3U] = (uint8_t)(height & 0xFFU);
|
||||
/* info[4..5] LOGO 魔数;info[6..7] 保留(已归零) */
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 4U] = SB_LOGO_MAGIC_HI;
|
||||
p_frame[SB_FRAME_B_INFO_OFF + 5U] = SB_LOGO_MAGIC_LO;
|
||||
|
||||
sb_put_crc(p_frame, (uint16_t)SB_FRAME_B_CRC_OFF, sb_crc16(p_frame, (uint16_t)SB_FRAME_B_CRC_OFF));
|
||||
return BSP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:自校验一帧帧 B(重算 CRC 并与帧尾比对)
|
||||
* 入口参数:p_frame - 待校验帧首地址
|
||||
* len - 帧长度,必须等于 SB_FRAME_B_LEN
|
||||
* 返回值:BSP_OK / BSP_ERR_CRC(不一致)/ BSP_ERR_PARAM(指针空或长度不对)
|
||||
* 限定条件:只验 CRC,不检查填充段是否全 0、也不检查掩码位语义。
|
||||
* 函数说明:供组帧路径自查(2-4 的「组帧自校验 100%」接这里)。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_verify(const uint8_t *p_frame, uint16_t len)
|
||||
{
|
||||
uint16_t crc;
|
||||
|
||||
if (p_frame == 0)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
if (len != SB_FRAME_B_LEN)
|
||||
{
|
||||
return BSP_ERR_PARAM;
|
||||
}
|
||||
|
||||
crc = sb_crc16(p_frame, (uint16_t)SB_FRAME_B_CRC_OFF);
|
||||
if ((p_frame[SB_FRAME_B_CRC_OFF] != (uint8_t)(crc & 0xFFU)) ||
|
||||
(p_frame[SB_FRAME_B_CRC_OFF + 1U] != (uint8_t)((crc >> 8) & 0xFFU)))
|
||||
{
|
||||
return BSP_ERR_CRC;
|
||||
}
|
||||
return BSP_OK;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 模块名称:屏板链路协议层(帧 A 解析 / 帧 B 组帧 / CRC-16)
|
||||
* 模块功能:把「主机与屏板之间那两种帧」的编解码收成一处纯逻辑:
|
||||
* - CRC-16/MODBUS(表与屏板固件逐字节一致,见 Drivers/BSP/ScreenLink/sb_proto.c)
|
||||
* - 帧 A(屏板 → 主机,固定 6 B)解析:校验后吐出「命令类型 + 按键值」
|
||||
* - 帧 B(主机 → 屏板,固定 4208 B)组帧:普通图(LED 掩码)/ LOGO(宽高 + 魔数)
|
||||
* - 帧 B 自校验(组完立刻能验,供组帧路径自查)
|
||||
* 本模块**不碰寄存器、不碰 DMA、不碰任务**:串口怎么配、谁来发、什么时候发都不归它管
|
||||
* (那是批 1 的 1-2 / 1-3 与批 2 的事)。
|
||||
* 适用平台:STM32F4 系列(ARMCC v5);不依赖 HAL,主机侧可直接编译(故可主机单测)。
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-29
|
||||
* 修改记录:
|
||||
* 2026-10-29 王建锋 创建:按屏板固件 D:\Code\DTU Code\DTU-LCD 的 CRC16.c / MODBUS.c 逐字节对齐
|
||||
*
|
||||
* 协议事实源:屏板固件工程(不在本仓库)
|
||||
* Middlewares/Modbus/CRC16.c —— 两张 256 字节查表 + Calculate_CRC() / Check_CRC()
|
||||
* Middlewares/Modbus/MODBUS.c —— RS485_RefreshScreenCMD()(帧 A 组帧,6 B)
|
||||
* RS485_Process()(帧 B 解析:3200 点阵 + 8 字节 info + CRC 在帧尾)
|
||||
* Drivers/BSP/RS485/rs485.h —— UART_RX_LEN = 4208(帧 B 的线上长度,注释写 3208 是错的)
|
||||
* 需求侧口径见 docs/产品需求/产品需求设计文档.md §5.16.2(帧结构)与 §5.16.3(按键枚举)。
|
||||
*
|
||||
* ⚠ 两个长度别混:屏板 `BMP_LEN` 的 3208 是**写 Flash 的记录长度**(点阵 3200 + 宽 4 + 高 4),
|
||||
* 不是帧长;帧长以 `Check_CRC(..., UART_RX_LEN)` 的 **4208** 为准。本模块只认 4208。
|
||||
*/
|
||||
|
||||
#ifndef SB_PROTO_H
|
||||
#define SB_PROTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bsp_err.h" /* bsp_err_t / BSP_OK / BSP_ERR_*:本模块只报统一错误码 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ============================================================================
|
||||
* 一、帧长与字段偏移(单一出处,别在调用方另抄一份)
|
||||
* ==========================================================================*/
|
||||
|
||||
/* 帧 A:屏板 → 主机,固定 6 B。CRC 只覆盖前 4 B,高字节在前。 */
|
||||
#define SB_FRAME_A_LEN (6U)
|
||||
#define SB_FRAME_A_CRC_OFF (4U)
|
||||
|
||||
/* 帧 B:主机 → 屏板,固定 4208 B。CRC 覆盖 [0, 4205],高字节在前。 */
|
||||
#define SB_FRAME_B_LEN (4208U)
|
||||
#define SB_FRAME_B_BMP_LEN (3200U) /* 点阵:160 行 x 20 B/行,1 bpp,MSB 在前 */
|
||||
#define SB_FRAME_B_INFO_LEN (8U) /* info[0..7] */
|
||||
#define SB_FRAME_B_INFO_OFF (3200U)
|
||||
#define SB_FRAME_B_PAD_OFF (3208U) /* 保留填充起点 */
|
||||
#define SB_FRAME_B_PAD_LEN (998U) /* [3208, 4205]:屏板不读,但参与 CRC,须固定填 0 */
|
||||
#define SB_FRAME_B_CRC_OFF (4206U)
|
||||
|
||||
/* 帧 A 的固定字段值 */
|
||||
#define SB_ADDR (0x01U) /* 从站地址:**主机的地址**,不是屏板的 */
|
||||
#define SB_FUNC_READ_HOLDING (0x03U) /* 功能码:读保持寄存器 */
|
||||
#define SB_CMD_KEY (0x00U) /* 命令类型:按键事件(立即召唤) */
|
||||
#define SB_CMD_RT (0x01U) /* 命令类型:周期刷新 */
|
||||
|
||||
/* 帧 B 的 LOGO 魔数(info[4] / info[5]) */
|
||||
#define SB_LOGO_MAGIC_HI (0x89U)
|
||||
#define SB_LOGO_MAGIC_LO (0x45U)
|
||||
|
||||
/* 屏幕几何:LOGO 的宽高要在这一片点阵装得下 */
|
||||
#define SB_SCREEN_W (160U)
|
||||
#define SB_SCREEN_H (160U)
|
||||
|
||||
/* ============================================================================
|
||||
* 二、帧 A 的解析结果
|
||||
* ==========================================================================*/
|
||||
|
||||
/* 解析成功后的有效字段。addr / func 恒为 SB_ADDR / SB_FUNC_READ_HOLDING,
|
||||
* 不再回吐(校验不过直接报错,能走到这里就一定是这两个值)。 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t cmd; /* SB_CMD_KEY / SB_CMD_RT */
|
||||
uint8_t key; /* cmd == SB_CMD_KEY 时有效;cmd == SB_CMD_RT 时恒为 0(见 sb_frame_a_parse 说明) */
|
||||
} sb_frame_a_t;
|
||||
|
||||
/* ============================================================================
|
||||
* 三、公开接口
|
||||
* ==========================================================================*/
|
||||
|
||||
/*
|
||||
* 函数功能:计算 CRC-16/MODBUS(多项式 0xA001 反转式、初值 0xFFFF、查表法)
|
||||
* 入口参数:p_data - 待校验数据首地址
|
||||
* len - 参与校验的字节数(不含 CRC 本身)
|
||||
* 返回值:16 位**标准 CRC-16/MODBUS 值**(教科书校验值:数据 "123456789" → 0x4B37)
|
||||
* 限定条件:p_data 必须非空且 len >= 1;本函数是纯算术,不做空指针检查(与相量系纯算法函数同口径)。
|
||||
* 写进帧里一律**低字节在前**——屏板固件那个同名函数返回的是高低互换后的「线上字」,
|
||||
* 两处命名都是反的,但线上字节是对的。陷阱与实测向量见 sb_proto.c 文件头,改前务必看。
|
||||
* 函数说明:查表法而非逐位法。帧 B 一次要算 4206 B:逐位法约 13 万拍(毫秒量级),
|
||||
* 查表法约 2.5 万拍(0.15 ms 量级,168 MHz 估算),后者才留得住 2-1 的 0.3 ms
|
||||
* 组帧预算——而那份预算还要分给 3200 B 的点阵拷贝,2-1 实测时两者要一起算。
|
||||
*/
|
||||
uint16_t sb_crc16(const uint8_t *p_data, uint16_t len);
|
||||
|
||||
/*
|
||||
* 函数功能:解析一帧帧 A(屏板发来的 6 B 召唤 / 按键帧)
|
||||
* 入口参数:p_buf - 收到的字节首地址
|
||||
* len - 收到的字节数(空闲中断断出来的实际长度)
|
||||
* p_out - 解析结果输出(成功时写 cmd / key,失败时不改)
|
||||
* 返回值:BSP_OK 解析成功,*p_out 有效
|
||||
* BSP_ERR_CRC CRC 不符:帧被破坏,应计数后静默丢弃(不要回任何东西)
|
||||
* BSP_ERR_PARAM 不是一个合法的召唤帧:指针空 / len != 6 / 地址不是 0x01 /
|
||||
* 功能码不是 0x03 / 命令类型不是 0 或 1 —— 丢弃即可,不必计数
|
||||
* 限定条件:只认固定 6 B 的帧 A;屏板的帧 B 应答方向相反,不经过本函数。
|
||||
* 函数说明:
|
||||
* 1. **先校 CRC 再看字段**:CRC 不过说明字节已经乱了,再去解释地址/功能码没有意义。
|
||||
* 2. 地址与功能码不符也报 BSP_ERR_PARAM 而不是新造错误码——bsp_err.h 的码是跨模块的
|
||||
* 「能不能重试」语义,不是协议细节;对调用方而言这几种都等于「这不是给本机的合法召唤帧」。
|
||||
* 要区分细节就比对原始字节(屏板这两个字段是写死的,出现不符说明已经错位)。
|
||||
* 3. cmd == SB_CMD_RT 时若 key 非 0 **不报错**,按 0 忽略:屏板的周期召唤本应带 0,
|
||||
* 但为这一项丢一帧就等于丢一次刷新(280 ms 的画面不动),代价不对等。
|
||||
*/
|
||||
bsp_err_t sb_frame_a_parse(const uint8_t *p_buf, uint16_t len, sb_frame_a_t *p_out);
|
||||
|
||||
/*
|
||||
* 函数功能:组一帧帧 B「普通图」(点阵 + 32 位 LED 掩码)
|
||||
* 入口参数:p_frame - 输出缓冲,长度必须 >= SB_FRAME_B_LEN
|
||||
* p_bmp - 点阵源,长度必须 >= SB_FRAME_B_BMP_LEN(160x160 @1bpp)
|
||||
* led_mask - 状态灯位掩码,小端填进 info[0..3](info[0] = bit0..7)
|
||||
* 返回值:BSP_OK 组帧完成,整帧(含 CRC)已就绪
|
||||
* BSP_ERR_PARAM 指针为空
|
||||
* 限定条件:p_frame 会被**整帧重写**(含 998 B 填充与 CRC),调用方不必预先清零;
|
||||
* 组好的帧在下次调用前有效,本模块不持有任何缓冲。
|
||||
* 函数说明:填充段固定填 0x00(屏板不读,但参与 CRC)。掩码的位语义不归本模块管
|
||||
* (bit0 恒 0 / bit20~31 恒 0 这类业务约束属状态灯映射表,见任务规划批 3)。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_build_image(uint8_t *p_frame, const uint8_t *p_bmp, uint32_t led_mask);
|
||||
|
||||
/*
|
||||
* 函数功能:组一帧帧 B「LOGO」(点阵 + 宽高 + 魔数 0x89 0x45)
|
||||
* 入口参数:p_frame - 输出缓冲,长度必须 >= SB_FRAME_B_LEN
|
||||
* p_bmp - 点阵源,长度必须 >= SB_FRAME_B_BMP_LEN
|
||||
* width - 图片宽度(像素),大端填进 info[0..1]
|
||||
* height - 图片高度(像素),大端填进 info[2..3]
|
||||
* 返回值:BSP_OK 组帧完成
|
||||
* BSP_ERR_PARAM 指针为空 / 宽高为 0 / 超出 160x160 点阵容量
|
||||
* 限定条件:宽高必须满足 ((width + 7) / 8) * height <= 3200 —— 屏板拿这两个数直接显示,
|
||||
* 装不下的宽高会让它读越 3200 B 的点阵缓冲,故本函数按字节数而非像素数判容量。
|
||||
* 函数说明:LOGO 帧会把屏板 Flash 0x0803F000(扇区 126)整块覆盖,且屏板不校验是否同一张图;
|
||||
* 本模块只负责把帧组对,**什么时候发、能不能重复发由调用方决定**。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_build_logo(uint8_t *p_frame, const uint8_t *p_bmp, uint16_t width, uint16_t height);
|
||||
|
||||
/*
|
||||
* 函数功能:自校验一帧帧 B(重算 CRC 并与帧尾比对)
|
||||
* 入口参数:p_frame - 待校验帧首地址
|
||||
* len - 帧长度,必须等于 SB_FRAME_B_LEN
|
||||
* 返回值:BSP_OK CRC 与帧尾一致
|
||||
* BSP_ERR_CRC 不一致(帧被改写 / 组帧时漏算)
|
||||
* BSP_ERR_PARAM 指针空或 len 不是 4208
|
||||
* 限定条件:只验 CRC,不检查填充段是否全 0、也不检查掩码位语义。
|
||||
* 函数说明:给组帧路径自查用(2-4 的「组帧自校验 100%」直接接这里)。
|
||||
*/
|
||||
bsp_err_t sb_frame_b_verify(const uint8_t *p_frame, uint16_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SB_PROTO_H */
|
||||
@@ -337,7 +337,7 @@
|
||||
<MiscControls>--diag_suppress=1,68,111,188,191,546,1293</MiscControls>
|
||||
<Define>USE_HAL_DRIVER,STM32F407xx,NDEBUG</Define>
|
||||
<Undefine />
|
||||
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/algo;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/protocol/iec104;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib</IncludePath>
|
||||
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/ScreenLink;../Drivers/BSP/NET;../Drivers/BSP/NET/lftpd;../Drivers/BSP/NETCFG;../App;../App/app;../App/algo;../App/cfg;../App/fs;../App/log;../App/sync;../App/task;../App/protocol/iec104;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs;../test/board;../Lib/lvgl;../Drivers/BSP/UI;../Lib</IncludePath>
|
||||
</VariousControls>
|
||||
</Cads>
|
||||
<Aads>
|
||||
@@ -1623,6 +1623,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\RS485\rs485.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>sb_proto.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\ScreenLink\sb_proto.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>net_select.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
|
||||
@@ -135,6 +135,7 @@ SIM_EXEMPT = [
|
||||
("Drivers/BSP/NET/net_pending.c", "MCU 挂起 recv 模型(依赖 ch395f_* SPI 读出与 FreeRTOS 通知),模拟器另有 net_socket_sim.c 按外部语义模拟"),
|
||||
("Drivers/BSP/RS485/rs485.c", "MCU RS-485 驱动"),
|
||||
("Drivers/BSP/SD2506/sd2506.c", "MCU RTC 驱动"),
|
||||
("Drivers/BSP/ScreenLink/sb_proto.c", "屏板链路协议层纯逻辑(帧 A 解析 / 帧 B 组帧 / CRC-16),消费者 = MCU 侧 USART1 收发起发(批 1/2),模拟器无消费路径;主机侧纯逻辑单测覆盖于 test/host/sb_proto"),
|
||||
("Drivers/BSP/TPAFE5160/tpafe5160.c", "MCU 计量芯片驱动"),
|
||||
("Drivers/BSP/UI/ui_port.c", "MCU UI 端口层(显示/触摸硬件),模拟器另有 sim/sim_port.c"),
|
||||
]
|
||||
|
||||
@@ -344,6 +344,14 @@
|
||||
"lines_covered": 108,
|
||||
"lines_valid": 112
|
||||
},
|
||||
"Drivers/BSP/ScreenLink/sb_proto.c": {
|
||||
"func_rate": 1.0,
|
||||
"funcs_covered": 6,
|
||||
"funcs_valid": 6,
|
||||
"line_rate": 1.0,
|
||||
"lines_covered": 74,
|
||||
"lines_valid": 74
|
||||
},
|
||||
"Drivers/BSP/UI/ui_data.c": {
|
||||
"func_rate": 0.9767,
|
||||
"funcs_covered": 42,
|
||||
@@ -607,6 +615,13 @@
|
||||
"lines_total": 961,
|
||||
"ok": true
|
||||
},
|
||||
"sb_proto": {
|
||||
"files": {
|
||||
"Drivers/BSP/ScreenLink/sb_proto.c": 74
|
||||
},
|
||||
"lines_total": 74,
|
||||
"ok": true
|
||||
},
|
||||
"sha256": {
|
||||
"files": {
|
||||
"App/util/sha256.c": 114
|
||||
@@ -741,22 +756,23 @@
|
||||
"Drivers/BSP/NET/net_socket.c",
|
||||
"Drivers/BSP/NETCFG/ftp_cfg.c",
|
||||
"Drivers/BSP/NETCFG/net_cfg.c",
|
||||
"Drivers/BSP/ScreenLink/sb_proto.c",
|
||||
"Drivers/BSP/UI/ui_data.c",
|
||||
"Drivers/BSP/UI/ui_net_sink.c",
|
||||
"Drivers/BSP/UI/ui_w_soe.c"
|
||||
],
|
||||
"totals": {
|
||||
"files_generated_excluded": 5,
|
||||
"files_tested": 46,
|
||||
"files_untested": 151,
|
||||
"func_rate": 0.9317,
|
||||
"funcs_covered": 532,
|
||||
"funcs_valid": 571,
|
||||
"line_rate": 0.8983,
|
||||
"lines_covered": 7058,
|
||||
"lines_valid": 7857,
|
||||
"modules_ok": 41,
|
||||
"modules_total": 41
|
||||
"files_tested": 47,
|
||||
"files_untested": 164,
|
||||
"func_rate": 0.9324,
|
||||
"funcs_covered": 538,
|
||||
"funcs_valid": 577,
|
||||
"line_rate": 0.8993,
|
||||
"lines_covered": 7132,
|
||||
"lines_valid": 7931,
|
||||
"modules_ok": 42,
|
||||
"modules_total": 42
|
||||
},
|
||||
"untested_sources": [
|
||||
{
|
||||
@@ -768,7 +784,7 @@
|
||||
"path": "Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 407,
|
||||
"code_lines": 420,
|
||||
"path": "App/app/ui_data_feed.c"
|
||||
},
|
||||
{
|
||||
@@ -796,7 +812,7 @@
|
||||
"path": "Drivers/BSP/SD2506/sd2506.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 249,
|
||||
"code_lines": 263,
|
||||
"path": "App/task/adc_task.c"
|
||||
},
|
||||
{
|
||||
@@ -815,6 +831,10 @@
|
||||
"code_lines": 206,
|
||||
"path": "Drivers/BSP/CH395F/ch395f.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 206,
|
||||
"path": "Drivers/BSP/UI/ui_data.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 205,
|
||||
"path": "Drivers/BSP/UI/ui_page_net_cfg.c"
|
||||
@@ -827,14 +847,14 @@
|
||||
"code_lines": 200,
|
||||
"path": "Drivers/BSP/UI/ui_w_ytview.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 196,
|
||||
"path": "Drivers/BSP/UI/ui_data.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 157,
|
||||
"path": "Drivers/BSP/TPAFE5160/tpafe5160.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 152,
|
||||
"path": "Drivers/BSP/UI/ui_page_runreport.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 150,
|
||||
"path": "Drivers/BSP/UI/ui_page_password.c"
|
||||
@@ -844,12 +864,12 @@
|
||||
"path": "Drivers/BSP/UI/ui_page_device_info.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 134,
|
||||
"path": "App/task/protect_task.c"
|
||||
"code_lines": 135,
|
||||
"path": "App/app/app_main.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 132,
|
||||
"path": "App/app/app_main.c"
|
||||
"code_lines": 134,
|
||||
"path": "App/task/protect_task.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 129,
|
||||
@@ -999,6 +1019,10 @@
|
||||
"code_lines": 40,
|
||||
"path": "Drivers/BSP/UI/ui_page_deadband.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 36,
|
||||
"path": "Drivers/BSP/ScreenLink/sb_proto.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 36,
|
||||
"path": "Drivers/BSP/UI/ui_w_records.h"
|
||||
@@ -1115,6 +1139,18 @@
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/NETCFG/ftp_cfg.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_accident.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_alarm.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_oper.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_runlog.c"
|
||||
@@ -1127,6 +1163,14 @@
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_soe.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_start.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 22,
|
||||
"path": "Drivers/BSP/UI/ui_page_yk.c"
|
||||
},
|
||||
{
|
||||
"code_lines": 20,
|
||||
"path": "Drivers/BSP/UI/ui_net_sink.h"
|
||||
@@ -1339,6 +1383,14 @@
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_ac.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_accident.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_alarm.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_dc.h"
|
||||
@@ -1347,6 +1399,22 @@
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_device_info.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_oper.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_runreport.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_start.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 5,
|
||||
"path": "Drivers/BSP/UI/ui_page_yk.h"
|
||||
},
|
||||
{
|
||||
"code_lines": 4,
|
||||
"path": "Drivers/BSP/UI/ui_page_runlog.h"
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""屏板链路协议层(sb_proto)的主机侧纯逻辑单元测试运行器(纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/sb_proto/sb_proto_test.c(它以 #include 纳入**生产源文件**
|
||||
Drivers/BSP/ScreenLink/sb_proto.c),在 PC 上离线验证帧 A 解析 / 帧 B 组帧 /
|
||||
CRC-16/MODBUS:教科书校验值、屏板实测的召唤帧字节、坏帧分支、4208 B 帧的
|
||||
点阵-掩码-填充-CRC 落位、LOGO 宽高容量守卫、以及"按屏板 Check_CRC 的口径复核
|
||||
本模块组的帧会不会被屏板接受"。零硬件、零烧录、毫秒级。
|
||||
|
||||
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
|
||||
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||
|
||||
为什么要主机侧单测(而不是只用台架):
|
||||
屏板不认帧时,它的超时判定只看"收没收到一整包"、不看 CRC,现场只表现为**画面不刷新**——
|
||||
这条现象与波特率错、时序错、线接反完全同形,台架上无法归因。把 CRC 与字节序钉在主机侧,
|
||||
台架上剩下的就只可能是物理层问题。另一处只能在这里测:CRC 的**字节序**口径。
|
||||
屏板固件 Calculate_CRC() 返回的是高低互换后的「线上字」,它自己的注释把低字节叫"高字节";
|
||||
照字面实现会让每一帧都差一次字节交换,而现象又是"画面不刷新"——这里钉了两条屏板实测向量。
|
||||
|
||||
本层为**纯逻辑**:无寄存器、无 RTOS、无文件/外设依赖,故**不需要任何桩目录**。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/sb_proto/run_unit_test.py
|
||||
或统一用 .venv: & '.venv\\Scripts\\python.exe' test/host/sb_proto/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:docs/产品需求/产品需求设计文档.md §5.16.2(帧结构);
|
||||
docs/任务规划/屏板链路.md §3 批 1(1-1 的判据);
|
||||
协议事实源 = 屏板固件工程 D:\\Code\\DTU Code\\DTU-LCD 的 CRC16.c / MODBUS.c。
|
||||
创建日期:2026-10-29
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
# harness 位于 test/host/common/
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
# 无桩目录:被测代码只依赖 stdint.h / string.h。
|
||||
# 两个 include 目录各管一个头:ScreenLink 找 sb_proto.h,Drivers/BSP 找 bsp_err.h。
|
||||
BUILD = TestBuild(
|
||||
name="ScreenLink Proto Tests",
|
||||
src=os.path.join(HERE, "sb_proto_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(ROOT, "Drivers", "BSP", "ScreenLink"),
|
||||
os.path.join(ROOT, "Drivers", "BSP"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== ScreenLink Proto Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* 模块名称:屏板链路协议层(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;
|
||||
}
|
||||
Reference in New Issue
Block a user