28 KiB
28 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封装平台差异。 - 可维护性:按模块职责划分(菜单/显示/网络/线程/输入)。
- 系统进行严格的分层设计
- 分布规划:
- 应用层
- 服务层
- 设备驱动层 (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 - 职责:提供屏幕的显示
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 页面管理器设计
主要是通过页面管理器对不同页面之间切换进行调度。
多页面目录结构设计:
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) # 其他页面模块
数据结构设计
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 执行页面导航:先在注册表中查找页面,再将其压入页面栈。- 在
page_registry中找到对应的页面pageId - 找到
page_stack的栈顶页面currentTop = PageManager_GetTop() - 栈顶页面执行退出
currentTop->on_exit(currentTop) - 将
pageId放到page_stack顶部 - 如果页面没有创建则执行创建页面
newPage->on_create(newPage) - 执行页面进入
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 实现,实现页面间的彻底解耦。
/* -------------------------------------------------------------------------
* 枚举名: 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 浪费和低功耗异常,每个钩子的执行时机、职责完全固定,禁止越权操作。
/* -------------------------------------------------------------------------
* 模块内静态对象说明:
* 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 主循环流程
[系统初始化]
|
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 远程显示协议流程
[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
维护命令:
tasklist /FI "IMAGENAME eq DTU-HMI.exe" #查找程序 PID
taskkill /PID 66464 /F #根据 PID 关闭程序
10. 模块依赖关系(代码级)
main.c依赖:menu、key、remoteDisplay、tcp、thread_utilsmenu.c依赖:lcd、display、keyremoteDisplay.c依赖:lcd、key、tcp、thread_utilslcd.c依赖:asciidisplay.c提供:静态菜单表(被menu.c使用)
11. 已知风险与改进建议
- 并发一致性风险:远程线程与主线程共享状态无锁访问。
- 建议:为显存快照与按键事件引入互斥保护或双缓冲。
- 协议缓冲鲁棒性:当前异常数据采用清空缓冲策略,存在丢包窗口。
- 建议:增加更精细的帧边界恢复策略与统计日志。
- 可测试性边界:部分逻辑仍与全局状态耦合较深。
- 建议:逐步引入接口注入(如
TcpOps、delay_ms)降低耦合。
- 建议:逐步引入接口注入(如
12. 版本与维护
- 文档版本:v1.0
- 适配代码基线:当前
DTU-HMI仓库主干实现 - 维护建议:
- 每次新增模块或调整主流程时同步更新本文档
- 测试策略更新需同步维护“第 9 章 测试架构”