Files
DTU-HMI/docs/系统架构设计文档.md

369 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.
# DTU-HMI 系统架构设计文档
## 1. 文档目的
本文档用于描述 `DTU-HMI` 工程的整体架构、核心模块职责、关键数据流、线程与通信模型、构建与测试体系,作为后续开发、联调、测试与维护的统一基线。
## 2. 系统概述
`DTU-HMI` 是一个基于纯 C 实现的 PC 端 HMI 菜单逻辑模拟系统,目标是复现现场 DTU 设备的人机界面行为。
系统支持本地菜单交互与远程显示协议RemoDispBus通信主要包含
- 菜单树构建与路由(多级菜单、同级循环导航)
- LCD 显存与绘制160x160 单色像素缓冲)
- 按键输入(本地/远程按键注入)
- TCP 服务(远程显示数据交互)
- 跨平台线程与网络适配Windows/Linux
## 3. 架构目标与设计原则
- **可移植性**:通过 `tcp.c``thread_utils.c` 封装平台差异。
- **可维护性**:按模块职责划分(菜单/显示/网络/线程/输入)。
- **可测试性**:优先抽取并覆盖纯逻辑函数,逐步推进集成测试。
- **低耦合高内聚**:上层业务通过明确接口调用下层能力。
## 4. 系统分层架构
```mermaid
block-beta
columns 1
a["菜单"]
block:textdraw
columns 2
a1["文字显示"] a2["图像显示"]
end
block:draw
columns 2
block:text_group
columns 2
block:left_text
columns 1
b["Lcd_ShowStr"]
block:showstr
columns 2
s1["Lcd_Pub_Ascii"]
s2["Lcd_Pub_UTF8"]
end
end
b1["utf8_next"]
end
block:draw_group
columns 4
c1["Lcd_LineH"]
c2["Lcd_LineV"]
c3["Lcd_Invert"]
c4["Lcd_FillRect"]
end
end
block:lcd
lcda["Lcd_Init"] lcdb["Lcd_SetPixel"] lcdc["Lcd_GetPixel"]
end
```
### 4.1 应用层
- 文件:`src/main.c`
- 职责:
- 系统初始化菜单、按键、TCP 线程)
- 主循环调度(菜单路由、周期显示刷新)
- 生命周期管理(线程退出、网络清理)
### 4.2 菜单业务层
- 文件:`src/Drv/menu/``src/Drv/menu/app/menu.h`
- 职责:
- 采用 MVP 分层组织菜单模块(`Model / Presenter / View`
- 基于静态菜单模型构建运行时菜单树
- 处理按键驱动的菜单状态迁移与路径重建
- 执行菜单显示坐标计算与多级菜单渲染调度
### 4.3 显示层
- 文件:`src/Drv/lcd/lcd.c``src/Drv/lcd/lcd_draw.c``src/Drv/lcd/lcd_text.c``src/Drv/lcd/text_codec.c``src/Drv/lcd/ascii.c``src/Drv/menu/model/display.c`
- 职责:
- 管理 LCD 显存 `g_tCVsr` 与像素绘制
- 提供 ASCII/UTF-8 字符显示能力
- 提供静态菜单模型定义 `g_tMenuModelTab`
### 4.4 输入层
- 文件:`src/Drv/key.c``src/Drv/key.h`
- 职责:
- 提供按键读写状态控制(消费式读取)
- 接收远程模块写入的按键事件并供菜单模块读取
### 4.5 远程显示通信层
- 文件:`src/remoteDisplay.c``src/remoteDisplay.h`
- 职责:
- 实现 RemoDispBus 协议解析与回复
- 提供 TCP 服务器线程入口与启动逻辑
- 处理保活、初始化、按键下发、显存上传等命令
### 4.6 平台适配层
- 文件:`src/TCP/tcp.c``src/thread_utils.c`
- 职责:
- 提供跨平台 socket 与线程封装
- 隔离 Windows/Linux API 差异
## 5. 核心数据结构设计
### 5.1 菜单模型与菜单树
- 静态菜单模型:`tagMenuModel`(定义于 `src/Drv/menu/model/display.h`,数据在 `src/Drv/menu/model/display.c`
- 运行时菜单项:`tagMenuItem`(定义于 `src/Drv/menu/common/menu_item_types.h`,实例由 `src/Drv/menu/app/menu.c` 管理)
- 运行时控制:`tagMenuCtrl``tagDspCtrl`(定义于 `src/Drv/menu/common/menu_state_types.h`,实例为 `app/menu.c` 内部私有)
关键关系:
- `ptHigher` / `ptLower`:父子层级关系
- `ptBefore` / `ptBehind`:同级双向关系(首尾成环)
- `ptRoute[]`当前路径缓存0~3 级)
### 5.2 显示控制结构
- `tagScreenControl g_tCVsr`
- 显存缓冲 `pwbyLCDMemory`
- 前景/背景色
- ASCII 与汉字字体参数
### 5.3 远程按键结构
- `tagRKeyCtrl g_tRemoteKey`
- `byKeyValid`:是否有新按键
- `byKeyValue`:按键值
- `bUseRkey`:远程按键开关(当前实现中初始化为启用)
## 6. 关键业务流程
### 6.1 主循环流程
```text
[系统初始化]
|
v
[MenuApp_PollInput]
|
v
[Sleep 20ms]
|
v
[计数器累加]
|
v
[是否到刷新周期?] --否--> [MenuApp_PollInput]
|
+--是--> [MenuApp_Render] --> [MenuApp_PollInput]
```
### 6.2 菜单交互流程
- 输入来源:`Key_Read()`(含远程写入按键)
- 行为:
- 上/下:同级循环移动
- 左/ESC回退上级或退回主层
- 右/确认:进入子级或执行叶子回调
- 渲染:
- `MenuApp_Render` 调用 Presenter/View根据路径进行增量刷新或全量刷新
### 6.4 当前 Menu 详细设计MVP
#### 6.4.1 模块拆分
- `src/Drv/menu/app/menu.c`
- 菜单应用 Facade`MenuApp_*` 对外接口)
- 持有运行时私有状态(`s_menuCtrl``s_dspCtrl``s_menuItems`
- `src/Drv/menu/presenter/menu_presenter.c`
- 控制调度中枢:处理输入事件、调用导航器、触发重建路径与刷新
- `src/Drv/menu/model/menu_model.c`
- 模型初始化:树构建、菜单名修饰、初始状态建立
- `src/Drv/menu/view/menu_view.c`
- 纯渲染:顶部栏、多级菜单框、高亮反显、全量/增量刷新策略
- `src/Drv/menu/presenter/menu_navigator.c`
- 纯导航状态机:按键到 `MenuNavResult`(是否刷新、是否跳过渲染)
- `src/Drv/menu/view/menu_layout.c`
- 菜单布局计算:宽度统计、层级矩形定位、越界回退策略
- `src/Drv/menu/model/display.c`
- 静态菜单模型表 `g_tMenuModelTab`(业务菜单定义)
#### 6.4.2 运行时调用时序
```text
MenuApp_Init
-> MenuPresenter_Setup
-> MenuPresenter_InitModel
-> MenuModel_Bootstrap
-> MenuTree_0LevelNumCal
-> MenuTree_MainCreate
-> MenuView_Layout
-> MenuLayout_PositionCal
主循环:
MenuApp_PollInput
-> Key_Read
-> MenuApp_HandleInput(key)
-> MenuPresenter_HandleInput
-> MenuNavigator_ProcessKey
-> (needRefresh) MenuNavigator_RebuildRoute
-> MenuPresenter_Refresh
-> MenuView_RenderByState
```
#### 6.4.3 关键状态数据
- `tagMenuItem`
- 菜单节点实体,包含树关系(`ptHigher/ptLower/ptBefore/ptBehind`)与显示矩形
- `tagMenuCtrl`
- 导航核心状态(`ptCurrent``ptRoute[4]``ptCurBak``pt0Level` 等)
- `tagDspCtrl`
- 显示控制状态(当前主要使用首帧标记 `bFirst`
- `MenuNavState / MenuNavResult`
- Presenter 与 Navigator 之间的状态快照与处理结果
#### 6.4.4 对外数据接口(当前基线)
- `void MenuApp_Init(void)`
- 初始化菜单应用Presenter/Model/View
- `void MenuApp_HandleInput(uint8_t keyVal)`
- 注入输入事件并驱动导航状态变更
- `void MenuApp_PollInput(void)`
-`Key_Read()` 读取按键并转发到 `MenuApp_HandleInput`
- `void MenuApp_Render(void)`
- 主动触发一次渲染(用于周期刷新或外部强制重绘)
- `const tagMenuItem *MenuApp_GetMenuItems(uint16_t *count)`
- 只读导出当前菜单项数组(调试/测试使用)
#### 6.4.5 内部接口边界(模块间)
- Model -> View
- 仅通过控制状态与布局结果共享,不直接调用绘制原语
- Presenter -> Model
- 仅在初始化阶段触发 `MenuModel_Bootstrap`
- Presenter -> View
- 通过 `MenuPresenter_Refresh` 驱动渲染,不暴露底层 LCD 细节
- Presenter -> Navigator
- 通过 `MenuNavState` 快照交互,避免导航器直接操作外部全局变量
### 6.3 远程显示协议流程
```text
[Accept客户端]
|
v
[接收缓冲区累积]
|
v
[parse_frame 校验解析]
|
+--成功--------> [按 cmd 分发] --> [send_reply 回包] --> [接收缓冲区累积]
|
+--失败/不完整--> [接收缓冲区累积]
```
命令语义RemoDispBus
- `CMD_INIT`:返回 LCD 宽高与显存尺寸
- `CMD_LCDMEM`:返回显存数据(支持起始地址)
- `CMD_KEY`:注入远程按键到 `g_tRemoteKey`
- `CMD_KEEPLIVE`:保活响应
## 7. 并发与线程模型
- 主线程:
- 负责菜单路由与本地显示刷新
- TCP 服务器线程:
- 监听连接、解析协议、处理远程请求
共享状态:
- `g_tCVsr.pwbyLCDMemory`(远程读取 + 本地写入)
- `g_tRemoteKey`(远程写入 + 菜单读取)
当前实现未使用锁机制,依赖业务访问模式降低冲突风险。
后续若并发复杂度提升,建议引入细粒度互斥或无锁缓冲策略。
## 8. 构建与运行架构
- 构建系统CMake`C_STANDARD 99`
- 可执行目标:`DTU-HMI`
- 平台链接:
- Windows`ws2_32`
- Linux/macOS`pthread`
- 可选调试:`ENABLE_DEBUG=ON` 自动定义 `DEBUG`
## 9. 测试架构
测试目录:`tests/`
- 框架:`ctest + 自定义断言宏`
- 分层策略:
- P0纯逻辑单元测试协议解析、UTF-8 解析、字库查找)
- P1状态/计算单测按键、菜单、LCD 基础像素操作)
- P2集成测试TCP 回环)
建议执行命令:
```bash
cmake -S . -B build
cmake --build build
ctest --test-dir build -C Debug --output-on-failure
```
## 10. 模块依赖关系(代码级)
- `main.c` 依赖:`menu``key``remoteDisplay``tcp``thread_utils`
- `src/Drv/menu/app/menu.c` 依赖:`presenter/menu_presenter``model/display``key``lcd`
- `src/Drv/menu/presenter/menu_presenter.c` 依赖:`model/menu_model``view/menu_view``presenter/menu_navigator`
- `src/Drv/menu/view/menu_view.c` 依赖:`model/menu_layout``view/menu_render_port``lcd`
- `remoteDisplay.c` 依赖:`lcd``key``tcp``thread_utils`
- `lcd.c` 依赖:`ascii`
- `src/Drv/menu/model/display.c` 提供:静态菜单表(被 menu 模块使用)
## 11. 已知风险与改进建议
- **并发一致性风险**:远程线程与主线程共享状态无锁访问。
- 建议:为显存快照与按键事件引入互斥保护或双缓冲。
- **协议缓冲鲁棒性**:当前异常数据采用清空缓冲策略,存在丢包窗口。
- 建议:增加更精细的帧边界恢复策略与统计日志。
- **可测试性边界**:部分逻辑仍与全局状态耦合较深。
- 建议:逐步引入接口注入(如 `TcpOps``delay_ms`)降低耦合。
## 12. 版本与维护
- 文档版本v1.0
- 适配代码基线:当前 `DTU-HMI` 仓库主干实现
- 维护建议:
- 每次新增模块或调整主流程时同步更新本文档
- 测试策略更新需同步维护“第 9 章 测试架构”
## 13. 菜单重构故障复盘2026-03
### 13.1 现象
- 菜单分层重构后,程序在启动阶段(当前入口为 `MenuApp_Init`)出现访问冲突,表现为“运行即崩溃”。
### 13.2 根因
- 根因位于菜单树构建模块 `MenuTree_MainCreate`
- 在“层级回退(`byCurClass > byNextClass`)”分支中,缺少对中间层级链表的及时收口(首尾成环)处理。
- 后续 `Menu_PositionCal` 在遍历同级链表时访问到异常节点,导致崩溃。
### 13.3 修复措施
- 恢复并对齐原稳定逻辑:
- 当层级回退时,立即对回退区间层级执行首尾成环收口。
- 每轮处理后更新 `ptCurrent = ptNextNode`,保证状态推进一致。
- 循环结束后按当前实际层级执行最终收口。
### 13.4 回归防线
- 新增启动路径集成回归用例:`tests/test_p2_menu_runtime_startup.c`
- 覆盖最易回归的启动路径:
- `MenuApp_Init()`
- `Key_Init()`
- 首次 `MenuApp_PollInput()`
- `MenuApp_Render()`
- 该用例用于防止“菜单树可编译但启动崩溃”的问题再次进入主干。