Files
STM32F4-Base/docs/UI_图形界面实施规划.md
T
2026-08-30 13:30:59 +08:00

200 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UI 图形界面实施规划(160×160 单色 · 树形菜单 · 离屏渲染 + 网络推流)
> 本文档用于在我(实现方)与你(需求方)之间对齐"如何规划与实施 UI"。
> 当前阶段:**需求澄清 + 方案设计**,尚未写任何 UI 源码。
> ✅ = 已根据你的最近答复冻结;【待确认】= 仍需你拍板;**(建议)** = 我的默认推荐。
---
## 1. 背景与现状
| 项 | 现状 |
|---|---|
| MCU | STM32F407ZGTx,168MHz,192KB RAM,1MB Flash |
| 工具链 | 仅 Keil MDK-ARM v5 (ARMCC) |
| OS | FreeRTOS (CMSIS-RTOS V2),已有 `defaultTask/netTask/ftpTask` |
| 网络 | CH395F 以太网(SPI2),单连接 socket 模型(Socket0 复用监听/数据) |
| 显示 | **无真实屏**:UI 在内存中离屏渲染,按请求经网络返回帧 |
| 目标屏规格 | ✅ **160×160 像素,单色(1bpp,黑白)**,"少占用 RAM" |
| 参考项目 | `D:\Code\DTU 程序\DTU-HMI`(自建 MVP 菜单,过于复杂,改用成熟方案复现菜单) |
| 参考协议 | ✅ **RemoDispBus**(`DTU-HMI/src/remoteDisplay.c`,帧格式见第 3.4 节) |
| 源码规范 | 中文注释;新增 .c/.h 需手动加入 Keil 工程;禁止整文件文本重写 |
### 关键推论
- 我们目标 **1bpp**:整帧 = 160×160÷8 = **3200 字节(3.1KB)**,直接整帧作绘制缓冲。git
- 参考项目 `key.h` 已有 `g_tRemoteKey`(`bUseRkey` 远程按键)→ ✅ 菜单导航由远端发键值驱动(双向交互已定)。
- 参考项目 `def.h`:✅ 中文 12px、行高 14px、可视约 11 行。
---
## 2. 复现目标(已明确)
1. MCU 内存中渲染 160×160、1bpp 画面。
2. ✅ **菜单驱动、多页面可切换**:复现 DTU-HMI 树形菜单(第 3.3 节),菜单进入不同页。
3. ✅ **不含折线/柱状图**:内容页以文字 + 数值列表 + 简单指示为主;YC(遥测)页数值列表,无曲线。
4. ✅ **A 复现范围**:仅复现**菜单树结构与导航**;所有内容页先**占位**(含装置信息/遥测也先占位)。
5. ✅ **双向交互(pull 模型)**:远端按 RemoDispBus 发 `CMD_LCDMEM` 拉帧、`CMD_KEY` 下发键;MCU 渲染后应答帧。
6. 全程 RAM 占用尽量低,不挤占现有 netTask/ftp/FatFs/dhara。
---
## 3. 参考项目与协议
### 3.1 输入键码(来自 `DTU-HMI/src/Drv/key.h`)
| 键 | 键值 | LVGL 映射 |
|---|---|---|
| 上 KEY_U | 0x02 | LV_KEY_UP |
| 下 KEY_D | 0x40 | LV_KEY_DOWN |
| 左 KEY_L | 0x10 | LV_KEY_LEFT |
| 右 KEY_R | 0x08 | LV_KEY_RIGHT |
| 确认 KEY_ENT | 0x20 | LV_KEY_ENTER |
| 返回 KEY_ESC | 0x01 | LV_KEY_ESC |
### 3.2 视觉规格(来自 `def.h`)
- ✅ 160×160,1bpp;中文 12px,行高 14px,可视约 11 行。
- ✅ 菜单视觉:采用 LVGL `lv_menu` 整屏列表 + 返回栈(小屏最清晰),不复原级联框。
### 3.3 菜单树(取自 `model.c` 的 `menuTab[]`,仅复现结构与导航)
```
[0] 主界面 (root)
├[1] 装置信息 → 占位页
├[1] 实时数据
│ ├[2] 交流量 / [2] 直流量 / [2] 遥信量 → 占位页(YC类)
├[1] 参数定值
│ ├[2] 整定 → [3]参数/定值/控制字/软压板 → 占位
│ └[2] 查看 → [3]参数/定值/控制字/软压板 → 占位
├[1] 三遥设置 → [2]遥测死区/系数/遥信类型/防抖/双点遥信 → 占位
├[1] 装置维护 → [2]时钟设置/强制复归/手动录波/清除记录 → 占位
├[1] 通讯参数 → [2]通讯设置/网口设置/SNTP设置 → 占位
└[1] 记录查询 → [2]SOE/事故/操作/保护告警/保护启动/遥控/自检/运行记录/运行报告 → 占位
[0] 厂家设置 (root, 密码 CN_COP_PWD)
├[1] 元件配置 / [1]恢复默认([2]全部/默认参数/定值/软压板/元件配置) / [1]交流显示
├[1] 装置调试 → [2]虚拟遥信/交流虚遥测/直流虚遥测/电度虚遥测/动作虚事件/告警虚事件/动作出口/信号出口 → 占位
└[1] 版本信息 → 占位
```
> 叶子(含"装置信息""实时数据"等)统一进入**占位页**(显示菜单路径 + "功能未实现"),仅验证导航与返回栈。
### 3.4 RemoDispBus 协议(✅ E 与参考项目一致,来自 `remoteDisplay.c`)
**帧格式(请求/应答通用)**:`[TAG][cmd][len_hi][len_lo][data...][crc]`
- `TAG`:客户端→设备 `0xAA`;设备→客户端 `0xBB`
- `cmd`:`0` KEEPLIVE,`1` INIT,`2` KEY,`3` LCDMEM
- `len`:data 区长度,**大端 16 位**
- `crc`:data 区逐字节**异或**取低 8 位
- 整帧长度 = `4 + data_len + 1`
**命令语义**
| cmd | 方向 | data | 说明 |
|---|---|---|---|
| INIT(1) | 设备应答 | `[W_hi,W_lo,H_hi,H_lo, memsize(4B)]` = 8B | 回报分辨率与显存大小 |
| LCDMEM(3) | 客户端请求 | `4B 大端起始偏移` | 请求从 offset 开始的显存 |
| LCDMEM(3) | 设备应答 | `[4B 偏移][显存数据]` | 回当前帧缓冲内容 |
| KEY(2) | 客户端请求 | `[键值]`(见 3.1) | 设备写入按键队列驱动导航 |
| KEEPLIVE(0) | 双向 | 空 | 保活 ACK |
**我们的落地适配**
- ✅ **传输层完全照搬** RemoDispBus(TAG/cmd/len/crc、四命令)。
- ✅ **1bpp 内容**:帧缓冲 3200 字节。INIT 回报 `memsize=3200`;远端客户端据 `bpp = memsize*8/(W*H)` 自行推导为 1bpp(=1),无需改协议即可兼容。
- ✅ **pull 模型**:设备持续用 `lv_timer_handler()` 刷新帧缓冲;收到 `CMD_LCDMEM` 即回当前 3200 字节(通常 offset=0 全帧)。
- ⚠️ **位序约定**:1bpp 行优先、每字节 **MSB-first**(pixel(x,y) 存于 `byte(y*W/8 + x/8)` 的 bit `7-(x&7)`)。LVGL `LV_COLOR_FORMAT_L1` 的位序若与此不同,在 `ui_net_sink` 发送前做位重排(实现期用一块临时缓冲转换,或在 `lv_conf` 选匹配格式)。
- ⚠️ **与旧客户端兼容说明**:参考项目旧 `remo_disp_server.py` 期望 8bpp(25600B)。若你要求**字节级完全兼容旧客户端**(不改客户端),则需改为 8bpp(25600B)——但会多占 ~22KB RAM,与"少占用 RAM"冲突。默认按 1bpp(3200B) 实施;如需兼容旧客户端请告知(见第 8 节备注)。
---
## 4. 总体架构方案(建议)
```
┌──────────────────────────────────────────────────────────┐
│ 应用数据层 (现有: ADC/RTC/网络/NAND/LED,本阶段仅占位不接) │
└───────────────┬──────────────────────────────────────────┘
│ (本阶段不接数据;仅菜单导航)
┌───────────────▼──────────────────────────────────────────┐
│ UI 引擎 (LVGL v9, LV_COLOR_DEPTH=1, 关动画) │
│ - lv_menu 主菜单(复现 3.3 树) + 占位内容页 │
│ - lv_indev(KEYPAD) 读远端键队列 → 导航/切换 │
│ - lv_timer_handler() 周期调用 → 渲染到 3200B 帧缓冲 │
└───────────────┬──────────────────────────────────────────┘
│ flush_cb → 写入 fbuf[3200] (CCM RAM)
┌───────────────▼──────────────────────────────────────────┐
│ 推流/应答适配层 (Drivers/BSP/UI/ui_net_sink.c) │
│ - 解析 RemoDispBus 请求(INIT/LCDMEM/KEY/KEEPLIVE) │
│ - LCDMEM → 回当前 fbuf;KEY → 入键队列;INIT → 报 3200 │
└───────────────┬──────────────────────────────────────────┘
│ net_socket_send() / recv()
┌───────────────▼──────────────────────────────────────────┐
│ 网络层 (CH395F,✅ 新开专用 TCP 监听 socket,端口 7070) │
└──────────────────────────────────────────────────────────┘
```
### 模块落点(贴合现有 BSP 风格)
| 新增文件 | 职责 |
|---|---|
| `Lib/lvgl/` | LVGL v9 源码(放入后**手动加进 Keil 工程**) |
| `Drivers/BSP/UI/lv_conf.h` | 1bpp、关动画/日志、字号、键盘 indev 使能 |
| `Drivers/BSP/UI/ui_port.c/.h` | `lv_display` + `flush_cb`(写 fbuf);`lv_indev` 读键队列 |
| `Drivers/BSP/UI/ui_menu.c/.h` | 复现 3.3 菜单树(lv_menu 构建) |
| `Drivers/BSP/UI/ui_page_placeholder.c` | 占位内容页(显示路径+"未实现") |
| `Drivers/BSP/UI/ui_net_sink.c/.h` | RemoDispBus 解析/应答(INIT/LCDMEM/KEY/KEEPLIVE) |
| `App/task/ui_task.c/.h` | 独立 FreeRTOS 任务:跑 `lv_timer_handler` + 网络收发 |
> **不要**把 UI 渲染塞进 `netTask` 的 10ms 轮询——渲染 CPU 密集。新建 `ui_task`(栈 1024–1536,1bpp 下很轻),优先级低于 `netTask`。网络收发走现有 `net_socket` 串行消息队列。
---
## 5. 需讨论条件 — 当前状态
| 项 | 状态 | 结论 |
|---|---|---|
| A 复现范围 | ✅ 已定 | 仅菜单树+导航;所有内容页占位 |
| B 菜单视觉 | ✅ 已定 | lv_menu 整屏列表+返回栈 |
| C socket 策略 | ✅ 已定 | 新开专用 TCP(端口 7070) |
| D 刷新/动画 | ✅ 已定 | pull 模型(按 LCDMEM 应答);关过渡动画 |
| E 帧编码 | ✅ 已定 | RemoDispBus(见 3.4);1bpp 内容,memsize=3200 |
| F 单色定义 | ✅ 默认 | 纯 1bpp,无灰阶抖动 |
| G 字体/中文 | ✅ 默认 | 中文 12px;仅收录菜单/占位用到的字(fontconverter 造 1bpp 字库) |
| H 旋转/扫描 | ✅ 已定 | 默认不旋转,行优先 |
| I RAM/Flash | ✅ 按建议 | 帧缓冲+draw buffer 放 **CCM RAM**;集成时实测空闲余量 |
| J 库选型 | ✅ 默认 | LVGL v9(LV_COLOR_DEPTH=1) |
| K 数据通道 | ⏸ 本期不涉及 | 占位阶段业务数据不接 UI;后续事件/共享结构体 |
| L 持久化 | ✅ 默认 | 不需;每次上电回根菜单 |
| M 异常 | ✅ 建议 | 客户端掉线暂停应答,不影响主业务;ui_task 软看门狗;键队列溢出丢 oldest |
---
## 6. 实施阶段分解(建议顺序)
1. **阶段 0 · 决策冻结**:本文件第 5 节已冻结(仅 K/L/M 默认)。
2. **阶段 1 · 骨架接入**:导入 LVGL v9 到 Keil;`lv_conf.h`(1bpp/关动画/字号);`ui_task` 渲染测试图;帧缓冲在 CCM。
3. **阶段 2 · RemoDispBus 应答**:`ui_net_sink` 实现 INIT/LCDMEM/KEY/KEEPLIVE;新开 CH395F TCP(7070);远端按协议拉到 3200B 帧。
4. **阶段 3 · 菜单复现**:`lv_menu` 复现 3.3 树(中文 12px 字库);远端 `CMD_KEY` 驱动上下/确认/返回导航。
5. **阶段 4 · 占位页**:叶子进占位页(显示路径+"未实现"),验证返回栈与多级导航。
6. **阶段 5 · 健壮性**:掉线恢复、软看门狗、RAM 余量复测、`@build` 0 警告验证。
7. (后续)阶段 6 · 真实数据联动:按需把 ADC/RTC/网络/NAND/LED 接入 AppInfo/YC 等页。
---
## 7. 风险与约束
- **RAM 最紧**:3200B 帧缓冲 + LVGL 运行时(约 4–8KB)+ draw buffer(整帧即可,同 3200B 或更小)放 CCM;集成时实测 `netTask/ftp/FatFs/dhara` 后空闲堆/栈。
- **ARMCC 兼容**:LVGL 纯 C99 可编译;`lv_conf.h` 关浮点/动画等单色屏用不到特性。
- **不阻塞 netTask**:UI 渲染/收发在独立任务,走 `net_socket` 串行队列。
- **位序对齐**:发送前确保 1bpp 位序(MSB-first 行优先)与客户端一致(见 3.4 ⚠️)。
- **编码安全**:含中文,仅用 Edit/Write 工具改,绝不用 PowerShell 文本重写。
---
## 8. 决策待办清单
- [x] A 复现范围(仅菜单树+导航,其余占位)
- [x] B 菜单视觉(lv_menu 整屏列表)
- [x] C UI 通道(新开专用 TCP 7070)
- [x] D 刷新/动画(pull 应答,关动画)
- [x] E 帧编码(RemoDispBus,1bpp memsize=3200)
- [x] F 单色(纯 1bpp,无抖动)
- [x] G 字号(中文 12px)
- [x] H 朝向(不旋转)
- [x] I RAM(CCM 帧缓冲,集成实测)
- [x] J 库(LVGL v9 1bpp)
- [x] L 持久化(不需)
- [x] M 异常(掉线暂停+软看门狗)