docs(屏板链路): 规划文档收编为设计说明事实源,全仓引用与端口口径同步

- 重写 docs/子系统设计/屏板链路设计说明.md:链路角色、串口与端口配置、
  物理接线、协议实现口径、键值与状态灯映射、资源与耗时、对外接口、
  已验证/未闭环、坑账本——只写现状事实,演进过程查 git 历史
- 删除 docs/任务规划/屏板链路.md(内容已全部沉淀进上述设计说明,
  登记簿「完成即销项」;docs/任务规划/README.md 相应两行一并移除)
- 代码与主机侧测试的文档指针改指设计说明对应章节:
  sb_link.h/.c、sb_proto.h/.c、sb_led_map.h、console_log.c、ui_task.c、
  test/host 的 sb_link/sb_proto/sb_led_map 三套与两个台架脚本
- docs/README.md:设计文档索引行与设计说明描述更新,任务规划目录 7→6 文件
- docs/产品需求/产品需求设计文档.md:串口通道现状口径同步——第 11/12/13 条
  已交付(4208 B 应答/状态灯掩码/键上行/双出口并发)、LOGO 下发出局不实现、
  帧长实测固化、调试日志口 UART5/RS232#1(§4.2/§5.16/§6.6/§9.12/§9.13)
- docs/子系统设计/控制台单写者日志设计说明.md v1.8:全文端口口径由
  USART1 同步为 UART5/RS232#1(CON_UART* 单点宏),ORE 机理标注跨端口
  同因,时间线补端口迁移行;头部修订指针改为实际章节名

