Files
STM32F4-Base/Drivers/BSP/ScreenLink/sb_proto.h
T
wandering1 0b84c43d2e feat(屏板): 状态灯位映射 sb_led_map + 104 RUN 观测口(批 3);附 4-1 并发压测脚本
批 3(LOGO 按用户裁定出局,不实现、sb_proto 的 LOGO 组帧挂待删标注):
- 新增 App/algo/sb_led_map.c/h:业务状态 → 屏板 LED 位掩码的唯一映射表
  (纯逻辑,主机单测可跑)。ID1 运行心跳 500ms 半周期闪(与主板 LED 同拍)、
  ID2 保护动作、ID3 104 链路 RUN、ID4 采样电平故障(不选 sticky——灯要能灭)、
  ID5 保护启动 picked;bit0 与 ID 6~19 恒 0。这张表参考源码里没有,属自定,
  出处 docs/产品需求/产品需求设计文档.md §5.16.3 ①。
- iec104_serv 新增 iec104_serv_link_run()(透传 link 状态机,不另存镜像量)。
- ui_task.c 帧组装块每拍现采四路状态源(均为任意上下文可读观测口)折算后
  传 sb_link_frame_update——替换批 2 的恒 0 占位;掩码变了帧就重组,
  复用 busy/dirty 机制,无新通路。
- 主机单测 test/host/sb_led_map 17/17(位语义/恒零位/闪烁相位/tick 回绕/
  NULL 契约);注册豁免走 SIM_EXEMPT(模拟器无屏板串口)。

批 4 的 4-1 双通道并发回归(真屏板在线实测):
- 新增 test/host/remolcd_pull_test.py:RemoDispBus 拉帧压测客户端
  (INIT→LCDMEM 10 Hz、逐帧 XOR+长度校验、往返延迟统计、--dump PBM)。
- 实测:网络侧两轮 2200 拉帧零错零超时(min 35/avg 63/max 131 ms);
  屏板侧 60 s 隔离窗口 rx +233 全收、drop 零新增、bad=0、off=0、无
  OFFLINE——两侧互不拖。观察项 len=7(断帧尺寸≠6,累计 7 次)出现窗口
  无法归因压测、隔离窗口零新增、有效帧零损失,已挂设计说明 §5.7 待查。

附带:sb_link.h frame_update 注释回填实测耗时(509/688 µs 取代旧估算)。
Keil 0 错误 0 警告;ci_local 全绿(SKIP=nested-comment,主树脚本另查 0 新发);
Code=239,064 B / RW_IRAM1 65.5 % / RW_CCM 87.9 %;覆盖率 90.1 % / 44 套件。
2026-10-30 00:39:57 +08:00

167 lines
10 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])
* ⚠ 待删除(用户裁定 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 */