f754d59218
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/, 顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。 - 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变) - 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts - 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob (分层后不递归扫描=追溯门禁直接瞎掉) - scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError, 让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把 stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法 - docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref 搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链 不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原) - docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的 「启动日志原子性复验流程」一行 - .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新, 该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线 (实测重建后仅 watchdog-module 一条哈希变化) - 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与 .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行), 任务表块与钩子声明一字未动 验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿; check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增 CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
306 lines
18 KiB
Markdown
306 lines
18 KiB
Markdown
# RemoDispBus 协议与接收设计说明
|
||
|
||
> 版本:v1.0 日期:2026-09-10
|
||
> 适用工程:STM32F4-Base(STM32F407ZGTx + CH395F 以太网 / LVGL UI)
|
||
> 范围:UI 远程显示(RemoDispBus)的**协议设计**与**接收解析设计**——
|
||
> 帧格式、字段语义、命令集、交互时序、屏幕位图格式,以及服务端逐字节状态机解析的实现。
|
||
> 对应模块:`Drivers/BSP/UI/ui_net_sink.c`(MCU 与模拟器**共用同一份**源码,协议与解析一致);
|
||
> 平台差异仅在网络后端(MCU `net_socket.c`/CH395F vs 模拟器 `net_socket_sim.c`/Winsock)
|
||
> 与 LCDMEM/按键注入的平台 backend(`ui_net_sink_backend_mcu.c` / `ui_net_sink_backend_sim.c`)。
|
||
|
||
---
|
||
|
||
## 0. 速览
|
||
|
||
| 项目 | 内容 |
|
||
|---|---|
|
||
| **协议名** | RemoDispBus(UI 远程显示总线) |
|
||
| **用途** | 远端查看设备 UI 画面 + 远端导航按键 |
|
||
| **传输** | TCP,端口 **7003**(须与 DTU-RemoteLCD 客户端默认端口一致) |
|
||
| **连接模型** | 单连接(服务端 `backlog=1`,同一时刻一个客户端) |
|
||
| **帧格式** | `[TAG(1)][CMD(1)][LEN_H(1)][LEN_LO(1)][DATA(n)][CRC(1)]` |
|
||
| **方向标识** | TAG `0xAA`=客户端→服务端,`0xBB`=服务端→客户端 |
|
||
| **命令集** | KEEPLIVE(0) / INIT(1) / KEY(2) / LCDMEM(3) |
|
||
| **校验** | CRC = 数据段逐字节**异或**(取低 8 位;不含 TAG/CMD/LEN) |
|
||
| **字节序** | 多字节字段**大端**(网络字节序) |
|
||
| **最短帧** | 5 字节(LEN=0 时) |
|
||
| **接收方式** | 逐字节有限状态机(TAG 定界 + 长度定长 + CRC 校验,自动重同步) |
|
||
|
||
---
|
||
|
||
## 1. 协议设计
|
||
|
||
### 1.1 定位与角色
|
||
|
||
RemoDispBus 让远端(PC 上的 DTU-RemoteLCD 客户端 / 浏览器页面)实时查看设备 UI 的 1bpp 画面,并下发导航按键。角色:
|
||
|
||
| 角色 | 进程 | 说明 |
|
||
|---|---|---|
|
||
| **服务端** | `ui_net_sink`(设备 / 模拟器) | 监听 7003,收命令、回送帧缓冲 |
|
||
| **客户端** | DTU-RemoteLCD(PC) | 连接服务端,拉取画面、下发按键 |
|
||
|
||
请求—响应模型:客户端发请求帧(TAG `0xAA`),服务端回应答帧(TAG `0xBB`)。
|
||
|
||
### 1.2 帧格式
|
||
|
||
| 偏移 | 字段 | 大小(B) | 说明 |
|
||
|---|---|---|---|
|
||
| 0 | `TAG` | 1 | 方向标识:`0xAA` 客户端帧 / `0xBB` 服务端帧 |
|
||
| 1 | `CMD` | 1 | 命令码(见 §1.5) |
|
||
| 2 | `LEN_H` | 1 | 数据段长度高字节(大端) |
|
||
| 3 | `LEN_LO` | 1 | 数据段长度低字节 |
|
||
| 4 | `DATA` | `LEN` | 数据段(命令相关,见 §1.5) |
|
||
| 4+LEN | `CRC` | 1 | 数据段逐字节异或(低 8 位) |
|
||
|
||
- 整帧长度 = `4 + LEN + 1`;`LEN=0` 时最短为 5 字节。
|
||
- `LEN` 为 16 位大端,理论最大 65535 数据字节。
|
||
|
||
**图形版**(SVG 矢量,字段宽 ∝ 字节数;由 `tools/arch_diagram/gen_frame_svg.py` 生成,浏览器打开可无损缩放):
|
||
|
||

