12 KiB
12 KiB
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. 系统分层架构
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) - 基于静态菜单模型构建运行时菜单树
- 处理按键驱动的菜单状态迁移与路径重建
- 执行菜单显示坐标计算与多级菜单渲染调度
- 采用 MVP 分层组织菜单模块(
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
- 管理 LCD 显存
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 主循环流程
[系统初始化]
|
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)
- 菜单应用 Facade(
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 运行时调用时序
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 远程显示协议流程
[Accept客户端]
|
v
[接收缓冲区累积]
|
v
[parse_frame 校验解析]
|
+--成功--------> [按 cmd 分发] --> [send_reply 回包] --> [接收缓冲区累积]
|
+--失败/不完整--> [接收缓冲区累积]
命令语义(RemoDispBus):
CMD_INIT:返回 LCD 宽高与显存尺寸CMD_LCDMEM:返回显存数据(支持起始地址)CMD_KEY:注入远程按键到g_tRemoteKeyCMD_KEEPLIVE:保活响应
7. 并发与线程模型
- 主线程:
- 负责菜单路由与本地显示刷新
- TCP 服务器线程:
- 监听连接、解析协议、处理远程请求
共享状态:
g_tCVsr.pwbyLCDMemory(远程读取 + 本地写入)g_tRemoteKey(远程写入 + 菜单读取)
当前实现未使用锁机制,依赖业务访问模式降低冲突风险。
后续若并发复杂度提升,建议引入细粒度互斥或无锁缓冲策略。
8. 构建与运行架构
- 构建系统:CMake(
C_STANDARD 99) - 可执行目标:
DTU-HMI - 平台链接:
- Windows:
ws2_32 - Linux/macOS:
pthread
- Windows:
- 可选调试:
ENABLE_DEBUG=ON自动定义DEBUG宏
9. 测试架构
测试目录:tests/
- 框架:
ctest + 自定义断言宏 - 分层策略:
- P0:纯逻辑单元测试(协议解析、UTF-8 解析、字库查找)
- P1:状态/计算单测(按键、菜单、LCD 基础像素操作)
- P2:集成测试(TCP 回环)
建议执行命令:
cmake -S . -B build
cmake --build build
ctest --test-dir build -C Debug --output-on-failure
10. 模块依赖关系(代码级)
main.c依赖:menu、key、remoteDisplay、tcp、thread_utilssrc/Drv/menu/app/menu.c依赖:presenter/menu_presenter、model/display、key、lcdsrc/Drv/menu/presenter/menu_presenter.c依赖:model/menu_model、view/menu_view、presenter/menu_navigatorsrc/Drv/menu/view/menu_view.c依赖:model/menu_layout、view/menu_render_port、lcdremoteDisplay.c依赖:lcd、key、tcp、thread_utilslcd.c依赖:asciisrc/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()
- 该用例用于防止“菜单树可编译但启动崩溃”的问题再次进入主干。