# DTU-HMI 系统架构设计文档 ## 1. 文档目的 本文档用于描述 `DTU-HMI` 工程的整体架构、核心模块职责、关键数据流、线程与通信模型、构建与测试体系,作为后续开发、联调、测试与维护的统一基线。 ## 2. 系统概述 `DTU-HMI` 是一个基于纯 C 实现的 PC 端 HMI 菜单逻辑模拟系统,目标是复现现场 DTU 设备的人机界面行为。 系统支持本地菜单交互与远程显示协议(RemoDispBus)通信,主要包含: - 菜单树构建与路由(多级菜单、同级循环导航) - LCD 显存与绘制(160x160 单色像素缓冲) - 按键输入(本地/远程按键注入) - TCP 服务(远程显示数据交互) - 跨平台线程与网络适配(Windows/Linux) ## 3. 架构目标与设计原则 - **可移植性**:通过 `tcp.c`、`thread_utils.c` 封装平台差异。 - **可维护性**:按模块职责划分(菜单/显示/网络/线程/输入)。 - 系统进行严格的分层设计 - 分布规划: - 应用层 - 服务层 - 设备驱动层 (LCD 屏幕,TCP/IP) - 分层规律 - 单向依赖 - 禁止跨层调用 - 职责单一 - 接口标准化 - 全局变量零容忍 - 实时性高的数据采用**观察者模式**进行处理 - 实时性低的数据通过**消息队列**的方式处理 - **可测试性**:优先抽取并覆盖纯逻辑函数,逐步推进集成测试。 - **低耦合高内聚**:上层业务通过明确接口调用下层能力。 - **错误检查**:对可预见的错误进行检查 - **断言检查**:增加调试编译条件下的断言检查,快速定位一般情况下的错误。 - **错误码处理** ## 4. 系统分层架构 **目录结构:** ```text DTU-HMI/ ├── CMakeLists.txt ├── README.md ├── include/ # 公共头文件 │ └── types.h # 类型定义 ├── src/ │ ├── main.c # ⭐ 应用入口,主循环调度 │ ├── common/ │ │ └── utf8.c/h │ ├── remoteDisplay.c/h │ ├── thread_utils.c/h │ ├── TCP/ │ │ └── tcp.c/h │ └── Drv/ # 📦 驱动层 │ ├── key.c/h # 按键输入抽象 │ ├── menu/app/ │ │ └── menu.c/h │ ├── lcd/ # LCD 显示驱动 │ │ ├── ascii.c/h # ASCII 字库 │ │ ├── lcd.c/h │ │ ├── lcd_draw.c/h │ │ └── lcd_text.c/h │ └── pages/ # ⭐ 新版多页面系统根目录 │ ├── page.h # 标准页面抽象定义 │ ├── page_manager.c/h # 页面栈调度器 │ ├── global/ # 🌍 全局状态管理 │ │ ├── global_state.c/h # 跨页面共享数据 │ │ └── renderer_lcd.c/h # LCD 渲染 HAL 接口 │ └── menu/ # 📋 Menu 页面的 MVP 实现 │ ├── def.h │ ├── model.c/h │ ├── presenter.c/h │ ├── view.c/h # 菜单渲染绘制 │ └── page.c/h # 页面生命周期注册 └── tests/ # 🧪 测试目录 ├── CMakeLists.txt ├── test_p0_remote_display.c ├── test_p0_utf8_hz12_get.c ├── test_p1_key.c ├── test_p1_lcd_basic.c ├── test_p1_menu.c ├── test_p1_page_manager.c └── test_p2_tcp_loopback.c ``` ### 4.1 LCD 驱动层 - 文件夹:`src/Drv/lcd` - 职责:提供屏幕的显示 ```mermaid block-beta columns 1 block:menu a["LCD"] end 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.2 按键驱动层 - 文件:`src/Drv/menu.c`、`src/Drv/menu.h` - 职责: - 基于静态菜单模型构建运行时菜单树 - 处理按键驱动的菜单状态迁移 - 执行菜单显示坐标计算与多级菜单渲染调度 ### 4.3 多页面管理层设计 #### 4.3.1 页面管理器设计 主要是通过页面管理器对不同页面之间切换进行调度。 **多页面目录结构设计:** ```txt src/ └── Drv/ └── pages/ # 所有页面模块根目录 ├── page_manager.c # 页面管理实现 ├── page_manager.h # 页面管理头文件 ├── global/ # 全局状态管理器 + 页面交互中间件 │ ├── renderer_lcd.c # 抽象LCD底层,统一渲染接口 │ └── renderer_lcd.h # 抽象LCD底层,统一渲染接口 ├── menu/ # 菜单页面(MVP架构) │ ├── model.c │ ├── model.h │ ├── view.c │ ├── view.h │ ├── presenter.c │ ├── presenter.h │ ├── page.c │ └── page.h └── (other pages) # 其他页面模块 ``` ###### 数据结构设计 ```c typedef struct { page_t *page_stack[MAX_PAGE_STACK_DEPTH]; /* 页面导航栈 */ int8_t stack_top; /* 栈顶索引(-1 表示空栈) */ page_t *page_registry[MAX_PAGE_COUNT]; /* 页面注册表 */ uint8_t page_count; /* 注册页面数量 */ } page_manager_t; /* 页面导航栈,记录着页面进入的顺序 */ page_stack = [page_id0, page_id1, page_id2, page_id3] page_registry /*存储被页面管理器的页面*/ ``` ##### 函数设计 页面管理器 Page Manager 主要函数: * `void PageManager_Init(void)` * `int PageManager_Register(page_t *page)`:将页面对象注册到页面管理器的注册表中,供后续按 page_id 查找与导航。 * `int PageManager_Navigate(page_id_t pageId)`: 根据目标 page_id 执行页面导航:先在注册表中查找页面,再将其压入页面栈。 1. 在`page_registry`中找到对应的页面 `pageId` 2. 找到`page_stack` 的栈顶页面 `currentTop = PageManager_GetTop()` 3. 栈顶页面执行退出 `currentTop->on_exit(currentTop)` 4. 将 `pageId` 放到 `page_stack` 顶部 5. 如果页面没有创建则执行创建页面 `newPage->on_create(newPage)` 6. 执行页面进入 `newPage->on_enter(newPage)` * `void PageManager_Loop(void)`: 驱动当前栈顶页面的循环回调(on_loop),用于执行周期性任务。 * `void PageManager_DispatchEvent(input_event_t *event)`: 将输入事件分发到当前栈顶页面,并在页面未处理时执行全局兜底处理。 * `static void PageManager_GlobalEventHandle(input_event_t *event)`: 页面管理器的全局事件兜底处理入口,用于处理未被页面消费的通用按键逻辑。 * `page_t *PageManager_Find(page_id_t pageId)`: 在管理器的页面注册表中按 page_id 线性查找,返回对应的 page_t 指针。 * `page_t *PageManager_GetTop(void)`: 返回当前页面栈栈顶元素,即当前“前台/可见”页面对应的 page_t 指针。 * `int PageManager_Push(page_t *newPage)`: 将指定页面压入页面栈并切换为当前页:必要时触发旧页 on_exit、新页 on_create/on_enter。 * `int PageManager_Pop(void)`: 弹出当前栈顶页面并回到下一层:触发当前页 on_exit,非缓存页可 on_destroy, #### 4.3.2 标准化页面抽象(Page 接口层) 这是整个系统的基础单元,**每个独立业务页面对应一个 Page 实例,每个 Page 内部封装一套完全独立的 MVP 三元组**,对外仅暴露标准接口,页面管理器仅通过标准接口与页面交互,完全不关心页面内部的 MVP 实现,实现页面间的彻底解耦。 ```c /* ------------------------------------------------------------------------- * 枚举名: page_id_t * 作用: * 定义系统内可导航页面的逻辑标识(Page ID)。 * * 取值说明: * PAGE_ID_NONE - 无效页面 ID / 未初始化占位值 * PAGE_ID_MENU - 菜单页 ID(当前主运行页) * PAGE_ID_APP_INFO - 预留页面 ID(当前版本可注册与否由上层决定) * PAGE_ID_MAX - 上界哨兵,不可作为有效页面 ID 使用 * * 使用约束: * - PageManager_Register() 会校验 page_id,PAGE_ID_NONE 与 >= PAGE_ID_MAX * 均视为非法值。 * ------------------------------------------------------------------------- */ typedef enum { PAGE_ID_NONE = 0, PAGE_ID_MENU = 1, PAGE_ID_APP_INFO = 2, /* 预留ID:当前版本未注册运行 */ PAGE_ID_MAX } page_id_t; /* ------------------------------------------------------------------------- * 枚举名: page_event_type_t * 作用: * 定义页面层可分发的事件类型。 * * 当前约定: * PAGE_EVENT_KEY - 按键输入事件 * * 扩展说明: * - 后续可按需扩展触摸、定时器、通信消息等事件类型。 * ------------------------------------------------------------------------- */ typedef enum { PAGE_EVENT_KEY = 1 } page_event_type_t; /* ------------------------------------------------------------------------- * 结构体名: input_event_t * 作用: * 页面事件分发的数据载体,由 PageManager_DispatchEvent() 传入页面 on_event。 * * 字段说明: * type - 事件类型,取值来自 page_event_type_t * keyVal - 按键值(当 type 为 PAGE_EVENT_KEY 时有效) * * 使用约束: * - 调用方应保证 type/keyVal 与事件来源一致; * - 页面 on_event 可基于该结构返回 EVENT_HANDLED / EVENT_UNHANDLED。 * ------------------------------------------------------------------------- */ typedef struct { uint8_t type; uint8_t keyVal; } input_event_t; /* ------------------------------------------------------------------------- * 枚举名: event_result_t * 作用: * 定义页面事件处理结果,用于控制事件链是否继续兜底处理。 * * 取值说明: * EVENT_UNHANDLED - 页面未消费事件;允许交由管理器执行全局兜底逻辑 * EVENT_HANDLED - 页面已消费事件;事件链终止 * ------------------------------------------------------------------------- */ typedef enum { EVENT_UNHANDLED = 0, EVENT_HANDLED = 1 } event_result_t; /* 前置声明:支持在回调签名中使用 page_t* */ typedef struct page_t page_t; /* ------------------------------------------------------------------------- * 结构体名: page_t * 作用: * 页面抽象基元,描述“一个可被 PageManager 管理的页面实例”。 * * 字段分组: * 1) 基础元信息 * page_id - 页面逻辑标识 * is_cached - 是否缓存页(1: Pop 后不销毁;0: Pop 后可销毁) * is_created - 是否已执行过 on_create(由管理器维护) * * 2) 生命周期与事件回调 * on_create - 首次创建时调用 * on_enter - 页面进入前台时调用 * on_exit - 页面离开前台时调用 * on_destroy - 页面销毁时调用(常用于非缓存页) * on_event - 输入事件处理回调 * on_loop - 周期循环回调 * * 3) 三层对象挂载指针(MVP) * presenter / view / model - 页面内部层对象地址(可为空,按页面实现决定) * * 生命周期约定(由 PageManager 驱动): * - Push 到新页面时:旧页 on_exit -> 新页(必要时 on_create)-> 新页 on_enter * - Pop 回退时:当前页 on_exit ->(非缓存页可 on_destroy)-> 新栈顶 on_enter * ------------------------------------------------------------------------- */ struct page_t { page_id_t page_id; uint8_t is_cached; uint8_t is_created; void (*on_create)(page_t *page); void (*on_enter)(page_t *page); void (*on_exit)(page_t *page); void (*on_destroy)(page_t *page); event_result_t (*on_event)(page_t *page, input_event_t *event); void (*on_loop)(page_t *page); void *presenter; void *view; void *model; }; **菜单页面首次进入流程图:** ```txt [系统启动] | v MenuPage_GetInstance() | v 返回 &s_menuPage 给 PageManager_Register(...) | v PageManager_Navigate(PAGE_ID_MENU) | v PageManager_Push(s_menuPage) | +--> (首次进入) MenuPage_OnCreate(page) | | | +--> MenuModel_Init(&s_model) // 1. 模型初始化 | +--> MenuView_Init(&s_view) // 2. 视图初始化 | +--> MenuPresenter_Init(...) // 3. 主持器初始化 | +--> page/s_menuPage 绑定 model/view/presenter | +--> MenuPage_OnEnter(page) | +--> s_presenter.dspCtrl.bFirst = 1 +--> MenuPresenter_Refresh(&s_presenter) // 首帧刷新 ``` #### 4.3.3 页面标准接口与生命周期定义 嵌入式场景下,生命周期钩子的设计必须严格对应资源的申请 / 释放时机,避免 RAM 浪费和低功耗异常,每个钩子的执行时机、职责完全固定,禁止越权操作。 ```c /* ------------------------------------------------------------------------- * 模块内静态对象说明: * s_model - 菜单页 Model 实例(菜单数据与运行时结构) * s_view - 菜单页 View 实例(布局与渲染能力) * s_presenter - 菜单页 Presenter 实例(输入处理与状态驱动) * s_menuPage - 页面管理器可注册的 page_t 描述对象 * * 说明: * - 以上对象均为文件内静态单例,生命周期覆盖进程运行期。 * - 通过 MenuPage_GetInstance() 暴露 s_menuPage 给 PageManager 注册。 * ------------------------------------------------------------------------- */ static menu_model_t s_model; static menu_view_t s_view; static menu_presenter_t s_presenter; static page_t s_menuPage; /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnEnter * 功能: * 页面进入回调:将菜单渲染标记为“首帧全量刷新”,并立即触发一次刷新。 * * 参数: * page - 当前页面对象指针(本实现未直接使用) * * 边界处理: * - 本函数不依赖 page 内容,统一转为 (void)page 消除未使用告警。 * * 说明: * - 通过 dspCtrl.bFirst = 1 告知 Presenter 下一次刷新走首帧路径。 * - 进入页面后立即刷新,确保界面可见状态与内部状态同步。 * * 返回值: * - 无 * ------------------------------------------------------------------------- */ static void MenuPage_OnEnter(page_t *page) { (void)page; s_presenter.dspCtrl.bFirst = 1; MenuPresenter_Refresh(&s_presenter); } /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnExit * 功能: * 页面退出回调占位点;当前版本无额外退出动作。 * * 参数: * page - 当前页面对象指针(本实现未直接使用) * * 边界处理: * - 使用 (void)page 防止未使用参数告警。 * * 说明: * - 预留给后续扩展(如停止定时任务、冻结动画、保存瞬时 UI 状态等)。 * * 返回值: * - 无 * ------------------------------------------------------------------------- */ static void MenuPage_OnExit(page_t *page) { (void)page; } /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnDestroy * 功能: * 页面销毁回调:清空菜单页内部三层对象(Model/View/Presenter)状态。 * * 参数: * page - 当前页面对象指针(本实现未直接使用) * * 边界处理: * - 使用 memset 全量清零静态对象,避免残留状态影响后续重建。 * * 说明: * - 与 is_cached 策略配合:当页面被标记为非缓存并弹栈销毁时,该函数用于复位。 * - page_t 元信息不在此函数复位,由页面生命周期创建阶段重新赋值。 * * 返回值: * - 无 * ------------------------------------------------------------------------- */ static void MenuPage_OnDestroy(page_t *page) { (void)page; memset(&s_model, 0, sizeof(s_model)); memset(&s_view, 0, sizeof(s_view)); memset(&s_presenter, 0, sizeof(s_presenter)); } /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnEvent * 功能: * 页面事件回调:校验输入事件后,将按键转交 Presenter 处理。 * * 参数: * page - 当前页面对象指针(本实现未直接使用) * event - 输入事件指针 * * 边界处理: * - event 为 NULL 时返回 EVENT_UNHANDLED。 * - 仅处理 PAGE_EVENT_KEY 事件类型,其它类型返回 EVENT_UNHANDLED。 * - keyVal 为 0 视为无效按键,返回 EVENT_UNHANDLED。 * * 说明: * - 事件有效时调用 MenuPresenter_HandleInput() 执行业务输入流转。 * - 返回 EVENT_HANDLED,表示该事件已被菜单页消费,不再交给上层页面逻辑。 * * 返回值: * - EVENT_HANDLED : 事件已处理 * - EVENT_UNHANDLED : 事件无效或不属于本页处理范围 * ------------------------------------------------------------------------- */ static event_result_t MenuPage_OnEvent(page_t *page, input_event_t *event) { (void)page; if ((event == NULL) || (event->type != PAGE_EVENT_KEY) || (event->keyVal == 0)) { return EVENT_UNHANDLED; } MenuPresenter_HandleInput(&s_presenter, event->keyVal); return EVENT_HANDLED; } /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnLoop * 功能: * 页面循环回调:周期性驱动 Presenter 执行刷新逻辑。 * * 参数: * page - 当前页面对象指针(本实现未直接使用) * * 边界处理: * - 使用 (void)page 防止未使用参数告警。 * * 说明: * - 实际刷新策略(全量/增量)由 Presenter 内部状态控制。 * - 该函数通常由 PageManager_Loop() 在主循环节拍中调用。 * * 返回值: * - 无 * ------------------------------------------------------------------------- */ static void MenuPage_OnLoop(page_t *page) { (void)page; MenuPresenter_Refresh(&s_presenter); } /* ------------------------------------------------------------------------- * 函数名: MenuPage_OnCreate * 功能: * 页面创建回调:按 Model -> View -> Presenter 顺序完成菜单页运行时装配, * 并初始化 page_t 描述对象字段。 * * 参数: * page - 当前页面对象指针(由 PageManager 传入) * * 边界处理: * - 先清零 s_menuPage,再统一重建其元信息与回调绑定,避免脏状态遗留。 * - 假定 page 非空且来自 PageManager 生命周期调用链。 * * 说明: * - 初始化顺序固定: * 1) MenuModel_Init(&s_model) * 2) MenuView_Init(&s_view) * 3) MenuPresenter_Init(&s_presenter, &s_model, &s_view) * - 将 model/presenter/view 回填到 page 与 s_menuPage,便于调试与统一访问。 * - s_menuPage 作为静态页面实例,对外由 MenuPage_GetInstance() 返回。 * * 返回值: * - 无 * ------------------------------------------------------------------------- */ static void MenuPage_OnCreate(page_t *page) { memset(&s_menuPage, 0, sizeof(s_menuPage)); /* 1) model init */ MenuModel_Init(&s_model); /* 2) view init */ MenuView_Init(&s_view); /* 3) presenter setup + runtime build */ MenuPresenter_Init(&s_presenter, &s_model, &s_view); page->model = &s_model; page->presenter = &s_presenter; page->view = &s_view; s_menuPage.presenter = &s_presenter; s_menuPage.view = &s_view; s_menuPage.model = &s_model; s_menuPage.page_id = PAGE_ID_MENU; s_menuPage.is_cached = 1; s_menuPage.on_create = MenuPage_OnCreate; s_menuPage.on_enter = MenuPage_OnEnter; s_menuPage.on_exit = MenuPage_OnExit; s_menuPage.on_destroy = MenuPage_OnDestroy; s_menuPage.on_event = MenuPage_OnEvent; s_menuPage.on_loop = MenuPage_OnLoop; } /* ------------------------------------------------------------------------- * 函数名: MenuPage_GetInstance * 功能: * 获取菜单页静态实例指针,供 PageManager_Register() 注册使用。 * * 参数: * 无 * * 边界处理: * - 返回文件内静态对象地址,无空指针分支。 * * 说明: * - 该函数仅暴露页面入口,不负责初始化;初始化由 on_create 生命周期完成。 * * 返回值: * - 指向静态页面对象 s_menuPage 的 page_t* 指针 * ------------------------------------------------------------------------- */ page_t *MenuPage_GetInstance(void) { return &s_menuPage; } ``` ### 菜单页面设计 **设计思想:** 基于经典的 MVP 范式,彻底解耦**数据、视图、控制逻辑**,解决复杂 UI 与业务逻辑的协同维护问题。 - **Model(模型层)**:纯业务数据与状态管理,负责系统参数、设备状态、采集数据的读写、校验、存储,与 UI 完全无关。 - **View(视图层)**:纯渲染显示,仅根据 Model 的数据绘制菜单界面、焦点高亮、弹窗、动画,不处理任何业务逻辑。 - **Presenter(控制层)**:核心调度中枢,接收输入事件、更新 Model 数据、控制 View 刷新、处理菜单跳转逻辑,是 Model 与 View 的唯一桥梁。 **菜单页面的目录设计:** ```txt 菜单页面的目录结构设计: src/ └── Drv/ └── pages/ └── menu/ # 项目菜单逻辑根目录 ├── model.c # 菜单模型层实现 ├── model.h # 菜单模型层头文件 ├── view.c # 菜单视图层实现 ├── view.h # 菜单视图层头文件 ├── presenter.c # 菜单控制层实现 ├── presenter.h # 菜单控制层头文件 ├── page.c # 菜单页面逻辑实现 └── page.h # 菜单页面逻辑头文件 ``` 当有外部的事件产生时,MVP 架构的数据流图如下: ### 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`(定义于 `display.h`,数据在 `display.c`) - 运行时菜单项:`tagMenuItem`(定义于 `menu.c` 内) - 全局控制:`g_tMenuCtrl`、`g_tDspCtrl` 关键关系: - `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 [Menu_Route] | v [Sleep 20ms] | v [计数器累加] | v [是否到刷新周期?] --否--> [Menu_Route] | +--是--> [Menu_Show_Proc] --> [Menu_Route] ``` ### 6.2 菜单交互流程 - 输入来源:`Key_Read()`(含远程写入按键) - 行为: - 上/下:同级循环移动 - 左/ESC:回退上级或退回主层 - 右/确认:进入子级或执行叶子回调 - 渲染: - `Menu_Show_Proc` 根据路径增量刷新或全量刷新 ### 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 ``` 维护命令: ```bash tasklist /FI "IMAGENAME eq DTU-HMI.exe" #查找程序 PID taskkill /PID 66464 /F #根据 PID 关闭程序 ``` ## 10. 模块依赖关系(代码级) - `main.c` 依赖:`menu`、`key`、`remoteDisplay`、`tcp`、`thread_utils` - `menu.c` 依赖:`lcd`、`display`、`key` - `remoteDisplay.c` 依赖:`lcd`、`key`、`tcp`、`thread_utils` - `lcd.c` 依赖:`ascii` - `display.c` 提供:静态菜单表(被 `menu.c` 使用) ## 11. 已知风险与改进建议 - **并发一致性风险**:远程线程与主线程共享状态无锁访问。 - 建议:为显存快照与按键事件引入互斥保护或双缓冲。 - **协议缓冲鲁棒性**:当前异常数据采用清空缓冲策略,存在丢包窗口。 - 建议:增加更精细的帧边界恢复策略与统计日志。 - **可测试性边界**:部分逻辑仍与全局状态耦合较深。 - 建议:逐步引入接口注入(如 `TcpOps`、`delay_ms`)降低耦合。 ## 12. 版本与维护 - 文档版本:v1.0 - 适配代码基线:当前 `DTU-HMI` 仓库主干实现 - 维护建议: - 每次新增模块或调整主流程时同步更新本文档 - 测试策略更新需同步维护“第 9 章 测试架构”