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

28 KiB
Raw Permalink 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 封装平台差异。
  • 可维护性:按模块职责划分(菜单/显示/网络/线程/输入)。
    • 系统进行严格的分层设计
      • 分布规划:
        • 应用层
        • 服务层
        • 设备驱动层 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.csrc/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 执行页面导航:先在注册表中查找页面,再将其压入页面栈。
    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 实现,实现页面间的彻底解耦。

/* -------------------------------------------------------------------------
 * 枚举名: 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 浪费和低功耗异常,每个钩子的执行时机、职责完全固定,禁止越权操作。

/* -------------------------------------------------------------------------
 * 模块内静态对象说明:
 *   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.csrc/remoteDisplay.h
  • 职责:
    • 实现 RemoDispBus 协议解析与回复
    • 提供 TCP 服务器线程入口与启动逻辑
    • 处理保活、初始化、按键下发、显存上传等命令

4.6 平台适配层

  • 文件:src/TCP/tcp.csrc/thread_utils.c
  • 职责:
    • 提供跨平台 socket 与线程封装
    • 隔离 Windows/Linux API 差异

5. 核心数据结构设计

5.1 菜单模型与菜单树

  • 静态菜单模型:tagMenuModel(定义于 display.h,数据在 display.c
  • 运行时菜单项:tagMenuItem(定义于 menu.c 内)
  • 全局控制:g_tMenuCtrlg_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_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

维护命令:

tasklist /FI "IMAGENAME eq DTU-HMI.exe" #查找程序 PID
taskkill /PID 66464 /F  #根据 PID 关闭程序

10. 模块依赖关系(代码级)

  • main.c 依赖:menukeyremoteDisplaytcpthread_utils
  • menu.c 依赖:lcddisplaykey
  • remoteDisplay.c 依赖:lcdkeytcpthread_utils
  • lcd.c 依赖:ascii
  • display.c 提供:静态菜单表(被 menu.c 使用)

11. 已知风险与改进建议

  • 并发一致性风险:远程线程与主线程共享状态无锁访问。
    • 建议:为显存快照与按键事件引入互斥保护或双缓冲。
  • 协议缓冲鲁棒性:当前异常数据采用清空缓冲策略,存在丢包窗口。
    • 建议:增加更精细的帧边界恢复策略与统计日志。
  • 可测试性边界:部分逻辑仍与全局状态耦合较深。
    • 建议:逐步引入接口注入(如 TcpOpsdelay_ms)降低耦合。

12. 版本与维护

  • 文档版本v1.0
  • 适配代码基线:当前 DTU-HMI 仓库主干实现
  • 维护建议:
    • 每次新增模块或调整主流程时同步更新本文档
    • 测试策略更新需同步维护“第 9 章 测试架构”