说明:本任务经用户显式授权在任务树内编辑 docs/任务规划/ 与产品需求基线
(铁律「单写者」豁免一次);主树合并后统一跑全量 check_doc_links --all。
This commit is contained in:
2026-10-30 09:22:44 +08:00
parent 613fc25651
commit 8744c833e5
19 changed files with 308 additions and 772 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
* 作者:王建锋
* 创建日期:2026-10-29
* 修改记录:
* 2026-10-29 王建锋 创建初始版本(批 3 的 3-1:映射表落纸,docs/任务规划/屏板链路.md §3)
* 2026-10-29 王建锋 创建初始版本(映射表口径见 docs/子系统设计/屏板链路设计说明.md §6)
*
* 这张表为什么必须"自定":屏板源码(DTU-LCD LED_Printf)只规定了"位 → 灯号"的
* 物理口径(bit1~bit19 → ID 1~19,ID 0 电源灯硬件常亮、不看位,bit20~31 未使用),
+2 -2
View File
@@ -103,8 +103,8 @@
/* ===== 控制台端口(单点收口)=====
* 换口只改这一段:句柄 / IRQn / 初始化函数。时钟宏随句柄走——UART5 在 APB1、
* USART1 在 APB2,别照抄;串口模式与波特率在 Src/usart.c 的 MX_*_Init 里,本模块不管。
* 2026-10-29 批 0:USART1 让位给屏板链路(700 kbaud 只有它 MCU 引脚直出),控制台迁
* RS232#1/UART5。缘由、端子与验收见 docs/任务规划/屏板链路.md 的批 0。 */
* 控制台在 RS232#1/UART5(USART1 专用给屏板链路——700 kbaud 只有它 MCU 引脚直出)。
* 端口排布、台架端子与判据见 docs/子系统设计/屏板链路设计说明.md 的 §2 与 §3.2。 */
#define CON_UART huart5
#define CON_UART_IRQN UART5_IRQn
#define CON_UART_CLK_DISABLE() __HAL_RCC_UART5_CLK_DISABLE()
+2 -2
View File
@@ -868,12 +868,12 @@ static void ui_task_wait_net_ready(void) {
/*
* 函数功能:把屏板键号映射为 UI 键码
* 入口参数:board_key - 屏板帧 A 的 key 字节(1~10,语义见 docs/任务规划/屏板链路.md §4.3)
* 入口参数:board_key - 屏板帧 A 的 key 字节(1~10,语义见 docs/子系统设计/屏板链路设计说明.md §5)
* 返回值:UI_KEY_* 之一;0 = 不映射(无键 / RESET / FACTORY / 屏板无物理入口的 5、6)
* 限定条件:只在 uiTask 上下文调用(ui_port_queue_key 的契约是输入子系统已初始化)
* 函数说明:1. 屏板 key_configs[] 只有 **7 项**,可达值仅 1/2/3/4/7/8/9——别按屏板 readme
* 的 8 键表实现(5/6 = ADD/DEC 在实物上没有入口,永远收不到)。
* 2. 9(RESET)/10(FACTORY)属决策四待裁定项:**先丢弃并留痕**,不要擅自
* 2. 9(RESET)/10(FACTORY)语义待产品裁定:**先丢弃并留痕**,不要擅自
* 映射成参考机的 F1 / F1+ENT——否则现场按一下"复位"会把菜单弹到未知页面,
* 而且那种误映射极难从现象反推回映射表。
*/
+4 -4
View File
@@ -7,8 +7,8 @@
* 作者:王建锋
* 创建日期:2026-10-29
* 修改记录:
* 2026-10-29 王建锋 创建初始版本(批 1 的 1-2 / 1-3)
* 2026-10-29 王建锋 批 2:组帧入口 + 中断起发 + TxCplt 收尾 + 按键队列 + 离线判定
* 2026-10-29 王建锋 创建初始版本(收中断 + 帧 A 校验)
* 2026-10-29 王建锋 增加:组帧入口 + 中断起发 + TxCplt 收尾 + 按键队列 + 离线判定
*
* 四个容易踩的点(改之前先读,前三条都已在台架上被别的东西咬过一次):
*
@@ -291,7 +291,7 @@ void sb_link_on_rx_event(UART_HandleTypeDef *p_huart, uint16_t size)
if (frame.cmd == SB_CMD_KEY)
{
s_stats.key_events++;
/* 只入队,不注键:注键会走 ui_port 的输入队列,属业务动作(决策三)。
/* 只入队,不注键:注键会走 ui_port 的输入队列,属业务动作。
* key == 0 是"无键",屏板自己的语义就是没有事件,不必占队列。 */
if (frame.key != 0U)
{
@@ -299,7 +299,7 @@ void sb_link_on_rx_event(UART_HandleTypeDef *p_huart, uint16_t size)
}
}
/* 决策三:起发就在这儿,不在任务里轮询。一召唤 → 一答 4208 B,绝不叠发。
/* 起发就在这儿,不在任务里轮询。一召唤 → 一答 4208 B,绝不叠发。
* 上一帧还没发完(屏板自己超时重问过)就丢这一轮:多发一帧会让屏板把
* 两帧粘成一帧撕裂,而且它接收溢出后会永久收不到东西(见 §4.4 的警示)。 */
if (s_frame_ready == 0U)
+15 -14
View File
@@ -3,7 +3,7 @@
* 模块功能:把主机与屏板之间那条 700 kbaud 链路完整地管起来:
* - 收:IDLE 空闲中断断帧、6 B 帧 A 校验、错误中断里清 ORE 并重挂接收;
* - 发:任务侧组一帧 B(4208 B)进静态缓冲,中断侧收到召唤帧**直接点 TX DMA**,
* TxCplt 里收尾(决策三:不排队、不攒批、不在任务里逐字节轮询);
* TxCplt 里收尾(不排队、不攒批、不在任务里逐字节轮询);
* - 按键上行:中断只入队,注键由任务侧取走(**注键不能在中断里做**);
* - 链路监视:连续 >=10 s 无召唤帧才判离线;CRC 错帧静默丢弃并计数。
* 协议字节级编解码(CRC / 帧 A 解析 / 帧 B 组帧)在 sb_proto,本层只做搬运与时序。
@@ -11,8 +11,8 @@
* 作者:王建锋
* 创建日期:2026-10-29
* 修改记录:
* 2026-10-29 王建锋 创建初始版本(批 1 的 1-2 / 1-3:700 kbaud + 收中断)
* 2026-10-29 王建锋 批 2:帧 B 组帧入口、中断起发、TxCplt 收尾、按键队列、离线判定
* 2026-10-29 王建锋 创建初始版本(700 kbaud + 收中断)
* 2026-10-29 王建锋 增加:帧 B 组帧入口、中断起发、TxCplt 收尾、按键队列、离线判定
*
* 为什么单独一层、不并进 sb_proto:
* sb_proto 是纯逻辑(CRC / 组帧 / 解析),能在 test/host 里原样 include 跑单测;
@@ -55,13 +55,13 @@ extern "C" {
* 接收缓冲:16 B 足够。
* 屏板只发 6 B 的帧 A(周期召唤 / 按键召唤),16 B 留出一次噪声冲击的余量。
* ⚠ 不要照抄屏板侧的 4208 B——那是帧 B 的长度,方向相反(主机发、屏板收),
* 放进接收侧白占 SRAM(判据见 docs/任务规划/屏板链路.md §1.3)。
* 放进接收侧白占 SRAM(判据见 docs/子系统设计/屏板链路设计说明.md §2)。
*/
#define SB_LINK_RX_BUF_SIZE (16U)
/*
* 统计上报周期(ms)。只在计数有变化时才打一行,链路空闲不刷屏;
* 台架验收靠的就是这一行里的累计值(判据见 docs/任务规划/屏板链路.md §3 的 1-3)。
* 台架验收靠的就是这一行里的累计值(判据见 docs/子系统设计/屏板链路设计说明.md §9)。
*/
#define SB_LINK_REPORT_MS (5000U)
@@ -70,7 +70,7 @@ extern "C" {
* 复位期间约 1 s 不召唤;再叠上主机的重挂/噪声恢复,短窗口会把"屏板活着但歇了口气"
* 误判成离线,然后在下一次召唤时又"恢复"——现场看到的是离线标志反复跳。
* 10 s 的取法:比 1 s 自复位空窗大一个数量级,又远小于"人发现屏板黑屏"的时间。
* 出处 docs/任务规划/屏板链路.md §3 的 2-5。
* 出处 docs/子系统设计/屏板链路设计说明.md §2 链路监视。
*/
#define SB_LINK_OFFLINE_MS (10000U)
@@ -105,7 +105,7 @@ typedef struct
uint32_t rx_crc_err; /* CRC 不符 */
uint32_t rx_len_err; /* 长度不是 SB_FRAME_A_LEN */
uint32_t rx_fmt_err; /* 地址/功能码/命令字不符 */
uint32_t key_events; /* 其中 cmd == SB_CMD_KEY 的帧数(按键召唤,2-3 的消费源) */
uint32_t key_events; /* 其中 cmd == SB_CMD_KEY 的帧数(按键召唤,按键队列的消费源) */
uint32_t err_irq; /* 错误中断次数(ORE/NE/FE/PE) */
uint32_t err_bits; /* 错误中断里见过哪些位(HAL_UART_ERROR_* 的并集,见下) */
uint32_t rearm_fail; /* 重挂接收失败次数(正常应恒 0,非 0 说明链路已静默) */
@@ -113,7 +113,7 @@ typedef struct
uint8_t last_cmd; /* 最近一帧有效帧的命令类型(SB_CMD_*) */
uint8_t last_key; /* 最近一帧有效帧的键值(无键为 0) */
/* ---- 批 2:发送侧(帧 B 组帧 + TX DMA 起发 + 收尾) ---- */
/* ---- 发送侧(帧 B 组帧 + TX DMA 起发 + 收尾) ---- */
uint32_t tx_kick; /* 起发成功次数(每次有效召唤帧 +1) */
uint32_t tx_done; /* TxCplt 次数(链路正常时应恒等于 tx_kick) */
uint32_t tx_busy_drop; /* 召唤到了但上一帧还在发 → 本次不答(屏板 280 ms 后会再问) */
@@ -122,9 +122,9 @@ typedef struct
uint32_t frame_build; /* 组帧次数(只有内容/掩码变化才 +1) */
uint32_t frame_bad; /* 组完自校验失败次数(正常恒 0) */
uint32_t frame_us; /* 最近一次组帧耗时(µs,DWT 实测) */
uint32_t frame_us_max; /* 组帧耗时峰值(µs)——2-1 的"≤0.3 ms"就看这一列 */
uint32_t frame_us_max; /* 组帧耗时峰值(µs),预算 ≤1 ms(docs/子系统设计/屏板链路设计说明.md §7) */
/* ---- 批 2:按键上行与链路监视 ---- */
/* ---- 按键上行与链路监视 ---- */
uint32_t key_drop; /* 按键队列满导致的丢弃次数 */
uint8_t offline; /* 1 = 连续 >= SB_LINK_OFFLINE_MS 无有效召唤帧 */
} sb_link_stats_t;
@@ -180,7 +180,7 @@ bsp_err_t sb_link_receive_start(void);
* 返回值:无
* 限定条件:**中断上下文**,只做 CRC 与计数(4 B 校验约 1 µs),不得打日志、不得阻塞。
* 函数说明:长度不是 6 就直接记 rx_len_err 并丢弃;是 6 才解析。解析通过后**直接点 TX DMA**
* 把已经组好的帧 B 发出去(决策三:起发必须在中断里,不能在任务里轮询——屏板按
* 把已经组好的帧 B 发出去(起发必须在中断里,不能在任务里轮询——屏板按
* "总线空闲 1 个字符"断帧,一次几十 µs 的任务抢占就能把帧切断,而屏板不报超时)。
* 业务动作仍然不做:按键只入队,注键由任务侧 sb_link_key_take() 取走后做。
* 末尾无条件重挂接收。
@@ -223,7 +223,7 @@ void sb_link_poll(uint32_t now_ms);
* BSP_ERR_CRC 组完自校验失败(已计数 frame_bad,帧不会发出去)
* 限定条件:**任务上下文**,且必须与 ui_port_step() 同一个任务(显存是单写者)。
* 变化帧实测 ≈0.5 ms(典型)/ 0.8 ms(峰值),没变只付 memcmp(≈20 µs);
* 判据口径见 docs/任务规划/屏板链路.md §3 的 2-1。
* 判据口径见 docs/子系统设计/屏板链路设计说明.md §7。
* 函数说明:1. **不持有显存副本**——直接拿 p_live_bmp 与帧缓冲里的点阵段 memcmp,
* 没变就立刻返回(约 20 µs)。省掉 3200 B 影子缓冲,也省掉每拍重算 CRC。
* 2. **TX 占用期间绝不写帧缓冲**(那会让 DMA 发出半新半旧的"脏帧"):
@@ -235,12 +235,13 @@ bsp_err_t sb_link_frame_update(const uint8_t *p_live_bmp, uint32_t led_mask);
/*
* 函数功能:取走一个待注的屏板按键(任务侧)
* 入口参数:无
* 返回值:屏板 key 字节(1~10,语义见 docs/任务规划/屏板链路.md §4.3);
* 返回值:屏板 key 字节(1~10,语义见 docs/子系统设计/屏板链路设计说明.md §5);
* 0 = 队列空(屏板自己的 0 就是"无键",两者同义,不必再分)
* 限定条件:任务上下文;中断侧在入队,本函数出队。单生产者单消费者,不需要关中断。
* 函数说明:只吐**屏板原始键号**,不映射 UI_KEY_*——键号是协议语义,UI 键码是 UI 语义,
* 两者在 uiTask 里汇合(那一层才知道当前是哪个页面在接键)。
* key == 9/10(RESET/FACTORY)照原样吐出,丢不丢由调用方按决策四裁定。
* key == 9/10(RESET/FACTORY)照原样吐出,丢不丢由调用方定(当前口径见
* docs/子系统设计/屏板链路设计说明.md §5)。
*/
uint8_t sb_link_key_take(void);
+2 -2
View File
@@ -105,7 +105,7 @@ uint16_t sb_crc16(const uint8_t *p_data, uint16_t len)
uint16_t crc = 0xFFFFU;
/* 32 位计数:用 uint16_t 循环变量时 ARMCC 每轮会多生成一条 UXTH(实测见
* fromelf 反汇编),4206 B 一轮就是 4206 条无用指令。帧 B 每帧都要算满 4206 B,
* 这条值得省(组帧预算见 docs/任务规划/屏板链路.md §3 的 2-1)。 */
* 这条值得省(组帧预算见 docs/子系统设计/屏板链路设计说明.md §7)。 */
uint32_t n = (uint32_t)len;
while (n > 0U)
@@ -176,7 +176,7 @@ bsp_err_t sb_frame_a_parse(const uint8_t *p_buf, uint16_t len, sb_frame_a_t *p_o
* led_mask - 状态灯位掩码,小端填进 info[0..3]
* 返回值:BSP_OK / BSP_ERR_PARAM(指针为空)
* 限定条件:整帧重写(含 998 B 填充与 CRC),调用方不必预先清零。
* 函数说明:掩码的位语义不归本模块管(见任务规划批 3)。
* 函数说明:掩码的位语义不归本模块管(见 App/algo/sb_led_map.h)。
*/
bsp_err_t sb_frame_b_build_image(uint8_t *p_frame, const uint8_t *p_bmp, uint32_t led_mask)
{
+1 -1
View File
@@ -6,7 +6,7 @@
* - 帧 B(主机 → 屏板,固定 4208 B)组帧:普通图(LED 掩码)/ LOGO(宽高 + 魔数)
* - 帧 B 自校验(组完立刻能验,供组帧路径自查)
* 本模块**不碰寄存器、不碰 DMA、不碰任务**:串口怎么配、谁来发、什么时候发都不归它管
* (那是批 1 的 1-2 / 1-3 与批 2 的事)。
* (那是 sb_link 与传输层的事)。
* 适用平台:STM32F4 系列(ARMCC v5);不依赖 HAL,主机侧可直接编译(故可主机单测)。
* 作者:王建锋
* 创建日期:2026-10-29
+2 -2
View File
@@ -93,7 +93,7 @@ docs/
| [`ADC采样节拍设计说明.md`](子系统设计/ADC采样节拍设计说明.md) | **ADC 采集链路采样节拍(现状说明)**:TIM3 1 kHz 更新事件同时触发 TPAFE CONVST 与(经 TRGO)STM32 ADC1 + DMA 乒乓缓冲;节拍源为什么是 TIM3、DMA 硬约束、事件驱动消费与就绪门、实现落点、观测计数、性能特征与设计边界 |
| [docs/子系统设计/故障录波设计说明.md](子系统设计/故障录波设计说明.md) | **故障录波设计与口径属主**:1 kHz 起步与 80 点口径的偏离登记、存原始码值的理由、四段所有权与四条并发陷阱、段文件字节布局、现场单通道回放显示契约、预算与 R6 前置 |
| [`IEC104上行设计说明.md`](子系统设计/IEC104上行设计说明.md) | **B4 上行规约**:IEC 60870-5-104 被控站(TCP 2404)范围/点表(V0.1 有效子集)/链路流程(k/w/t1~t3、总召、COV、遥控两段式、定值)/netTask 接入决策/预算与测试方案;协议核心纯逻辑 + 主机侧纯逻辑单测全栈单测 |
| [`屏板链路设计说明.md`](子系统设计/屏板链路设计说明.md) | **屏板串口链路(`Drivers/BSP/ScreenLink/`)实现记录**:一句话架构与数据流、`sb_proto`/`sb_link` 全部对外接口、八条关键实现决定(不留影子缓冲/busy 冻结/中断起发等)、资源占用实测(`s_frame` 落 RW_IRAM1 非 CCM)、单测与台架/真屏板冒烟证据链、接线教训(排针丝印视角陷阱)、坑账本、**尚未实测项清单**;任务分解与协议事实源仍在 `docs/任务规划/屏板链路.md` |
| [`屏板链路设计说明.md`](子系统设计/屏板链路设计说明.md) | **屏板串口链路(主机侧,`Drivers/BSP/ScreenLink/`)设计**:链路与角色、USART1 700 kbaud 配置与中断收发、物理接线(含台架日志口与 PC 顶替屏板)、协议实现口径(CRC 字节序 / 整帧 DMA / 时序)、键值与状态灯映射表、资源与耗时、对外接口表、验证状态与未闭环项、坑账本 |
| [`网络/BSD_Socket_API_使用指南.md`](网络/BSD_Socket_API_使用指南.md) | **上层视图**:BSD Socket API 公开语义、示例、常见问题 |
| [`网络/BSD_Socket_API_函数调用关系.md`](网络/BSD_Socket_API_函数调用关系.md) | **内部视图**:netTask / 消息队列 / 保护判据挂起 / 重试对账的调用关系与设计意图 |
| [`网络/Net层阻塞模型架构设计.md`](网络/Net层阻塞模型架构设计.md) | **等待模型**:recv/send 挂起请求架构、数据结构与挂起槽状态机、recv 五路 + send 四路完成语义、模块接口契约(14 函数)、实现落点(20 集成点)、调用方契约、设计边界与性能特征 |
@@ -243,7 +243,7 @@ docs/
| 位置 | 内容 |
|---|---|
| [`任务规划/`](任务规划/README.md) | **统一待办入口(7 个文件)**:`README.md` = 现在做什么与全项目顺序;`UI补齐.md` = 当前唯一执行清单;`登记簿.md` = 后续任务、外部窗口与长期观察;`故障录波.md` = 录波的卡片与判据(1 kHz · 8 路原始码值 · 单通道回放);`数据可信.md` = 采集数据可信度的五级台阶与任务卡(含手边可做的动态波形验证)+ 被代码引用的设计约束;`暂缓-RS485.md`、`屏板链路.md` = 两个专项(`测试与架构专项.md` 已删除 2026-10-28,未闭环约束并入 `暂缓-RS485.md`)。完成即删,重复内容只保留一个归属 |
| [`任务规划/`](任务规划/README.md) | **统一待办入口(6 个文件)**:`README.md` = 现在做什么与全项目顺序;`UI补齐.md` = 当前唯一执行清单;`登记簿.md` = 后续任务、外部窗口与长期观察;`故障录波.md` = 录波的卡片与判据(1 kHz · 8 路原始码值 · 单通道回放);`数据可信.md` = 采集数据可信度的五级台阶与任务卡(含手边可做的动态波形验证)+ 被代码引用的设计约束;`暂缓-RS485.md` = 暂缓专项(`测试与架构专项.md` 已删除 2026-10-28,未闭环约束并入 `暂缓-RS485.md`;`屏板链路.md` 已完成,转为 `子系统设计/屏板链路设计说明.md` 后删除)。完成即删,重复内容只保留一个归属 |
| `硬件平台/` | 网表详细分析报告与语义图(V0.0 / V0.1);**[`ADC通道映射说明.md`](硬件平台/ADC通道映射说明.md)——V0.1 十七路 ADC 语义/网名/通道号权威映射表(固件取数、UI 显示、自检注入以此为准)** |
| `芯片手册/` | 外部芯片数据手册转录(CH395F / GD5F2GQ5UE / SD2506 / TPAFE5160 / RTL8305NBI 等) |
| `images/` | 文档技术配图(`.svg` 产物,由 `tools/arch_diagram/`、`tools/refgraph/` 生成) |
+33 -37
View File
@@ -113,7 +113,7 @@ flowchart LR
C[DTU-RemoteLCD 或任意 TCP 客户端]
end
DEV -- "USB:ST-Link 下载器" --> TGT
DEV -- "USB 串口 USART1 @115200 调试日志" --> TGT
DEV -- "USB→RS-232 适配器:RS232#1 @115200 调试日志" --> TGT
TGT -- "以太网 CH395F<br/>FTP:21 / 对时:8888 / 远程显示:7003" --> RMT
```
@@ -330,7 +330,7 @@ tTimer_NoUI 无压无流判据(CN_
| **外部资源**(标准源、端子变比) | 定值标定、保护判据解卡 | 两点标定(§9.2 第 4 步) |
| **硬件缺失**(X3 / X4) | 开入 138、开出 31、电度 8、同期、三相通道 | V0.2 硬件 |
| **立项未排** | 多规约、SNTP、定值铺开 | 按 §9.2 顺序。**录波最小闭环已交付代码面**(2026-10-29,卡片 docs/任务规划/故障录波.md、设计 docs/子系统设计/故障录波设计说明.md;1 kHz 起步与本文 80 点/周波口径的偏离在该设计文档 §2 显式登记);**4 kHz 全速、多段、COMTRADE、104 文件传输仍未排**,属二期范围 |
| **算力 / 形态**(X1 / X2) | 双核分工、VxWorks | 不解除。**屏板链路已从范围外项移入范围内待做**(规格 §5.16,执行规划 docs/任务规划/屏板链路.md):主机侧链路与参考 1:1 同构,不属"替代方案" |
| **算力 / 形态**(X1 / X2) | 双核分工、VxWorks | 不解除。**屏板链路已从范围外项移入范围内**(规格 §5.16,主机侧实现 docs/子系统设计/屏板链路设计说明.md;串口通道已实现,网络通道未做):主机侧链路与参考 1:1 同构,不属"替代方案" |
> **结论**:本产品与参考产品的差距**全部是"进度"而非"范围"**——没有一项是"参考有而我们不做"。
> 已对齐的是**软件骨架**(采样节拍、相量算法、定值模型、104 上行、SOE 落盘、自检闭锁);
@@ -488,10 +488,10 @@ sequenceDiagram
| **GD5F2GQ5UE NAND** | SPI1 | PE0 CS / PB3 SCK / PB4 MISO / PB5 MOSI / PB8 WP / PE1 HOLD | 256 MB SPI NAND(2 Gbit) |
| **TPAFE5160 ADC** | 并行 16 位 | PG0~PG15 数据 / **PC6 FRSTDATA** / **PC7 BUSY**(EXTI) / **PC8 RD** / **PC9 CONVST** / **PA8 OS2** / **PA11 OS1** / **PA12 OS0** | 高精度多通道 ADC |
| **SD2506 RTC** | I2C1 | PB6 SCL / PB7 SDA | 提供墙钟基准与日志时间戳 |
| **USART1(调试串口 / U5 直连 TTL)** | UART | PA9 TX / PA10 RX | 115200 8N1;现为 printf 日志 + `@test` 采集通道。**端口重排后改作屏板链路**(700 kbaud,§5.16.2);PA10 双向均已接线 |
| **USART1(U5 直连 TTL)** | UART | PA9 TX / PA10 RX | **700000 8N1 → 屏板链路**(TTL 直连、无 DE,§5.16.2);PA10 双向均已接线;日志已迁 UART5 |
| **UART4(RS-485)** | UART | PC10 TX / PC11 RX / **PA15 DE** | 半双工通信(V0.1 轮换后 RS485#4 = UART4,`docs/硬件平台/V0.1_网表详细分析报告.md` §4.2) |
| **USART2(RS-232 #3)** | UART | PD5 TX / PD6 RX | 经 U10 隔离 → U13 TPT3232E → CN1;**端口重排后为日志口** |
| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;备选日志口 |
| **USART2(RS-232 #3)** | UART | PD5 TX / PD6 RX | 经 U10 隔离 → U13 TPT3232E → CN1;固件目前未占用(日志实际走 UART5/RS232#1) |
| **UART5(RS-232 #1)** | UART | PC12 TX / PD2 RX | 经 U9 隔离 → U13 → CN1;**日志口**(115200,`@test` 采集通道;台架端子 docs/子系统设计/屏板链路设计说明.md §3.2) |
| **状态 LED ×6** | GPIO | PC5 / PB0 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** |
| SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 |
| 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 |
@@ -1052,7 +1052,7 @@ sequenceDiagram
> (`docs/芯片手册/TP8485E-SR.md`)这件事,对屏板链路**不再构成约束**。
> 作为交换,**调试日志迁到 RS-232 通道**(V0.1 板上 RS232#3 = **USART2 PD5/PD6**,
> 备选 RS232#1 = UART5 PC12/PD2,两者都经隔离器 → `U13` TPT3232E → CN1),
> 迁移在屏板链路跑通之后做,见下方"主机侧端口重排"。
> 迁移已实施——控制台现走 UART5/RS232#1,见下方"主机侧端口重排"。
>
> **PA10 一直是接线的**(2026-10-28 用户确认)→ 屏板链路**不需要额外飞线**。
>
@@ -1099,7 +1099,7 @@ CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法),
> ⚠ **帧长以代码为准:4208 B**(屏板 readme 与 `BMP_LEN` 写的"3208 字节"不作准):接收侧实参是
> `Check_CRC(DR, UART_RX_LEN)`,`UART_RX_LEN = 4208` → CRC 取 `DR[4206]/DR[4207]`、覆盖 `DR[0..4205]`。
> 有效载荷只有前 3208 B,其后 998 B 为凑齐 CRC 窗口的填充;**主机只发 3210 B 时屏板会在
> `DR[4206]` 读到陈旧字节,CRC 必失败并静默丢弃**。**此项须台架实测复核后固化。**
> `DR[4206]` 读到陈旧字节,CRC 必失败并静默丢弃**。**已台架与真屏板实测固化**(屏板出图 = 端到端证据,`docs/子系统设计/屏板链路设计说明.md` §9.1)。
> ⚠ **应答帧不是合法 Modbus RTU 响应**:没有地址、没有功能码、没有字节数域,是一段裸二进制块。
> 因此**不能套用现成 Modbus 从站栈**,只能按上表手工组帧。
@@ -1136,15 +1136,15 @@ CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法),
ISR 抢占,一抢就撞第 1 条,而后果在屏板侧完全不可见。时间预算 4208 × 10 bit ÷ 705 882 ≈ **59.6 ms**
(按标称 700 000 算是 60.1 ms),占 280 ms 周期约 21 %;「≤200 ms 内发完」仍是我们对自己的要求——
理由是**不与下一轮召唤撞总线、不多一轮画面滞后**,不是因为屏板会把迟到的帧判废。宿主与起发方式见
`docs/任务规划/屏板链路.md` 决策三(`uiTask` 组帧 + USART1 空闲中断点 DMA 起发)。
`docs/子系统设计/屏板链路设计说明.md` §1(`uiTask` 组帧 + USART1 空闲中断点 DMA 起发)。
**主机侧端口重排(2026-10-28 用户裁定)**
| 串口 | 引脚 | V0.1 板上通道(`docs/硬件平台/V0.1_网表详细分析报告.md` §4.2) | 重排后用途 |
|---|---|---|---|
| **USART1** | PA9 / PA10 | 调试口 + **U5 直连 TTL**(ST_TX0/RX0),**MCU 直出、不经任何收发器** | ✅ **屏板链路**(700 kbaud) |
| **USART2** | PD5 / PD6 | **RS232#3**(U10 隔离 → U13 TPT3232E → CN1) | ✅ **调试日志**(115200,屏板链路之后迁移) |
| UART5 | PC12 / PD2 | **RS232#1**(U9 隔离 → U13 → CN1) | 备选日志口,与 USART2 二选一 |
| **USART2** | PD5 / PD6 | **RS232#3**(U10 隔离 → U13 TPT3232E → CN1) | 已初始化、固件未占用(日志最终选了 UART5) |
| UART5 | PC12 / PD2 | **RS232#1**(U9 隔离 → U13 → CN1) | ✅ **调试日志**(115200,已迁移、台架在用;端子见 docs/子系统设计/屏板链路设计说明.md §3.2) |
| UART4 | PC10 / PC11,DE = **PA15** | **RS485#4**(U11 隔离 → U15 → A/B → CN1) | 冻结簇,本项不碰 |
| USART3 | PB10 / PB11 | U5 直连 TTL(ST_TX2/RX2) | 空闲 |
@@ -1152,12 +1152,9 @@ ISR 抢占,一抢就撞第 1 条,而后果在屏板侧完全不可见。时
(`U15 = TP8485E-SR` 只有 250 kbps,`docs/芯片手册/TP8485E-SR.md`)。换到这条线上,速率问题从根上消失,
也不用改任何硬件。UART4 保持 RS-485 冻结簇不动——`rs485_init(&g_rs485, &huart4, …)` 与 V0.1 一致,**无需摘除**。
**这次重排带来一件事,必须在任务里体现:**
1. **日志口迁移不是一行改动**:`App/log/console_log.c` 里 `huart1` 出现 5 处(含
`huart1.Instance == NULL` 的空指针短路判据),自愈函数 `con_uart_reinit()` 还直接写
`USART1_IRQn`、`__HAL_RCC_USART1_CLK_*`、`MX_USART1_UART_Init()`。建议收口成**一处可配置端口宏**,
而不是逐处散改。
**这次重排的落地形态(2026-10-29 已实施)**:控制台端口已收口成 `App/log/console_log.c` 的
**`CON_UART*` 单点宏**(句柄 / IRQn / 时钟 / 自愈 `con_uart_reinit()` 一体,现指向 UART5/RS232#1),
换口只改那一段——当初"日志口迁移不是一行改动、建议收口成一处可配置端口宏"的顾虑由此闭环。
> ⚠ **TTL 直连没有方向控制**:主机侧不再有半双工方向切换,建立时间、"等 TC 收尾"一类约束全部不适用。
@@ -1264,7 +1261,7 @@ for (i = 1; i < LED_COUNT; i++) /* LE
然后点背光、`ClearScreen()`、`LOGO_Printf()`、延时 **2 s**(4 × `delay_ms(500)`)。
- 上电时 `BMP_Get()` 从同一地址读回并显示。
- **LOGO 帧不刷新 LED、不进入常规画面**;一帧 LOGO 会**永久覆盖**屏板上一次的 LOGO。
- 主机侧要提供"下发 LOGO"的一次性操作(建议放在屏板联调/出厂配置流程,不进常规刷新循环)。
- **LOGO 下发已出局、不实现**:主机侧不提供该操作,总线上不会出现 `info[4..5] = 0x89 0x45` 的帧。
**⑤ 屏板自身的健壮性机制**
@@ -1411,9 +1408,9 @@ for (i = 1; i < LED_COUNT; i++) /* LE
| 8 | 键值集(网络通道) | 8 键(含 F1 / F2) | 6 键(无 F1 / F2) | 补 F1 / F2;**仅网络通道**——屏板无 F1/F2 物理键(§5.16.3 ②) |
| 9 | 任务模型 | 独立任务 prio 111 / 栈 212 KB | `uiTask` 10 ms 轮询(prio BelowNormal / 16 KB) | **形态差异,不追平**(X1 / X2) |
| 10 | 接收侧 CRC | 仅 LCDMEM / KEY 校验 | 全帧校验 | **本产品更严,保留** |
| 11 | **串口屏板通道** | **有**:显存 3200 B + 状态灯 4 B 下行、键号上行,变化驱动 + 500 ms 兜底 | **无**(只有 7003 网络通道) | **新建**,按 §5.16.2 逐字节实现,见 §9.13 |
| 12 | 双出口并发 | 同一份显存同时喂串口与网络,互不阻塞 | 只有网络一个出口 | 随第 11 条一并实现 |
| 13 | 屏板状态指示灯 | 20 路(ID 0 电源常亮 + ID 1~19 位掩码),**4 B 小端**随每帧下发 | **无**;主板另有 6 路 GPIO LED(`LED1~6`) | **新建**;"位 → 业务状态"映射须自定(参考源码无此表,§5.16.3 ①) |
| 11 | **串口屏板通道** | **有**:显存 3200 B + 状态灯 4 B 下行、键号上行,变化驱动 + 500 ms 兜底 | **已交付**:700000 8N1、一召一答 4208 B、键号上行(docs/子系统设计/屏板链路设计说明.md) | 验收尾巴见该设计说明 §9.2 |
| 12 | 双出口并发 | 同一份显存同时喂串口与网络,互不阻塞 | 两出口并存(7003 + 串口通道),60 s 并发台架无互扰 | —(屏板链路设计说明 §9.1) |
| 13 | 屏板状态指示灯 | 20 路(ID 0 电源常亮 + ID 1~19 位掩码),**4 B 小端**随每帧下发 | **已交付**:5 位映射 `App/algo/sb_led_map.h`(屏板链路设计说明 §6);主板另有 6 路 GPIO LED(`LED1~6`) | 逐位目视验收未闭环(设计说明 §9.2) |
| 14 | 背光 / 对比度 | 主机 BSP 有 `Lcd_BackLight` / `Lcd_SetContrast` + 空闲 120 s 熄灭 | 无 | **不做**——屏板固件自管背光,两条路径不能同时启用 |
**来自屏板固件的新增约束(参考主机侧源码里看不出来,但对实现是硬约束)**:
@@ -1430,7 +1427,7 @@ for (i = 1; i < LED_COUNT; i++) /* LE
> 注意:**16 B 与参考的 29 B 不是同一口径**——客户端**不得**按 29 B 预留缓冲再截取前 16 B 使用
> (**从偏移 4 起含义即不同**:参考偏移 4 是颜色位,本产品偏移 4 是显存大小的高字节)。
>
> 第 11 / 12 / 13 条是**整条链路缺失**,不是报文细节差异——它决定了本产品能否脱离 PC 独立带屏运行。
> 第 11 / 12 / 13 条(串口通道三件套)**主机侧已交付**(docs/子系统设计/屏板链路设计说明.md)——本产品已可脱离 PC 独立带屏运行;仍开放的是第 2~9 条(7003 网络通道报文口径)。
>
> 第 14 条**已决定不做**:屏板固件自管背光与超时熄灭,主机再插一手会打架。
@@ -1462,8 +1459,7 @@ for (i = 1; i < LED_COUNT; i++) /* LE
13. **屏板自复位容忍**:屏板跑满 ≈5000 s 触发自复位,主机**不误判"屏板离线"**(无告警、不触发保护),
约 1 s 后自动续上。
14. 拔掉屏板 → 7003 侧不受影响;插回后自动恢复,无需复位主机。
15. **LOGO 下发**:下发一帧 LOGO(`info[4..5] = 0x89 0x45`)→ 屏板写 Flash `0x0803F000` 并显示 2 s;
断电重启后仍显示该 LOGO,且**屏板 LED 状态未被这帧改动**。
15. **LOGO 下发(出局,不实现)**:主机不提供 LOGO 帧,本条不参与验收;本表串口侧判据按其余十四条计。
---
@@ -1576,7 +1572,7 @@ mac=00:00:00:00:00:00
| 项 | 值 |
|---|---|
| 端口 | USART1(PA9 TX / PA10 RX),115200 8N1 |
| 端口 | UART5(RS232#1:PC12 TX / PD2 RX,经 U9 隔离 → U13 → CN1 端子),115200 8N1;台架端子接法见 docs/子系统设计/屏板链路设计说明.md §3.2 |
| 用途 | printf 日志 + `@test` 采集通道 |
| 配置源 | `.serialtest/config.json`(单一配置源) |
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
@@ -1940,7 +1936,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| **SNTP 对时** | 17 | 参考对时走 SNTP | 私有 TCP 8888 + 104 `C_CS` | SNTP 客户端 |
| **固有参数** | 1 | `8001H~800AH` 字符串类参数 | 未实现(无法短浮点上送) | 随送检点表设计 |
| **遥测增强** | 4、5 | k=1~9 谐波方和根 + 4 点平均 | 仅基波轨 | 谐波 / 等周波调频 / 零序自产外接切换 三条算法轨 |
| **屏板链路对齐** | 20 | **串口屏板通道**(**700000 8N1 / USART1 TTL 直连、无 DE**,帧 A 6 B 召唤、帧 B **4208 B** 应答:显存 3200 B + **状态灯 4 B 位掩码** + 4 B info)+ **7003 四命令**(INIT **16 B**,单色口径)+ 续传 / 拒绝握手 / 链路监视 / F1·F2 | 只有 7003 四命令(INIT **8 B**);**串口通道 0**;无状态灯下行;无续传、无 `bNeedFlash` 拒绝、无 1 分钟监视、键值 6 个 | 串口通道整条(协议已定版)+ 7003 报文口径 6 项(明细见 §9.13) |
| **屏板链路对齐** | 20 | **串口屏板通道**(**700000 8N1 / USART1 TTL 直连、无 DE**,帧 A 6 B 召唤、帧 B **4208 B** 应答:显存 3200 B + **状态灯 4 B 位掩码** + 4 B info)+ **7003 四命令**(INIT **16 B**,单色口径)+ 续传 / 拒绝握手 / 链路监视 / F1·F2 | **串口通道已交付**(4208 B 应答 / 状态灯掩码 / 键上行 / 双出口并发,屏板链路设计说明 §9.1);7003 四命令(INIT **8 B**);无续传、无 `bNeedFlash` 拒绝、无 1 分钟监视、键值 6 个 | 7003 报文口径 6 项(§9.13 第 2~9 条);串口侧验收尾巴见屏板链路设计说明 §9.2 |
> **一句话**:**「标定闭锁」是唯一"再排期也没用"的缺口**(等标准源);「出口与开入」「三相与同期」
> 「电度计量」等 V0.2 硬件;「对下采集」等用户决策;其余规模型条目**全部可在现有硬件上推进**,
@@ -2514,7 +2510,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 软压板 | 9 面全部可投退且行为可观测(投退后对应功能确实停走/恢复) |
| 出口与开入 | 保护动作 → 出口继电器动作 → 开入回读确认,全链路 ≤ 120 ms |
| 标定解卡 | 8 通道两点标定后 `ADC_CAL_F_VALID` 全置位;注入标准源,保护动作值与定值误差 ≤ 3 % |
| 屏板链路 | **串口通道**:屏板 280 ms 召唤、主机 ≤200 ms 内**发完** **4208 B**(整帧无 2 个字符时间以上的间隙),连续 10 min **主机侧**零超时、零 CRC 错帧;显存 3200 B 与 `g_ui_fbuf` 逐字节一致;7 个物理键(`1/2/3/4/7/8/9`)动作正确;**状态灯位掩码逐位一致、LED 0 恒亮**;屏板 ≈5000 s 自复位不误判离线;LOGO 下发后断电重启仍在;**网络通道**:INIT 应答 **16 B** 逐字段一致且无颜色字段、`LCDMEM` 偏移 0 得 3204 B、重复召唤无应答、偏移 ≥3200 无应答、`KEEPLIVE` 无应答且 1 分钟不掉链、8 键(含 `F1\|ENT` 组合)动作正确(§5.16.10 十五条) |
| 屏板链路 | **串口通道**:屏板 280 ms 召唤、主机 ≤200 ms 内**发完** **4208 B**(整帧无 2 个字符时间以上的间隙),连续 10 min **主机侧**零超时、零 CRC 错帧;显存 3200 B 与 `g_ui_fbuf` 逐字节一致;7 个物理键(`1/2/3/4/7/8/9`)动作正确;**状态灯位掩码逐位一致、LED 0 恒亮**;屏板 ≈5000 s 自复位不误判离线;LOGO 下发出局(不实现);**网络通道**:INIT 应答 **16 B** 逐字段一致且无颜色字段、`LCDMEM` 偏移 0 得 3204 B、重复召唤无应答、偏移 ≥3200 无应答、`KEEPLIVE` 无应答且 1 分钟不掉链、8 键(含 `F1\|ENT` 组合)动作正确(§5.16.10 十四条,第 15 条 LOGO 出局) |
---
@@ -2533,8 +2529,8 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
> 两条通道共用同一份显存快照,但**帧格式、校验、时序完全不同**(网络用 `0xAA/0xBB` + XOR,
> 串口用裸二进制 + CRC-16/MODBUS),不能共用组帧代码。
>
> **执行细则另立**:逐项任务的改动点、判据、代码骨架、台架联调步骤与风险清单在
> `docs/任务规划/屏板链路.md`;本节只保留**需求视角**的缺口与顺序,不复制执行细节。
> **实现事实另立**:主机侧的接线、串口配置、协议实现口径、验证状态与未闭环项在
> `docs/子系统设计/屏板链路设计说明.md`;本节只保留**需求视角**的缺口与顺序,不复制实现细节。
| 序 | 任务 | 改动点 | 判据 | 跨端 |
|---|---|---|---|---|
@@ -2547,12 +2543,12 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 7 | **KEEPLIVE 静默** | 去掉空帧回显,改为仅重置计时 | §5.16.10 第 4 条通过 | **是** |
| 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 | 否 |
| 10 | **串口物理层落地** | 按 §5.16.2 定版:**700000 8N1、`BRR = 0x0078`**(USART1,APB2 84 MHz)、**无 DE**(TTL 直连,不走收发器)、RX 缓冲 16 B + 空闲中断断帧、发送走 **DMA 一次性发完**(禁逐块阻塞;主机侧 TX 用 `DMA2_Stream7`,RX 走 IDLE 中断、不用 RX DMA) | 环回 + 示波器实测 700 kbaud 分频零误差;屏板能收到帧 A 并回帧 B | 否 |
| 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 条通过 | 否 |
| 15 | **LOGO 下发流程** | 一次性下发 LOGO 帧(`info[4..5] = 0x89 0x45`,宽高大端);放在出厂 / 联调流程,**不进常规刷新循环** | §5.16.10 第 15 条通过;断电重启后 LOGO 仍在 | 否 |
| 15 | **LOGO 下发流程(出局,不实现)** | 主机不提供 LOGO 帧,总线上不会出现 `info[4..5] = 0x89 0x45` | —(不参与验收) | 否 |
| 16 | **日志口迁移(USART1 → RS-232 通道)** | ① `console_log.c` 的 `huart1`、`USART1_IRQn`、`__HAL_RCC_USART1_CLK_*`、`MX_USART1_UART_Init()` 收口成**一处端口宏**,指向 RS232#1(`huart5` / UART5);② **不动** UART4 的 `g_rs485` 绑定 | 日志在 RS-232 侧(CN1)可读且内容与迁移前一致;USART1 **完全空出**;连续 10 分钟无 `[ERR]` | 否 |
**批次划分**:
@@ -2561,7 +2557,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
|---|---|---|
| 第一批(无跨端风险,可独立发版) | 3、6、8 | 只动本端,旧客户端不受影响 |
| 第二批(跨端,须与客户端同步发版) | 2、4、5、7、9 | 报文口径变更,两端必须同版本 |
| 第三批(**独立立项**,不随 RS-485 冻结) | **16**、10、11、12、13、14、15 | 屏板串口通道是**独立需求**,与 §5.10 冻结的"对下采集簇"不是一回事。**第 16 条必须排在 10 之前**:日志不腾出 USART1,屏板链路就没法独占这条通道(两者共用同一个 `USART1->DR`,同时用就是互相插字节) |
| 第三批(**独立立项**,不随 RS-485 冻结) | **16**、10、11、12、13、14 | 屏板串口通道是**独立需求**,与 §5.10 冻结的"对下采集簇"不是一回事。**第 16 条必须排在 10 之前**:日志不腾出 USART1,屏板链路就没法独占这条通道(两者共用同一个 `USART1->DR`,同时用就是互相插字节) |
> ⚠ **唯一破坏性变更**:第 2 条把 INIT 应答从 8 B 改成 **16 B**。**旧客户端会把 16 B 的前 8 B 当全部、
> 把后 8 B 当作下一帧的 TAG 去找**,解析状态机会被这 8 B 拖住直到超时——表现为"连上了但不出图"。
@@ -2573,18 +2569,18 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
>
> **屏板固件边界**:本表不覆盖屏板侧固件(帧解析 / 显存写入 / 按键扫描 / 状态灯驱动)。
> 屏板固件工程在 `D:\Code\DTU Code\DTU-LCD`,**不在本仓库**,其契约已按源码逐字节成文(§5.16.2 / §5.16.3)。
> 本产品只要满足 §5.16.10 的十五条判据即为兼容。
> 本产品只要满足 §5.16.10 的十四条判据即为兼容(第 15 条 LOGO 出局)。
>
> **第三批的前置阻塞**:第 11~15 条需要**屏板实物**才能验收(无实物只能做到"帧发出去了")。
> 第 11 条的帧长仍有一处待实测复核——代码实参是 **4208 B**,屏板 readme 写 **3208 B**(§5.16.2);
> 台架抓到第一帧真实报文后**立即固化**,在此之前以代码实参为准,不得按 readme 实现。
> **第三批状态**:串口通道(第 10~14、16 条)主机侧已实现并经台架与真屏板验收,屏板实物已上台架;
> 帧长**已固化 4208 B**(代码实参为准,屏板 readme 的 3208 B 不作数——真屏板出图为端到端证据)。
> 未闭环项(2 h 长稳、仪器项等)见 `docs/子系统设计/屏板链路设计说明.md` §9.2;**网络通道(第 2~9 条)未开工**。
>
> **一条物理前置(已完成 2026-10-29)+ 一条实现纪律**:
> ① **台架串口线已挪位**——日志口迁到 RS-232 侧后,台架那根线改接 CN1 的 RS232#1 端子
> (`CN1.10` 接适配器 TXD、`CN1.12` 接适配器 RXD、`CN1.7` 或 `CN1.8` 接 GND),台架日志口
> 因此由 `COM6` 变为 **`COM3`**,`.serialtest/config.json` 的 `portName` 已同步,`@test` 与
> `serial-assistant` 不必再手工指端口。这些端子是 **RS-232 电平**,TTL 的 CH340 直连不了,
> 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/任务规划/屏板链路.md` §3 批 0。
> 用的是 USB 转 RS-232 适配器;接法与端子出处见 `docs/子系统设计/屏板链路设计说明.md` §3.2。
> **这条要在第 16 条动代码之前完成。**
> ② **RX 要真的读、ORE 要真的清**——PA10 一直是接线的(§5.16.2),
> 但"RX 使能却不读 `DR`"本身就足以让 ORE 永久锁存。第 11 条实现时必须在
@@ -2635,7 +2631,7 @@ docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5
| 10 | 104 遥控与定值 | `YK12` 软压板投退、`C_SE` 写定值 | 两段式正答、压板行为可观测、定值跨复位保持 |
| 11 | 保护闭锁态 | 未标定状态下观察 `YX31` 与日志 | 判据停走且**明确可见**(不得静默不动作) |
| 12 | 记录可追溯 | FTP 下载 `/log/soe.log`、`/log/run.log`、`/log/selftest.log` | 记录连续、时标正确、解码校验通过 |
| 13 | 屏板链路一致性 | 串口屏板 + TCP 客户端同时接入,按 §5.16.10 **十五条**逐项验证 | 串口侧:275 ms 召唤 / ≤200 ms 回 4208 B、显存 3200 B 逐字节一致、**状态灯 4 B 掩码逐位一致**、7 键动作正确、自复位不误判离线;网络侧:INIT **16 B**、重复召唤 / 越界无应答、KEEPLIVE 静默、8 键(含组合)动作正确 |
| 13 | 屏板链路一致性 | 串口屏板 + TCP 客户端同时接入,按 §5.16.10 **十四条**逐项验证(第 15 条 LOGO 出局) | 串口侧:275 ms 召唤 / ≤200 ms 回 4208 B、显存 3200 B 逐字节一致、**状态灯 4 B 掩码逐位一致**、7 键动作正确、自复位不误判离线;网络侧:INIT **16 B**、重复召唤 / 越界无应答、KEEPLIVE 静默、8 键(含组合)动作正确 |
### 10.4 台架验收(已闭环)
-2
View File
@@ -12,7 +12,6 @@
| UI 之后还有什么、哪些事在等条件 | `登记簿.md` |
| ADC 标定为什么没做、保护为什么不动作 | `数据可信.md` |
| RS-485 为什么冻结、重启要满足什么 | `暂缓-RS485.md` |
| 怎么让主机跟屏板通信、先决条件是什么 | `屏板链路.md` |
**一件事只有一个归属。** 本文件只负责导航与顺序,不复制各任务的验收明细;
`登记簿.md` 不再复制当前 UI 任务,`数据可信.md` 的其余章节是被代码引用的设计约束,
@@ -66,7 +65,6 @@ docs/测试说明/模拟器差距清单.md §2.3「开机账本 boot_stats」一
| **6** | 保护元件与采集增强 | 等待标准源或硬件改版 | 保护先解除未标定闭锁;三相同时刻类功能等 V0.2 硬件 |
| **7** | 验证窗口事项 | 逢条件顺带 | CI runner、PC peer、CH340、负向故障注入条件到位时执行 |
| **8** | 独立模块与暂缓项 | 当前不排 | 开入开出、SNTP、104 文件传输单独立项;RS-485 与内存优化按各自重启条件 |
| **独立** | **屏板链路(对上屏板)** | **规划已出(未开工)** | 独立立项,插在任一顺位之间都能做,不占主序列;**端口已裁定(2026-10-28):屏板占 USART1,日志迁 RS-232 通道**,所以原来的 250 kbps 收发器阻塞已解除;剩下两条前置——**台架串口线挪到 CN1、日志口迁移**(见 docs/任务规划/屏板链路.md §0 与 §3 批 0);**软件可先用 PC + USB 转 TTL 台架验证,不必等屏板实物** |
| **独立** | **故障录波(1 kHz · 8 路原始码值 · 单通道回放)** | **代码面已交付(板级判据一轮未跑)** | 用户裁定 2026-10-29 **录波先行**:数据面最小闭环 → 手动与保护启动触发 → 回放页。三张卡的**代码侧判据已收口**(主机单测 100 / 170 / 110 全 rc=0、双端编译 0 错误 0 警告、板端编码器与 PC 侧解码器字节级互验、手动录波与录波回放两叶已绑页、内容页黄金参考扩至 66 张);**没跑的是板级那一轮**(注 1 V / 50 Hz、FTP 拉回、与 `meas:` 行和 104 浮点遥测对表、段文件 CRC 不符必须拒收、回放页看真段)。卡片判据见 docs/任务规划/故障录波.md,口径与设计见 docs/子系统设计/故障录波设计说明.md §10;**与 UI 道互斥**(菜单树与中文字库是整文件生成物,同刻只允许一条道写)——本波按此串行执行:UI 道在数据面与接线冻结之后才动生成物 |
### 2.1 关键依赖
-486
View File
@@ -1,486 +0,0 @@
# 屏板链路任务规划(主机侧执行细则)
> **本文定位**:让本产品**能跟 `DTU-LCD` 屏板真正通信**的执行级任务规划——顺序、改动点、判据、联调步骤、风险。
> **需求侧**(这条链路要求做什么、与参考的差异)在 `docs/产品需求/产品需求设计文档.md` §5.16 与 §9.13,本文不重复。
> **协议事实源**在屏板固件工程 `D:\Code\DTU Code\DTU-LCD`(不在本仓库)。
>
> **边界**:只做**主机侧**。屏板固件不改——屏板已经把 `BAUDRATE` 编译成 700000 且跑得动,我们迁就它即可。
> 屏板实物是验收前置条件——没有实物,本文只能做到"帧发出去了"。
---
## 0. 先看结论:裁定已定,三条流程前置顶上来
**2026-10-28 用户裁定:屏板走 USART1,日志口让位给 RS-232 通道。** 700 kbaud 只有 USART1 这一条 MCU
引脚直出的通道吃得到(其余串口都要过板上的收发器);代价是换成几条**流程**前置——都不是软件能解决的
(①②已于 2026-10-29 解除,见 §3 批 0)。
| # | 项 | 实测 / 出处 | 后果 |
|---|---|---|---|
| 1 | **日志口占着 USART1** | `console_log.c` 里 `huart1` 5 处;自愈函数直写 `USART1_IRQn` / `MX_USART1_UART_Init()` | 两者共用同一个 `USART1->DR`,同时用就是互相插字节 → **日志必须先迁到 RS-232 通道** |
| 2 | **台架串口线接在 USART1 上** | TTL 的 CH340 → `COM6`,`@test` / `serial-assistant` 都靠它抓日志 | 日志一迁,台架立刻瞎 → **先把线挪到 RS-232 侧(CN1)再动代码**;且这根 TTL 模块挪不过去,须换 **USB 转 RS-232 适配器**(见 §2 决策二) |
**执行顺序因此是**:挪台架线 → 迁日志口 → 才开始写屏板链路。
前两步**已于 2026-10-29 完成**(§3 批 0 的 0-1 / 0-2:台架线接 CN1 的 RS232#1,日志口迁到 UART5,
台架串口由 `COM6` 变成 `COM3`,`.serialtest/config.json` 已同步);第三项 0-3 的接法也已落纸。
**批 0 / 批 1 / 批 2 均已收尾(2026-10-29):USART1 在 700 kbaud 上收发双向通了(收 6 B 帧 A、
回 4208 B 帧 B)。批 3 按用户裁定(2026-10-29)缩为两项:状态灯映射表 + 下发;
**LOGO 功能出局**(不实现,`sb_proto` 里既有 LOGO 组帧函数列为待删除)。**
---
## 1. 现状盘点
### 1.1 已经具备的(不用重建)
| 能力 | 在哪 | 怎么用 |
|---|---|---|
| 取 3200 B 显存 | `Drivers/BSP/UI/ui_net_sink_backend.h` 的 `ui_net_sink_get_lcdmem()` | **直接复用**,与 7003 通道同一数据源,天然一致 |
| 注入按键 | 同上,`ui_net_sink_inject_key(uint8_t)` | **直接复用**,串口与 7003 走同一入口,无需改 `ui_net_sink.c` |
| 1bpp 显存 | `g_ui_fbuf`(`ui_port.c`),`UI_FB_SIZE = 3200` | 160×160÷8,与屏板 `Picture[3200]` 逐字节同构 |
| UART 外设已初始化 | `Src/usart.c`:USART1/2/3、UART4/5 全部 `MX_*_Init()` 已调用(`Src/main.c:130-137`) | 端口是现成的,只需改波特率(USART1 侧 TTL 直连,没有方向切换) |
| 静态任务分配 | `Inc/FreeRTOSConfig.h` `configSUPPORT_STATIC_ALLOCATION = 1` | 只有走退路(独立任务)时才用得上:仓内新建任务的先例是 `Src/freertos.c` 的 `USER CODE` 区里 `osThreadNew`(全工程没有一处调用 `xTaskCreateStatic`),**且不能建在 `app_main_init`**——它跑在 `osKernelStart` 之前,那里禁调任何创建类 API。采纳方案不新建任务,这条只是备着 |
### 1.2 缺什么(要新建)
1. **协议层**:CRC-16/MODBUS(仓内没有现成的,`App`/`Drivers` 里搜不到)、帧 A 解析、帧 B 组帧。
2. **传输层**:USART1 的 700 kbaud 配置、6 B 收 + 4208 B 发的时序(**无方向控制**——TTL 直连是 TX / RX 两根独立线)。
3. **状态灯映射表**:19 路业务状态 → 屏板 LED 位的表,**参考源码里没有,必须自己定义**(`docs/产品需求/产品需求设计文档.md` §5.16.3 ①)。
4. **一个宿主**:谁来跑这个链路(决策三)。
### 1.3 内存与 DMA 实况(决定实现方式)
| 项 | 现值 | 加屏板链路后 | 判断 |
|---|---|---|---|
| `RW_CCM` | 57,376 / 65,280 = **87.9 %**(门禁 92 %) | 不动(不新建任务、不占堆) | ✅ |
| `RW_IRAM1` | 81,536 / 131,072 = **62.2 %** | +4208 B(发送缓冲)≈ **65.4 %**;双缓冲 8416 B ≈ **68.6 %** | ✅ 宽裕 |
| 接收缓冲 | — | **16 B 足够**(屏板只发 6 B 召唤帧) | ✅ 别照抄屏板的 4208 B |
| RTOS 堆 | 57,344 B 配置;最近一次台架实测 `free` 16,400 / `min_ever` **8,464 B**(告警线 3,072 B,口径 `docs/内存管理/RAM使用分析与优化方案.md` §2.3) | **不加任务则不动** | ⚠ 是**紧张**,不是「必然创建失败」:768 字(3 KB)栈 + TCB 只剩几 KB 余量 → 仍首选不新建 |
**DMA 不是可选项,是「整帧不断帧」的唯一保证。** 三个数别混:
| 数 | 是什么 | 硬 / 软 |
|---|---|---|
| **59.6 ms** | 4208 B 的**线上占用时间**(8N1 → 10 bit/字节;屏板实跑 705 882 bps,按标称 700 000 算是 60.1 ms) | 硬(协议 + 物理,省不掉) |
| **约 14 µs** | 屏板的**断帧判据**:它的串口 IP 没有接收超时寄存器,整帧靠「总线空闲 1 个字符时间」收尾;间隙 ≥2 字符(约 28 µs)**必把一帧切成两帧**,两半都过不了 CRC | 硬——**这条才是真正卡实现方式的** |
| **205 ms / 280 ms** | 屏板的**放弃计时**与**下一轮召唤**(第 41 / 56 拍 × 5 ms) | 截止期,不是成本;见决策三与 §7 第 1 条 |
所以「在任务里逐字节轮询 `USART1->DR` 发 60 ms」这种写法,问题不只是占住一个任务——**它随时可能被 ISR 抢走
几十 µs**(本板有 TIM3 1 kHz 采样节拍、SysTick、SPI1/SPI2 DMA 完成、CH395F 中断),一抢就毁帧;而屏板侧
不报超时(它的超时判定只看「收到一整包」、不看 CRC),现场只表现为「画面偶尔不刷新」,极难归因。
**整帧一次性交给 TX DMA、由中断起发,才能同时拿到「CPU 零占用」和「不可能有间隙」。**
> **USART1 的 TX DMA 是现成的、且全工程没人用**:`Src/usart.c`:267-282 已配 `DMA2_Stream7`
> (`DMA_CHANNEL_4` / `DMA_NORMAL` / 字节宽度 / `MINC`)并 `__HAL_LINKDMA` 挂到 `huart1`;
> NVIC 优先级 9(`Src/dma.c`:65-67,满足「外设中断 ≥5」);`Src/stm32f4xx_it.c`:644-652 已接
> `HAL_DMA_IRQHandler(&hdma_usart1_tx)`。在用的是 SPI1(DMA2 Stream0/3)、SPI2(DMA1)、ADC1(Stream4)
> ——**与 Stream7 不冲突**,不必重新分配流,`.ioc` 也不用为 DMA 改任何东西(1-2 只改波特率)。
> 主机侧 RX 仍然只要 16 B + 空闲中断(屏板只发 6 B),**不要开 RX DMA**。
> **三个收尾回调的现状(2026-10-29 核实,2-4 已落地)**:`HAL_UART_RxCpltCallback` 仍零实现(本链路不用
> 逐字节接收,走 IDLE 断帧);`HAL_UART_ErrorCallback` 与 `HAL_UARTEx_RxEventCallback`
> **已经有一处实现**——`Drivers/BSP/RS485/rs485.c`,只分发它自己那个句柄。所以本链路**不是"自写这三个"**:
> 全局回调一个工程只能有一份定义,正确做法是**在那两处各加一行转发**,按句柄过滤放在 `sb_link` 里
> (1-3 已按这个做法落地)。错误分支里清 ORE / NE / FE 再按需重挂。
> **`HAL_UART_TxCpltCallback` 原本零实现,2-4 也是在 `rs485.c` 加第三行转发**(而不是在 `sb_link.c`
> 自写一份)——三个全局回调的落点收在同一处,下一个加通道的人一眼能看到全部,不必猜
> "TxCplt 有没有被占"。
> **`.ioc` 在仓库根**(`STM32F407-Demo.ioc`,不在 `MDK-ARM/` 下):`Dma.Request0/1` 已列 `USART1_TX` / `USART1_RX`,
> `DMA2_Stream7` 的 NVIC 优先级 9——与 `Src/dma.c`、`Src/stm32f4xx_it.c` 三处互相自洽(换流会被打回优先级 0,须回 `Src/dma.c` 核对)。
---
## 2. 四个决策(一、二、三已由用户裁定,四待定)
### 决策一:波特率与物理介质(**已裁定 2026-10-28**)
用户裁定:**屏板挂 USART1(PA9/PA10),日志迁 RS-232 通道。** 700 kbaud 只有这条 MCU 引脚直出的通道吃得到:
| 项 | 结论 |
|---|---|
| 端口 | **USART1**——唯一一条 MCU 引脚直出 TTL、不经收发器的通道,同时引到 U5 子板连接器 |
| 波特率 | 700000 8N1,`BRR = 0x0078`(APB2 84 MHz → `84 000 000 / (16 × 700 000) = 7.5`,误差 0 %)。**2026-10-29 开机日志实测就是 `0x0078`**(1-2) |
| 方向控制 | **不需要**——TTL 直连是 TX / RX 两根独立线(全双工),没有方向切换 |
| 剩余子决策 | **USART1 那端怎么接**:TTL 直连屏板 UART2 侧(PA2/PA3,绕过屏板自己的收发器)。**台架按这条做**;产品形态的线束长度与隔离需求届时单独评估 |
**接法与验收(0-3 的交付物,2026-10-29 落纸)**
主机侧取 U5 子板连接器上的两个网络(出处 `docs/硬件平台/V0.1.tel`:`ST_TX0 ; U5.A10 U17.101`、
`ST_RX0 ; U5.B10 U17.102`;`U17` 就是 MCU 本体,101/102 = PA9/PA10):
| 主机侧(V0.1) | 屏板侧 | 方向 |
|---|---|---|
| `U5.A10`(网络 `ST_TX0`,PA9) | 屏板 `PA3`(屏板 USART2_RX) | 主机发 → 屏板收 |
| `U5.B10`(网络 `ST_RX0`,PA10) | 屏板 `PA2`(屏板 USART2_TX) | 屏板发 → 主机收 |
| 板地(`CGND`,或板上任一 GND 脚) | 屏板 `GND` | 共地,必须接 |
> ⚠ **本表的"屏板侧 PA 编号"与 2026-10-29 真屏板首通实测相反**:照表接 `rx` 彻底静默,
> **两根信号线对调后立即通**。表里"TX 对 RX"的方向逻辑没错,错的是屏板排针对 PA2/PA3
> 谁是发的假定(未经屏板方确认)。**实操以排针丝印 + 计数为准**:主机 TX 接丝印 **RX**、
> 主机 RX 接丝印 **TX**、GND 必接;接完看主机 `[SBLINK] rx` 是否涨(涨=方向对,与内容无关),
> 不涨当场对调。全证据与推论见 `docs/子系统设计/屏板链路设计说明.md` §5.3/§5.6——
> 对调后屏板出了正确画面,等于屏板按 CRC 验收了主机的发送线**就是**它标 RX 的那根,
> 即**屏板实际发送脚是表中标 `PA3` 的那根**(与屏板固件"USART2 TX=PA2"的常规映射相反,
> 除非它的 USART2 做了重映射)。产品线束定版前向屏板方核实。
三条容易接错的地方:
- **必须交叉**:TX 对 RX。同边对接(`U5.A10` 接屏板 `PA2`)两端口都静默,且没有任何报错可看。
- **USART1 的 TTL 只从 U5 出去**:`ST_TX0` / `ST_RX0` 在网表里只连 `U5.A10` / `U5.B10` 与 MCU 的
101/102 脚,**CN1 上没有任何 USART1 端子**——CN1 的 `RS232_*` 是 UART5 / USART2 经收发器之后的电平,
别去 CN1 找。同一个 `U5` 上还挂着 USART3 的 `ST_TX2` / `ST_RX2`(`U5.C7` / `U5.C6`),**别抓错针**。
- **不串电阻、没有方向控制**:3.3 V TTL 直连,全双工两根独立线,没有 DE 脚可接。
**验收方式**(判据落在 1-3,0-3 只把方法写死):
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_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 步,并确认屏板画面按召唤节拍刷新。
### 决策二:用哪个 UART(**已裁定 2026-10-28**)
| 端口 | 引脚 | V0.1 板上通道 | 重排后用途 |
|---|---|---|---|
| **USART1** | PA9 / PA10 | 调试口 + U5 直连 TTL(ST_TX0/RX0) | ✅ **屏板链路** |
| **UART5** | PC12 / PD2 | **RS232#1**(ST_TX1/ST_RX1 → U9 隔离 → U13 TPT3232E → CN1.10/CN1.12) | ✅ **日志**(2026-10-29 已定) |
| USART2 | PD5 / PD6 | **RS232#3**(ST_TX3/ST_RX3 → U10 隔离 → U13 → CN1.9/CN1.11;另有网口座 `U14` 同信号) | 备选日志口 |
| USART3 | PB10 / PB11 | U5 直连 TTL(ST_TX2/RX2) | 空闲 |
**日志口已定为 UART5(RS232#1)**——2026-10-29 用户裁定,理由是**台架那根线落在哪个端子**,不是能力差异:
两条 RS232 通道都经隔离器 → `U13` TPT3232E → CN1,控制台又是**只发不收、寄存器轮询**,DMA 也用不上。
选定的 `ST_TX1` / `ST_RX1` 就是 RS232#1 在 MCU 侧的网络名(PC12 / PD2,UART5)。
> ⚠ **RS232#1 没有 TTL 出口**:`ST_TX1` / `ST_RX1` 在网表里只连 `U9` 的 3 脚 / 2 脚与 MCU 两个脚,
> **既无连接器也无测试点**;唯一外部出口是 CN1 的 RS-232 端子。所以台架**必须用 USB 转 RS-232 适配器**,
> 现有的 TTL CH340 接不上去(接上去轻则不通信,重则打坏 `U13` 或适配器)。
> 另:`U14` 是 RS232#3 的网口座(3 脚 `RS232_TX3` / 6 脚 `RS232_RX3`),**本通道用不上**——别把它当成备选端子。
**迁移时收口成一处端口宏,别散改**:`huart5` 已是 `115200 8N1`(`Src/usart.c:74-81`),
业务代码里没人用,只有 `Src/stm32f4xx_it.c:565` 挂着 IRQ 入口。
> ⚠ **USART1 交接前必须确认两件事**:① 控制台已不再写 `USART1->DR`(否则两条流互相插字节,
> 症状是"屏板偶尔花屏 + 日志偶尔乱码",很容易误判成 CRC 或时序问题);
> ② 屏板链路的错误回调里**要清 ORE**(overrun 一旦锁存,接收再也进不来),否则一次噪声就永久锁死。
### 决策三:谁来跑这个链路(宿主)——**已裁定 2026-10-29:`uiTask` 组帧 + 中断点 DMA 起发,不新建任务**
**先把四个名目拆开**——它们是四件不同的事,混在一起就会把实现方式选错:
| 数 | 它是什么 | 能不能避免 |
|---|---|---|
| **59.6 ms** | 4208 B 的**线上占用时间**(§1.3) | 不能(协议 + 物理);只有改屏板砍掉 998 B 填充才省 14.3 ms,本项不改屏板 |
| **约 60 ms 的任务阻塞** | 「在任务里逐字节轮询发送」这个**实现选择**的成本 | **能**:整帧交 TX DMA 后,这 60 ms 里 CPU 占用为零 |
| **205 ms / 280 ms** | 屏板的**放弃计时**与**下一轮召唤** | 不是成本,是截止期;真实边界见下面的说明与 §7 第 1 条 |
| **数秒量级的等待** | `uiTask` / `netTask` / `defaultTask` 里那一族**等别的任务回话或等外设**的阻塞(实测与推导见方案表后的注) | 与屏板链路本身无关,但**决定谁不能当起发者**:任务不在就绪态,抬优先级救不了 |
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| **`uiTask` 组帧 + USART1 空闲中断点 DMA 起发** | 这条链路的人机交互语义(组帧、状态灯映射、注键、在离线、LOGO)**全留在 UI 上下文**,与 7003 同一份数据源、同一把笔;零新增任务、不占堆;起发延迟 = 中断延迟(µs 级),与 `uiTask` 这一轮卡不卡无关 | 组帧要占 `uiTask` 一次 `memcpy` + CRC(约 0.15 ms/帧,3.6 帧/s 约 0.05 %);多一个 4208 B 的 SRAM 发送缓冲;帧缓冲与 DMA 读取之间要做 dirty 或双缓冲 | **采纳** |
| 独立任务里**阻塞**发送 | 与 `uiTask` 隔离;不占堆 | 那 60 ms 的 CPU 轮询本身就是断帧风险(§1.3);起发仍要等任务被调度到;多一个任务要登记栈、句柄、台账 | 退路:仅当要求「状态灯在 `uiTask` 卡死期间仍刷新」时才值得 |
| 把起发挂在 `uiTask` 主循环里 | 改动最省 | `uiTask` 主循环现在就有**无上界**的等待:`ui_net_sink_poll()` → `net_accept` → `net_wait_result()` 走到 `xTaskNotifyWait(portMAX_DELAY)`(`App/task/ui_task.c`:505-511、`Drivers/BSP/NET/net_socket.c`:846-861);实测最坏见方案表后那组注。**这类阻塞是等另一个任务回话——任务不在就绪态,抬优先级救不了** | 否决 |
| 把起发挂在 `defaultTask` | 零新增任务 | 周期 100 ms 量级只是表面;它同属「数秒量级的等待」那一族——FTL 挂载、日志排空、对时突发都压在这条任务上 → 起发延迟不可控 | 否决 |
> **「数秒量级等待」那一行的实测锚点**(引用这一族时别把推导值当实测值):
> - **≈4 s 是推导上界,不是实测**:netTask 里 `ch395f_poll_cmd_status(4000U)` 的忙等上限(`Drivers/BSP/CH395F/ch395f.c`:651,:1088 / :1103 / :1118 / :1133 / :1148 六处调用)+ 调用方 `portMAX_DELAY`;`docs/UI设计/RemoDispBus协议与接收设计说明.md` 里那个「约 4 s」就是这个推导数,**没有实测样本**。
> - **实测值**:7003 往返 `uis` 峰 183 ms;`uiTask` 常态渲染地板 `uip` 61.3 ms(`docs/子系统设计/看门狗设计说明.md` §11.4);对时 relisten 突发把 `uiTask` 压成**单轮 800 ms 以上缺席**(`docs/任务规划/登记簿.md` 条目「reconcile 突发饿死 uiTask」,`uip` 在途 307 → 1083 ms)。
> - 结论不变:这一族的延迟量级是**百 ms 到秒**,屏板只给 205 / 280 ms → 起发不能挂在任何任务态。
**采纳方案的分工——起发者不等于业务归属**(这两件事分开看,否决理由才站得住):
- **`uiTask`(10 ms 一拍)= 业务与组帧**:显存或 LED 掩码有变化时把 4208 B 帧拼好(3200 B 显存 + 4 B 灯 +
`info[4..7]` + 998 B 填充 + 2 B CRC);消费中断留下的 `key` 字节 → `ui_net_sink_inject_key()`
(**注键不能在中断里做**);外加在离线判定、LOGO 一次性下发。
- **USART1 空闲中断 = 唯一起发者**:校 6 B 召唤帧(4 字节 CRC 约 1 µs)→ 错帧丢弃并计数 → 取走 `key` →
点 TX DMA(帧已备好)→ 立刻退出;DMA 传完中断清 busy,攒了 dirty 就让 `uiTask` 重组。
距召唤帧末位**留 ≥30 µs 再起发**(屏板发完才放开它自己那根方向控制脚,按 2 个字符时间留量),无上限要求。
- **两条必须防**:① DMA 正在读时 `uiTask` 改帧 → dirty 标志(传完再重组)或双缓冲(仓里 `meas` 的双区 +
`seq` 是现成先例);② **屏板一次接收溢出就永久收不到、且自己毫无指示**(它错误回调里的重启被注释掉)
→ 主机严格「一召唤 → 一答 4208 B」,**不提前发、不超发、不叠发**。
- **不新建任务**:不动 `Src/freertos.c`、不动 `App/task/task_handles.h`、不占 RTOS 堆(§1.3 的余量本来就紧);
- **看门狗一个位都不加**:产品构建的必需位掩码现值是 **`0x1F7`**(`App/util/task_watchdog.c` 的 `wd_required_mask()`:
default / net / ftp / ui / timesync / adc / iec104 / protect;口径 `docs/子系统设计/看门狗设计说明.md` §1,
default / net / ftp / ui / timesync / adc / iec104 / protect;口径见 `docs/子系统设计/看门狗设计说明.md` 的「必需掩码」行)。
本项不给屏板链路新增心跳位,也不改变「同一任务缺席 800 ms」那条判定。起发在中断里,与任务心跳无关。
也不改变「同一任务缺席 800 ms」那条判定。
- **屏板那 200 ms 的真实语义**(它不是帧作废线,但主机仍按 ≤200 ms 要求自己,理由是下面这两条):它到点只置一个「可以重发」
的标志,不丢已收字节、不清缓冲、不重启串口;**迟到的完整帧照样会被显示**(收帧处理与超时判定相互独立,
帧里也没有序号)。真硬的是 280 ms 那轮召唤——而且**有按键待报时最早 205 ms 就会再来一帧**,
上一答还在总线上就是**双向对撞**。所以 ≤200 ms 内发完仍要做,理由是**防撞总线 + 不多一轮画面滞后**。
- **失败模式说清楚**(别当成零成本):`uiTask` 真卡死时画面本来就没人重绘,谁来发都一样冻;本方案的区别是
**不撞总线、不丢键、不进屏板那套超时阶梯,`uiTask` 一醒立刻恢复**。独立任务方案唯一独占的价值是
**状态灯**(来自业务状态、不来自 LVGL)能在 `uiTask` 卡死期间继续刷;真要这条就把组帧挪回独立任务,
但**起发仍留在中断里**——别让那 60 ms 回到任务态。
**裁定经过**:用户提问「那 200 ms 的阻塞一定存在吗、为什么会存在、有没有办法避免」→ 逐条核屏板源码与
主机侧观测面之后,于 2026-10-29 批准本口径(屏板侧事实源是外部工程 DTU-LCD,本项不改它的固件)。
### 决策四:屏板键值 9 / 10 的语义
屏板 `KEY_TYPE`:`9 = KEY_RESET`、`10 = KEY_FACTORY`;参考主机把 9/10 当 `F1` / `F1|ENT`。
我们 `ui_port.h` 的 `UI_KEY_*` 只有 6 个(`ENTER 0x20 / UP 0x02 / DOWN 0x40 / ESC 0x01 / LEFT 0x10 / RIGHT 0x08`),
**没有 F1/F2,也没有 RESET/FACTORY**。
| 选项 | 做法 | 影响 |
|---|---|---|
| **映射为「复位」** | 9 → 触发 UI 的复位页(`ui_page_reset`)或直接软复位 | 需产品确认"按复位键就重启"是否可接受 |
| **映射为 ESC** | 9 → `UI_KEY_ESC` | 最省事,但语义丢了 |
| **暂不映射** | 收到 9/10 直接丢弃并记一行日志 | 保守,等产品定 |
**建议先按「暂不映射 + 日志」实现**,把决定权留给产品,别在协议层猜语义。
---
## 3. 任务分解
分 5 批。**批 0 不做完就不要碰屏板链路代码**——批 0 干的是"把 USART1 腾出来",腾不出来就没地方接屏板。
### 批 0 · 腾位置(3 项,全是前置,做完才有 USART1 可用)
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
| 0-1 ✅ | **台架串口线挪位** | 硬件:**换成 USB 转 RS-232 适配器**(TTL 的 CH340 直连不了),接 RS232#1 的三个端子——适配器 TXD → **CN1.10**(板子 `RS232_RX1`)、RXD → **CN1.12**(板子 `RS232_TX1`)、GND → **CN1.7 或 CN1.8**(`CGND`)。端子号出自 `docs/硬件平台/V0.1.tel`;`U14` 是 RS232#3 的网口座,本通道用不上 | **已完成 2026-10-29**:COM3 在 RS-232 侧可读可抓(`serial-assistant` 正常) |
| 0-2 ✅ | **日志口迁移(USART1 → RS-232)** | `console_log.c` 的 `huart1`、`USART1_IRQn`、`__HAL_RCC_USART1_CLK_*`、`MX_USART1_UART_Init()` 收口成**一处端口宏**,指向 **`huart5`(RS232#1,已定)** | **已完成 2026-10-29**(`f5161e15`):COM3 抓到完整开机日志、8 个锚点全中、启动到 `net init: OK` 661 ms;连续 10 min 1218 行零 `[ERR]`/零 dropped;USART1 已无控制台写入 |
| 0-3 ✅ | **落定 USART1 到屏板的接线** | 按决策一:TTL 直连屏板 UART2 侧(PA2/PA3);接法与验收方式见 §2 决策一下方的「接法与验收」 | **接法已落纸 2026-10-29**:`U5.A10` / `U5.B10` ↔ 屏板 TTL 排针 + 共地,交叉接。**真屏板实测(同日):照表对 PA 编号接不通、信号线对调后才通**——验收以"主机 `rx` 计数上涨"为准,屏板侧 PA2/PA3 的实际收发方向待向屏板方核实(见决策一表下 ⚠ 与 `docs/子系统设计/屏板链路设计说明.md` §5.6) |
> PA10 已接线(用户 2026-10-28 确认),屏板链路**不需要额外飞线**。
> 0-2 是最容易出岔子的一步:`console_log.c` 是**看门狗喂狗节奏的唯一写者**
> (机制见 `docs/子系统设计/控制台单写者日志设计说明.md`),改端口后必须台架跑一轮长稳,
> 确认没把发送超时判据、drain 预算、僵尸通道自愈这些保护改坏。
> 改完先跑一次 `@test`,别急着往下做。
> 批 0 期间可以先做**批 1 的第 1 项**(协议层纯软件、不碰硬件),不冲突。
### 批 1 · 物理层打通(3 项)
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
| 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 ✅ | **波特率与格式落地** | **改 `Src/usart.c` 的 `USER CODE BEGIN USART1_Init 2` 区**(覆盖 `huart1.Init.BaudRate` 后重调一次 `HAL_UART_Init`)——**`.ioc` 这条路不存在**:`.ioc` 不保存任何 UART 的波特率(只有 `USART1.IPParameters=VirtualMode`),GUI 里改也不落盘,与 IWDG 那些"写进 `.ioc` 才不怕重生成"的情形不同 | **已完成 2026-10-29**:开机日志实测 `BRR=0x0078`(APB2 84 MHz / (16 × 700 kHz) = 7.5 → 整数 7、小数 0.5×16 = 8,误差 **0 %**),与理论值逐位相符,不必再架示波器;700 kbaud 下 10 分钟收帧零 CRC 错 |
| 1-3 ✅ | **USART1 收中断与全局回调分支** | 新建 `Drivers/BSP/ScreenLink/sb_link.c/h`:开 IDLE 中断接收(缓冲 **16 B**);`HAL_UARTEx_RxEventCallback` / `HAL_UART_ErrorCallback` 里**各加一行转发**(两个回调是全局单例,唯一实现在 `rs485.c`,按句柄过滤在 `sb_link` 内做);错误分支**清 ORE/NE/FE/PE 并按需重挂** | **已完成 2026-10-29**(PC + USB 转 TTL 顶替屏板,`COM6` @700000):`rx == 发送帧数`、`ok == rx`、`crc=0`、`len=0`、`size=6`,`keys` 随按键帧递增;先用错波特率灌 64 B 垃圾(`len=31`、`bits=0x06`)后**自动恢复**,`rearm=0`。主机单测 **81/81 PASSED**(`test/host/sb_link/`),Keil **0 错误 0 警告** |
> ⚠ **`HAL_UARTEx_RxEventCallback` / `HAL_UART_ErrorCallback` 是全局单例**——全工程只有一处实现,
> 现在只按它自己那个 `huart` 句柄分发(定位:全库搜这两个回调名就能找到实现处)。
> USART1 必须加自己的分支,否则中断进来了但没人处理——表象是"屏板一直超时",很容易误判成协议错。
> **分支只能"加一行转发",不能"自写一份"**:再定义一次就是链接期 multiple definition。
> ⚠ **错误回调里不能无条件重挂**(2026-10-29 台架实测踩到):HAL 把错误分成两类——
> **ORE 是阻塞型**(先 `UART_EndRxTransfer` 把 `RxState` 打回 READY,再回调,必须重挂);
> **NE/FE/PE 是非阻塞型**(接收照跑,`RxState` 仍是 `BUSY_RX`,此时再调 `ReceiveToIdle_IT`
> 只会拿到 `HAL_BUSY`)。旧写法无条件重挂,于是台架上出现 `err=160 / rearm=160` 完全同步增长——
> 看着像"每帧都重挂失败、链路快死了",其实一次都没真失败。判据用 `RxState == HAL_UART_STATE_READY`
> 而不是错误位:它直接回答"现在挂着接收没有"。主机单测 `TC-SBLINK-015` 钉住了这条。
> ⚠ **PC + 杜邦线顶替屏板时会有 NE(噪声错)**:实测 `bits=0x02`、每帧约 1.3 次,
> 但 `crc=0` 且 `ok == rx`——数据完好,只是采样点被判噪声(线材无屏蔽、无终端匹配)。
> 判读口径写进了 `sb_link.h` 的 `err_bits` 注释:**NE/FE 而 `rx_ok` 照涨 = 能用的告警,不是故障**;
> 真出问题的是 ORE(`0x08`)——那是处理不过来,本链路 6 B/280 ms 不该出现。
> 台架上没造出 ORE(CPU 读 DR 太快),那条路径由主机单测按 RM0090 的清除语义覆盖。
### 批 2 · 协议实现(5 项,2026-10-29 全部收尾;实现记录见 `docs/子系统设计/屏板链路设计说明.md`)
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
| 2-1 ✅ | **帧 B 组帧(在 `uiTask` 里做)** | `sb_link_frame_update(p_live_bmp, led_mask)`:取活帧缓冲的 3200 B → 拼 4 B 状态灯 → `info[4..5]` 填非 LOGO 值 → `info[6..7]` 保留 → **998 B 填充(固定 0x00)** → 2 B CRC(覆盖 `[0, 4205]`);缓冲 = 静态 4208 B,**落 `RW_IRAM1`、不落 CCM**。**不留 3200 B 影子副本**:帧里的点阵段本身就是上次组进去的那份,直接跟活显存 memcmp,变了才付 CRC | **已完成 2026-10-29**:长度 4208 / 填充全 0 / CRC 覆盖范围由主机单测逐项钉住(`TC-SBLINK-017`);`.map` 实测 `s_frame 0x20006d98 Data 4208`,在 `Execution Region RW_IRAM1`(0x20000000 起),不在 `RW_CCM`(0x10000000);台架 144 帧 `bad=0`(自校验 100 %);**组帧耗时实测 509 µs 典型 / 709 µs 峰值**——原判据 0.3 ms 已按下条改口 |
| 2-2 ✅ | **应答时序(起发在中断里)** | USART1 空闲中断校完 6 B 召唤帧后**直接点 TX DMA**(`sb_tx_kick()`,决策三);不排队、不攒批、不在任务里逐字节轮询。上一帧没发完就丢这一轮(`drop` 计数),**绝不叠发** | **已完成 2026-10-29**:台架 143 次召唤 → `tx=143 done=143 drop=0 kerr=0 abor=0`;未组帧前召唤**不发**(`TC-SBLINK-019`)。⚠ 三条时序判据(末位→首位 30 µs~5 ms、整帧 59.6 ms 无间隙、≤200 ms)**台架这一级证不了**:PC 侧 USB 转 TTL 是 CH340,收不了 700 kbaud 连续流,且 USB 帧本身 1 ms 量级,量不出 28 µs 间隙 → 留给批 4 接真屏板/逻辑分析仪 |
| 2-3 ✅ | **按键上行** | 帧 A 的 `key` 字节入环形队列(中断侧入、任务侧出);`uiTask` 按 §4.3 的表映射成 `UI_KEY_*` 后调 **`ui_port_queue_key()`**(注键不在中断里)。`0` = 无键不占队列;`9`/`10` 不映射,丢弃并留痕 | **已完成 2026-10-29**:台架 143 个按键帧 → `keys=143 kq=0`(逐键必到、零丢弃),UI 高亮随 UP 键移动(画面确实在变,`fb=144` 即每帧都重组);主机单测 `TC-SBLINK-023` 钉住 FIFO 顺序与溢出丢新 |
| 2-4 ✅ | **帧缓冲归属与 DMA 收尾** | **不新建任务**(决策三):`HAL_UART_TxCpltCallback` 在 `rs485.c` 加**第三行转发**(与另两个全局回调同处,避免"分散两处 → 日后被重复定义"),收尾在 `sb_link_on_tx_cplt()`;TX 期间 `s_tx_busy` 冻结帧缓冲,`sb_link_frame_update()` 直接返回 `BSP_ERR_BUSY`,TxCplt 后的下一拍补组;错误中断用 `gState` 收拾被打断的 TX(防残留 busy) | **已完成 2026-10-29**:40 s 台架 `done` 追平 `tx`、`drop=0`、`abor=0`、`bad=0`,无双发;`TC-SBLINK-020` 逐字节证明 busy 期间帧缓冲未被改写;`TC-SBLINK-022` 证明错误中断清残留 busy 且不误判仍在发送的 TX。**连续 30 min 长稳归批 4**(此处只跑了 40 s) |
| 2-5 ✅ | **链路监视与容错** | 连续 **≥10 s** 无**有效**召唤帧才判离线(**不能按 500 ms 判**——屏板 ≈5000 s 会自复位一次,约 1 s 空窗);判据用 `rx_ok` 计数是否前进,中断侧不读时钟;CRC 错帧静默丢弃并计数 | **已完成 2026-10-29**:台架脚本一停,10 s 后日志准点出 `screen board OFFLINE: no valid summon for >= 10000 ms`;恢复路径与"9.999 s 不判"由 `TC-SBLINK-024` 钉住;同一轮 `crc=0 len=0 fmt=0` |
> ⚠ **2-1 的 0.3 ms 是估算,实测 509 µs,判据已改口为 ≤1 ms**(2026-10-29)。
> 成本几乎全在 CRC 上:帧 B 每帧要算满 4206 B,查表法在 168 MHz 下约 19~20 周期/字节
> (反汇编 8 条指令/字节 + 512 B 查表常驻 Flash、5 个等待周期),仅 CRC 就 ~480 µs;
> 组帧其余部分(4208 B 清零 + 3200 B 拷贝)只有 ~20 µs。**这条预算没有实际约束力**:
> 一次组帧只占 280 ms 召唤周期的 0.2 %,且只在画面真的变了才付(3.5 次/秒时约 0.17 % CPU)。
> 真要压到 0.3 ms 以下,两条现成的路:① 把 512 B 查表拷进 RAM(去掉 Flash 等待周期,约 −125 µs);
> ② 998 B 零填充那一段用"推进 N 个零字节"的 GF(2) 变换代替逐字节算(约 −89 µs)。
> 两条都没做——代价(512 B SRAM + 一段需要长期维护的矩阵代码)换不来这个占空比下的任何收益。
> ⚠ **台架收不了 4208 B 应答不是固件问题**(2026-10-29 实测):PC 侧只收到约 2373 B,
> 等效 9.5 kB/s,而同一次运行固件侧 `tx=143 done=143` 说明 4208 B 每次都发完了。
> 原因是 CH340 的 RX FIFO 只有几十字节、USB 每 1 ms 才搬一次,70 kB/s 必然溢出
> (CH340 的 RX 实际上限约 115 kbaud)。**别据此改固件**——换 FTDI / CP210x 再验,
> 或者按 §5 直接接真屏板。`screenlink_bench.py --reply` 已内置这条提示。
### 批 3 · 状态灯(3-1 / 3-2 两项,2026-10-29 收尾;LOGO 出局)
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
| 3-1 | **状态灯位映射表** | 落 `App/algo/sb_led_map.c/h`(纯逻辑,主机单测 17/17):**ID 1 = 运行心跳**(500 ms 半周期闪烁,与主板 LED1~3 同拍)、**ID 2 = 告警**(`prot_elm_active_mask != 0`)、**ID 3 = 通信**(104 链路 RUN,新增 `iec104_serv_link_run()`)、**ID 4 = 采样故障**(`adc_health_flags()` 电平位,不选 sticky——灯要能灭)、**ID 5 = 保护启动**(`prot_elm_pick_mask`);**bit0 恒 0**(ID 0 硬件常亮、屏板不看位)、**ID 6~19 恒 0**(保留,扩展从 ID 6 顺序取) | 表落纸并进代码 ✓;主机单测用「掩码 & ~定义集 == 0」钉住无表外位 ✓;每位点亮可单独验证(真屏板目视,见 §5) |
| 3-2 | **状态灯下发** | `ui_task.c` 帧组装块每拍现采四路状态源(全部是"任意上下文可读"观测口)→ `sb_led_map_eval()` → 掩码进 `sb_link_frame_update()` 的 `info[0..3]`(小端);**掩码变了帧就重组**,走批 2 已有的 busy/dirty 机制,无新通路 | 主机改掩码 → 屏板对应 LED 变化;**LED 0 不受掩码影响**(真屏板目视待验,见 §7 清单) |
| 3-3 ❌ | ~~LOGO 下发~~ **出局**(用户裁定 2026-10-29:LOGO 功能无用,不实现、不接线) | 不做下发入口;`sb_proto.c/h` 里既有的 `sb_frame_b_build_logo()` + `SB_LOGO_MAGIC_*` 常量与 `test/host/sb_proto` 的相应用例**列为待删除**(删除时连同 `docs/子系统设计/屏板链路设计说明.md` 的 LOGO 段落一并清)。帧 B 的 `info[4..5]` 常规图像路径恒填 0,屏板按普通图分支处理,与 LOGO 无冲突 | 无——本项不产生代码改动 |
### 批 4 · 并发与验收(3 项)
| 序 | 任务 | 改动点 | 判据 |
|---|---|---|---|
| 4-1 ✅ | **双通道并发回归** | 7003 客户端拉帧 + 屏板召唤同时进行 | **已完成 2026-10-30(真屏板 + 新脚本 `test/host/remolcd_pull_test.py`,7003 侧 INIT→LCDMEM 10 Hz 重压)**:网络侧两轮(150 s / 60 s 隔离窗口)请求应答 **1500/1500 + 700/700、CRC 错 0、超时 0**,往返 min 35 / avg ~63 / max 131 ms(远小于 LCDMEM 应答"最坏 ≈4 s"的口径上限)。屏板侧 60 s 隔离窗口(clear 后开窗,计数只读窗口内):**rx +233 帧全收、`drop` 零新增、`kerr=0 abor=0 bad=0`、`off=0`、无 OFFLINE 行**,`tx` 追平 `rx`(末帧 59.6 ms 采样窗差 1)→ **7003 拉帧期间屏板每召必答**(屏板的 205 ms 超时计数在屏侧看不到,但"每召必答"是其不超时的充分证据)。"两侧画面一致"由机制保证:两出口读**同一份活显存**(`ui_net_sink_get_lcdmem` 与 `sb_link_frame_update` 的入参同为 `ui_port_get_framebuffer()`),无第二份账可漂;目视对图为机会性复核项。⚠ 观察项(不属 4-1 判据):`len=7` 的出现窗口夹在"烧录后冒烟(23:49 时 len=0)"与"第一轮 150 s 重压结束(00:15:26 已 len=7)"之间,**不能排除与首轮重压相关**;但第二轮 60 s 隔离窗口零新增、全程 `crc=0 fmt=0`、有效帧一帧没少(ok 增速与 rx 同频),功能影响为零。挂设计说明观察,若再现再查 |
| 4-2 | **长稳与自复位** | 连续跑 ≥2 h(跨过屏板一次自复位) | 零帧错误、零误判离线、UI 无可见卡顿 |
| 4-3 | **文档收口** | 把实测结论回填 `docs/产品需求/产品需求设计文档.md` §5.16.2(尤其**帧长 4208 vs 3208 那条待复核项**)与 §9.13 | 待复核项清零;§9.13 对应行销项 |
---
## 4. 代码骨架与关键实现点
### 4.1 模块划分
```
Drivers/BSP/ScreenLink/
├── sb_proto.c/h 纯逻辑:CRC-16、帧 A 解析、帧 B 组帧 / 自校验 ← 可主机单测(1-1 ✅)
└── sb_link.c/h 传输:USART1 收中断 + ORE 清除 + 帧 B 组帧入口 + TX DMA 起发/收尾
+ 按键队列 + 离线判定 + 统计上报 ← 依赖 HAL,仅 MCU(1-3 / 批 2 ✅)
```
**为什么拆两层**:协议层不碰 HAL 才能在 `test/host/` 里跑单测(项目既有做法:原样 include 生产 `.c`,
stub 提供 HAL)。屏板侧 CRC 有查表实现,**用同一张表做交叉验证**是最省事的正确性保证。
**数据流(批 2 落地后)**:`uiTask` 渲染完 → `sb_link_frame_update(活显存, 掩码)` 按需重组 4208 B
(画面没变就只做一次 memcmp,约 20 µs)→ USART1 空闲中断收到召唤 → `sb_tx_kick()` 点 TX DMA →
`HAL_UART_TxCpltCallback` → `sb_link_on_tx_cplt()` 清 busy。**中断里只起发、不组帧**:
组帧要 0.5 ms,放进中断会拖累全系统中断响应。
### 4.2 帧 B 组帧(逐字段)
```
偏移 0 3200 B ui_net_sink_get_lcdmem() 拷贝(160×160,1bpp,20 B/行,MSB 在前,1=白)
偏移 3200 4 B 状态灯 32 位小端:info[0]=bit0~7 … info[3]=bit24~31;bit0 恒 0
偏移 3204 1 B info[4]:普通图填 0x00(非 0x89)
偏移 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],低字节在前(口径见 §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 掩码当宽高。
> ⚠ **帧长 4208 而不是 3208**:屏板接收侧实参是 `Check_CRC(DR, UART_RX_LEN)`,`UART_RX_LEN = 4208`,
> 即 CRC 取 `DR[4206]/DR[4207]`。只发 3210 B 会让屏板读到陈旧字节 → **CRC 必失败且静默丢弃**
> (屏板接收侧 CRC 错只累加计数,不报错、不回帧,表象就是"屏板不亮")。
> 这是**最容易踩且最难查**的一条,批 2 第一件事就是按 4208 组帧。
### 4.3 键值映射表
| 屏板 `key` | 语义 | `UI_KEY_*` | 备注 |
|---|---|---|---|
| 0 | 无键 | — | 直接返回,不产生事件 |
| 1 | ENTER | `0x20` | |
| 2 | UP | `0x02` | |
| 3 | DOWN | `0x40` | |
| 4 | ESC | `0x01` | |
| 7 | LEFT | `0x10` | |
| 8 | RIGHT | `0x08` | |
| 9 | RESET | **待定**(决策四) | 先丢弃 + 日志 |
| 10 | FACTORY | **待定** | 屏板无物理入口,理论上收不到 |
| 5 / 6 | ADD / DEC | — | **屏板无物理入口**,不会出现 |
> 屏板 `key_configs[]` 只有 **7 项**(上/下/左/右/确认/取消/复位),可达值仅 `1/2/3/4/7/8/9`。
> 别按 readme 的 8 键表实现。
### 4.4 不要做的事
- ❌ **不要让控制台继续写 `USART1->DR`**——这是本项最容易犯的错。`console_log.c` 是逐字节轮询直写,
和屏板链路的 4208 B 共用同一个数据寄存器,两条流一混,症状是"屏板偶尔花屏 + 日志偶尔乱码",
查起来会误以为是 CRC 或时序问题。批 0 的 0-2 就是干这个的。
- ❌ **不要在 `Src/freertos.c` 里加任务**——那是 CubeMX 生成物,重生成会丢。
- ❌ **不要把 4208 B 缓冲放到栈上,也不要放到 CCM**——放错段就是 DMA 读不到,发出去的是旧数据(§1.3)。
- ❌ **不要按屏板 readme 的 3208 B 组帧**(见 §4.2 的警示)。
- ❌ **不要在任务里逐字节轮询 `USART1->DR` 发这 4208 B**——屏板按「总线空闲 1 个字符」断帧,一次几十 µs 的
ISR 抢占就把帧切断了,而屏板侧不报超时(§6)→ 整帧交 DMA 是唯一稳的写法(决策三、§1.3)。
- ❌ **不要提前发、超发、叠发**:屏板收帧是「一次 IDLE 一包」,多出来的字节会让它把正在处理的那一帧撕裂;
它一旦接收溢出就**永久收不到东西**(错误回调里的重启被注释掉),现场表现为「画面与状态灯一起冻死」,
只能给屏板断电重上或等它 83 min 自复位(自复位后是白屏)。严格「一召唤 → 一答 4208 B」。
- ❌ **不要指望屏板告诉你它没收到**:它的超时判定只看「收到一整包」、不看 CRC(坏帧也清掉超时计数),
屏幕上也没有任何通信状态指示(运行灯翻转被注释、故障显示函数无人调用)→
**观测面只有主机侧计数**(2-5 的 CRC 错帧计数 + 召唤间隔统计)。
---
## 5. 台架联调步骤(可以逐级停下来)
**核心思路:先在 USART1 上把软件全部跑通,再接真屏板。** 屏板不是必需的前置——PC 就能当屏板。
| 步 | 做什么 | 需要什么 | 停下也能交付什么 |
|---|---|---|---|
| 0 | **腾位置**:串口线挪到 RS-232 侧、日志迁走 | 改接串口线 + 一个 **USB 转 RS-232 适配器** | USART1 空出来,日志照旧能看(批 0) |
| 1 | 协议层单测(`test/host/screenlink/`) | 无硬件 | CRC 与组帧的正确性,可回归 |
| 2 | PC **模拟屏板**:按屏板节拍发 6 B 召唤帧(脚本 `test/host/screenlink_bench.py`,`--port COM6 --key-fixed 2` 可持续改画面以拿多次组帧样本;`--glitch-baud 115200` 灌噪声验自愈;`--reply` 收应答) | **USB 转 TTL 模块**接 U5 的 `ST_RX0`/`ST_TX0`(PA10/PA9)+ 主机板 | 收侧**已做**(`ok == rx`、`crc=0`、`size=6`、噪声后自愈);发侧固件计数**已做**(`tx == done`、`drop=0`、`bad=0`、`fb` 随画面变化增长)。⚠ `--reply` 收不全 4208 B 是 CH340 的限制,不是固件问题,见批 2 下方那条 ⚠ |
| 3 | 反向:PC 收主机帧,用脚本核对 4208 B / CRC / 显存内容 | 同上 | 帧内容正确性(与 `g_ui_fbuf` 逐字节比) |
| 4 | **接真屏板**(TTL 直连屏板 UART2 的 PA2/PA3 一侧) | 屏板实物 + 屏板 TTL 引出 | **冒烟首通 + 10 min 零错 2026-10-29**:出图(用户目视确认)、`rx=4258 ok=4258 crc=0 err=0`、应答 `tx=4249 done=4248 drop=0(窗口内) abor=0`、按键 `keys=53 kq=0`、`off` 自动恢复。⚠ 接线实测教训:**按排针丝印同名直连收不到,必须"主机 TX 接丝印 RX"**(丝印是屏板视角),且两板各供电须接第三根 GND——详见 `docs/子系统设计/屏板链路设计说明.md` §5.6。状态灯已实现(批 3)待目视、双通道并发**已过(4-1,见 `docs/子系统设计/屏板链路设计说明.md` §5.7)**;长稳/时序仪器项仍未测 |
| 5 | 产品形态:按最终线束接法定版 | 线束 | 产品形态验证 |
> 第 2、3 步是**性价比最高的两步**:不用屏板、不用改硬件,就能把 90 % 的软件问题挖出来。
> 注意这两步用的是 **USB 转 TTL**——USART1 侧没有收发器,直连 TTL 就对了。
---
## 6. 风险与回退
| 风险 | 触发条件 | 影响 | 回退/应对 |
|---|---|---|---|
| **日志迁移把台架弄瞎** | 先改代码、后挪串口线 | 什么都看不到,误以为固件挂了 | 顺序钉死:0-1 先挪线、0-2 再改代码;改完先跑一次 `@test` |
| **TTL 适配器直插 CN1** | 沿用那根 CH340 去接 RS-232 端子 | 不通信,且 RS-232 的 ±电平可能打坏 `U13` 或适配器 | 0-1 已写死「换 USB 转 RS-232 适配器」;RS232#1 无 TTL 出口,只能走电平转换后的 CN1 |
| **控制台与屏板链路抢 `USART1->DR`** | 日志没迁干净就接屏板 | 屏板花屏 + 日志乱码,像 CRC / 时序错 | 0-2 的判据明确要求"USART1 完全空出" |
| **一次噪声后链路永久静默** | 错误回调里不清 ORE | 屏板召唤再也收不到,症状与"协议不对"完全一样 | 1-3 判据含"拔插 TX 线制造噪声后自动恢复" |
| 帧长按 3208 实现 | 照 readme 写 | CRC 全失败、屏板不亮 | 已写进 §4.2 警示;联调第一步就核 4208 |
| 中断回调漏加分支 | 只改自己模块,忘了那三个全局 UART 回调里唯一的一处实现只分发另一条通道的句柄(§1.3) | 收不到召唤帧,表象像协议错 | 批 1 的 1-3 已列为判据 |
| 屏板自复位被误判离线 | 链路监视窗口设得太小 | 每 83 分钟误报一次 | 判据定为"连续 ≥10 s 无帧" |
| 状态灯语义返工 | 映射表拍脑袋定的 | 后期改表要重测 | 表 = `App/algo/sb_led_map.h` 位宏 + 单点 `sb_led_map_eval()`(唯一出处),折算不散落调用点;主机单测钉「掩码 & ~定义集 == 0」,改表漏测会被套件拦住(3-1 落纸时的口径微调:5 路输入判据形态各异——电平/闪烁——const 数组统一不了谓词,纯函数分支更直白) |
| RAM 超门禁 | 误把 RX 缓冲也做成 4208 B | `RW_IRAM1` 逼近 92 % | RX 只要 16 B(§1.3) |
| **整帧被 ISR 切断** | 在任务里轮询发送,中途被采样 / 网口 / 系统节拍 ISR 抢走 ≥2 个字符时间 | 一帧拆两包、两半 CRC 全错;症状只是「画面偶尔不刷新」,而**屏板不报超时** | 决策三:整帧 TX DMA + 中断起发;2-2 判据含「无 28 µs 以上间隙」 |
| **屏板接收溢出后永久静默** | 主机提前发 / 超发 / 叠发 | 屏板再也收不到,画面与状态灯一起冻死,**它自己毫无指示**,只能断电重上 | 严格一召唤一应答、正好 4208 B(LOGO 出局后总线上不会再有其它帧形态,风险源只剩叠发/提前发,批 2 的 busy 冻结已挡) |
| **把 200 ms 当成「帧作废」线** | 误读屏板那个放弃计时 | 白加一层重试与加急逻辑;或反过来以为迟到无害,撞上 205 ms 的按键召唤 | 屏板真实规则:迟到的完整帧照样显示;**有按键待报时最早 205 ms 就再召唤** → 仍按 ≤200 ms 完成,理由换成「防撞 + 不滞后」(决策三) |
| **按键被当成偶发失灵** | 那一轮窗口被错过 | 屏板的键**只发一次不重试**(发完立刻清备份)→ 该次按键直接丢失 | 起发留在中断里(µs 级);2-3 按「逐键必到」测,不看统计均值 |
| **屏板背光 100 s 熄灭被当成掉线** | 现场长时间没人按键 | 误报链路故障 | 按任意键即复亮;判离线只看召唤帧有无(2-5 的 10 s 窗口) |
| **采纳方案继承 `uiTask` 的挤占面** | `uiTask` 是 BelowNormal,排在 defaultTask(Normal)之后,而后者一次日志排空就是 250 ms(`App/log/console_log.c` 的 `DBG_CON_DRAIN_BUDGET_MS`)、NAND 写数百 ms;对时 relisten 突发实测把 `uiTask` 单轮压到 800 ms 以上 | 只表现为**画面滞后与注键延迟**——起发在中断,不撞总线、不进屏板那套超时阶梯 | 与 7003 同一来源,属已知成本;现场不可接受就走退路(组帧挪独立任务),**起发仍留在中断里** |
---
## 7. 验收清单
做完这一项,要能同时满足(逐条可测,对应 `docs/产品需求/产品需求设计文档.md` §5.16.10 第 8~15 条):
0. **腾位置**:日志在 RS-232 侧可读、内容与迁移前一致;USART1 已无任何控制台写入。
1. 屏板按 **280 ms** 召唤(判据是 RT_count > 55 → 第 56 拍 × 5 ms;屏板侧常写的 275 ms 是 55×5 的算法),主机每帧在 **≤200 ms** 内**发完** **4208 B**,连续 10 min **主机侧**零超时、零 CRC 错帧。
2. 帧 B 前 3200 B 与 `g_ui_fbuf` **逐字节一致**。
3. 屏板 7 个物理键逐个按下 → UI 动作正确;无键时 `key = 0` 不产生事件。
4. **状态灯**:掩码逐位对应,LED 0 恒亮、bit0 不影响任何灯。
5. **双通道并发**:7003 与串口同时跑,互不拖超时、画面一致。
6. **屏板自复位**(≈5000 s)不被误判离线,约 1 s 后自动续上。
7. 拔插屏板:7003 侧不受影响,插回自动恢复,不用复位主机。
8. ~~LOGO 下发后断电重启仍在,且不改变 LED 状态~~ **随 LOGO 出局划掉**(用户裁定 2026-10-29,见批 3 的 3-3)。
**另测两项**(属本方案的实现判据,不在 §5.16.10 编号内):
- 逻辑分析仪量整帧发送期间**不得出现 ≥2 个字符时间(约 28 µs)的间隙**——这是断帧安全性的直接证据,
也是必须用 DMA 的理由(决策三);同时记下召唤帧末位到应答首位的实测值,确认 30 µs 的切换留量够用。
- **7003 拥塞时串口侧照常应答**:挂着 7003 客户端不读、或制造对时 / relisten 突发把 `uiTask` 卡住,
屏板侧应**零超时**、画面停在最后一帧(这就是「起发不在任务态」的直接证据);`uiTask` 一醒画面立刻续上、总线无对撞。
---
## 8. 与其它工作的关系
- **与日志/控制台工作强耦合**:批 0 的 0-2 要改 `App/log/console_log.c`——那是看门狗喂狗节奏的唯一写者
(机制见 `docs/子系统设计/控制台单写者日志设计说明.md`)。这项改动**不能与其它动控制台的活儿并行**,
而且必须台架长稳验证过才算完;**它比屏板链路本身风险更高**,建议单独排一轮、单独回滚。
- **与 UI 的耦合只有一处**:决策三把组帧与注键消费放进 `uiTask`——只用 `ui_net_sink_get_lcdmem()` /
`ui_net_sink_inject_key()` 这两个现成接口,**不改任何页面与绘制逻辑**。UI 页面已在 2026-10-28 收口,
正好可以直接拿来当被测对象;`uiTask` 单轮耗时的增量由 2-4 的判据盯着(≤0.3 ms)。
- **不占用并行工作树名额的额外理由**:改的是新目录 + `.ioc` 一处(决策三不新建任务 → 不再动
`Src/freertos.c` / `App/task/task_handles.h` / `App/app/app_main_init()`),与主线其它工作写域几乎
不重叠;但 `.ioc` 改动属**单点事项**(CubeMX 重生成),必须与其它改 `.ioc` 的工作串行。
- **需求侧销项**:完成后回填 `docs/产品需求/产品需求设计文档.md` §9.13 与 §5.16.2 的待复核项。
+207 -182
View File
@@ -1,201 +1,226 @@
# 屏板链路设计说明(主机侧实现记录)
# 屏板链路设计说明(主机侧)
> 模块:`Drivers/BSP/ScreenLink/`(`sb_proto` + `sb_link`),任务 `screenlink`。
> 本文档记**已实现的内容与实测证据**;任务分解、协议事实源、四个决策的来龙去脉在
> `docs/任务规划/屏板链路.md`(下称规划文档),两边不重复维护同一张表。
> 创建:2026-10-29(真屏板首通当晚落笔)。
> **模块**:`Drivers/BSP/ScreenLink/`(`sb_proto` 协议层 + `sb_link` 传输层)· `App/algo/sb_led_map`(状态灯折算)· `App/task/ui_task.c`(宿主)。
> **需求与屏板侧契约**(这条链路要做什么、屏板行为逐字节)在 `docs/产品需求/产品需求设计文档.md` §5.16,本文不重抄其表;
> 本文是**主机侧实现事实**:接线、串口配置、协议口径、键/灯映射、资源、验证状态与未闭环项。
> **屏板固件事实源**:外部工程 `DTU-LCD`(不在本仓,本链路不改它)。
---
## 1. 链路与角色
## 1. 一句话架构
- 屏板是 **Modbus 主站**:每 280 ms 发一帧 6 B 召唤(帧 A,`RT_CMD`),有键待报时插一帧带 key 的立即召唤(`KEY_CMD`)。
主机是**从站 `0x01`**:校验收下帧 A,回一帧 4208 B 图像应答(帧 B)。**主机从不主动推送。**
- **严格一召唤 → 一答 4208 B**:不提前发、不超发、不叠发。屏板接收溢出即**永久静默**且不可自恢复(它错误回调里的
串口重启被注释掉),只能断电重上或等 ≈83 min 自复位——多发是唯一风险源,busy 冻结在主机侧挡死。
- **一份显存、两个出口**:串口(本链路)为主,TCP 7003 网络为镜像;两出口读**同一份活显存**
(`ui_port_get_framebuffer()`),无第二份账可漂。
- **宿主 = `uiTask`(组帧 + 注键),唯一起发者 = USART1 空闲中断(µs 级)**。不新建任务、不占堆、
不加看门狗位(产品 `required_mask` 维持 `0x1F7`,起发在中断、与任务心跳无关)。
屏板是 Modbus 主站、每 280 ms 发一帧 6 B 召唤(帧 A),主机是从站 `0x01`:
**任务侧组好一帧 4208 B(帧 B)挂在手上,空闲中断校完召唤直接点 TX DMA 回一帧**;
屏板按键骑在召唤帧里上来,主机排给 UI。串口为主出口,7003 网络是同一份显存的镜像。
```
uiTask(10 ms 节拍) USART1 空闲中断(屏板召唤到达)
ui_port_step() 渲染 sb_link_on_rx_event()
sb_link_frame_update(活显存, 掩码) ├─ sb_frame_a_parse 校验 6 B
├─ 点阵 memcmp 没变 → 立刻返回(~20 µs) ├─ 按键 ≠ 0 → 入环形队列
├─ TX busy → BSP_ERR_BUSY,下拍再来 ├─ 帧有效 → sb_tx_kick() 点 TX DMA
└─ 变了 → sb_frame_b_build_image 组整帧 └─ 无条件重挂 ReceiveToIdle_IT
+ sb_frame_b_verify 自校验
→ s_frame_ready = 1 DMA2_Stream7 TC 中断
sb_link_key_take() 出队 sb_link_on_tx_cplt() 清 busy
→ ui_task_board_key_map() 映射 UI_KEY_*
→ ui_port_queue_key()
sb_link_poll(tick) 统计上报 + ≥10 s 无召唤判离线
```mermaid
flowchart TB
subgraph UITASK["uiTask(10 ms 一拍)"]
R["ui_port_step 渲染"] --> F["sb_link_frame_update(活显存, 灯掩码)"]
F -->|"点阵/掩码 memcmp 没变(~20 µs)"| R
F -->|TX busy → BSP_ERR_BUSY| R
F -->|变了 → 组帧 + 自校验| S["s_frame 就绪"]
K["sb_link_key_take 出队"] --> M["ui_task_board_key_map 映射"] --> Q["ui_port_queue_key 注键"]
P["sb_link_poll 统计 + 离线判定"]
end
subgraph ISRCTX["USART1 空闲中断(屏板召唤到达)"]
A["sb_link_on_rx_event:校 6 B 帧 A"] -->|"key ≠ 0"| KQ["按键环形队列(入队)"]
A -->|帧有效 且 s_frame 就绪| D["sb_tx_kick 点 TX DMA"]
A -->|无条件| A2["重挂 ReceiveToIdle_IT"]
end
S -.-> D
KQ -.-> K
D --> TC["DMA2_Stream7 传完中断"] --> C["清 busy(攒了 dirty → 下一拍补组)"]
```
分层铁律:`sb_proto` 纯逻辑(不碰 HAL/寄存器/任务),原样 include 进 `test/host/` 跑单测;
`sb_link` 依赖 HAL 与中断上下文,仅 MCU。
分层铁律:`sb_proto` 纯逻辑(不碰 HAL / 寄存器 / 任务),原样 include 进 `test/host/` 跑单测;
`sb_link` 依赖 HAL 与中断上下文,仅 MCU(`new-source-file` 双端注册时走 MCU-only 豁免)。
## 2. 对外接口(单一出处在头文件,这里只列形状)
## 2. 串口与端口配置
| 项 | 值 | 说明 |
|---|---|---|
| 端口 | **USART1** PA9 / PA10(`U5` 子板连接器网络 `ST_TX0` / `ST_RX0`) | MCU 引脚直出 TTL,**不经任何收发器**,无 DE、全双工两根独立线 |
| 波特率 | 700000 8N1,实测 `BRR = 0x0078`(APB2 84 MHz → 7.5,误差 0 %) | 开机日志自证(`sb_link_init` 交叉核对,对不上报 `BSP_ERR_STATE`) |
| 波特率落点 | `Src/usart.c` 的 `USER CODE BEGIN USART1_Init 2` 覆盖 `huart1.Init` 后重调 `HAL_UART_Init` | **`.ioc` 不保存任何 UART 的波特率**,GUI 里改也不落盘 |
| RX | **16 B 缓冲 + IDLE 中断**(`HAL_UARTEx_ReceiveToIdle_IT`) | 屏板只发 6 B,别照抄它侧的 4208 B;**一次性 API,每次回调必须重挂**,漏一次 = 永久静默;**不开 RX DMA** |
| TX | **整帧交 TX DMA**:`DMA2_Stream7` CH4(NVIC 优先级 9,`Src/dma.c` 已配、`Src/stm32f4xx_it.c` 已接 IRQ) | CubeMX 预置、全工程原本没人用;`.ioc` 无需为此改动;帧缓冲必须在 SRAM(**CCM 对 DMA1/2 物理不可达**,放错段 = 静默发旧数据) |
| 全局 UART 回调 | `HAL_UARTEx_RxEventCallback` / `HAL_UART_ErrorCallback` / `HAL_UART_TxCpltCallback` 的**唯一实现处在 `Drivers/BSP/RS485/rs485.c`**,各含一行 `sb_link_on_*` 转发;按句柄过滤在 `sb_link` 内做 | 三个都是全局单例:再定义一份就是链接期 multiple definition;收在同一文件,下一个加通道的人一眼看到全部 |
| 错误处理 | 按 **SR → DR 顺序**清 ORE/NE/FE/PE;**重挂判据用 `RxState == READY` 而非错误位**(ORE 阻塞型须重挂,NE/FE 非阻塞、接收还在跑,再挂只拿 `HAL_BUSY`);TX 被错误打断时按 `gState != BUSY_TX` 清残留 busy | ORE 锁存 = 接收再也进不来;残留 busy = 之后每次召唤都被判"上一帧还在发"→ rx 涨 tx 冻结 |
| 链路监视 | `rx_ok` 计数 **≥10 s 不前进 → 判离线**(`SB_LINK_OFFLINE_MS`),恢复由下一次有效召唤;5 s 统计行只在计数有变化时打 | 屏板 ≈5000 s 自复位有 ≈1 s 空窗,按 500 ms 判会把"歇口气"判成离线反复跳;屏板无背光 100 s 熄灭也**不是**掉线(按键即复亮) |
| 日志口联动 | 控制台已迁 **UART5(RS232#1)**,USART1 专用屏板;收口在 `App/log/console_log.c` 的 `CON_UART*` 单点宏 | 两条流共用同一个 `USART1->DR` 会互相插字节(症状 = 屏板偶发花屏 + 日志乱码);台架端子见 §3.2 |
## 3. 物理接线
### 3.1 主机 ↔ 屏板(TTL 直连,PA10 已接线、无需飞线)
| 主机侧(V0.1) | 屏板侧 | 方向 |
|---|---|---|
| `U5.A10`(网络 `ST_TX0`,PA9) | 屏板排针**丝印 `RX`** | 主机发 → 屏板收 |
| `U5.B10`(网络 `ST_RX0`,PA10) | 屏板排针**丝印 `TX`** | 屏板发 → 主机收 |
| 板地 `CGND`(或任一 GND 脚) | 屏板 `GND` | **两板各自供电必须接第三根共地线**,浮地的形态同样是彻底收不到 |
- 丝印 `TX/RX` 是**屏板自己发出/接收的视角**,所以主机侧**交叉同名接**:主机 TX 接丝印 `RX`、主机 RX 接丝印 `TX`。
- 屏板排针收发脚与屏板网表(PA2=发 / PA3=收)的对应关系未经屏板方确认(实测按网表接不通、对调才通)。
**唯一实操验收口径 = 接完看主机 `rx` 计数涨不涨**(涨 = 方向对,与帧内容无关),不涨当场对调两根信号线;
产品线束定版前向屏板方核实实际收发脚。**不要按屏板 readme 与"TX 对 TX"直连**——同边对接两端都静默且无任何报错可看。
- **别去 CN1 找 USART1 端子**:`ST_TX0` / `ST_RX0` 在网表里只连 `U5.A10` / `U5.B10` 与 MCU 101/102 脚;
CN1 上的 `RS232_*` 是 UART5 / USART2 经收发器之后的电平。同一个 `U5` 上还挂着 USART3 的 `ST_TX2` / `ST_RX2`
(`U5.C7` / `U5.C6`),别抓错针。3.3 V TTL 直连,不串电阻,没有方向控制脚可接。
### 3.2 台架日志口(RS232#1)
- 台架须用 **USB 转 RS-232 适配器**(TTL 的 CH340 直连不了,且 RS-232 的 ±电平可能打坏 `U13` TPT3232E 或适配器):
适配器 `TXD` → `CN1.10`(板子 `RS232_RX1`)、`RXD` → `CN1.12`(板子 `RS232_TX1`)、`GND` → `CN1.7` 或 `CN1.8`(`CGND`)。
- 台架串口因此是 **`COM3`**,`.serialtest/config.json` 的 `portName` 已按此配置,`@test` 与 `serial-assistant` 不必手工指端口。
- RS232#1 **没有 TTL 出口**(`ST_TX1`/`ST_RX1` 只连 `U9` 隔离器与 MCU),唯一外部出口是 CN1 的 RS-232 端子;
`U14` 是 RS232#3 的网口座,本通道用不上。
### 3.3 PC 顶替屏板(无屏板实物的台架验证)
- USB 转 TTL 适配器:`TXD` → `U5.B10`、`RXD` → `U5.A10`、`GND` → 板地;700000 8N1;
激励脚本 `test/host/screenlink_bench.py` 按屏板节拍发 6 B 帧 A(`--key-fixed` 持续改画面取组帧样本、
`--glitch-baud` 灌噪声验自愈、`--reply` 收应答)。
- ⚠ **CH340 收不了 700 kbaud 连续流**(RX FIFO 几十字节 + USB 每 1 ms 才搬一次,实测只收到 ≈2373 B / 9.5 kB/s):
**收不全 ≠ 固件没发**,同轮 `tx == done` 即自证;要收全 4208 B 帧验内容须换 FTDI / CP210x。
## 4. 协议实现口径
帧 A / 帧 B 的逐字段表与屏板时序常量在 `docs/产品需求/产品需求设计文档.md` §5.16.2,此处只记主机侧实现事实:
- **CRC-16/MODBUS**(`0xA001` 反射、初值 `0xFFFF`、16 位合并表):帧 A 覆盖前 4 B、帧 B 覆盖 `[0, 4205]`,
**一律低字节在前**(标准 Modbus RTU 线上序)。屏板源码 `Calculate_CRC()` 的返回值与写入处注释把高低**都写反了**
(`123456789` 它返回 `0x374B`,标准值 `0x4B37`;两处名字反、线上字节对)——照字面实现成"高字节在前",
屏板会把每一帧判成 CRC 错,而它超时判定只看"收没收到一整包"、坏帧也清超时计数,现场只表现为"画面不刷新"。
实测向量钉在 `test/host/sb_proto/sb_proto_test.c`(`01 03 01 00 F0 48`、`01 03 00 09 31 DE`)。
- **帧 B 正好 4208 B**:3200 显存 + 4 B 灯掩码(小端)+ `info[4] = info[5] = 0x00`(普通图分支)+ `info[6..7]` 保留 +
**998 B 填充固定 `0x00`(屏板不读但参与 CRC)** + 2 B CRC。短一字节屏板就在 `DR[4206]` 读到陈旧字节 → CRC 必失败
静默丢弃;多发会把它正在处理的那一帧撕裂(收满即回调、缓冲从头再填)。
- **LOGO 形态不实现、不下发**(出局):总线上不会出现 `info[4..5] = 0x89 0x45` 的帧;`sb_proto` 里既有的
`sb_frame_b_build_logo()` 与魔数常量为待删除遗留(§9.2),勿新增调用。
- **组帧**(`sb_link_frame_update`,uiTask 每拍):拿帧缓冲里的点阵段与活显存 memcmp,变了才组 + `sb_frame_b_verify`
自校验,不过不置 ready(宁可不发旧帧)。**不留显存影子副本**(省 3200 B SRAM 与每拍重算 CRC,代价只是每拍一次
~20 µs 的 memcmp)。**TX DMA busy 期间冻结帧缓冲**:DMA 读的就是 `s_frame` 本体,中途改写会发出半新半旧的"脏帧"
——CRC 仍对、屏板照收、画面撕裂,极难归因;忙就 `BSP_ERR_BUSY` 下一拍补组(双缓冲被否:最坏慢 10 ms,
280 ms 周期无感,不值再花 4208 B)。
- **时序事实**(屏板实跑 **705 882 bps**——它自己 36 MHz 时钟比标称高 0.84 %,判据换算按它、不按 700 000):
1 字符 ≈14.17 µs,整帧 4208 × 10 bit ≈ **59.6 ms**(8N1)。
- **断帧窗口**:屏板按"总线空闲 1 个字符时间"断帧且无接收超时寄存器可调——**间隙 ≥2 字符(≈28 µs)必把一帧
切成两半**,两半都过不了 CRC。这是"必须整帧一次交 DMA"的全部理由:任务里逐字节轮询 `DR`,一次几十 µs 的
ISR 抢占(TIM3 采样、SysTick、SPI/CH395F DMA)就毁帧,而屏板不报超时、不可归因。
- **起发留量**:距召唤帧末位 ≥30 µs(2 个字符时间,等屏板发完放开它自己的 DE 脚)再起发,无上限要求。
- **屏板的 205 ms 不是帧作废线**:到点只置"可以重发"标志,不清缓冲、不重启串口,**迟到的完整帧照样显示**
(帧里没序号);真硬的是下一轮召唤——无键 280 ms、**有键待报最早 ≈205 ms 就再来一帧**,上一答还在总线上
就是双向对撞。所以"≤200 ms 内发完"仍要做,理由是**防撞总线 + 不多一轮画面滞后**。
- **按键只发一次不重试**(屏板发完即清备份):错过那一轮 = 丢键——这是起发必须留在中断(µs 级)、
不能挂在任何任务态的直接原因。`uiTask` 属"数秒量级等待"那一族(netTask 忙等推导上界 ≈4 s、
对时 relisten 突发实测单轮 800 ms 以上缺席),屏板只给 205/280 ms,任务态起发救不了。
- **按键上行**:中断校验帧 A 后 `key ≠ 0` 入环形队列(可存 3 个,满了丢**最**新的一帧并计数——旧键已在路上),
`uiTask` 出队 → 映射 → 注键;**注键不在中断里**。
## 5. 键值映射(帧 A `key` → `UI_KEY_*`)
| 屏板 `key` | 语义 | 映射 | 备注 |
|---|---|---|---|
| 0 | 无键 | — | 不产生事件、不占队列 |
| 1 / 2 / 3 / 4 | ENTER / UP / DOWN / ESC | `0x20` / `0x02` / `0x40` / `0x01` | |
| 7 / 8 | LEFT / RIGHT | `0x10` / `0x08` | |
| 9 / 10 | RESET / FACTORY | **丢弃 + 一行日志**(`unmapped`) | 语义待产品裁定(映射复位 / 映射 ESC / 不映射);10 无物理入口理论收不到 |
| 5 / 6 | ADD / DEC | — | 屏板 `key_configs[]` 仅 7 项,**无物理入口**,不会出现——别按屏板 readme 的 8 键表实现 |
- 映射表落点在 `ui_task.c` 的 `ui_task_board_key_map()`;`sb_link_key_take()` 只吐**屏板原始键号**——键号是协议
语义、UI 键码是 UI 语义,汇合归 uiTask(那一层才知道当前哪个页面在接键)。
- 与 7003 网络的注入(`ui_net_sink_inject_key()`)**同一个输入队列**(都走 `ui_port_queue_key`),两路无需互斥。
## 6. 状态灯映射(帧 B `info[0..3]`,32 位小端)
权威定义 = `App/algo/sb_led_map.h` 位宏 + `sb_led_map_eval()`(**映射的唯一出处**,折算不散落调用点)。
`uiTask` 每拍现采四路状态源 → 掩码进 `sb_link_frame_update()`;掩码变了帧就重组,走 busy/dirty 既有通路,无新机制。
| 位 | 语义 | 来源 |
|---|---|---|
| bit1 | 运行心跳 | 500 ms 半周期翻转,与主板 LED1~3 同拍 |
| bit2 | 告警 | `prot_elm_active_mask() != 0` |
| bit3 | 通信(104 RUN) | `iec104_serv_link_run()` |
| bit4 | 采样故障 | `adc_health_flags()` 电平位(不选 sticky——灯要能灭) |
| bit5 | 保护启动 | `prot_elm_pick_mask() != 0` |
| bit0 | 恒 0 | ID 0 电源灯硬件常亮,屏板不看 bit0 |
| bit6~31 | 恒 0 | 保留,扩展从 ID 6 顺序取 |
- 折算做成**纯函数分支**而不是 const 表 + 谓词回调:5 路输入判据形态各异(电平 4 路 + 闪烁 1 路),
纯函数直白且可单测;主机单测 `test/host/sb_led_map/` 钉「掩码 & ~定义集 == 0」,改表漏测会被套件拦住。
- ⚠ 产品构建下 bit2 / bit5 **恒灭**(保护判据未标定闭锁,`ADC_CAL_F_VALID` 未置位)——验证这两路要等标准源
标定后或临时构建(与屏上「保护动作 N」同一已知口径)。
## 7. 资源与耗时(`.map` / 日志可复核)
| 项 | 值 | 说明 |
|---|---|---|
| `s_frame` | 4208 B @ `0x20006d98` | `Execution Region RW_IRAM1`(`0x20000000` 起),**不在 `RW_CCM`** |
| `s_rx_buf` / `s_stats` 等 | 16 + 84 B | 同段 |
| 整模块增量 | Code ≈ +1.5 KB(237,464 → 238,936 B) | `.map` |
| 全镜像 | RW_IRAM1 85,904 B(65.5 %)/ RW_CCM 57,376 B(87.9 %) | 凭证 `scripts/size_attestation.json`(门禁红线 92 %) |
| 任务 / 堆 / 看门狗 | **零新增** | uiTask 宿主、静态缓冲、`required_mask` 不变 |
| 组帧耗时 | **509 µs 典型 / 812 µs 峰值,预算 ≤1 ms**;没变只付 memcmp ≈20 µs | 台架 `us=` / `max=` 字段 |
| 耗时结构 | ≈480 µs 全在 CRC:每帧算满 4206 B,168 MHz 查表 ≈19~20 周期/字节,512 B 表驻 Flash(5 等待周期);清零+拷贝只 ≈20 µs。**占空比无约束**:一帧 280 ms ≈0.2 %,3.5 帧/s ≈0.17 % CPU | 压降两条路已评估**未实施**:表搬 SRAM(≈ −125 µs,+512 B SRAM)、998 B 零段 GF(2) 推进变换(≈ −89 µs,需长期维护矩阵代码)——这个占空比换不来收益;另循环变量用 `uint32_t` 防 ARMCC 每轮多一条 `UXTH`(`sb_crc16`) |
## 8. 对外接口(单一出处在各头文件,这里只列形状)
| 接口 | 上下文 | 作用 |
|---|---|---|
| `sb_link_init(&huart1)` | 启动,`app_main.c` 步骤 2b | 交叉核对波特率(对不上报 ERR_STATE)+ 挂首轮接收 + 使能 DWT |
| `sb_link_receive_start()` | 中断/任务 | 重挂 IDLE 接收(一次性 API,漏一次链路永久静默) |
| `sb_link_init(&huart1)` | 启动,`app_main.c` 步骤 2b | 交叉核对波特率(对不上 `BSP_ERR_STATE`)+ 挂首轮接收 + 使能 DWT |
| `sb_link_receive_start()` | 中断/任务 | 重挂 IDLE 接收(一次性 API,漏一次永久静默) |
| `sb_link_on_rx_event(huart,size)` | 中断(rs485.c 转发) | 帧 A 校验、计数、按键入队、**点 TX DMA 应答**、重挂 |
| `sb_link_on_error(huart)` | 中断(rs485.c 转发) | 按 SR→DR 顺序清错、按需重挂 RX、按 `gState` 清被打断 TX 的残留 busy |
| `sb_link_on_error(huart)` | 中断(rs485.c 转发) | 按 SR→DR 清错、按 `RxState` 决定重挂 RX、按 `gState` 清被打断 TX 的残留 busy |
| `sb_link_on_tx_cplt(huart)` | 中断(rs485.c 转发) | 清 busy、计数;**不在中断里组帧** |
| `sb_link_frame_update(bmp,mask)` | uiTask | 按需组帧(memcmp 判变化),busy 返 BUSY,自校验不过不发 |
| `sb_link_key_take()` | uiTask | 出队屏板原始键号(0=空);映射归 uiTask |
| `sb_link_poll(now_ms)` | uiTask | 离线判定(每拍)+ 5 s 变化才打的 RX/TX 两行统计 |
| `sb_link_get_stats(&st)` | 任意 | 计数快照(rx/ok/crc/keys/tx/done/drop/fb/us/offline…) |
| `sb_crc16` / `sb_frame_a_parse` / `sb_frame_b_build_image` / `sb_frame_b_build_logo`(⚠ 待删除,LOGO 出局见批 3 的 3-3)/ `sb_frame_b_verify` | 纯逻辑 | 协议层五件套,主机单测 59/59 |
| `sb_link_frame_update(bmp,mask)` | uiTask | 按需组帧(memcmp 判变化),busy 返 `BSP_ERR_BUSY`,自校验不过不发 |
| `sb_link_key_take()` | uiTask | 出队屏板原始键号(0 = 空);映射归 uiTask |
| `sb_link_poll(now_ms)` | uiTask | 离线判定(每拍)+ 5 s 变化才打的统计行(RX/TX 两行自分频) |
| `sb_link_get_stats(&st)` | 任意上下文 | 计数快照(rx/ok/crc/len/fmt/keys/tx/done/drop/fb/bad/us/off…) |
| `sb_crc16` / `sb_frame_a_parse` / `sb_frame_b_build_image` / `sb_frame_b_verify` | 纯逻辑 | 协议层四件套,主机单测可回归 |
| `sb_frame_b_build_logo` | 纯逻辑 | **待删除**(LOGO 出局,勿新增调用,§9.2) |
全局回调落点:`HAL_UARTEx_RxEventCallback` / `HAL_UART_ErrorCallback` /
`HAL_UART_TxCpltCallback` 三个的**唯一实现处都在 `rs485.c`**,各含一行
`sb_link_on_*` 转发;按句柄过滤在 `sb_link` 内部做。三个必须收在一处见全——
分开写,下一个加通道的人会以为 TxCplt 没人用而再定义一份(multiple definition)。
## 9. 验证状态
## 3. 关键实现决定(为什么是现在这个样子)
### 9.1 已验证
1. **不留 3200 B 影子缓冲**:帧里的点阵段就是"上次组进去的那一份",直接拿它跟活显存
memcmp。省下 3200 B SRAM 与每拍 0.15 ms 的 CRC,代价只是每拍一次 memcmp(~20 µs)。
2. **busy 期间冻结帧缓冲**:DMA 读的就是 `s_frame` 本体,中途改写 = 半新半旧的脏帧
(CRC 仍对、屏板照收、画面撕裂,极难归因)。宁可 `BSP_ERR_BUSY` 下一拍再组。
双缓冲被否:一召唤一答的节奏下"下一拍补组"最坏慢 10 ms,屏板 280 ms 周期无感,
不值再花 4208 B。
3. **起发在中断里**(规划文档的决策三):4208 B 线上 59.6 ms,任务里轮询 DR 一次几十 µs
的 ISR 抢占就把帧切断(屏板按 1 字符时间断帧、且坏帧也清超时计数——现场只剩"画面不刷新")。
4. **注键走 `ui_port_queue_key`、不绕 `ui_net_sink_inject_key`**:后者只是 7003 后端对
同一函数的转发,绕过去要多一次 include 且语义不变。两条入口进同一个输入队列。
5. **错误中断顺手收拾 TX**:HAL 的 TX 被错误打断时不调 TxCplt,busy 会永久挂着,
之后每次召唤都被判"上一帧还在发"→ rx 涨 tx 冻结。判据用 `huart->gState != BUSY_TX`。
6. **离线判 10 s 不判 500 ms**:屏板 ≈5000 s 自复位有 ≈1 s 空窗,短窗会把"歇了口气"
判成离线再"恢复",标志反复跳。
7. **LED 掩码走独立纯逻辑模块折算**(批 3 落地):位定义 + 求值收在 `App/algo/sb_led_map.c/h`
(映射表唯一出处),`uiTask` 每拍现采四路状态源(`prot_elm_active_mask` / `prot_elm_pick_mask` /
`adc_health_flags` / `iec104_serv_link_run`)喂进去,掩码变了帧就重组——复用批 2 的 busy/dirty
机制,无新通路。折算不做成 const 表:5 路输入判据形态各异(电平 4 路 + 心跳 1 路),
纯函数分支比"表 + 谓词回调"直白,且主机单测能钉「掩码 & ~定义集 == 0」。
8. **键 9/10 丢弃留痕**:决策四未裁,先按 `docs/任务规划/屏板链路.md` §4.3 的表把
1/2/3/4/7/8 映射到 `UI_KEY_*`,9/10 出日志不注键。
9. **LOGO 出局**(用户裁定 2026-10-29):不做下发入口。`sb_proto` 的 `sb_frame_b_build_logo()`
与魔数常量**列为待删除**(头文件已挂 ⚠ 勿新增调用);常规图像路径 `info[4..5]` 恒 0,
屏板按普通图分支处理,总线上不会再出现 LOGO 形态——「屏板接收溢出」风险表随之只剩叠发一源。
| 层级 | 证据(计数名 = 台架/真机日志 `[SBLINK]` 行字段) |
|---|---|
| 主机单测 | `test/host/sb_proto/` **59/59**(屏板实测向量、CRC 表与屏板查表逐字节交叉验证);`test/host/sb_link/` **146/146**(ORE 的 SR→DR 顺序在桩里真执行、非阻塞错误不得重挂、busy 期间帧缓冲逐字节冻结、帧 B 逐字段、周期→µs 换算、离线 9.999 s/10 s 边界与恢复、按键 FIFO 与溢出丢新);`test/host/sb_led_map/` **17/17** |
| 台架(PC + USB 转 TTL 顶替屏板) | `rx == 发送帧数`、`ok == rx`、`crc=0 len=0 size=6`;错波特率灌 64 B 垃圾后**自动恢复**(`rearm=0`,一次都没真失败);杜邦线噪声形态 `bits=0x02`(NE)每帧 ~1.3 次但数据零错;激励停止 10 s 准点 OFFLINE |
| 真屏板冒烟(2026-10-29) | 5 s ≈18 帧 → 278 ms/帧,与屏板 280 ms 节拍吻合;`tx==done`、一召一答无双发;**屏板出图(用户目视确认)**——屏板收帧只有"CRC 过 → 刷屏 / 不过 → 静默丢弃"两种结局且无报错通道,**出图 = 屏板亲手端到端验收了帧长 4208、CRC 覆盖 `[0,4205]`、低字节在前、1 bpp 20 B/行 MSB 在前的布局**,这是任何主机侧计数都给不出的一条证据 |
| 真屏板 10 min 连续(2026-10-29) | `rx=4258 ok=4258 crc=0 len=0 fmt=0 err=0 bits=0x00 rearm=0`(**连错误中断都没进过**);`tx=4249 done=4248`(`drop=9` 全在开机头两秒帧未就绪期,窗口内零新增);`keys=53 kq=0`(逐键必到、零丢弃);`fb=902 bad=0`(每帧自校验通过);组帧峰值 `us=687 max=812` < 1 ms 预算 |
| 双通道并发(2026-10-30,真屏板在线) | 7003 侧 `test/host/remolcd_pull_test.py` INIT→LCDMEM 10 Hz 重压两轮 **1500/1500 + 700/700**、CRC 错 0、超时 0,往返 min 35 / avg 63 / max 131 ms;屏板侧 60 s 隔离窗口 rx +233 全收、**每召必答**、`drop` 零新增、`off=0`;画面一致性由"两出口同一份活显存"机制保证 |
| 离线与恢复 | 断接期间 `off=1`,恢复后 `off=0`——`offline` 标志**只有**恢复分支会清,1→0 的转变即恢复路径执行过的证明(全程未重启主机) |
## 4. 资源占用(实测,`.map` 可复核)
### 9.2 未闭环
| 项 | 值 | 备注 |
|---|---|---|
| `s_frame` | 4208 B @ `0x20006d98` | `Execution Region RW_IRAM1`——**不在 CCM**(DMA1/2 物理不可达 CCM,放错段=静默发旧数据) |
| `s_rx_buf` / `s_stats` 等 | 16 + 84 B | 同段 |
| 整模块增量 | Code 约 +1.5 KB | 批 2 前后 237,464 → 238,936 B |
| 全镜像 | RW_IRAM1 85,904 B (65.5 %) / RW_CCM 57,376 B (87.9 %) | 凭证 `scripts/size_attestation.json` |
| TX DMA | `DMA2_Stream7` CH4,NVIC 优先级 9 | CubeMX 已配好、此前全工程没人用;无需动 `.ioc` |
| 波特率 | USART1 = 700000,BRR=0x0078(误差 0 %) | 写在 `Src/usart.c` 的 `USER CODE BEGIN USART1_Init 2`(`.ioc` 不保存波特率),开机日志自证 |
| RX | 16 B + IDLE 中断 | 不上 RX DMA(屏板只发 6 B) |
| 项 | 内容 |
|---|---|
| 2 h 长稳 | 跨一次屏板自复位(≈5000 s + 余量):零误判 OFFLINE、零 kerr/abort、自动续上 |
| 仪器项 | 逻辑分析仪:整帧期间无 ≥28 µs 间隙、召唤末位→应答首位 ≥30 µs、59.6 ms 整帧时长;FTDI/CP210x 收满 4208 B 后与 `g_ui_fbuf` 逐字节比对 |
| uiTask 拥塞面 | 制造 7003/netTask 拥塞(客户端不读、对时 relisten 突发),屏板侧应零超时、画面停最后一帧——"起发不在任务态"的直接证据 |
| 目视项 | 7 个物理键逐个按、动作对照 `docs/产品需求/产品需求设计文档.md` §8.2 菜单树 + 键 9 出 `unmapped` 日志;状态灯逐位(bit1 肉眼即见;bit3 挂 104 主站看亮灭;bit4 拔采样前端;bit2/bit5 见 §6 闭锁提示);主动拔插回归(含 7003 并发面) |
| 剖析对照 | 画面静止 1 min,开屏板链路前后对比 `[PROF] uiw`(memcmp ~20 µs 成本判据) |
| 待删除 | `sb_proto` 的 `sb_frame_b_build_logo()` + 魔数常量与对应单测用例(LOGO 出局遗留,现仅"勿新增调用") |
| 待确认 | 屏板排针实际收发脚方向(向屏板方,线束定版前);键 9/10 语义(产品侧) |
| 观察项 | 一次上电累计 `len=7` 共 7 次(IDLE 断帧尺寸 ≠ 6),出现窗口夹在"烧录后冒烟"与"首轮 150 s 重压结束"之间、不能排除与重压相关;但同固件上一上电周期 10 min 窗 `len=0`,第二轮 60 s 隔离窗口**零新增**,全程 `crc=0 fmt=0`、`ok` 增速与 `rx` 同频——功能影响为零。若再现首查:那包是否伴随 `err` 位(NE 噪声尾巴粘进下一包 = 无屏蔽杜邦线的正常形态) |
## 5. 实测记录
## 10. 坑账本
### 5.1 主机单测(可回归)
- `test/host/sb_proto/`:**59/59**,含屏板实测向量与 CRC 表逐字节交叉验证。
- `test/host/sb_link/`:**146/146**(批 2 新增 65 条)。桩把 `SR/DR/CYCCNT` 做成函数指针,
能在主机上钉住:ORE 清除的 SR→DR 顺序(RM0090 语义在桩里真执行)、非阻塞错误**不得**重挂、
busy 期间帧缓冲逐字节冻结、错误中断清残留 busy 但不误杀在发的 TX、帧 B 每个字段、
周期→µs 换算除数、离线 9.999 s/10 s 边界与恢复、按键 FIFO 与溢出丢新。
### 5.2 台架(PC + USB 转 TTL 顶替屏板,2026-10-29 白天)
40 s `--key-fixed 2`:`rx=143 ok=143 keys=143 crc=0` / `tx=143 done=143 drop=0 abor=0
fb=144 bad=0`;杜邦线噪声形态 `bits=0x02`(NE)每帧 ~1.3 次但数据零错——已定口径
"NE/FE 而 rx_ok 照涨 = 能用的告警";错波特率灌 64 B 垃圾后自动恢复;激励停止 10 s
准点 OFFLINE。组帧耗时 **509 µs 典型 / 709 µs 峰值**(成本全在 4206 B 查表 CRC、512 B
表驻 Flash)——原 0.3 ms 估算判据已改口 1 ms,压降路径记录未实施,见规划文档批 2 的 ⚠。
### 5.3 真屏板冒烟首通(2026-10-29 21:24,接 §2 决策一的 TTL 线)
| 证据 | 读数 | 结论 |
|---|---|---|
| `rx=125 ok=125 crc=0 len=0 fmt=0 err=0 bits=0x00 rearm=0` | 一字节没错、**连错误中断都没进过** | 真屏板线路质量优于台架杜邦线(台架每帧 ~1.3 次 NE) |
| 5 s ≈ 18 帧 → 278 ms | 与屏板固件 280 ms(第 56 拍 × 5 ms)吻合 | 对端确认是屏板本尊的周期召唤 |
| `tx=116 done=116 kerr=0 abor=0`,`drop=9` 集中在开机头两秒 | 一召一答、无双发、无失败 | 2-2/2-4 的时序骨架在真硬件上成立 |
| 错接期间 `off=1` → 对调线后连打三轮 `off=0` | 离线判定与自动恢复走通 | 2-5 判据闭环(全程未重启主机)。⚠ `back online` 那一行本身没取到证据——恢复发生在 21:24 抓日志的 `clear` 之前 ~10 s,行被抹掉了;但 `offline` 字段**只有**恢复分支会清,1→0 的转变即该分支执行过的证明 |
| **屏板出图,用户目视确认"非常完美"** | 见 §5.4 的推论 | 帧 B 的 4208/CRC 口径端到端正确 |
### 5.4 "出图"这两个字的证明力(别低估它)
屏板收帧**只有两种结局**:整包 CRC 过 → 刷屏;不过 → 静默丢弃,且它超时计数照清、
没有任何报错通道(`docs/任务规划/屏板链路.md` §4.4 预言过的"只表现为画面不刷新")。所以肉眼看到正确画面,等于
**屏板亲手验收了**:帧长 4208、CRC 覆盖 [0,4205]、**低字节在前**(两处命名相反的屏板
源码口径)、1 bpp 20 B/行 MSB 在前的显存布局、info 字段布局——全部端到端正确。
这是主机侧任何计数都给不出的一条证据,台架阶段(CH340 收不全)也补不了。
### 5.5 真屏板 10 min 连续零错(2026-10-29 21:30~21:44,验收清单第 1 条的主机侧半边)
挂机窗口内(日志清零后累计)末轮计数:
| 计数 | 读数 | 判据 |
|---|---|---|
| `rx=4258 ok=4258 crc=0 len=0 fmt=0 err=0 bits=0x00 rearm=0` | **连错误中断都没进过一次** | 10 min 零超时、零 CRC 错帧 ✓ |
| 末段 5 s 采样 4240→4258 = 18 帧 | 277.8 ms/帧 | 与屏板 280 ms 节拍吻合 ✓ |
| `tx=4249 done=4248 drop=9 kerr=0 abor=0` | `drop=9` 全部是开机头两秒(帧未就绪),窗口内零新增;`done` 差 1 是采样瞬间最后一帧还在发(59.6 ms 窗) | 一召一答、无双发、无失败 ✓ |
| `keys=53 kq=0` | 按压期间 53 个按键帧全部入队出队、**零丢弃** | 按键"逐键必到"的主机侧半边 ✓(动作语义合不合菜单树需目视,见 §7-1) |
| `fb=902 bad=0`,`us=687 max=812` | 10 min 组帧 902 次(待机时钟秒跳等 LVGL 自刷新驱动,≈1.5 次/s),自校验全程通过;峰值 812 µs < 1 ms 改口判据 | 常态组帧占空比对 10 ms 节拍 <7 % |
| 窗口内无 `OFFLINE`/`back online` 行 | 连接全程稳定 | — |
### 5.6 接线实测教训(决策一的接法表照接后**不通**,对调才通)
- 按规划文档决策一的表接"主机 A10(PA9)→屏板 PA3、B10(PA10)→屏板 PA2"**rx=0 彻底静默**;
**把两根信号线对调后立即通**。表本身的信号方向逻辑没错(TX 对 RX),错在**屏板侧引脚的
认定**——排针丝印 TX/RX 是"屏板自己发出/接收"的视角,与网表里 PA2=发送/PA3=接收的对应
关系未经屏板方确认。实操判据比丝印可靠:**接完看主机 `rx` 有没有计数**(rx 一动=方向对,
与帧内容无关),不动就当场对调。产品形态线束定版前,以"rx 计数"为唯一验收口径。
- 两块板各自供电必须接第三根线 GND;只连两根信号线时是浮地,形态同样是彻底收不到。
### 5.7 双通道并发回归(批 4 的 4-1,2026-10-30 00:10~00:18,真屏板在线)
激励脚本 `test/host/remolcd_pull_test.py`(新):INIT(8 B:宽/高/帧字节数,大端)→ LCDMEM
偏移 0 以 **10 Hz** 重压拉整帧 3200 B,逐帧 XOR 校验 + 长度核对。每轮 LCDMEM 在固件侧都是
「uiTask 入队 + 等 netTask 回执」的真往返——这就是"7003 在真实占用 uiTask"的压力形态。
| 侧 | 读数 | 判据 |
|---|---|---|
| 网络侧·第一轮 | 150 s:请求 1500 / 应答 1500、CRC 错 0、超时 0,往返 min 35 / avg 63 / max 88 ms | **串口发送期间 7003 不超时** ✓(4208 B 线上 59.6 ms 与拉帧并行,应答照样 100 % 到手) |
| 网络侧·第二轮 | 60 s 隔离窗口:700 / 700、零错零超时,max 131 ms | 同上,重复性 ✓ |
| 屏板侧·60 s 隔离窗口(日志 clear 后开窗,计数只读窗口内) | rx +233 帧(278 ms/帧节拍精确)、**`drop` 零新增、`kerr=0 abor=0 bad=0`、`off=0`、全程无 OFFLINE**,`tx` 追平 `rx`(末帧 59.6 ms 采样窗差 1) | **7003 拉帧期间屏板每召必答** ✓(屏板 205 ms 超时计数在主机侧看不到,每召必答是其不超时的充分证据) |
| 画面一致性 | 机制证明:两出口读**同一份活显存**(`ui_port_get_framebuffer()` 单点),无第二份账可漂;7003 末帧已 `--dump` 成 PBM/PNG 留档(`ci-artifacts/`,不入库) | 目视对图为机会性复核 |
**观察项(不属 4-1 判据,如实记)**:本轮上电累计 `len=7`(7 次 IDLE 断帧尺寸 ≠ 6)。
出现窗口夹在"烧录后冒烟 23:49(未截 len 字段)"与"首轮重压结束 00:15(已 len=7)"之间,
**不能排除与首轮 150 s 重压相关**;但上一上电周期 10 min 窗(§5.5)`len=0`,同一份固件,
说明非确定性、疑瞬态;第二轮 60 s 隔离窗口**零新增**,且全程 `crc=0 fmt=0`、`ok` 增速与
`rx` 同频——有效帧一帧没少,功能影响为零。若后续再现再查(首查方向:断帧尺寸 7 的那一包
是否伴随 `err` 位——若是 NE 噪声尾巴粘进下一包,属杜邦级线噪的正常形态)。
## 6. 坑账本(都已在代码注释/规划文档留痕,这里只汇总)
1. 中文字面量 → ARMCC `#870-D`(源码无 BOM 按本地代码页解)。日志字面量纯 ASCII。
2. `@test` 的 forbidden `FAIL;ERROR;HardFault;[ERR]` **大小写不敏感**——日志字段名带
fail/error/abort 会把启动自检判红(`fail=0` 实测中招,已改 `kerr=`/`abor=`)。
3. `ReceiveToIdle_IT` 一次性:漏重挂 = 链路永久静默;ORE 不自己清 = 一次噪声打死接收。
4. NE/FE 非阻塞、ORE 阻塞:重挂判据用 `RxState==READY`,不看错误位。
5. CH340 收不了 700 kbaud 连续流(4208 B 只到 ~2373 B)——**收不全 ≠ 固件没发**,
同轮 `tx==done` 即自证。
6. `.ioc` 不保存 UART 波特率;改波特率只能在 `USER CODE` 覆盖后重调 `HAL_UART_Init`。
7. 纯注释改动也会让体积凭证报"不同码":先重建、再 `ram_baseline --update`。
## 7. 尚未实测项(按优先级)
| # | 项 | 怎么测 | 状态(2026-10-29 夜) |
|---|---|---|---|
| 1 | **7 个物理键逐个按** | 真屏板已就位:每按一键,日志 `keys=` 应 +1、`kq` 恒 0,画面动作对照 `docs/产品需求/产品需求设计文档.md` §8.2 菜单树;键 9 应只出 `unmapped` 日志 | **半闭环**:10 min 窗口 `keys=53 kq=0`(必到、零丢弃)已实测(§5.5);剩"逐键动作合菜单树 + 键 9 出 unmapped 日志"需边按边看 |
| 2 | **10 min 连续零错**(屏板在线) | 挂机后 `grep SBLINK` 抽末行:`crc=len=fmt=err=rearm=bad=kerr=abor=0`、`tx≈rx`、`done=tx` | ✅ **2026-10-29 实测通过**(§5.5,rx=4258 零错) |
| 3 | **拔插回归**(正式版) | 拔屏板线 ≥10 s → 一条 OFFLINE;插回 ≤2 s 续图、一条 back online;期间 7003 与日志口不受影响 | 冒烟时被动验过一半(对调线前 off=1、对调后自动恢复);主动拔插 + 7003 并发面未测 |
| 4 | **uiTask 单轮增量**(判据"画面静止时 memcmp 成本 ~20 µs") | 画面静止 1 min,对比开屏板前后的 `[PROF] uiw`;按键刷屏时段观察 `uiw` 尖峰 ≈ 组帧 0.7 ms 以内 | 未测(§5.5 已给出组帧侧成本上限 `max=812 µs`,`[PROF]` 对照仍缺) |
| 5 | **2 h 长稳跨自复位** | 挂机 ≥5000 s + 余量:零误判离线(自复位 ≈1 s 空窗不该出 OFFLINE)、零 kerr/abort、恢复自动 | 无 |
| 6 | **召唤→应答延迟 30 µs~5 ms / 帧内无 ≥28 µs 间隙 / 59.6 ms** | 逻辑分析仪挂 `U5.A10/B10`;或 FTDI/CP210x 适配器 + `screenlink_bench.py --reply` 只能验整帧时长与内容(USB 1 ms 粒度量不了间隙) | 仪器 |
| 7 | **帧 B 与 `g_ui_fbuf` 逐字节比** | FTDI/CP210x 收满 4208 后 `--dump` 出 PBM 对 UI 目检;严格逐字节需 PC 收全帧脚本比对(CH340 做不了) | 仪器 |
| 8 | **双通道并发**(批 4 的 4-1) | 挂 DTU-RemoteLCD 持续拉帧 + 屏板同时召唤:两侧画面一致、串口发送期 7003 不超时、7003 拉帧期屏板不超时 | ✅ **2026-10-30 实测通过**(§5.7:7003 侧 2200 拉帧零错零超时;屏板侧隔离窗口每召必答、零新增丢弃;画面一致由"同一份活显存"机制保证) |
| 9 | **7003 拥塞时串口照常应答** | 制造 netTask 往返卡顿(对时风暴/客户端不读),屏板侧应零超时、画面停最后一帧——"起发不在任务态"的直接证据 | 无 |
| 10 | **状态灯逐位**(批 3 已实现,待目视) | ID 1 运行灯 1 Hz 闪(肉眼即见);ID 3 = 挂 104 主站(`test/host/iec104_master.py`)后亮、拆线灭;ID 4 = 拔采样前端后亮、插回灭;**ID 2 / ID 5 在产品构建恒灭**(未标定闭锁,`protect_task.c` 的 VALID 门禁——与屏上"保护动作 N"同一已知口径),要验证这两路得等标准源标定后或临时构建 | 真屏板已就位 |
| 11 | ~~LOGO 断电留存~~ | **随 LOGO 出局划掉**(用户裁定 2026-10-29):不下发 LOGO,`sb_proto` 的 LOGO 组帧函数列为待删除 | — |
| 12 | **键 9/10 语义裁定**(决策四) | 需要产品侧定 RESET/FACTORY 行为,代码已留 unmapped 分支 | 人 |
| 13 | 真屏板 ORE 路径 | 主机侧只能被动等:若某天 `bits` 出现 0x08,看 `rearm` 是否跟涨;台架造不出(CPU 读 DR 太快),已由主机单测按 RM0090 覆盖 | 机会性 |
第 2 项已代跑通过(§5.5);第 3、4 项接线不动就能跑;第 1 项剩下半边需要你手按。
1. **`@test` 的 forbidden 标记(`FAIL;ERROR;HardFault;[ERR]`)大小写不敏感**——日志字段名带 fail / error / abort
会把启动自检判红,本模块统计行用 `kerr=` / `abor=` / `off=` 命名。
2. **`ReceiveToIdle_IT` 是一次性的**:漏重挂 = 链路永久静默,且没有任何报错;ORE 不自己清,一次噪声打死接收。
3. **NE/FE 非阻塞、ORE 阻塞**:重挂判据用 `RxState == READY` 而不是错误位;台架"NE 每帧出现 + `rx_ok` 照涨"
= 能用的告警,**不是**故障(无屏蔽杜邦线常态);真故障形态是 ORE(`bits=0x08`),本链路 6 B/280 ms 不该出现。
4. **CH340 收不了 700 kbaud 全帧**(§3.3)——收不全 ≠ 固件没发,`tx == done` 自证,别据此改固件。
5. **`.ioc` 不保存 UART 波特率**:改波特率只能在 `USER CODE` 区覆盖 `Init` 后重调 `HAL_UART_Init`(§2)。
6. **全局 UART 回调是工程单例**:新通道只在 `rs485.c` 加转发行、不自写一份(§2)——"中断进来了但没人处理"
的表象是"屏板一直超时",极易误判成协议错。
7. **帧缓冲不可上栈、不可进 CCM**(§7)——放错段不报错,只是 DMA 读不到、发出去的是旧数据。
8. **`info[0..3]` 双解释**:先判 `info[4..5]` 魔数再解释这 4 字节(普通图 = LED 掩码、LOGO = 宽高),
顺序反了会把 LED 掩码当宽高。
9. **纯注释改动也会让体积凭证报"不同码"**:先重建、再 `ram_baseline.py --update`。
@@ -1,7 +1,7 @@
# 控制台单写者日志设计说明
> **版本**:v1.7(已实施;修订史见文末「修订记录」)
> **日期**:2026-10-26(v1.7:崩溃上报归一进单写者 + 多段裸发收口,道G task-26;v1.6:未注册期裸发加「行级单一所有者」,O1 第二个实例收口;v1.5 于 2026-10-24;初版 v1.0 于 2026-09-25)
> **版本**:v1.8(已实施;修订史见文末 §11「实施时间线」)
> **日期**:2026-10-30(v1.8:控制台端口由 USART1 改为 **UART5/RS232#1**,全文端口口径同步——物理口与台架端子见 `屏板链路设计说明.md` §2 / §3.2;v1.7 于 2026-10-26:崩溃上报归一进单写者 + 多段裸发收口,道G task-26;v1.6 同日:未注册期裸发加「行级单一所有者」;v1.5 于 2026-10-24;初版 v1.0 于 2026-09-25)
> **状态**:已实施并验证——`@build` 0 错误 0 警告、`@test` 硬件闭环 PASS(2026-09-25,见 §10);设计 2026-09-24 加固实施(当时位于 `Src/usart.c` USER CODE 区),2026-09-25 实现迁出至 `App/log/`(HAL 生成文件不含应用代码);同日写者任务体自 `Src/freertos.c` 迁至 `App/task/default_task.c`(与其余 5 任务文件同址归位)
> **关联文档**:`项目架构总览.md` · `运行与自检记录设计说明.md`(姊妹模块:持久化日志,同一写者任务) · `测试说明/链路延迟测试说明.md`(D6 日志税 / 步骤 8 非阻塞日志,需求出处) · AGENTS.md「平台约束」
@@ -56,14 +56,14 @@ HAL 并发语义(本仓库实现实测,`stm32f4xx_hal_uart.c:1135`):轮
|------|------|------|
| 生产者 | 全部任务 + 内核前主线程 + ISR | 调用点格式化整行(库 `snprintf`)→ `dbg_console_write_line()` |
| 写者 | **defaultTask(唯一)** | 进主循环前 `dbg_console_writer_attach()` 注册为唯一发送者;循环内周期 `dbg_console_drain()` |
| 物理通道 | USART1(PA9 TX / PA10 RX,115200,8N1) | 轮询发送(`HAL_UART_Transmit`);CubeMX 配置的 DMA TX(DMA2_Stream7)未使用 |
| 物理通道 | **UART5(RS232#1:PC12 TX / PD2 RX,经 U9 隔离 → U13 TPT3232E → CN1;115200 8N1)** | 轮询发送(`HAL_UART_Transmit`);端口以 `CON_UART*` 宏收口在 `console_log.c`;DMA2_Stream7 现由屏板链路 USART1 TX 占用,与本模块无关 |
| 唤醒 | `SYS_EVT_CONSOLE_TX`(`sys_evt` bit 6) | **消费型命令位**(`osFlagsWaitAny` 默认消费,与 AGENTS.md 事件位语义一致;区别于就绪位必须 `osFlagsNoClear`) |
**与其他日志子系统的关系**(同一"单写者纪律",同一属主任务):
| 子系统 | 通道 | 写者 | 持久化 |
|--------|------|------|--------|
| 控制台单写者(本文档) | USART1 串口 | defaultTask | 无(易失,复位丢失)——实时调试流 |
| 控制台单写者(本文档) | UART5 串口(RS232#1) | defaultTask | 无(易失,复位丢失)——实时调试流 |
| sys_log 运行/自检记录 | FatFS `/log/*.log` | defaultTask(同一任务) | 是(定长 64B 记录,`运行与自检记录设计说明.md`) |
| 配置引擎 | FatFS `/config.ini` | defaultTask(同一任务) | 是 |
@@ -93,7 +93,7 @@ flowchart LR
R["环形缓冲 4KB<br/>BSS(SRAM1)<br/>head/tail 16 位回绕"]
E["sys_evt bit6<br/>SYS_EVT_CONSOLE_TX"]
W["写者 = defaultTask<br/>dbg_console_drain()"]
U["USART1 115200<br/>(PA9/PA10, 轮询)"]
U["UART5 115200<br/>(RS232#1, 轮询)"]
P1 -- "dbg_console_write_line(整行 ≤192B)" --> R
R --> E --> W
@@ -111,7 +111,7 @@ flowchart LR
| 区间 | 发送者构成 | 并发行为 |
|------|-----------|---------|
| 内核启动前 | 仅主线程(单线程) | 无并发,直发安全 |
| 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `USART1->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 |
| 写者未注册(内核启动 → attach) | 任何任务都可能记日志(2026-10-26 起这是**常态而非违规**) | **整行独占**(行级单一所有者,§4.4.1):同一时刻只有一个上下文在写 `CON_UART->DR`,抢不到独占的整行转入环形缓冲延后发 → 交错结构性消除,且不丢行 |
| 写者已注册(稳态) | 任务上下文唯一调用者 = 写者 | 任务上下文零竞争;残余窗口 = ISR 直发撞写者发送中(丢失、不挂死,§7) |
**纪律(2026-10-10 固化,2026-10-10 / 2026-10-26 两次台架实证其不足)**:周期 / 常驻任务**不得**在写者注册前记日志——用 `dbg_console_writer_ready()`(§5)门控自身日志(`adcTask` 的首帧与 10 s 心跳、`protectTask` 的启动锚点行即按此门控——后者 2026-10-12 B6 台架实证:B5 接线时启动行未门控,注册前裸发被交错损坏致 `@test` 锚点 MISS,见 §11 变更记录)。内核前代码仍可直接裸发(单线程,无并发)。
@@ -173,7 +173,7 @@ dbg_console_drain();
`drain` 算法(仅写者任务调用):
1. 循环 `while (used != 0)`:取连续段 `tail → 环尾`,截断至 `DBG_CON_CHUNK_MAX`(256B) → `HAL_UART_Transmit(&huart1, &s_con_ring[tail], n, 0xFFFFu)` → **临界区内推进 `tail`**(交给 UART 的缓冲指针恒为环形缓冲——G2);
1. 循环 `while (used != 0)`:取连续段 `tail → 环尾`,截断至 `DBG_CON_CHUNK_MAX`(256B) → `HAL_UART_Transmit(&CON_UART, &s_con_ring[tail], n, 0xFFFFu)` → **临界区内推进 `tail`**(交给 UART 的缓冲指针恒为环形缓冲——G2);
2. 刷丢弃计数:`s_con_drop != 0` → 组帧 `"\r\n[ERR] CONSOLE dropped N lines\r\n"` → **`write_raw` 直发**(不经环:写者自身输出若走环会 `con_append` → `drain` 重入当前 `drain`,无限递归)。
稳态时序:
@@ -184,7 +184,7 @@ sequenceDiagram
participant R as 环形缓冲 4KB(BSS)
participant E as sys_evt 事件标志
participant W as 写者 defaultTask
participant U as USART1(115200)
participant U as UART5(115200)
P->>R: dbg_console_write_line(整行 ≤192B)
Note over P,R: 临界区:整行拷贝 + 推进 head
@@ -229,12 +229,12 @@ sequenceDiagram
- **顺带堵掉第二个洞**:`dbg_console_drain()` 现在也必须先拿到所有权才开轮,拿不到就让位(`tail` 不推进 → 环里一条不少,≤50 ms 后重发)。旧写法下 attach 就发生在 `defaultTask` 里,完全可能撞上别任务**已发出半行**的裸发,两边逐字节交错。
- **新增 API**:`dbg_console_tx_begin()` / `dbg_console_tx_end()`(同样非阻塞:拿不到返回 0,调用方照发即可)——给"一条行分多段裸发"的调用方收口用。本模块只提供 API 与主机单测;调用点改造已随 **task-26(2026-10-26)** 落地,但**只落在任务态**:`App/util/task_watchdog.c` 的 STALL 行(正文 + CRLF 共 2 段)包进一次独占,调用点一律写 `(void)dbg_console_tx_begin();`、不拿返回值分支;`Src/freertos.c` 栈溢出/断言钩子与 `Src/stm32f4xx_it.c hard_fault_report` 三处**刻意不包**,理由见下面那条 bullet。
- **异常态的整行性不靠凭证**(task-26 语义,必须写清免得误读):`IPSR != 0` 时 `dbg_console_tx_begin()` **返回 1 但并不持有凭证**(异常上下文绝不允许等待或失败)。所以四个 Fault 与栈溢出钩子的整行性来自「**Fault 优先级 -1、外设中断 ≥5、任务全冻结** ⇒ 没有第二个写者能跑进来」。唯一残留 = **被抢占任务已在移位寄存器里的那 ≤1 个字节**,它出现在故障行**之前**,不影响 `[ERR]` 前缀可 grep。已明确否决「发送循环逐字节检查让位标志」那类修法——那会把正在发的启动日志行截断,换不来什么。
- **崩溃上报也归一(task-26)**:`Src/stm32f4xx_it.c` 的 `hard_fault_report`(四个 Fault 共用,原本 **14 段**裸发)**自己调 `HAL_UART_Transmit`**,是绕过本模块行级独占的**第三个写者**;现改为 `dbg_console_write_raw`(**但刻意不包 `tx_begin()/tx_end()`**,见下面两条 bullet)。三条理由(详见该函数 `hard_fault_write` 头):① HAL 超时判据是 `HAL_GetTick()`(TIM7 停摆则永不成立,第 8a/8b 轮实证;正常上限也是 0xFFFF ≈ 65 s),本口是 DWT/指令计数双硬上界;② HAL 状态机在僵尸通道(ORE 锁存)下恒快速失败 ⇒ **`[ERR]` 崩溃行整条蒸发**,而它正是台架「出现即不通过」的判据来源;③ `huart1.Instance == NULL` 时本口立即短路,绝不解引用地址 0(机器判据 = `TC-CONLOG-100`/`TC-CONLOG-102`)。同处删除输出后的 `HAL_Delay(200)`:本口等到 TC 才返回,延时冗余,且 `HAL_Delay` 也依赖 TIM7 tick——tick 停摆会**卡在复位之前**(启动段 IWDG 尚未启动,无兜底)。
- **崩溃上报也归一(task-26)**:`Src/stm32f4xx_it.c` 的 `hard_fault_report`(四个 Fault 共用,原本 **14 段**裸发)**自己调 `HAL_UART_Transmit`**,是绕过本模块行级独占的**第三个写者**;现改为 `dbg_console_write_raw`(**但刻意不包 `tx_begin()/tx_end()`**,见下面两条 bullet)。三条理由(详见该函数 `hard_fault_write` 头):① HAL 超时判据是 `HAL_GetTick()`(TIM7 停摆则永不成立,第 8a/8b 轮实证;正常上限也是 0xFFFF ≈ 65 s),本口是 DWT/指令计数双硬上界;② HAL 状态机在僵尸通道(ORE 锁存)下恒快速失败 ⇒ **`[ERR]` 崩溃行整条蒸发**,而它正是台架「出现即不通过」的判据来源;③ `CON_UART.Instance == NULL` 时本口立即短路,绝不解引用地址 0(机器判据 = `TC-CONLOG-100`/`TC-CONLOG-102`)。同处删除输出后的 `HAL_Delay(200)`:本口等到 TC 才返回,延时冗余,且 `HAL_Delay` 也依赖 TIM7 tick——tick 停摆会**卡在复位之前**(启动段 IWDG 尚未启动,无兜底)。
- **⚠ 行为变化:让路会改变行序与时间戳顺序(不是回归)**。让路的整行落进环形缓冲,因此它在流里**位置后移**(最多一轮 drain,实测毫秒级)。台架 2026-10-26 复验 5 次(1 次烧录 + 4 次仅复位)稳定观察到 **`[NAND_FTL] Initializing NAND FTL...` 排在 `[PROT] uncalibrated -> protection blocked …` 前一行**;而缺陷形态下的旧一手记录正好相反(PROT 在前,NAND 缺 `[20` 行首,`docs/测试说明/boot_log_data/20261026_prod_console_interleave_evidence.txt`)。原因:谁先抢到整行所有权谁立即发完,另一方整行让路延后;常态下 `defaultTask` 的存储 bring-up 行先开火,`protectTask` 的未标定闭锁边沿要等 adcTask 首次给出标志才产生。**所以**:① **行序不是判据**——它是竞态结果,两台设备/两次启动都可能不同;② **时间戳先后也不是判据**——让路行带的是它**自己产生时刻**的时间戳(刻意不重打,见上面判据⑦),于是可能出现"后出现的那一行时间戳更早";③ 正确的判据写法是**按整行逐字节锚定**两行各自是否完整(本次台架就是这么判的,5/5 命中),不要锚定相对顺序、也不要锚定"某行必须在另一行之前"。
- **哪些调用点包整行独占、哪些刻意不包(task-26 定案,不是遗漏)**:**任务态**多段裸发才包(现网唯一实例 = `App/util/task_watchdog.c` 的 STALL 行:既不空转,也不挂在 configASSERT 子树上)。**异常态与终点路径一律不包**——`hard_fault_report`(14 段)、`vApplicationStackOverflowHook`(3 段)、`vApplicationAssertFailed`(5 段):① `IPSR != 0` 时 `tx_begin()` 放行且**不持有**凭证、`tx_end()` 又只对持有者有效 ⇒ 包上是**纯空转**,保护本来就来自"Fault 优先级 -1 + 外设 ≥5 + 任务全冻结";② 这三处都挂在 `configASSERT` 子树上(`vPortEnterCritical/vPortExitCritical` 会进断言钩子),在那上面每加一句,armlink 就把它**乘进每一条含临界区的任务链**——实测包上时 `con_tx_release` 子树 24→152 B、发送环单趟贡献 128→208 B,`defaultTask`/`netTask`/`iec104Task` 三条链各 **+80 B**,`iec104Task` margin 从 1,096 被顶到 **1,016 < 1,024** ISR 底线(栈记账 FAIL);③ 崩溃行发完就 `NVIC_SystemReset()`,"归还所有权"没有下游读者。断言钩子那对 begin/end 还曾只包住"有文件名"一支(else 分支不在窗口内)——半套独比亚没有更误导人。
- **⚠ 结构性失效模式(这条环是我们自己造的,随时可能复活)**:断言钩子为了留痕要写控制台,而写控制台若要进内核临界区,就形成 `taskEXIT_CRITICAL() ⇒ vPortExitCritical() ⇒ configASSERT() ⇒ vApplicationAssertFailed() ⇒ dbg_console_write_raw ⇒ …⇒ 再进临界区` 这条**静态互递归环**;armlink 不是按运行时报错,而是把它**记进每一条含临界区的任务链**——一次性污染 9 条链的栈记账,且没有任何编译期或运行期告警。为此本模块的裸发/入环/取还凭证一律用 PRIMASK 窗口(`CON_CRIT_ENTER/EXIT`,机器判据 = `TC-CONLOG-015` 的 `con_crit_enter == 0` + `TC-CONLOG-015b` 两种屏蔽水位恒 0)。**判据留给后来人**:任何人在 `con_tx_*` / `con_ring_try` / `dbg_console_write_raw` / `dbg_console_drain` 的窗口里再放一处 `taskENTER_CRITICAL()` 或任何含 `configASSERT` 的内核 API,这条环立刻复活;提交前必须跑 `scripts/stack_report.py`(且**注明数字出自哪棵工作树、哪次构建**——本仓今天两次错误归因都是拿错了树的数字)。反向风险同样要记(task-38):若哪天把 `configASSERT` 裁成空宏,每条链"回收"的 80~90 B 是**静态记账口径变化,不是固件变好**,不得写成优化收益。
- **本笔没有消除的(别混)**:ORE/NE/FE 脏标志仍在(成因 = USART1 RX 悬空噪声,§12;用户已裁定不改 RX 配置)。本笔消除的是「崩溃日志整条蒸发」这一类,**不是**「控制台输出可能被脏标志污染」那一类,后者仍挂 §12 既有观察项。**极早期崩溃仍然完全静默**:`MX_USART1_UART_Init()` 之前进 Fault 时本口的 NULL 短路一个字节都不发——旧写法在同场景也不发(`gState != READY` ⇒ `HAL_BUSY`),所以没有变差,但也不能写成「现在能看到早期崩溃」。
- **本笔没有消除的(别混)**:ORE/NE/FE 脏标志仍在(成因 = 控制台口 RX 使能不消费,§12——当时端口 USART1、现 UART5;用户已裁定不改 RX 配置)。本笔消除的是「崩溃日志整条蒸发」这一类,**不是**「控制台输出可能被脏标志污染」那一类,后者仍挂 §12 既有观察项。**极早期崩溃仍然完全静默**:`MX_UART5_Init()` 之前进 Fault 时本口的 NULL 短路一个字节都不发——旧写法在同场景也不发(`gState != READY` ⇒ `HAL_BUSY`),所以没有变差,但也不能写成「现在能看到早期崩溃」。
- **实机验证缺口**:崩溃路径不能只靠静态论证(本仓最贵的教训就是「0 警告不代表安全」)。可执行验证设计见下方「验证设计」,**本笔未跑**(台架在别的任务手里)。
**为什么不做成"整段进环"**(曾评估):那会让 `storage_bring_up()` 期间串口**一个字都不出**,而这段正是 §「启动看门狗延后」注明的"死机表现为无任何输出"窗口(2026-10-02 那几轮就靠 bring-up 期流式日志取证)——等于把交错换成死机全瞎。现方案保留**胜者实时直发**,只有真撞车才让路一条行。
@@ -307,7 +307,7 @@ sequenceDiagram
| 缓冲满(日志突发 / 写者卡滞) | 生产者等 200ms 后丢行 | `s_con_drop` 累计 → 写者刷 `[ERR] CONSOLE dropped N lines`(@test FAIL,永不静默) | — |
| 写者未注册(内核前) | 单线程直发 | — | 无(无并发) |
| 未注册期其他任务直发 | **已消除**(2026-10-26):整行独占,抢不到就整行入环延后发(§4.4.1) | 让路不计数;只有"抢不到且环也满"才进 `s_con_drop` → `[ERR] CONSOLE dropped N lines` | 残余 = 让路行在流里位置后移(内容与直发逐字节相同);主机单测 `TC-CONLOG-170 至 TC-CONLOG-179` 钉死 |
| 崩溃上报(四个 Fault)原走独立 HAL 发送口 | **已归一**(2026-10-26 task-26):`hard_fault_report` 的 14 段裸发改走 `dbg_console_write_raw`(整行独占**不**用在这里——异常态空转,见 §4.4.1);ORE 快速失败(= 证据整条蒸发)与 `HAL_GetTick`/`HAL_Delay` 的 tick 依赖随之消失 | 崩溃行 `[ERR] <TAG> PC=… CFSR=…` 现在是单写者输出;实机验证设计见 §4.4.1 末(待台架窗口) | 极早期(`MX_USART1_UART_Init()` 之前)崩溃仍完全静默(NULL 短路);ORE 脏标志本身未消除(§12) |
| 崩溃上报(四个 Fault)原走独立 HAL 发送口 | **已归一**(2026-10-26 task-26):`hard_fault_report` 的 14 段裸发改走 `dbg_console_write_raw`(整行独占**不**用在这里——异常态空转,见 §4.4.1);ORE 快速失败(= 证据整条蒸发)与 `HAL_GetTick`/`HAL_Delay` 的 tick 依赖随之消失 | 崩溃行 `[ERR] <TAG> PC=… CFSR=…` 现在是单写者输出;实机验证设计见 §4.4.1 末(待台架窗口) | 极早期(`MX_UART5_Init()` 之前)崩溃仍完全静默(NULL 短路);ORE 脏标志本身未消除(§12) |
| ISR 直发撞写者发送中 | 与写者发送竞争 → 交错/丢失,不挂死(`IPSR != 0` 一律放行,§4.4.1 规则①);异常态没有第二写者(Fault 优先级 -1 + 任务全冻结),残留 ≤1 个移位寄存器字节且落在行**前** | 未计数 | ISR 日志频率极低,best-effort 语义 |
| 内核状态损坏(栈溢出 / 堆耗尽) | 钩子裸发固定文本 → 复位(栈溢出)/ 仅告警(堆) | `[ERR] STACK OVERFLOW task=…` / `[ERR] MALLOC FAILED …`;任务名范围校验 + 本地拷贝,TCB 指针不入 UART | — |
| 调用方指针损坏(脏指针) | 无法进入 UART(缓冲恒为环 / 模块本地) | 2026-09-24 HardFault 路径结构性消除 | — |
@@ -363,10 +363,11 @@ sequenceDiagram
| 2026-10-26 | **崩溃上报归一 + 多段裸发收口(道G task-26,接 task-23 第二段)**:`Src/stm32f4xx_it.c hard_fault_report`(四个 Fault 共用,14 段裸发)原**自行调用 `HAL_UART_Transmit`** ⇒ 绕过本模块行级独占的**第三个写者**,并带两条已实证失效:HAL 超时依赖 `HAL_GetTick()`(TIM7 停摆永不成立)与僵尸通道下恒快速失败(**关键 `[ERR]` 证据整条蒸发**)。现改 `dbg_console_write_raw`(**不包** `tx_begin()/tx_end()`,理由见 §4.4.1);`hard_fault_report` 与栈溢出钩子输出后的 `HAL_Delay(200)` 一并删除(本口已等 TC;少一条 tick 依赖,避免卡在复位之前)。同类收口只落在任务态:`App/util/task_watchdog.c` STALL 行 2 段包进一次独占;`Src/freertos.c` 栈溢出钩子(3 段)与断言钩子(5 段)以及 `hard_fault_report`(14 段)**不包**(异常态空转 + 代价 ×9 条任务链,见 §4.4.1)。**语义**:异常上下文 `tx_begin()` 放行不持有,故障行整行性靠 Fault 优先级与任务冻结,残留 ≤1 个移位寄存器字节(落在行前,不影响 `[ERR]` grep)。**未消除**:ORE/NE/FE 脏标志、极早期(串口未初始化)崩溃静默。主机侧复用既有判据 `TC-CONLOG-100 与 TC-CONLOG-102`(NULL 短路)与 `TC-CONLOG-174`(一次独占内多段连发不自我丢弃),未新增用例;实机验证设计见 §4.4.1 末。**行序口径**:让路行入环后位置后移,台架 5/5 观察到 `[NAND_FTL]` 稳定排在 `[PROT]` 前一行(旧缺陷形态相反)⇒ 行序与时间戳先后**都不再是判据**,只能按整行逐字节锚定(§4.4.1) |
| 2026-10-26 | **O1 第二个实例 → 未注册期裸发加「行级单一所有者」(道G task-23)**:台架同镜像两次"仅复位"启动,`[PROT]` 未标定闭锁边沿行内分别多出 `[20`(3 字节)与 `[`(1 字节),而紧随其后的 `[NAND_FTL] Initializing NAND FTL...` 行恰好缺同样那几个字节的**行首**(字节账与三次启动命中两次的频率见 §4.4.1)→ 判据"framing 错不会让行变长"锁定为**两个写者并发裸发**,即 O1 的第二个实例(第一个是 2026-10-12 B6)。修法 = 非阻塞行所有权(`con_tx_try_claim`/`con_tx_release`,只在几条指令的临界区里 test-and-set;发送全程**不关中断**)+ 让路整行原文入环延后发(不丢、不补前缀、不重打时间戳)+ drain 每轮先取凭证(堵掉"attach 撞上别人半行")+ `[ERR]` 只在"抢不到且环也满"时红。新增 `dbg_console_tx_begin/tx_end`(多段裸发收口,调用点改造 → task-26)。主机单测 127 → **152** 条(M 族 `TC-CONLOG-170 至 TC-CONLOG-179`,含逐字节相同判据),四处生产码注入反证各自转红(取消独占 11 红 / 让路改静默丢 5 红 / 非属主可归还 1 红 / 把发送包进临界区 3 红)。§3.4 / §4.4 / §7 / §13-O1 同步 |
| 2026-10-12 | **纪律盲区台架实证(B6)**:`protectTask`(B5 接线,台架腿当时未跑)启动锚点行在写者注册前裸发 → 与 defaultTask 启动日志**逐字节交错**(`st[art: cycle=220ms elm=70`,两次开机同形复现 = 启动调度确定性的竞态,非 USB 噪声)→ `@test` 锚点 MISS。修复 = 启动行改 `dbg_console_writer_ready()` 首拍补打(adcTask 首帧同款),`@build` 0/0 + `@test` PASS 复验(锚点完好 +577 ms)。§3.4 纪律示例更新 |
| 2026-10-29 | **端口迁移**:控制台从 USART1 迁到 **UART5(RS232#1,PC12 / PD2,经 U9 → U13 → CN1)**,`console_log.c` 收口成 `CON_UART*` 单点宏(句柄 / IRQn / 时钟 / 重初始化一体);USART1 现仅服务屏板链路(700 kbaud,`屏板链路设计说明.md`);台架日志口因此为 `COM3`(USB 转 RS-232 适配器 → CN1 端子,`.serialtest/config.json` 已配) |
## 12. ORE 僵尸通道(2026-10-02 台架实证)
## 12. ORE 僵尸通道(2026-10-02 台架实证;当时端口 USART1,现 UART5 同机理)
**机理**:USART1 使能了 RX(PA10 悬空、无上拉)→ 线上噪声持续产生帧错误/溢出 →
**机理**(当时端口 USART1;控制台现走 UART5,同一机理):控制台口使能了 RX 却无人消费 → 线上噪声或对端字节产生帧错误/溢出 →
**ORE(Overrun Error)置位** → HAL 发送路径卡在错误状态。对外设做软复位后 ORE
**毫秒级重锁**,于是每轮 drain 都会耗掉 100ms 量级的块超时。
@@ -392,7 +393,8 @@ sequenceDiagram
**已裁定不做(2026-10-26 用户决定)**:让 USART1 变成**只发不收**(`.ioc` 里不使能 RX、PA10 不再复用为
`USART1_RX`)确实能从机制上消除 ORE,但它是 `.ioc` 结构性改动 + CubeMX 重生成——要清空全部工作树、
独占一轮、复跑台架闭环,代价不抵收益。**现行缓解即长期方案**(单次 drain 250 ms 预算 + 发送故障后
300 ms 冷却 + DWT 时间源无关的超时判据)。机制根源继续记录在本节,将来若要重评不必重新踩一遍。
300 ms 冷却 + DWT 时间源无关的超时判据)。2026-10-29 起控制台在 **UART5(RS232#1)**,RX 使能未消费,
本节机理与缓解在新端口原样成立;USART1 的 RX 已由屏板链路(`sb_link`)消费,与本节无关。
## 13. 开放问题
@@ -401,7 +403,7 @@ sequenceDiagram
| O1 | 未注册期裸发无互斥 —— **已处置(2026-10-26,v1.6)** | 启动窗口(内核启动 → attach)`dbg_console_write_raw` 无互斥 → 多任务并发**逐字节交错**。两个台架实例:2026-10-12 B6(`protectTask` 启动锚点,靠自愿门控修)与 2026-10-26(`protectTask` 未标定闭锁边沿 × `defaultTask` `[NAND_FTL]` bring-up 行,字节账见 §4.4.1)——**第二个实例证明自愿门控本身不可靠**(边沿留痕无法预知自己落在哪个窗口)。现由机制收口:非阻塞整行独占 + 让路行入环延后发(§4.4.1),`dbg_console_writer_ready()` 降级为延迟优化。曾评估的两个替代方案(提前 attach / 整段进环)均因"把等串口塞进存储时序敏感段"与"死机期全瞎"被否,理由记录在 §4.4.1 末。**残余**:`IPSR != 0` 放行仍可能插入他任务半行(不构成标记漏检,§7);多段裸发钩子的段间缝 → task-26 |
| O2 | `HAL_UART_Transmit` 超时未计数 —— **已处置(2026-10-24,v1.5)** | 三本账已分离:`dbg_console_dropped()` = **真没落地**的数(缓冲满丢的一行,或没能确认送达而出环的一个块);`dbg_console_tx_fault()` = 外设**软复位自愈次数**(语义不变);新增 `dbg_console_tx_timeout()` = 写者排空时看到的**块超时次数**(TXE 与 TC 两条硬上界都算)。**口径边界**:裸发路径(内核前 / ISR / 两条告警行自身)的超时**不进**超时账——裸发没有「块」的概念,混进来读数失去意义。**尚未接线**:本读口暂无生产调用者;候选位置 `App/util/task_watchdog.c` 的 `STALL` 行是**被解析的证据行**,加字段要连解析端与台架判据一起改 → 列为后续项 |
| O3 | `printf` 逐字节开销 | 遗留 `printf` 路径 = 逐字节临界区 + 逐字节事件置位;遗留调用点全部切 `DBG_*` 宏后开销自然消失(不改变机制) |
| O4 | **ORE 僵尸未根治** | §12:现仅有缓解(250ms 预算 / 300ms 冷却 / DWT 超时),机制根源是 USART1 使能了 RX。根治 = `.ioc` 改只发不收 + CubeMX 重生成,**2026-10-26 用户裁定不做** → O4 是**长期现状**而非待办(见 §12) |
| O4 | **ORE 僵尸未根治** | §12:现仅有缓解(250ms 预算 / 300ms 冷却 / DWT 超时),机制根源是控制台口(现 UART5)使能 RX 不消费。根治 = `.ioc` 改只发不收 + CubeMX 重生成,**2026-10-26 用户裁定不做** → O4 是**长期现状**而非待办(见 §12) |
## 14. 相关文档
@@ -442,7 +444,7 @@ sequenceDiagram
D4 否决理由对现状仍逐条成立(115200 发送率低、日志行稀疏;IT/DMA 引入新内核
对象、缓冲所有权/双缓冲复杂度、DMA 可达性清单)。三点补充判断:
1. 把 drain 拖长的真凶是 **ORE 僵尸通道**(PA10 悬空 + RX 使能,软复位后毫秒级重锁 → 每轮块超时
1. 把 drain 拖长的真凶是 **ORE 僵尸通道**(当时 USART1 的 PA10 悬空 + RX 使能不消费——现 UART5 同机理见 §12,软复位后毫秒级重锁 → 每轮块超时
100ms 级);根治办法(`.ioc` 改只发不收)已于 2026-10-26 裁定不做 → **该形态长期存在**,靠缓解措施兜住;
2. 根治前后数据已可机读:`dbg_console_tx_timeout()`(§13-O2 三本账,v1.5 起)
就是为“IT/DMA 还剩多少收益”留的量尺;
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""RemoDispBus(7003) 拉帧压力客户端:批 4 的 4-1「双通道并发回归」的网络侧激励。
"""RemoDispBus(7003) 拉帧压力客户端:「双通道并发回归」的网络侧激励。
做什么:
连 192.168.1.100:7003,先发 INIT 查询屏幕参数(应得 8 B:宽u16/高u16/帧字节数u32,
@@ -30,7 +30,7 @@
退出码:0 = 全程无超时且 CRC 全对;1 = 出现超时/CRC 错/断连;2 = 用法错误。
设计依据:docs/任务规划/屏板链路.md §3 批 4 的 4-1。
设计依据:docs/子系统设计/屏板链路设计说明.md §9.1(双通道并发)。
创建日期:2026-10-30
"""
import argparse
+1 -1
View File
@@ -16,7 +16,7 @@
退出码:0=PASS / 1=测试失败 / 2=环境错误。
设计依据:docs/任务规划/屏板链路.md §3 批 3 的 3-1。
设计依据:docs/子系统设计/屏板链路设计说明.md §6(状态灯映射表)。
创建日期:2026-10-29
"""
import os
+1 -1
View File
@@ -28,7 +28,7 @@
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
设计依据:docs/任务规划/屏板链路.md §3 批 1(1-3 的判据)、§1.3(接收只要 16 B);
设计依据:docs/子系统设计/屏板链路设计说明.md §2(收中断与错误处理判据、接收只要 16 B);
docs/产品需求/产品需求设计文档.md §5.16.2(帧 A 结构)。
创建日期:2026-10-29
"""
+8 -8
View File
@@ -17,7 +17,7 @@
* 3. 坏帧分支(长度不对 / 地址不对 / 功能码不对 / CRC 错)在台架上要造噪声才出现,
* 一出现就被复位冲掉;主机侧是几行断言。
*
* 覆盖点(对应 docs/任务规划/屏板链路.md §3 批 1 的 1-3 判据):
* 覆盖点(对应 docs/子系统设计/屏板链路设计说明.md §2 的收中断与错误处理判据):
* TC-SBLINK-001 init 参数守卫:空句柄 / 波特率不是 SB_LINK_BAUD(后者必须不挂接收)
* TC-SBLINK-002 init 正常:挂接收一次、缓冲 = SB_LINK_RX_BUF_SIZE(16 B,不是 4208)
* TC-SBLINK-003 未初始化就重挂接收:BSP_ERR_STATE,且不碰外设
@@ -34,17 +34,17 @@
* TC-SBLINK-013 统计上报的时基回绕:now_ms 跨 0xFFFFFFFF 仍按差值法工作
* TC-SBLINK-014 统计快照:空指针守卫、字段逐项对得上
*
* 覆盖点(对应 docs/任务规划/屏板链路.md §3 批 2 的五项):
* 覆盖点(对应 docs/子系统设计/屏板链路设计说明.md §4 的组帧与时序判据):
* TC-SBLINK-016 组帧入口守卫:未初始化 / 空显存指针
* TC-SBLINK-017 帧 B 内容:点阵逐字节、info[0..3] 小端掩码、info[4..7] 非 LOGO、
* 998 B 填充全 0、CRC 覆盖 [0, 4205](2-1 判据)
* 998 B 填充全 0、CRC 覆盖 [0, 4205]
* TC-SBLINK-018 变化检测:内容不变不重组;点阵改一字节要重组;只改掩码也要重组
* TC-SBLINK-019 中断起发:未组帧不发;一召唤一帧 4208 B;TX 期间不叠发(2-2 判据)
* TC-SBLINK-020 TX 占用期间帧缓冲逐字节冻结,TxCplt 后补组(2-4 的"无脏帧")
* TC-SBLINK-019 中断起发:未组帧不发;一召唤一帧 4208 B;TX 期间不叠发
* TC-SBLINK-020 TX 占用期间帧缓冲逐字节冻结,TxCplt 后补组(防"脏帧")
* TC-SBLINK-021 起发失败要清 busy,否则链路此后永不发送
* TC-SBLINK-022 错误中断里清残留 busy;但 TX 真的还在发时不得判死(2-4 的"无残留 busy")
* TC-SBLINK-023 按键队列:单次交付、周期帧不带键、溢出丢新并计数、FIFO 顺序(2-3)
* TC-SBLINK-024 离线判定:9.999 s 不判、10 s 判、不重复刷屏、一帧即恢复(2-5 判据)
* TC-SBLINK-022 错误中断里清残留 busy;但 TX 真的还在发时不得判死(防残留 busy)
* TC-SBLINK-023 按键队列:单次交付、周期帧不带键、溢出丢新并计数、FIFO 顺序
* TC-SBLINK-024 离线判定:9.999 s 不判、10 s 判、不重复刷屏、一帧即恢复
* TC-SBLINK-025 组帧耗时换算:周期数 → µs 的除数(SystemCoreClock/1e6)
* TC-SBLINK-026 上报行:fb/tx 列可见;离线行排在周期行之前且不重复
*
+1 -1
View File
@@ -28,7 +28,7 @@
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
设计依据:docs/产品需求/产品需求设计文档.md §5.16.2(帧结构);
docs/任务规划/屏板链路.md §3 批 1(1-1 的判据);
docs/子系统设计/屏板链路设计说明.md §4(CRC 口径);
协议事实源 = 屏板固件工程 D:\\Code\\DTU Code\\DTU-LCD 的 CRC16.c / MODBUS.c。
创建日期:2026-10-29
"""
+8 -8
View File
@@ -5,7 +5,7 @@
做什么:
1. **发**:在一个 TTL 串口上按屏板的节拍(默认 280 ms)发帧 A,模拟屏板的周期召唤;
可穿插按键帧(`--key N`)。
2. **收**(`--reply`,批 2 起):每发一帧就收主机回的 4208 B 帧 B,校验长度 / CRC /
2. **收**(`--reply`):每发一帧就收主机回的 4208 B 帧 B,校验长度 / CRC /
998 B 填充,量"召唤末位 → 应答首位"的间隔与整帧耗时,可选把点阵 dump 成 PBM。
主机侧的统计由固件自己打(`[SBLINK] rx=... tx=...`),**收侧观测面是串口日志**;
本脚本的 `--reply` 是另一路观测(应答本身对不对),两者互补,不能互相替代。
@@ -13,14 +13,14 @@
为什么不用串口助手 GUI:
1-3 的判据含"连续 10 min 无丢帧",手点是做不到的;而且帧内容必须逐字节对
(CRC 低字节在前,写错一位就是 100 % CRC 错帧,与"波特率错"同形)。
2-2 的判据含"整帧 59.6 ms ±0.5 ms、≤200 ms 内结束",更是只能靠脚本量。
发送侧判据含"整帧 59.6 ms ±0.5 ms、≤200 ms 内结束",更是只能靠脚本量。
⚠ **USB 转 TTL 适配器量不到"帧内无间隙"**:USB 帧是 1 ms 量级,适配器把字节成批交上来,
所以 `--reply` 报的 `span` 只能证明"整帧在 60 ms 量级内发完",不能证明"没有 ≥28 µs
的间隙"(那是屏板断帧的硬约束,只能上逻辑分析仪或接真屏板时看)。别把 span 通过
当成那条判据通过。
接法(docs/任务规划/屏板链路.md §2 决策一):
接法(docs/子系统设计/屏板链路设计说明.md §3.3):
适配器 TXD -> U5.B10(网络 ST_RX0,PA10) 适配器 RXD -> U5.A10(网络 ST_TX0,PA9)
适配器 GND -> 板地
⚠ 必须交叉接(TX 对 RX);USART1 的 TTL 只从 U5 出去,CN1 上没有 USART1 端子。
@@ -35,13 +35,13 @@
& '.venv\\Scripts\\python.exe' test/host/screenlink_bench.py --port COM6 --seconds 600 # 10 min 判据
& '.venv\\Scripts\\python.exe' test/host/screenlink_bench.py --port COM6 --reply --dump lcd.pbm
--key N 每 N 个周期把召唤帧换成按键帧(键值在 1/2/3/4/7/8/9 里轮换;0 = 不发按键帧)
--reply 每帧收应答并校验(批 2 的 2-2 / 2-4)
--reply 每帧收应答并校验(长度 / CRC / 填充一致)
--dump P 把最后一帧应答的点阵存成 PBM(P4,160x160,可直接看)
退出码:0 = 按时发完且(`--reply` 时)所有应答都对;1 = 串口打不开 / 发送失败;
2 = 用法错误;3 = 有应答不合格。
设计依据:docs/任务规划/屏板链路.md §3 批 1 的 1-3 判据、批 2 的 2-2/2-4、§5 台架联调步骤第 2、3 步。
设计依据:docs/子系统设计/屏板链路设计说明.md §2(接收判据)、§4(发送口径)、§3.3(台架接法)。
创建日期:2026-10-29
"""
import argparse
@@ -83,7 +83,7 @@ def build_frame(cmd, key=0):
return body + bytes((crc & 0xFF, (crc >> 8) & 0xFF))
# ---- 帧 B(主机 -> 屏板,4208 B):批 2 的应答校验 ----
# ---- 帧 B(主机 -> 屏板,4208 B):应答校验 ----
FRAME_B_LEN = 4208
FRAME_B_BMP = 3200
FRAME_B_INFO = 3200
@@ -133,7 +133,7 @@ def capture_reply(ser, frame, budget_s=0.6):
⚠ **CH340 收不了 700 kbaud 的连续流**(实测 2026-10-29:4208 B 只收到 ~2373 B,
等效约 9.5 kB/s)。这不是固件问题——同一次运行里固件侧 `tx=94 done=93` 说明 4208 B
每次都发完了;CH340 的 RX FIFO 只有几十字节、USB 每 1 ms 才搬一次,70 kB/s 必然溢出。
要真正核对帧 B 的内容,换 FTDI / CP210x 之类的适配器,或等批 4 接真屏板。
要真正核对帧 B 的内容,换 FTDI / CP210x 之类的适配器,或接真屏板。
"""
ser.reset_input_buffer()
t_write = time.monotonic()
@@ -172,7 +172,7 @@ def main():
ap.add_argument("--glitch-bytes", type=int, default=32,
help="灌多少字节垃圾(默认 32)")
ap.add_argument("--reply", action="store_true",
help="每帧收主机回的 4208 B 应答并校验(批 2 的 2-2 / 2-4)")
help="每帧收主机回的 4208 B 应答并校验(长度 / CRC / 填充一致)")
ap.add_argument("--dump", default="",
help="把最后一帧应答的点阵存成 PBM(P4 160x160,可直接看)")
args = ap.parse_args()