# 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)**,直接整帧作绘制缓冲。 - 参考项目 `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 异常(掉线暂停+软看门狗) ### ⚠️ 仍需你最后确认的一点(影响 RAM 与客户端兼容) - **像素深度取舍**:默认按 **1bpp(3200B)** 实施(守"少占用 RAM"),远端客户端据 INIT 的 `memsize` 推导为 1bpp。若你坚持**字节级兼容旧 `remo_disp_server.py`(8bpp/25600B)**,请告知——那将多占约 22KB RAM,与本目标冲突,需你权衡。 > 上述像素深度确认后(默认 1bpp 即无需回复),我将把第 4 节架构落成代码骨架(阶段 1–2),并开始 `@build` 验证。