Files
STM32F4-Base/Drivers/BSP/ScreenLink/sb_proto.h
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

165 lines
9.8 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.
/*
* 模块名称:屏板链路协议层(帧 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 */