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 */
|
||||
Reference in New Issue
Block a user