369 lines
12 KiB
Markdown
369 lines
12 KiB
Markdown
# 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()`
|
||
- 该用例用于防止“菜单树可编译但启动崩溃”的问题再次进入主干。
|
||
|