|
||
|
||
### 1.3 字段语义
|
||
|
||
- **TAG(方向)**:`0xAA` = 客户端→服务端(服务端只接受此标签的帧);`0xBB` = 服务端→客户端。TAG 标识**方向**,不承载命令。
|
||
- **CMD(命令)**:同一命令在两个方向复用同一编码,靠 TAG 区分方向(见 §1.5)。
|
||
- **CRC(校验)**:`CRC = DATA[0] ^ DATA[1] ^ … ^ DATA[LEN-1]`(低 8 位)。**仅覆盖数据段**,不含 TAG/CMD/LEN;`LEN=0` 时 CRC 为 `0`。与 DTU-HMI 协议一致。
|
||
|
||
### 1.4 字节序
|
||
|
||
多字节字段一律**大端**(网络字节序):`LEN`(u16)、INIT 应答的宽/高(u16)与帧大小(u32)、LCDMEM 的偏移(u32)。
|
||
|
||
### 1.5 命令集与数据段
|
||
|
||
| CMD | 名称 | 请求数据段(客户端→服务端) | 应答数据段(服务端→客户端) | 说明 |
|
||
|---|---|---|---|---|
|
||
| 0 | KEEPLIVE | 无(LEN=0) | 无(LEN=0) | 保活/心跳,服务端原样回显 |
|
||
| 1 | INIT | 无(LEN=0,查询) | `[宽u16][高u16][帧字节数u32]`(**8B**) | 客户端查询屏幕几何与显存大小 |
|
||
| 2 | KEY | `[按键码u8]`(1B) | 无(LEN=0,确认) | 远端导航按键,服务端注入 UI |
|
||
| 3 | LCDMEM | `[起始偏移u32]`(4B) | `[偏移u32][1bpp位图 3200B]`(3204B) | 读取显存(画面);偏移 0 = 整帧 |
|
||
|
||
> **按键码**(`KEY` 数据段,与 `ui_port.h` 一致):`UP=0x02`、`DOWN=0x40`、`LEFT=0x10`、`RIGHT=0x08`、`ENTER=0x20`、`ESC=0x01`。
|
||
> **注意**:客户端(如 Python 参考实现)可不使用 `INIT`(直接按 160×160 处理);协议保留该命令供需查询几何的客户端使用。
|
||
>
|
||
> ⚠ **INIT 应答长度勘误(2026-09-22 M3 台架实测修复,D17)**:本表原写"6B",而字段定义
|
||
> `[宽u16][高u16][帧字节数u32]` 合计为 **8B**。旧实现按 6 发送 → u32 只剩高半字 →
|
||
> 客户端读到的"帧字节数"**恒为 0**(真机与 PC 模拟器一致复现)。现已改为发满 8B
|
||
> (`remo_send_init`),客户端按 `LEN>=8` 读 u32;若需兼容旧固件,可按 `LEN==6` 时
|
||
> 只取前 4B(宽高)。
|
||
|
||
### 1.6 屏幕位图格式(LCDMEM 数据段)
|
||
|
||
- **1bpp** 单色,**MSB 优先**(每字节 8 像素,bit7 为该字节第一个像素)。
|
||
- 像素 (x,y) 定位:`byte_i = (y*W + x) / 8`,`bit_i = 7 - (x % 8)`。
|
||
- 位值 `1` = 白(255),`0` = 黑(0)。
|
||
- 160×160 → `160*160/8 = 3200` 字节。
|
||
|
||
### 1.7 典型交互时序
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant C as 客户端 (DTU-RemoteLCD)
|
||
participant S as 服务端 (ui_net_sink)
|
||
C->>S: TCP 连接 :7003
|
||
opt 查询屏幕参数(可选)
|
||
C->>S: INIT 请求 (0xAA, CMD=1, len=0)
|
||
S-->>C: INIT 应答 (0xBB, CMD=1, 宽/高/帧大小)
|
||
end
|
||
loop 屏幕刷新(约 100ms)
|
||
C->>S: LCDMEM 请求 (0xAA, CMD=3, 偏移=0)
|
||
S-->>C: LCDMEM 应答 (0xBB, CMD=3, 偏移+位图 3200B)
|
||
end
|
||
opt 远端按键
|
||
C->>S: KEY 请求 (0xAA, CMD=2, 按键码)
|
||
S-->>C: KEY 应答 (0xBB, CMD=2, len=0)
|
||
end
|
||
opt 显式保活
|
||
C->>S: KEEPLIVE (0xAA, CMD=0)
|
||
S-->>C: KEEPLIVE (0xBB, CMD=0)
|
||
end
|
||
```
|
||
|
||
> 连接后服务端**不主动上报 INIT**(避免污染客户端首帧读取);由客户端按需请求(`INIT`)或按 LCDMEM 轮询拉取。
|
||
|
||
---
|
||
|
||
## 2. 接收解析设计(逐字节状态机)
|
||
|
||
### 2.1 设计动机
|
||
|
||
TCP 是**字节流**,不保留消息边界:一帧可能被拆到多次 `recv()`,一次 `recv()` 也可能带多帧或前导噪声。整块解析(假设"一次 recv = 一完整帧")在这种流上不可靠。因此服务端采用**逐字节有限状态机**:
|
||
|
||
| 关注点 | 整块解析 | 逐字节状态机 |
|
||
|---|---|---|
|
||
| 帧跨多次 recv | ✗ 会解析半帧 | ✓ 状态跨 recv 保持 |
|
||
| 一次 recv 多帧(粘包) | ✗ 只处理首帧 | ✓ 顺序处理每帧 |
|
||
| 前导噪声/失步 | ✗ 从头按帧头假设 | ✓ 扫描到 0xAA 重新同步 |
|
||
| 长度字段损坏 | ✗ 按错误长度读飞 | ✓ 越界即复位 |
|
||
| 内存 | 需容纳整帧 | 小块暂存 + 单帧重组缓冲 |
|
||
|
||
### 2.2 状态机
|
||
|
||
6 个状态,逐字节推进:
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> TAG
|
||
TAG --> CMD : 0xAA
|
||
TAG --> TAG : 其他字节(丢弃,重同步)
|
||
CMD --> LEN_H : 任意字节
|
||
LEN_H --> LEN_L : 任意字节
|
||
LEN_L --> DATA : 0 < plen <= 256
|
||
LEN_L --> CRC : plen == 0
|
||
LEN_L --> TAG : plen > 256 (失步复位)
|
||
DATA --> DATA : 未收满
|
||
DATA --> CRC : 收满 plen
|
||
CRC --> TAG : 校验通过→分发 / 校验错→丢弃
|
||
```
|
||
|
||
| 状态 | 含义 | 收到字节动作 | 迁移 |
|
||
|---|---|---|---|
|
||
| `TAG` | 等帧头 | 仅收 `0xAA`,否则丢弃 | →`CMD`(命中)/ 停留(未命中) |
|
||
| `CMD` | 等命令码 | 存入帧 | →`LEN_H` |
|
||
| `LEN_H` | 等长度高字节 | 存入帧 | →`LEN_L` |
|
||
| `LEN_L` | 等长度低字节 | 存入帧,算 `plen` | →`DATA` / `CRC` / `TAG`(越界) |
|
||
| `DATA` | 等数据段 | 存入帧,计数 | 收满 → `CRC` |
|
||
| `CRC` | 等校验字节 | 存入帧,校验 | →`TAG`(分发或丢弃) |
|
||
|
||
### 2.3 各状态处理要点
|
||
|
||
- **TAG**:只认 `TAG_CLIENT(0xAA)`。非帧头字节一律丢弃并停留在 `TAG`——这是**重同步**的核心:任意噪声后,下一个真帧头 `0xAA` 即恢复。
|
||
- **LEN_L**:`plen = (LEN_H<<8) | LEN_LO`。若 `plen > RM_MAX_RX_PAYLOAD(256)` 判为**长度损坏/失步**,立即复位回 `TAG`(防止按错误长度"读飞"吞掉后续帧);否则进入 `DATA`(`plen>0`)或 `CRC`(`plen==0`)。
|
||
- **DATA**:逐字节存入并按 `didx` 计数;`didx` 达到 `plen` 转 `CRC`。**数据段内的 `0xAA` 只当数据**(长度定界保证不误判为帧头)。
|
||
- **CRC**:存入校验字节后,重算 `XOR(数据段)` 与收到的 CRC 比较;**相等才回调 `remo_handle` 分发**,随后复位回 `TAG`(半帧/坏帧均丢弃)。
|
||
|
||
### 2.4 重同步与健壮性
|
||
|
||
| 机制 | 作用 |
|
||
|---|---|
|
||
| TAG 门控 | 非 `0xAA` 字节在 `TAG` 态丢弃,噪声后自动重新锁定帧头 |
|
||
| 长度越界复位 | `plen` 超上限 → 判损坏,丢弃半帧回 `TAG`,不被坏长度带偏 |
|
||
| CRC 校验 | 只有校验通过的整帧才分发,坏帧静默丢弃 |
|
||
| 长度定界 | 数据段内的 `0xAA` 不误判为帧头,避免假重同步 |
|
||
|
||
### 2.5 状态持久化(跨 recv)
|
||
|
||
解析器状态(`g_rm`:当前状态 + 帧缓冲 + `plen`/`didx`)是**全局常驻**,跨 `poll()` 调用保持——一帧可跨多次 `recv()` 收齐。**复位点**:新连接 `accept`、连接断开、长度越界失步。
|
||
|
||
### 2.6 缓冲设计
|
||
|
||
| 缓冲 | 大小 | 用途 |
|
||
|---|---|---|
|
||
| `g_rx[RM_RX_BUF]` | 64 B | `net_recv()` 暂存区(每次 poll 的到达字节,喂入后即弃) |
|
||
| `g_rm.frame[]` | 4+256+1=261 B | 帧重组缓冲(任一时刻只装一帧,分发后复位) |
|
||
| `RM_MAX_RX_PAYLOAD` | 256 | 接收数据段上限(客户端命令帧都很小,此为防损坏护栏 + 定缓冲尺寸) |
|
||
|
||
> 客户端→服务端 的命令帧数据段都很小(KEY=1、LCDMEM 请求=4、其余=0),256 上限有充足余量,同时把重组缓冲压到最小。
|
||
|
||
### 2.7 与轮询循环集成
|
||
|
||
```c
|
||
int n = net_recv(g_client, g_rx, sizeof(g_rx), NET_MSG_DONTWAIT); /* 非阻塞 */
|
||
if (n > 0) {
|
||
for (int i = 0; i < n; i++) {
|
||
rm_parser_feed(g_rx[i]); /* 逐字节喂入状态机 */
|
||
}
|
||
}
|
||
```
|
||
|
||
全程非阻塞,不拖慢 LVGL 刷新;`recv` 无数据(`WOULDBLOCK`)时直接返回。
|
||
|
||
### 2.8 边界情况
|
||
|
||
| 场景 | 状态机行为 |
|
||
|---|---|
|
||
| 一帧拆到多次 recv | 状态跨 recv 保持,收齐后分发 |
|
||
| 一次 recv 含多帧 | 顺序解析、逐帧分发 |
|
||
| 前导噪声/垃圾字节 | `TAG` 态丢弃,直到下一个 `0xAA` |
|
||
| 数据段内含 `0xAA` | 当数据处理,不误判帧头 |
|
||
| 长度字段损坏(超大) | 判失步,复位回 `TAG` 重新扫描 |
|
||
| CRC 校验失败 | 整帧丢弃,复位回 `TAG` |
|
||
| `LEN=0` 的空数据段 | 跳过 `DATA` 态,期望 CRC=0 |
|
||
|
||
---
|
||
|
||
## 3. 双端共用实现
|
||
|
||
协议两端一致(线格式相同,可互通),且**服务端实现已统一**:MCU 与模拟器编译**同一份**
|
||
`Drivers/BSP/UI/ui_net_sink.c`(§2 逐字节状态机 + `remo_handle` 分发)。平台差异被隔离到两处 backend:
|
||
|
||
| 关注点 | MCU(Keil / CH395F) | 模拟器(CMake / Winsock) | 说明 |
|
||
|---|---|---|---|
|
||
| 帧收发 | `net_socket.c`(CH395F 中断 + 消息队列) | `net_socket_sim.c`(Winsock 非阻塞) | 同一套 `net_*` BSD API(`socket`/`bind`/`listen`/`accept`/`recv`/`send`/`close`),仅底层不同;模拟器以 `NET_NO_OS` 剥离 FreeRTOS 部分 |
|
||
| LCDMEM 取帧 | `ui_net_sink_backend_mcu.c` → `ui_port_get_framebuffer()` | `ui_net_sink_backend_sim.c` → `sim_port_get_screen_mono()` | 同一 `ui_net_sink_get_lcdmem()` 接口,各端提供 1bpp 帧缓冲来源 |
|
||
| KEY 注入 | `ui_net_sink_backend_mcu.c` → `ui_port_queue_key()` | `ui_net_sink_backend_sim.c` → `lv_group_send_data()` | 同一 `ui_net_sink_inject_key()` 接口,各端注入 UI |
|
||
|
||
要点:
|
||
|
||
1. **单一解析实现**:两端均用 §2 的逐字节状态机(`rm_parser_feed`),对"帧跨 recv / 粘包 / 前导噪声"统一具备重同步能力,消除旧 MCU"整块解析"的脆弱性。
|
||
2. **最小帧长判据统一**:`remo_handle` 以 `n < 5` 拦截(最短合法帧 5 字节),两端一致——旧 MCU `n < 6` 会丢 5 字节 KEEPLIVE/INIT 帧的隐患已随统一消除。
|
||
3. **断线重连统一**:`ui_net_sink_poll` 在客户端断开后用 `net_get_sock(g_listen) == NULL` 判监听是否失效(MCU 单连接模型下监听随客户端一起失效),失效即 `bind_listen()` 重建——两端一致,并修复了 MCU 单连接模型断线后无法重连的隐患。(术语统一补注:本文「远程显示」与「远程画面推送」同义,均指 RemoDispBus 监听 7003 的 UI 帧推送;代码标识 RemoDispBus 不随文案改名而变动。)
|
||
4. **协议兼容**:线格式与命令语义不变,任一端都能与 DTU-RemoteLCD 参考客户端互通;双端行为一致已由 e2e 回归(`simulator/tests/check_remo_sink.py`,0/25600 位差异)与双端构建(Keil + CMake 均 0 错误 0 警告)验证。
|
||
5. **驱动者是宿主,不是协议自己**:`ui_net_sink_poll()` 由宿主周期调用——MCU 端目前 = **uiTask 主循环 10 ms**,模拟器 = `main.c` 主循环。协议本身不含节拍、不阻塞。
|
||
|
||
> ✅ **门控已落地(2026-10-10)**:`ui_net_sink_poll()` 现在**先查后动**——`net_can_accept()` /
|
||
> `net_can_read()` 纯 RAM 读控制块判"有没有活",无活直接返回,不再每 10 ms 发一次
|
||
> `net_accept`(原 100 msg/s 队列压力,与 FTP 抢深度 8 的队列)。门控判"无数据"时仍
|
||
> **每 32 轮无条件 recv 一次**兜底(`readable` 只由 RECV_OK 中断置位,事件未收到时纯门控
|
||
> 会把数据永久藏住)。模拟器后端恒返回"就绪",行为逐位不变。
|
||
>
|
||
> ⚠ **残留暴露(已知,刻意不处理)**:门控只消除"无谓的往返",消除不了"往返本身无上界"——
|
||
> 客户端在连且 netTask 拥塞时,3204 B 的 LCDMEM 应答仍可能让宿主任务等最长 ≈4 s。
|
||
> "把推流搬出 uiTask(独立任务 + 帧 seqlock)"已完整评估并**决定不做**:实测刷新最坏周期
|
||
> (`uip` ≈61 ms)是 **LVGL 帧周期地板**、与网络无关,搬任务不会让刷新变快;成本却含
|
||
> **必改 `.ioc`** 的重生成与全套回归,收益只剩极端并发下的观感隔离。若日后它成为实际问题,
|
||
> 正确方向是先治 netTask 排队公平性(见 `../网络/Net层阻塞模型架构设计.md`),不是把 UI 搬走。
|
||
> 观测手段:`[PROF]` 的 `uis`(往返最坏)与 `uisn`(此刻仍在推流段内多久)——
|
||
> **`uisn` 持续非 0 就是这一格真发生的铁证**;字段语义与判读见
|
||
> [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md) §11.4。
|
||
|
||
---
|
||
|
||
## 4. 关键常量与文件索引
|
||
|
||
| 常量 / 文件 | 值 / 职责 |
|
||
|---|---|
|
||
| `UI_NET_SINK_PORT` | `7003`(`ui_net_sink.h`,须与客户端默认端口一致) |
|
||
| `UI_REMO_TAG_CLIENT` / `UI_REMO_TAG_DEVICE` | `0xAA` / `0xBB` |
|
||
| `UI_REMO_CMD_KEEP / INIT / KEY / LCDMEM` | `0` / `1` / `2` / `3` |
|
||
| `RM_MAX_RX_PAYLOAD` | `256`(接收数据段上限,防长度字段损坏引发失步) |
|
||
| `RM_RX_BUF` | `64`(`net_recv` 暂存区) |
|
||
| `Drivers/BSP/UI/ui_net_sink.c` | **双端共用服务端**(§2 逐字节状态机;MCU/CH395F 与模拟器/Winsock 共同编译) |
|
||
| `Drivers/BSP/UI/ui_net_sink.h` | 共享头:协议常量 + `ui_net_sink_start`/`poll` 声明 |
|
||
| `Drivers/BSP/UI/ui_net_sink_backend.h` | 平台 backend 接口:`ui_net_sink_get_lcdmem()` / `ui_net_sink_inject_key()` |
|
||
| `Drivers/BSP/UI/ui_net_sink_backend_mcu.c` | MCU 实现:`ui_port_get_framebuffer()`(LCDMEM)/ `ui_port_queue_key()`(KEY) |
|
||
| `simulator/sim/ui_net_sink_backend_sim.c` | 模拟器实现:`sim_port_get_screen_mono()`(LCDMEM)/ `lv_group_send_data()`(KEY) |
|
||
| `simulator/sim/net_socket_sim.c` | 模拟器网络后端(Winsock 实现 `net_*` BSD API,`NET_NO_OS` 剥离 FreeRTOS) |
|
||
| `Drivers/BSP/NET/net_socket.c` | MCU 网络后端(CH395F 中断 + 消息队列实现 `net_*`) |
|
||
| DTU-RemoteLCD `remo_disp_server.py` | 参考客户端(Flask + WebSocket,协议编解码) |
|
||
|
||
**对外 API**(`ui_net_sink.h`):`ui_net_sink_start(port)` / `ui_net_sink_poll()`(无 `stop`;监听随网络代次变化 / 客户端断线自动重建)。
|
||
|
||
---
|
||
|
||
## 5. 陷阱
|
||
|
||
1. **CRC 只覆盖数据段**:不含 TAG/CMD/LEN;`LEN=0` 时 CRC 恒为 `0`。收发两端必须一致,否则校验必败。
|
||
2. **TAG 是方向标识**:服务端只接受 `0xAA`(客户端帧);收到 `0xBB` 视为非法(状态机在 `TAG` 态丢弃)。
|
||
3. **大端字节序**:`LEN` 及 payload 内 u16/u32 均大端;MCU(小端核)收发须显式转换(`put_u16`/`put_u32`)。
|
||
4. **位图 MSB 优先**:`bit_i = 7 - (x%8)`;搞反则画面左右镜像/错位。
|
||
5. **连接后不主动发 INIT**:避免污染客户端首帧读取(客户端按 LCDMEM 轮询或显式 INIT 获取)。
|
||
6. **平台须齐备两个后端**:`ui_net_sink.c` 依赖 `net_*` BSD Socket API 与 `ui_net_sink_backend`(`ui_net_sink_get_lcdmem` / `ui_net_sink_inject_key`);接入新平台时二者缺一不可(MCU:`net_socket.c` + `ui_net_sink_backend_mcu.c`;模拟器:`net_socket_sim.c` + `ui_net_sink_backend_sim.c`)。
|
||
7. **单连接**:`backlog=1`,同一时刻仅一个客户端;新连接需旧连接先断开。
|
||
|
||
---
|
||
|
||
## 6. 相关文档
|
||
|
||
| 主题 | 文档 |
|
||
|---|---|
|
||
| 网口配置(含远程显示服务自愈) | `docs/子系统设计/网络配置说明.md` |
|
||
| 项目分层架构 | `docs/架构设计/项目架构总览.md` |
|
||
| 启动顺序 / 就绪屏障 | `docs/架构设计/项目启动顺序管理.md` |
|
||
| UI 设计架构 | `docs/UI设计/UI_设计架构.md` |
|
||
| LVGL 模拟器部署 | `docs/UI设计/LVGL模拟器部署与使用说明.md` |
|
||
| CH395F 网络测试 | `docs/测试说明/板级测试/CH395F_Test_Guide.md` |
|