/* * 模块名称:屏板链路协议层(帧 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 #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]) * ⚠ 待删除(用户裁定 2026-10-29):LOGO 功能出局,不下发、不接线;本组 * LOGO 常量与 sb_frame_b_build_logo() 保留仅为协议层完整性,**勿新增调用**。 */ #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 */