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

12 KiB
Raw Blame History

DTU-HMI 系统架构设计文档

1. 文档目的

本文档用于描述 DTU-HMI 工程的整体架构、核心模块职责、关键数据流、线程与通信模型、构建与测试体系,作为后续开发、联调、测试与维护的统一基线。

2. 系统概述

DTU-HMI 是一个基于纯 C 实现的 PC 端 HMI 菜单逻辑模拟系统,目标是复现现场 DTU 设备的人机界面行为。
系统支持本地菜单交互与远程显示协议RemoDispBus通信主要包含

  • 菜单树构建与路由(多级菜单、同级循环导航)
  • LCD 显存与绘制160x160 单色像素缓冲)
  • 按键输入(本地/远程按键注入)
  • TCP 服务(远程显示数据交互)
  • 跨平台线程与网络适配Windows/Linux

3. 架构目标与设计原则

  • 可移植性:通过 tcp.cthread_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
    • 基于静态菜单模型构建运行时菜单树
    • 处理按键驱动的菜单状态迁移与路径重建
    • 执行菜单显示坐标计算与多级菜单渲染调度

4.3 显示层

  • 文件:src/Drv/lcd/lcd.csrc/Drv/lcd/lcd_draw.csrc/Drv/lcd/lcd_text.csrc/Drv/lcd/text_codec.csrc/Drv/lcd/ascii.csrc/Drv/menu/model/display.c
  • 职责:
    • 管理 LCD 显存 g_tCVsr 与像素绘制
    • 提供 ASCII/UTF-8 字符显示能力
    • 提供静态菜单模型定义 g_tMenuModelTab

4.4 输入层

  • 文件:src/Drv/key.csrc/Drv/key.h
  • 职责:
    • 提供按键读写状态控制(消费式读取)
    • 接收远程模块写入的按键事件并供菜单模块读取

4.5 远程显示通信层

  • 文件:src/remoteDisplay.csrc/remoteDisplay.h
  • 职责:
    • 实现 RemoDispBus 协议解析与回复
    • 提供 TCP 服务器线程入口与启动逻辑
    • 处理保活、初始化、按键下发、显存上传等命令

4.6 平台适配层

  • 文件:src/TCP/tcp.csrc/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 管理)
  • 运行时控制:tagMenuCtrltagDspCtrl(定义于 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
    • 菜单应用 FacadeMenuApp_* 对外接口)
    • 持有运行时私有状态(s_menuCtrls_dspCtrls_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 运行时调用时序

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
    • 导航核心状态(ptCurrentptRoute[4]ptCurBakpt0Level 等)
  • 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_tRemoteKey
  • CMD_KEEPLIVE:保活响应

7. 并发与线程模型

  • 主线程:
    • 负责菜单路由与本地显示刷新
  • TCP 服务器线程:
    • 监听连接、解析协议、处理远程请求

共享状态:

  • g_tCVsr.pwbyLCDMemory(远程读取 + 本地写入)
  • g_tRemoteKey(远程写入 + 菜单读取)

当前实现未使用锁机制,依赖业务访问模式降低冲突风险。
后续若并发复杂度提升,建议引入细粒度互斥或无锁缓冲策略。

8. 构建与运行架构

  • 构建系统CMakeC_STANDARD 99
  • 可执行目标:DTU-HMI
  • 平台链接:
    • Windowsws2_32
    • Linux/macOSpthread
  • 可选调试: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 依赖:menukeyremoteDisplaytcpthread_utils
  • src/Drv/menu/app/menu.c 依赖:presenter/menu_presentermodel/displaykeylcd
  • src/Drv/menu/presenter/menu_presenter.c 依赖:model/menu_modelview/menu_viewpresenter/menu_navigator
  • src/Drv/menu/view/menu_view.c 依赖:model/menu_layoutview/menu_render_portlcd
  • remoteDisplay.c 依赖:lcdkeytcpthread_utils
  • lcd.c 依赖:ascii
  • src/Drv/menu/model/display.c 提供:静态菜单表(被 menu 模块使用)

11. 已知风险与改进建议

  • 并发一致性风险:远程线程与主线程共享状态无锁访问。
    • 建议:为显存快照与按键事件引入互斥保护或双缓冲。
  • 协议缓冲鲁棒性:当前异常数据采用清空缓冲策略,存在丢包窗口。
    • 建议:增加更精细的帧边界恢复策略与统计日志。
  • 可测试性边界:部分逻辑仍与全局状态耦合较深。
    • 建议:逐步引入接口注入(如 TcpOpsdelay_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()
  • 该用例用于防止“菜单树可编译但启动崩溃”的问题再次进入主干。