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

770 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 系统分层架构
<img src="https://lsky.bitnasdaq.vip/8CaeLa.png" style="display: block; margin: auto; max-width: 90%;">
**目录结构:**
```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 页面管理器设计
主要是通过页面管理器对不同页面之间切换进行调度。
<img src="https://lsky.bitnasdaq.vip/cRml1o.png" style="display: block; margin: auto; max-width: 90%;">
**多页面目录结构设计:**
```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_idPAGE_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;
}
```
### 菜单页面设计
<img src="https://lsky.bitnasdaq.vip/uGb3n2.png" style="display: block; margin: auto; max-width: 90%;">
**设计思想:**
基于经典的 MVP 范式,彻底解耦**数据、视图、控制逻辑**,解决复杂 UI 与业务逻辑的协同维护问题。
- **Model模型层**:纯业务数据与状态管理,负责系统参数、设备状态、采集数据的读写、校验、存储,与 UI 完全无关。
- **View视图层**:纯渲染显示,仅根据 Model 的数据绘制菜单界面、焦点高亮、弹窗、动画,不处理任何业务逻辑。
- **Presenter控制层**:核心调度中枢,接收输入事件、更新 Model 数据、控制 View 刷新、处理菜单跳转逻辑,是 Model 与 View 的唯一桥梁。
<img src="https://lsky.bitnasdaq.vip/reEG2w.png" style="display: block; margin: auto; max-width: 90%;">
**菜单页面的目录设计:**
```txt
菜单页面的目录结构设计:
src/
└── Drv/
└── pages/
└── menu/ # 项目菜单逻辑根目录
├── model.c # 菜单模型层实现
├── model.h # 菜单模型层头文件
├── view.c # 菜单视图层实现
├── view.h # 菜单视图层头文件
├── presenter.c # 菜单控制层实现
├── presenter.h # 菜单控制层头文件
├── page.c # 菜单页面逻辑实现
└── page.h # 菜单页面逻辑头文件
```
当有外部的事件产生时MVP 架构的数据流图如下:
<img src="https://lsky.bitnasdaq.vip/yK2625.png" style="display: block; margin: auto; max-width: 90%;">
### 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 章 测试架构”