20 KiB
工业级嵌入式菜单组件开发文档
1. 项目概述
本项目是一个基于C语言开发的工业级嵌入式菜单组件,设计用于资源受限的嵌入式系统,具有高可移植性、模块化、可裁剪、低资源占用和事件驱动解耦等核心特点。
2. 架构设计
2.1 分层架构
项目采用分层架构设计,将硬件与业务逻辑彻底解耦,便于移植和扩展:
| 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 |
|---|---|---|---|---|
| 核心层 | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心(menu_core.c)、事件队列(menu_event.c)、哈希表索引(menu_hash.c)、栈管理(menu_stack.c) | src/core/ | 不可定制 |
| 功能扩展层 | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理(menu_param.c)、多语言支持(menu_lang.c) | src/param/、src/lang/ | 部分可定制 |
| 硬件端口层 | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象(menu_port.c) | port/ | 完全可定制 |
| API层 | 对外提供统一的API接口,屏蔽内部实现细节 | 统一API接口(menu_api.c) | api/ | 不可定制 |
2.2 模块间依赖关系
+------------------+
| 用户应用层 |
+------------------+
↓
+------------------+
| API层 |
+------------------+
↓
+------------------+
| 功能扩展层 |
+------------------+
↓
+------------------+
| 核心层 |
+------------------+
↓
+------------------+
| 硬件端口层 |
+------------------+
↓
+------------------+
| 硬件平台 |
+------------------+
3. 核心思想
3.1 无动态内存分配
为了保证系统的稳定性和可靠性,避免内存碎片问题,项目采用了完全静态的内存管理方式:
- 所有菜单节点、事件队列、哈希表等均使用静态数组分配
- 提供内存大小查询接口,便于用户根据实际需求调整内存配置
- 每个模块都有明确的内存占用上限,便于资源规划
3.2 事件驱动解耦
采用事件驱动设计,将硬件输入与软件逻辑解耦,提高系统响应性:
- 硬件事件通过事件队列异步处理,避免阻塞主程序
- 支持多优先级事件处理,保证关键事件优先处理
- 事件超时机制,避免无效事件占用系统资源
3.3 高效节点查找
为了提高菜单节点的查找效率,项目采用了哈希表索引技术:
- 将节点查找时间复杂度从O(n)优化到O(1)
- 采用优化的哈希函数,减少哈希冲突
- 支持动态注册和注销菜单节点,哈希表自动更新
3.4 可扩展状态机
设计了灵活的状态机框架,支持动态扩展状态转换规则:
- 内置基础状态转换规则,覆盖常见菜单操作
- 支持动态注册和注销自定义状态转换规则
- 状态转换动作可定制,便于扩展菜单行为
- 支持状态切换和状态查询,便于外部系统集成
3.5 模块化设计
采用模块化设计,各功能模块独立,便于维护和扩展:
- 每个模块都有清晰的接口定义,模块间低耦合
- 支持通过宏开关裁剪不需要的功能,减少资源占用
- 模块化的硬件适配层,便于移植到不同硬件平台
4. 核心数据结构
4.1 菜单节点结构体
typedef struct MenuNode {
MenuNodeId id; ///< 节点ID(唯一)
MenuNodeId parent_id; ///< 父节点ID(根节点为0)
const char* name; ///< 菜单名称(或多语言索引)
MenuCallback enter_cb; ///< 进入回调
MenuCallback exit_cb; ///< 退出回调
MenuNodeId first_child_id; ///< 第一个子节点ID(替代指针)
MenuNodeId next_sibling_id; ///< 下一个兄弟节点ID(替代指针)
MenuNodeId prev_sibling_id; ///< 上一个兄弟节点ID(替代指针)
MenuNodeId hash_next_id; ///< 哈希表下一个节点ID(替代指针)
// 位域:减少内存占用
struct {
bool is_registered : 1; ///< 是否已注册
bool is_selected : 1; ///< 是否被选中
unsigned int reserved : 6; ///< 保留位
} flags;
#if MENU_CONFIG_ENABLE_PARAM
uint16_t param_id; ///< 绑定的参数ID(启用参数时有效)
#endif
} MenuNode;
设计亮点:
- 使用索引代替指针,减少内存占用(32位系统节省12字节/节点,64位系统节省28字节/节点)
- 采用双向链表结构,便于兄弟节点间遍历
- 使用哈希表索引,提高节点查找效率
- 位域设计,减少内存占用
4.2 菜单核心上下文
typedef struct {
MenuNode nodes[MENU_CONFIG_MAX_NODES]; ///< 静态菜单节点池(无动态分配)
MenuNode* hash_table[MENU_CONFIG_HASH_TABLE_SIZE]; ///< 哈希表,用于快速查找节点
MenuStack stack; ///< 菜单导航栈
MenuNavPath nav_path; ///< 菜单导航路径
MenuEventQueue event_queue; ///< 事件队列
MenuNodeId current_node_id; ///< 当前选中的节点ID
MenuState current_state; ///< 当前菜单状态
uint32_t last_refresh_tick; ///< 上次刷新时间(ms)
bool is_initialized; ///< 是否已初始化
MenuErrCode error_code; ///< 当前错误码
const char* error_msg; ///< 当前错误信息
uint16_t node_count; ///< 已注册节点数量
uint16_t free_node_count; ///< 空闲节点数量
// 状态机扩展
#if MENU_CONFIG_ENABLE_STATE_MACHINE_EXT
MenuStateTransition custom_transitions[MENU_CONFIG_MAX_STATE_TRANSITIONS]; ///< 自定义状态转换规则
uint16_t custom_transition_count; ///< 自定义状态转换规则数量
#endif
} MenuCoreCtx;
设计亮点:
- 集中管理菜单组件的所有状态和资源
- 静态数组设计,无动态内存分配
- 包含所有核心数据结构,便于统一管理
- 支持状态机扩展,便于功能扩展
4.3 事件队列结构体
typedef struct {
MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区
uint8_t head; ///< 入队指针
uint8_t tail; ///< 出队指针
uint8_t count; ///< 队列元素数量
uint8_t reserved; ///< 保留字节,用于对齐
} MenuEventQueue;
设计亮点:
- 环形队列设计,避免内存碎片
- 支持多优先级事件处理
- 队列满时自动替换低优先级事件,保证高优先级事件优先处理
- 事件超时机制,避免无效事件占用资源
4.4 状态转换结构体
typedef struct {
MenuState current_state; ///< 当前状态
MenuEventType event; ///< 触发事件
MenuState next_state; ///< 下一个状态
MenuErrCode (*action)(MenuGlobalCtx* global_ctx); ///< 状态转换动作
const char* desc; ///< 状态转换描述(用于调试)
} MenuStateTransition;
设计亮点:
- 灵活的状态转换规则,支持动态注册
- 状态转换动作可定制,便于扩展菜单行为
- 支持状态转换描述,便于调试
4.5 参数结构体
typedef struct {
uint16_t id; ///< 参数ID
const char* name; ///< 参数名称
ParamType type; ///< 参数类型
void* value; ///< 参数值指针
void* default_value; ///< 默认值指针
void* min_value; ///< 最小值指针
void* max_value; ///< 最大值指针
bool is_read_only; ///< 是否只读
bool is_modified; ///< 是否已修改
} MenuParam;
设计亮点:
- 支持多种参数类型(int8/uint8/int16/uint16/int32/uint32/float)
- 自动范围检查和边界处理
- 参数与菜单节点绑定,支持菜单直接调整参数
- 支持默认值恢复功能
4.6 语言包结构体
typedef struct {
LangId id; ///< 语言ID
const char* name; ///< 语言名称(如"English"、"中文")
const LangStrItem* strings; ///< 字符串数组
uint16_t string_count; ///< 字符串数量
} LangPack;
设计亮点:
- 支持多种语言切换
- 字符串ID映射机制,便于管理和维护
- 自动回退到默认语言,提高系统容错性
- 优化的语言字符串查找算法
5. 核心算法
5.1 哈希表算法
功能:用于快速查找菜单节点,将节点查找时间复杂度从O(n)优化到O(1)
实现原理:
static uint16_t menu_core_hash_func(MenuNodeId node_id)
{
// 优化后的哈希函数:使用混合哈希算法,减少冲突,提高分布均匀性
// 结合了旋转和异或操作,适合嵌入式系统的高效计算
uint32_t hash = node_id;
// 旋转和异或操作,提高哈希分布的均匀性
hash = (hash << 15) | (hash >> 17); // 旋转15位
hash ^= 0x61C88647; // 黄金比例常数,提高随机性
hash *= 0x85EBca6B; // 另一个黄金比例常数
hash = (hash << 13) | (hash >> 19); // 旋转13位
hash ^= 0xC2B2AE35; // 再一次异或黄金比例常数
return (uint16_t)(hash % MENU_CONFIG_HASH_TABLE_SIZE);
}
设计亮点:
- 采用优化的混合哈希算法,减少冲突
- 结合旋转和异或操作,提高哈希分布的均匀性
- 适合嵌入式系统的高效计算,执行速度快
- 支持动态添加和删除节点,哈希表自动更新
5.2 事件队列算法
功能:实现基于优先级的事件处理机制,支持多优先级事件,队列满时自动替换低优先级事件
实现原理:
- 采用环形队列设计,避免内存碎片
- 支持4级事件优先级(低、普通、高、紧急)
- 同优先级事件采用FIFO顺序处理
- 队列满时,低优先级事件会被新的高优先级事件替换
- 事件包含时间戳,支持超时处理
关键函数:
menu_post_event:发送事件到事件队列menu_core_get_event:从事件队列获取下一个待处理事件menu_main_loop:菜单主循环,处理事件和刷新显示
5.3 状态机算法
功能:实现菜单的状态管理和状态转换,支持动态注册自定义状态转换规则
实现原理:
- 基于有限状态机(FSM)设计,定义了菜单的基本状态和状态转换规则
- 支持动态注册和注销自定义状态转换规则
- 状态转换时执行相应的动作函数,便于扩展菜单行为
- 支持状态查询和状态切换
内置状态:
MENU_STATE_INIT:初始化状态MENU_STATE_NORMAL:正常导航状态MENU_STATE_PARAM_EDIT:参数编辑状态MENU_STATE_CONFIRM:确认状态MENU_STATE_ERROR:错误状态
关键函数:
menu_state_register_transition:注册自定义状态转换规则menu_state_unregister_transition:注销自定义状态转换规则menu_state_get_current:获取当前状态menu_state_switch:切换到指定状态menu_state_transition:处理状态转换
5.4 菜单导航算法
功能:实现菜单的上下左右导航,支持多级菜单和栈管理
实现原理:
- 使用栈数据结构管理导航层级
- 支持环形导航(到达边界自动循环)
- 记录完整的导航路径,便于回溯
- 支持导航深度查询和路径获取
关键函数:
menu_core_navigate_enter:进入子菜单menu_core_navigate_back:返回上一级菜单menu_core_navigate_up:上移菜单项menu_core_navigate_down:下移菜单项menu_get_nav_path:获取导航路径menu_get_nav_depth:获取导航深度
6. 核心功能模块
6.1 菜单节点管理
功能:提供菜单节点的注册、删除、更新和查找功能
实现原理:
- 使用静态数组存储菜单节点,无动态内存分配
- 采用哈希表索引,提高节点查找效率
- 支持批量注册菜单节点,提高初始化效率
- 支持动态更新和删除菜单节点
关键函数:
menu_register_node:注册单个菜单节点menu_register_nodes:批量注册菜单节点menu_unregister_node:删除菜单节点menu_update_node:更新菜单节点menu_core_find_node:查找菜单节点
6.2 参数管理
功能:提供参数的注册、读写、范围检查和默认值恢复功能
实现原理:
- 支持多种参数类型(int8/uint8/int16/uint16/int32/uint32/float)
- 自动范围检查和边界处理
- 参数与菜单节点绑定,支持菜单直接调整参数
- 支持默认值恢复功能
关键函数:
menu_param_register:注册参数menu_param_set_value:设置参数值menu_param_get_value:获取参数值menu_param_get_type:获取参数类型
6.3 多语言支持
功能:提供多语言切换和字符串管理功能
实现原理:
- 支持多种语言切换
- 字符串ID映射机制,便于管理和维护
- 自动回退到默认语言,提高系统容错性
- 优化的语言字符串查找算法
关键函数:
menu_lang_set_current:设置当前语言menu_lang_get_current:获取当前语言menu_lang_register_str:注册语言字符串menu_lang_load_pack:加载语言包
6.4 Modbus映射
功能:实现参数与Modbus寄存器的双向映射
实现原理:
- 支持多种Modbus寄存器类型(线圈、离散输入、保持寄存器、输入寄存器)
- 支持多种字节序(小端、大端、Modbus标准)
- 自动类型转换和边界检查
- 灵活的读写权限控制
关键函数:
menu_modbus_map_register:注册参数与Modbus寄存器的映射关系menu_modbus_map_param_to_reg:参数值转换为Modbus寄存器数据menu_modbus_map_reg_to_param:Modbus寄存器数据转换为参数值
7. 硬件适配层
功能:提供硬件适配接口,对接具体硬件平台
实现原理:
- 定义统一的硬件驱动结构体,包含各种硬件接口
- 支持运行时配置硬件驱动,便于动态切换
- 屏蔽不同硬件平台的差异,提高组件的可移植性
硬件接口:
typedef struct {
void (*printf)(const char* fmt, va_list args); ///< 硬件打印接口
uint32_t (*get_tick)(void); ///< 获取系统滴答时间
void (*delay_ms)(uint32_t ms); ///< 硬件延迟函数
void (*display)(const char* menu_name, uint16_t menu_id); ///< 菜单显示接口
MenuEventType (*key_scan)(void); ///< 按键扫描接口(可选)
void (*irq_ctrl)(bool enable); ///< 中断管理接口(可选)
void (*error_handler)(MenuErrCode err_code); ///< 错误处理接口(可选)
MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); ///< Modbus发送接口(可选)
MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); ///< Modbus接收接口(可选)
} MenuPortDriver;
关键函数:
menu_port_init:硬件端口初始化menu_port_deinit:硬件端口反初始化
8. 配置管理
功能:提供灵活的配置选项,便于用户根据需求定制功能
实现原理:
- 所有配置项集中在
menu_config.h文件中 - 支持通过宏开关裁剪不需要的功能
- 支持调整资源大小,如最大节点数、栈深度、事件队列长度等
- 支持调试开关,便于开发和调试
核心配置项:
| 配置项 | 说明 | 默认值 |
|---|---|---|
| MENU_CONFIG_MAX_NODES | 最大菜单节点数 | 32 |
| MENU_CONFIG_HASH_TABLE_SIZE | 菜单哈希表大小(必须为质数) | 31 |
| MENU_CONFIG_STACK_DEPTH | 菜单栈深度 | 8 |
| MENU_CONFIG_EVENT_QUEUE_LEN | 事件队列长度 | 16 |
| MENU_CONFIG_ENABLE_ASSERT | 是否启用断言 | 1 |
| MENU_CONFIG_ENABLE_DEBUG | 是否启用调试打印 | 1 |
| MENU_CONFIG_ENABLE_MEM_MONITOR | 是否启用内存监控 | 1 |
| MENU_CONFIG_ENABLE_PARAM | 是否启用参数管理 | 1 |
| MENU_CONFIG_ENABLE_LANG | 是否启用多语言支持 | 1 |
| MENU_CONFIG_ENABLE_MODBUS_MAP | 是否启用Modbus映射 | 1 |
9. 工业级设计考量
9.1 可靠性设计
- 无动态内存分配:完全使用静态数组,避免内存碎片和内存泄漏
- 完善的错误处理:每个函数都有明确的错误码返回,便于调试和错误定位
- 边界检查:所有数组访问都有边界检查,防止越界访问
- 断言机制:关键函数入口有断言检查,便于调试
- 事件超时处理:避免无效事件占用系统资源
9.2 性能优化
- 高效的哈希表算法:将节点查找时间复杂度从O(n)优化到O(1)
- 位域设计:减少内存占用,提高缓存命中率
- 索引代替指针:减少内存占用,提高系统兼容性
- 事件驱动设计:提高系统响应性,减少CPU占用
- 模块化设计:支持功能裁剪,减少资源占用
9.3 可移植性设计
- 硬件抽象层:屏蔽不同硬件平台的差异,提高组件的可移植性
- 标准C语言实现:不依赖特定的编译器或操作系统
- 灵活的配置选项:支持根据不同硬件平台调整资源大小
- 模块化设计:各功能模块独立,便于移植和扩展
9.4 可维护性设计
- 清晰的代码结构:采用分层架构和模块化设计,代码结构清晰
- 完善的文档:提供详细的API文档和开发文档
- 统一的代码风格:采用清晰的命名规范和注释风格
- 调试支持:提供丰富的调试打印和日志功能
- 错误定位机制:每个错误都有明确的错误码和错误信息
10. 应用场景
本菜单组件适用于各种资源受限的嵌入式系统,特别是工业控制领域,如:
- 工业控制器
- 智能家居设备
- 仪器仪表
- 医疗设备
- 嵌入式人机界面
- Modbus从站设备
11. 开发流程
11.1 硬件适配
- 根据目标硬件平台,实现
menu_port.c中的硬件接口 - 配置
menu_config.h中的资源大小和功能开关 - 编译和测试硬件适配层
11.2 菜单设计
- 设计菜单结构,确定菜单节点的层级关系
- 注册菜单节点,构建菜单树
- 注册参数(可选)
- 注册Modbus映射(可选)
- 注册自定义状态转换规则(可选)
11.3 应用集成
- 在应用程序中初始化菜单组件
- 在主循环中调用
menu_main_loop处理事件 - 调用
menu_post_event发送硬件事件(如按键事件) - 根据需要调用其他API接口,实现自定义功能
12. 测试与验证
12.1 单元测试
项目提供了单元测试框架,位于test/menu_test.c,包含以下测试用例:
- 菜单初始化和反初始化测试
- 菜单节点注册和删除测试
- 菜单导航测试
- 事件队列测试
- 参数管理测试
- 多语言支持测试
- Modbus映射测试
12.2 集成测试
- 硬件适配测试:验证硬件接口的正确性
- 功能测试:验证各个功能模块的正确性
- 性能测试:测试菜单组件的执行效率和资源占用
- 可靠性测试:测试菜单组件在各种异常情况下的表现
12.3 现场测试
- 在目标硬件平台上进行测试,验证菜单组件的实际运行效果
- 测试各种用户操作场景,确保菜单组件的易用性和稳定性
- 测试长时间运行情况下的稳定性
13. 版本管理
| 版本 | 发布日期 | 主要变更 |
|---|---|---|
| v1.0.0 | 2025-12-19 | 初始版本,包含核心功能 |
14. 总结
本项目是一个设计精良的工业级嵌入式菜单组件,具有高可靠性、高性能、高可移植性和高可扩展性等特点。采用分层架构、事件驱动设计、哈希表索引、可扩展状态机等先进技术,适合各种资源受限的嵌入式系统。
通过本开发文档,开发者可以全面了解菜单组件的架构设计、核心思想、数据算法和使用方法,便于快速集成和定制开发。