From 2c69a6ca076d201a2f412318bdfaa91f20632c48 Mon Sep 17 00:00:00 2001
From: Wanderingss <1624155937@qq.com>
Date: Thu, 29 Oct 2026 19:18:11 +0800
Subject: [PATCH] =?UTF-8?q?docs(=E5=B1=8F=E6=9D=BF):=20=E8=AE=A2=E6=AD=A3?=
=?UTF-8?q?=20CRC=20=E5=AD=97=E8=8A=82=E5=BA=8F=E5=8F=A3=E5=BE=84=EF=BC=88?=
=?UTF-8?q?=E4=BD=8E=E5=AD=97=E8=8A=82=E5=9C=A8=E5=89=8D=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
写 1-1 时用屏板自己的算法复算了线上字节,发现文档里"CRC 高字节在前"的说法
与真实顺序相反,一并订正。
事实:屏板 Calculate_CRC() 返回的不是标准 CRC-16/MODBUS 值,而是高低互换后
的「线上字」("123456789" 它返回 0x374B,标准值 0x4B37)。于是
RS485_RefreshScreenCMD() 里 cmdbuf[4] = crc>>8(注释写"CRC校验高字节")
实际写出去的是低字节——两处命名都反,线上字节是对的(标准 Modbus 低字节在前)。
照字面实现会让屏板把每一帧都判成 CRC 错,而它的超时判定只看"收没收到一整
包"、不看 CRC,现场只表现为画面不刷新,极易误判成波特率或时序问题。
- 产品需求 §5.16.2:帧 A 表的 4/5 偏移改成低/高,补一段字节序说明与实测值;
帧 B 的 CRC 行、§5.16 的通道概述、实施清单第 11 条同步。
- 产品需求 §5.16.2:RT_CMD 周期 275 -> 280 ms(判据 RT_count > 55 即第 56
拍 x 5 ms;屏板源码注释里的 275 是少算一拍)。
- 屏板链路.md:4.2 的帧 B 布局与 0-3 的验收命令示例同步,并指向单测向量;
1-1 标完成。
---
docs/产品需求/产品需求设计文档.md | 24 ++++++++++++++++--------
docs/任务规划/屏板链路.md | 15 ++++++++++++---
2 files changed, 28 insertions(+), 11 deletions(-)
diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md
index e5868878..87f139f6 100644
--- a/docs/产品需求/产品需求设计文档.md
+++ b/docs/产品需求/产品需求设计文档.md
@@ -1068,13 +1068,21 @@ sequenceDiagram
|---|---|---|---|
| 0 | 1 | 从站地址 | 固定 `0x01`(**主机的从站地址**,不是屏板地址) |
| 1 | 1 | 功能码 | 固定 `0x03`(读保持寄存器) |
-| 2 | 1 | 命令类型 | `0x00` = `KEY_CMD`(按键事件,**立即**召唤)
`0x01` = `RT_CMD`(周期刷新,275 ms 一次) |
+| 2 | 1 | 命令类型 | `0x00` = `KEY_CMD`(按键事件,**立即**召唤)
`0x01` = `RT_CMD`(周期刷新,**280 ms** 一次:`RT_count > 55`,第 56 拍 × 5 ms) |
| 3 | 1 | 按键值 | `KEY_CMD` 时有效(见 §5.16.3 键值表);`RT_CMD` 时**恒为 `0`**(按键发完即清 `key_bk`,见 `RS485_Process()`) |
-| 4 | 1 | CRC-16 **高**字节 | CRC-16/MODBUS |
-| 5 | 1 | CRC-16 **低**字节 | 同上 |
+| 4 | 1 | CRC-16 **低**字节 | CRC-16/MODBUS |
+| 5 | 1 | CRC-16 **高**字节 | 同上 |
-CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法 `auchCRCHi[]`/`auchCRCLo[]`),
-**只覆盖前 4 字节**,**高字节在前**(`cmdbuf[4] = crc>>8`、`cmdbuf[5] = crc`)。
+CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法),
+**只覆盖前 4 字节**,**低字节在前**——这是标准 Modbus RTU 的线上顺序(教科书校验值:数据 `123456789`
+→ `0x4B37`,线上写 `37 4B`)。
+
+> ⚠ **屏板源码里的字节序注释是反的,别照抄**:`Calculate_CRC()` 返回的**不是**标准 CRC 值,而是高低
+> 互换后的「线上字」(`123456789` 它返回 `0x374B`)。于是 `RS485_RefreshScreenCMD()` 里那句
+> `cmdbuf[4] = crc>>8`(注释写"CRC校验高字节")实际写出去的是**低字节**——两处命名都反,**线上字节是对的**。
+> 主机侧按「标准值 + 低字节在前」实现,两侧字节逐一相同;若照字面写成"高字节在前",屏板会把每一帧
+> 都判成 CRC 错,而它的超时判定只看"收没收到一整包"、不看 CRC,现场只表现为画面不刷新,
+> 极易误判成波特率或时序问题。实测向量与实现见 `Drivers/BSP/ScreenLink/sb_proto.c` 文件头。
**帧 B:图像应答(主机 → 屏板,固定 4208 B)**
@@ -1086,7 +1094,7 @@ CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法 `auch
| 3205 | 1 | `info[5]` | LOGO 魔数低字节 `0x45` |
| 3206 | 2 | `info[6..7]` | 保留,屏板不读 |
| 3208 | 998 | 保留填充 | 屏板不读,**但参与 CRC 计算**,须固定填充(建议 `0x00`) |
-| 4206 | 2 | CRC-16 | 覆盖 `[0, 4205]`,**高字节在前** |
+| 4206 | 2 | CRC-16 | 覆盖 `[0, 4205]`,**低字节在前**(与帧 A 同一口径,见上方的字节序说明) |
> ⚠ **帧长以代码为准:4208 B**(屏板 readme 与 `BMP_LEN` 写的"3208 字节"不作准):接收侧实参是
> `Check_CRC(DR, UART_RX_LEN)`,`UART_RX_LEN = 4208` → CRC 取 `DR[4206]/DR[4207]`、覆盖 `DR[0..4205]`。
@@ -1480,7 +1488,7 @@ for (i = 1; i < LED_COUNT; i++) /* LE
> 逐项差异、以及为什么会有差异,见 §5.16.9。**以参考为准**,差异项按 §9.13 排期收敛。
>
> 本节只讲**网络**通道(TCP 7003)。**串口屏板通道不走本协议**——它是 RS-485 上的
-> **Modbus RTU 风格裸二进制帧**(6 B 召唤 + **4208 B** 图像应答,CRC-16/MODBUS 高字节在前),
+> **Modbus RTU 风格裸二进制帧**(6 B 召唤 + **4208 B** 图像应答,CRC-16/MODBUS **低字节在前**),
> 帧格式已按屏板源码逐字节定版,见 §5.16.2。
> 两条通道**共用同一份显存**,但帧格式、校验、时序、主从方向全都不同,**不能共用组帧代码**。
@@ -2536,7 +2544,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 8 | **发送重试评估** | 对比参考"BUSY + 200 ms 放弃"与现有 `net_send` 阻塞模型,出结论并回填 §5.16.9 第 7 行 | 结论有实测数据支撑(实现 / 明确不实现) | 否 |
| 9 | **客户端一致性回归** | `DTU-RemoteLCD` 按 **16 B** 解析;把 §5.16.10 第 1~7 条做成自动化用例 | 七条判据全 PASS | **是** |
| 10 | **串口物理层落地** | 按 §5.16.2 定版:**700000 8N1、`BRR = 0x0078`**(USART1,APB2 84 MHz)、**无 DE**(TTL 直连,不走收发器)、RX 缓冲 16 B + 空闲中断断帧、发送走 **DMA 一次性发完**(禁逐块阻塞,`DMA2_Stream7` TX / `DMA2_Stream2` RX 已在 `.ioc` 配好但控制台未用) | 环回 + 示波器实测 700 kbaud 分频零误差;屏板能收到帧 A 并回帧 B | 否 |
-| 11 | **帧 A 解析 / 帧 B 组帧** | 新增屏板通道模块:识别 6 B 请求(`01 03 type key CRC_H CRC_L`)、校验 CRC-16/MODBUS(**高字节在前**);组 **4208 B** 帧 B(3200 显存 + 4 B LED + 4 B info 保留 + 998 B 填充 + 2 B CRC) | 屏板出图与 `g_ui_fbuf` 逐字节一致;`info[0..3]` 与下发的 LED 掩码一致 | 否 |
+| 11 | **帧 A 解析 / 帧 B 组帧** | 新增屏板通道模块:识别 6 B 请求(`01 03 type key CRC_L CRC_H`)、校验 CRC-16/MODBUS(**低字节在前**,口径见 §5.16.2);组 **4208 B** 帧 B(3200 显存 + 4 B LED + 4 B info 保留 + 998 B 填充 + 2 B CRC) | 屏板出图与 `g_ui_fbuf` 逐字节一致;`info[0..3]` 与下发的 LED 掩码一致 | 否 |
| 12 | **状态灯位映射表** | 定义"**业务状态 → 屏板 LED 位**"映射(参考源码**无此表**,须自定);`info[0..3]` 按 **32 位小端**填,bit0 恒 0、bit20~31 恒 0 | §5.16.10 第 11 条通过 | 否 |
| 13 | **串口上行按键** | 帧 A 的 `key` 字节按**屏板 `KEY_TYPE`** 解释——实际可达值只有 **`1/2/3/4/7/8/9`**(`5`/`6`/`10` 无物理入口);映射为逻辑键值后与 7003 注入**同一入口**合并 | §5.16.10 第 10 条通过;`9`/`10` 语义按 §5.16.3 ② 处置 | 否 |
| 14 | **双通道并发 + 自复位容忍** | 两出口各自独立发送上下文;串口 4208 B 阻塞不拖 7003;链路监视按"**连续 ≥10 s 无应答**"判离线,**忽略屏板 ≈5000 s 周期自复位** | §5.16.10 第 12、13、14 条通过 | 否 |
diff --git a/docs/任务规划/屏板链路.md b/docs/任务规划/屏板链路.md
index e2d88ffc..d3822ebb 100644
--- a/docs/任务规划/屏板链路.md
+++ b/docs/任务规划/屏板链路.md
@@ -119,7 +119,8 @@
1. **无屏板实物时用 PC + USB 转 TTL 顶替屏板**(`docs/任务规划/README.md` 已注明"软件可先用 PC +
USB 转 TTL 台架验证,不必等屏板实物"):适配器 `TXD` → `U5.B10`、`RXD` → `U5.A10`、`GND` → 板地,
700000 8N1,按屏板的周期召唤节拍(约 275~280 ms,口径见 `docs/产品需求/产品需求设计文档.md` §5.16.2)
- 发 6 B 帧 `01 03 01 00 CRC_H CRC_L`(`RT_CMD`,按键时第 4 字节换成键值;CRC-16/MODBUS 只覆盖前 4 B、高字节在前)。
+ 发 6 B 帧 `01 03 01 00 CRC_L CRC_H`(`RT_CMD`,按键时第 4 字节换成键值;CRC-16/MODBUS 只覆盖前 4 B、
+ **低字节在前**——屏板源码的注释把高低写反了,口径见 `docs/产品需求/产品需求设计文档.md` §5.16.2)。
2. **判据**:`Size == 6`、CRC 通过率 100 %、连续 10 min 无丢帧;拔插一次 TX 线制造噪声后能自动恢复
(`HAL_UART_ErrorCallback` 里清 ORE 并重挂接收,否则一次噪声就永久静默)。
3. **有实物后**重复第 2 步,并确认屏板画面按召唤节拍刷新。
@@ -242,7 +243,7 @@
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
-| 1-1 | **协议层模块(纯逻辑,可主机单测)** | 新建 `Drivers/BSP/ScreenLink/sb_proto.c/h`:CRC-16/MODBUS(表与屏板 `auchCRCHi/Lo` 逐字节一致)、帧 A 解析、帧 B 组帧 | 主机单测:给定 6 B 召唤帧能算出屏板同样的 CRC;组出的 4208 B 帧 CRC 自校验通过 |
+| 1-1 ✅ | **协议层模块(纯逻辑,可主机单测)** | 新建 `Drivers/BSP/ScreenLink/sb_proto.c/h`:CRC-16/MODBUS(与屏板同一多项式,用 16 位合并表)、帧 A 解析、帧 B 组帧(普通图 / LOGO)、帧 B 自校验;双端注册:uvprojx 加 File + IncludePath,模拟器侧走 `scripts/check_registration.py` 的 MCU-only 豁免(纯逻辑,模拟器无消费路径) | **已完成 2026-10-29**:主机单测 **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` 口径复核均被接受;Keil 构建 **0 错误 0 警告**(`Code` 不变——尚无调用者,链接器按未用节丢弃) |
| 1-2 | **波特率与格式落地** | `STM32F407-Demo.ioc` 里把 **USART1** 改 **700000 / 8N1 / 16 倍过采样**(改 `.ioc` 而不是只改 `Src/usart.c`,否则 CubeMX 重生成会丢);`Src/usart.c` 只写 USER CODE | 示波器/逻辑分析仪实测位宽误差 **< 1 %**;理论值 `USARTDIV = 7.5`、`BRR = 0x0078`(APB2 84 MHz),误差 0 % |
| 1-3 | **USART1 收中断与全局回调分支** | 开 IDLE 中断接收(缓冲 **16 B**);`HAL_UARTEx_RxEventCallback` 里**加 USART1 分支**(现有回调只把事件分发给另一条在用通道的句柄,不加分支会静默收不到);`HAL_UART_ErrorCallback` 同样要加,且**必须清 ORE/NE/FE 并重挂接收**(`__HAL_UART_CLEAR_OREFLAG` 或读 SR 再读 DR) | 屏板每 280 ms 发来的 6 B 能稳定收到,`Size == 6`,CRC 通过率 100 %;**拔插一次 TX 线制造噪声后能自动恢复**(不清 ORE 会永久锁死) |
@@ -303,9 +304,17 @@ stub 提供 HAL)。屏板侧 CRC 有查表实现,**用同一张表做交叉
偏移 3205 1 B info[5]:普通图填 0x00(非 0x45)
偏移 3206 2 B info[6..7]:保留,填 0
偏移 3208 998 B 填充,填 0x00(屏板不读,但参与 CRC)
-偏移 4206 2 B CRC-16/MODBUS,覆盖 [0, 4205],高字节在前
+偏移 4206 2 B CRC-16/MODBUS,覆盖 [0, 4205],低字节在前(口径见 §4.2 下方的字节序说明)
```
+> ⚠ **CRC 的字节序:屏板源码的注释是反的**。屏板 `Calculate_CRC()` 返回的不是标准 CRC 值,
+> 而是高低互换后的「线上字」(数据 `123456789` 它返回 `0x374B`,标准值是 `0x4B37`),所以
+> `RS485_RefreshScreenCMD()` 里 `cmdbuf[4] = crc>>8`(注释写"CRC校验高字节")写出去的其实是**低字节**。
+> 两处命名都反,**线上字节是对的**:标准 Modbus RTU 的低字节在前。
+> 主机侧一律按「标准 CRC 值 + 低字节在前」写(帧 A 偏移 4、帧 B 偏移 4206 都是低字节),
+> 照字面写成"高字节在前"会让屏板把每一帧都判成 CRC 错,而它的超时判定只看"收没收到一整包",
+> 现场只表现为画面不刷新。实测向量已钉在 `test/host/sb_proto/sb_proto_test.c`。
+
> ⚠ **`info[0..3]` 是双解释字段**:普通图当 LED 掩码、LOGO 图当宽高。区分只靠 `info[4..5]`。
> **先判魔数再解释这 4 字节**,顺序反了会把 LED 掩码当宽高。