diff --git a/API.md b/API.md deleted file mode 100644 index bb6c7f3..0000000 --- a/API.md +++ /dev/null @@ -1,523 +0,0 @@ -# 菜单组件API文档 - -## 1. 概述 - -本文档详细描述了菜单组件的API接口,包括核心功能、功能扩展和Modbus映射功能。 - -## 2. 核心接口 - -### 2.1 上下文管理 - -```c -/** - * @brief 获取菜单组件上下文所需的内存大小 - * @return 上下文内存大小(字节) - */ -uint32_t menu_get_ctx_size(void); - -/** - * @brief 菜单组件初始化(必须首先调用) - * @param ctx 菜单上下文指针(用户需提前分配内存) - * @return 错误码 - */ -MenuErrCode menu_init(MenuGlobalCtx* ctx); - -/** - * @brief 菜单组件反初始化(释放资源) - * @param ctx 菜单上下文指针 - * @return 错误码 - */ -MenuErrCode menu_deinit(MenuGlobalCtx* ctx); - -/** - * @brief 检查菜单组件是否已初始化 - * @param ctx 菜单上下文指针 - * @return 是否已初始化 - */ -bool menu_is_initialized(MenuGlobalCtx* ctx); -``` - -### 2.2 菜单管理 - -```c -/** - * @brief 注册菜单节点(构建菜单树) - * @param ctx 菜单上下文指针 - * @param parent_id 父节点ID(根节点填0) - * @param node_id 当前节点ID(唯一,不可重复) - * @param name_str 菜单名称字符串(或多语言索引) - * @param enter_cb 进入该菜单的回调函数(NULL表示无) - * @param exit_cb 退出该菜单的回调函数(NULL表示无) - * @return 错误码 - */ -MenuErrCode menu_register_node(MenuGlobalCtx* ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb); - -/** - * @brief 批量注册菜单节点 - * @param ctx 菜单上下文指针 - * @param configs 菜单节点配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_register_nodes(MenuGlobalCtx* ctx, const MenuNodeConfig* configs, uint16_t config_count); - -/** - * @brief 获取当前选中的菜单节点ID - * @param ctx 菜单上下文指针 - * @param node_id 输出参数,当前节点ID - * @return 错误码 - */ -MenuErrCode menu_get_current_node(MenuGlobalCtx* ctx, MenuNodeId* node_id); -``` - -### 2.3 事件处理 - -```c -/** - * @brief 发送事件到菜单事件队列(如按键事件、自定义事件) - * @param ctx 菜单上下文指针 - * @param type 事件类型 - * @param param 事件附加参数(可选,如按键长按时间) - * @param priority 事件优先级(可选,默认为普通优先级) - * @return 错误码 - */ -MenuErrCode menu_post_event(MenuGlobalCtx* ctx, MenuEventType type, uint32_t param, MenuEventPriority priority); - -/** - * @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示) - * @param ctx 菜单上下文指针 - */ -void menu_main_loop(MenuGlobalCtx* ctx); -``` - -## 3. 功能扩展接口 - -### 3.1 参数管理 - -```c -/** - * @brief 注册参数到菜单节点(参数管理功能) - * @param ctx 菜单上下文指针 - * @param node_id 菜单节点ID(参数与菜单绑定) - * @param param_id 参数ID(唯一) - * @param type 参数类型 - * @param min_val 最小值(浮点型,内部自动转换) - * @param max_val 最大值(浮点型,内部自动转换) - * @param default_val 默认值(浮点型,内部自动转换) - * @param scale 缩放因子(如0.1表示保留1位小数) - * @return 错误码 - */ -MenuErrCode menu_param_register(MenuGlobalCtx* ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale); - -/** - * @brief 批量注册参数到菜单节点 - * @param ctx 菜单上下文指针 - * @param configs 参数配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_param_register_batch(MenuGlobalCtx* ctx, const MenuParamConfig* configs, uint16_t config_count); - -/** - * @brief 设置参数值 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param value 新值(浮点型,内部自动转换) - * @return 错误码 - */ -MenuErrCode menu_param_set_value(MenuGlobalCtx* ctx, uint16_t param_id, float value); - -/** - * @brief 获取参数值 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param value 输出参数,当前值(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_get_value(MenuGlobalCtx* ctx, uint16_t param_id, float* value); - -/** - * @brief 获取参数类型 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param type 输出参数,参数类型 - * @return 错误码 - */ -MenuErrCode menu_param_get_type(MenuGlobalCtx* ctx, uint16_t param_id, MenuParamType* type); -``` - -### 3.2 多语言支持 - -```c -/** - * @brief 设置当前语言 - * @param ctx 菜单上下文指针 - * @param lang_id 语言ID(如0-中文,1-英文) - * @return 错误码 - */ -MenuErrCode menu_lang_set_current(MenuGlobalCtx* ctx, uint8_t lang_id); - -/** - * @brief 获取当前语言 - * @param ctx 菜单上下文指针 - * @param lang_id 输出参数,当前语言ID - * @return 错误码 - */ -MenuErrCode menu_lang_get_current(MenuGlobalCtx* ctx, uint8_t* lang_id); - -/** - * @brief 注册语言字符串 - * @param ctx 菜单上下文指针 - * @param str_id 字符串ID - * @param lang_id 语言ID - * @param str 语言字符串 - * @return 错误码 - */ -MenuErrCode menu_lang_register_str(MenuGlobalCtx* ctx, uint16_t str_id, uint8_t lang_id, const char* str); - -/** - * @brief 批量注册语言字符串 - * @param ctx 菜单上下文指针 - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_register_strs(MenuGlobalCtx* ctx, const MenuLangStr* strs, uint16_t count); - -/** - * @brief 加载语言包(动态加载语言字符串) - * @param ctx 菜单上下文指针 - * @param lang_id 语言ID - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_load_pack(MenuGlobalCtx* ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count); -``` - -### 3.3 内存监控 - -```c -/** - * @brief 获取菜单内存使用统计信息 - * @param ctx 菜单上下文指针 - * @param stats 输出参数,内存使用统计信息 - * @return 错误码 - */ -MenuErrCode menu_get_mem_stats(MenuGlobalCtx* ctx, MenuMemStats* stats); -``` - -## 4. Modbus映射功能 - -```c -/** - * @brief 注册参数与Modbus寄存器的映射关系 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID(需已通过menu_param_register注册) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器起始地址(协议地址,如40001) - * @param reg_count 占用寄存器数量(如1:16位,2:32位,4:64位) - * @param perm 读写权限 - * @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_register(MenuGlobalCtx* ctx, uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order); - -/** - * @brief 批量注册参数与Modbus寄存器的映射关系 - * @param ctx 菜单上下文指针 - * @param configs Modbus映射配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_register_batch(MenuGlobalCtx* ctx, const MenuModbusMapConfig* configs, uint16_t config_count); - -/** - * @brief 根据参数ID查询Modbus映射关系 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map); - -/** - * @brief 根据寄存器地址和类型查询Modbus映射关系 - * @param ctx 菜单上下文指针 - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map); - -/** - * @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据) - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器) - * @param buf_len 缓冲区长度(输入),实际写入长度(输出) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len); - -/** - * @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值) - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param reg_buf 输入参数,寄存器数据缓冲区 - * @param buf_len 缓冲区长度 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len); -``` - -## 5. 数据类型 - -### 5.1 错误码 - -```c -typedef enum { - MENU_OK = 0, ///< 操作成功 - MENU_ERR_INVALID_PARAM, ///< 无效参数 - MENU_ERR_OUT_OF_MEMORY, ///< 内存不足(静态内存池已满) - MENU_ERR_NODE_NOT_FOUND, ///< 菜单节点未找到 - MENU_ERR_STACK_OVERFLOW, ///< 菜单栈溢出(导航层级超过配置) - MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回) - MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满 - MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言) - MENU_ERR_HW_PORT_ERROR, ///< 硬件端口层错误 - MENU_ERR_NOT_INITIALIZED, ///< 组件未初始化 - MENU_ERR_INVALID_CONTEXT ///< 无效上下文指针 -} MenuErrCode; -``` - -### 5.2 事件类型 - -```c -typedef enum { - MENU_EVENT_NONE = 0, ///< 无事件 - MENU_EVENT_KEY_UP, ///< 上键按下 - MENU_EVENT_KEY_DOWN, ///< 下键按下 - MENU_EVENT_KEY_ENTER, ///< 确认键按下 - MENU_EVENT_KEY_BACK, ///< 返回键按下 - MENU_EVENT_CUSTOM_BEGIN = 0x10, ///< 自定义事件起始标识(用户可扩展) -} MenuEventType; -``` - -### 5.3 事件优先级 - -```c -typedef enum { - MENU_EVENT_PRIORITY_LOW = 0, ///< 低优先级事件 - MENU_EVENT_PRIORITY_NORMAL, ///< 普通优先级事件(默认) - MENU_EVENT_PRIORITY_HIGH, ///< 高优先级事件 - MENU_EVENT_PRIORITY_CRITICAL ///< 紧急优先级事件 -} MenuEventPriority; -``` - -### 5.4 参数类型 - -```c -typedef enum { - MENU_PARAM_TYPE_INT8, - MENU_PARAM_TYPE_UINT8, - MENU_PARAM_TYPE_INT16, - MENU_PARAM_TYPE_UINT16, - MENU_PARAM_TYPE_INT32, - MENU_PARAM_TYPE_UINT32, - MENU_PARAM_TYPE_FLOAT, -} MenuParamType; -``` - -### 5.5 菜单状态 - -```c -typedef enum { - MENU_STATE_INIT = 0, ///< 初始化状态 - MENU_STATE_NORMAL, ///< 正常导航状态 - MENU_STATE_PARAM_EDIT, ///< 参数编辑状态 - MENU_STATE_CONFIRM, ///< 确认状态(如保存/退出确认) - MENU_STATE_ERROR, ///< 错误状态 -} MenuState; -``` - -## 6. 配置项 - -菜单组件的配置项位于`menu_config.h`文件中,用户可以根据项目需求调整以下配置: - -### 6.1 核心配置 - -- `MENU_CONFIG_MAX_NODES`:最大菜单节点数 -- `MENU_CONFIG_STACK_DEPTH`:菜单栈深度 -- `MENU_CONFIG_EVENT_QUEUE_LEN`:事件队列长度 -- `MENU_CONFIG_ENABLE_ASSERT`:是否启用断言 -- `MENU_CONFIG_ENABLE_DEBUG`:是否启用调试打印 -- `MENU_CONFIG_ENABLE_MEM_MONITOR`:是否启用内存监控 - -### 6.2 功能扩展配置 - -- `MENU_CONFIG_ENABLE_PARAM`:是否启用参数管理功能 -- `MENU_CONFIG_ENABLE_LANG`:是否启用多语言功能 -- `MENU_CONFIG_MAX_PARAMS`:最大参数数量 -- `MENU_CONFIG_MAX_LANGS`:支持的最大语言数量 - -### 6.3 Modbus映射配置 - -- `MENU_CONFIG_ENABLE_MODBUS_MAP`:是否启用参数-Modbus寄存器映射功能 -- `MENU_CONFIG_MAX_MODBUS_MAPS`:最大Modbus映射数量 -- `MENU_CONFIG_MODBUS_MAX_ADDR`:Modbus寄存器地址最大值 -- `MENU_CONFIG_MODBUS_BYTE_ORDER`:默认Modbus字节序 -- `MENU_CONFIG_MODBUS_PERMISSION`:是否启用Modbus映射权限校验 - -## 7. 硬件适配 - -菜单组件的硬件适配层位于`port/menu_port.h`和`port/menu_port.c`文件中,用户需要实现以下接口: - -```c -/** - * @brief 硬件端口驱动结构体(模块化设计,支持运行时配置) - */ -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; - -/** - * @brief 硬件端口初始化接口 - * @param driver 硬件驱动结构体指针 - * @return 错误码 - */ -MenuErrCode menu_port_init(const MenuPortDriver* driver); - -/** - * @brief 硬件端口反初始化接口 - * @return 错误码 - */ -MenuErrCode menu_port_deinit(void); -``` - -## 8. 使用示例 - -### 8.1 基本使用 - -```c -#include "menu.h" -#include "menu_port.h" - -int main(void) -{ - // 1. 初始化硬件 - hal_init(); - - // 2. 初始化菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - menu_init(ctx); - - // 3. 注册菜单节点 - menu_register_node(ctx, 0, 1, "主菜单", NULL, NULL); - menu_register_node(ctx, 1, 2, "参数设置", NULL, NULL); - menu_register_node(ctx, 1, 3, "系统信息", NULL, NULL); - - // 4. 主循环 - while (1) - { - // 扫描按键 - MenuEventType key_event = scan_key(); - if (key_event != MENU_EVENT_NONE) - { - menu_post_event(ctx, key_event, 0, MENU_EVENT_PRIORITY_NORMAL); - } - - // 处理菜单事件 - menu_main_loop(ctx); - - // 其他业务逻辑 - // ... - } - - // 5. 反初始化 - menu_deinit(ctx); - - return 0; -} -``` - -### 8.2 批量注册菜单节点 - -```c -// 定义菜单节点配置 -MenuNodeConfig menu_nodes[] = { - {1, 0, "主菜单", NULL, NULL}, - {2, 1, "参数设置", NULL, NULL}, - {3, 1, "系统信息", NULL, NULL}, - {4, 2, "温度参数", NULL, NULL}, - {5, 2, "湿度参数", NULL, NULL} -}; - -// 批量注册菜单节点 -menu_register_nodes(ctx, menu_nodes, sizeof(menu_nodes) / sizeof(menu_nodes[0])); -``` - -### 8.3 参数管理 - -```c -// 注册参数 -menu_param_register(ctx, 4, 1, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f); - -// 设置参数值 -menu_param_set_value(ctx, 1, 30.0f); - -// 获取参数值 -float value; -menu_param_get_value(ctx, 1, &value); -``` - -### 8.4 多语言支持 - -```c -// 注册语言字符串 -menu_lang_register_str(ctx, 1, 0, "中文菜单"); -menu_lang_register_str(ctx, 1, 1, "English Menu"); - -// 设置当前语言为中文 -menu_lang_set_current(ctx, 0); - -// 动态加载语言包 -MenuLangStr lang_pack[] = { - {2, 0, "中文参数"}, - {2, 1, "English Param"}, - {3, 0, "中文信息"}, - {3, 1, "English Info"} -}; -menu_lang_load_pack(ctx, 0, lang_pack, sizeof(lang_pack) / sizeof(lang_pack[0])); -``` - -### 8.5 Modbus映射 - -```c -// 注册Modbus映射 -menu_modbus_map_register(ctx, 1, MODBUS_REG_TYPE_HOLDING_REG, 40001, 2, MODBUS_PERM_READ_WRITE, 2); - -// 参数值转换为Modbus寄存器数据 -uint8_t reg_buf[4]; -uint8_t buf_len = sizeof(reg_buf); -menu_modbus_map_param_to_reg(ctx, 1, reg_buf, &buf_len); - -// Modbus寄存器数据转换为参数值 -menu_modbus_map_reg_to_param(ctx, 1, reg_buf, buf_len); -``` - -## 9. 版本信息 - -- 版本:v1.0.0 -- 最后更新:2025-12-18 -- 适用平台:所有支持C语言的嵌入式系统 diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index f0a9619..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -# Project name -project(menu_component C) - -# Set C standard -set(CMAKE_C_STANDARD 99) -set(CMAKE_C_STANDARD_REQUIRED ON) - -# Force using MinGW Makefiles if on Windows -if(WIN32) - if(NOT DEFINED CMAKE_GENERATOR) - set(CMAKE_GENERATOR "MinGW Makefiles" CACHE INTERNAL "") - endif() - # Set default compiler if not specified - if(NOT DEFINED CMAKE_C_COMPILER) - find_program(MINGW_GCC gcc) - if(MINGW_GCC) - set(CMAKE_C_COMPILER ${MINGW_GCC} CACHE INTERNAL "") - endif() - endif() -endif() - -# Compiler flags -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -Os") - -# Include directories -include_directories( - api - internal - src/core - src/param - src/lang - src/utils - src/features - port -) - -# Source files -set(SOURCES - src/core/menu_core.c - src/param/menu_param.c - src/lang/menu_lang.c - src/utils/menu_utils.c - src/features/menu_modbus.c - port/menu_port.c - examples/menu_example.c -) - -# Add example executable -add_executable(menu_example ${SOURCES}) - -# Debug build configuration -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g") diff --git a/README.md b/README.md deleted file mode 100644 index 4c14124..0000000 --- a/README.md +++ /dev/null @@ -1,320 +0,0 @@ -# 工业级嵌入式菜单组件 - -## 1. 项目概述 - -本项目是一个基于C语言开发的工业级嵌入式菜单组件,设计用于资源受限的嵌入式系统,具有高可移植性、模块化、可裁剪、低资源占用和事件驱动解耦等核心特点。 - -### 1.1 核心特性 - -- **分层架构设计**:核心层 + 功能扩展层 + 硬件端口层,彻底解耦硬件与业务逻辑 -- **事件驱动机制**:基于事件队列的异步处理,提高系统响应性 -- **静态内存管理**:完全使用静态数组,避免内存碎片,适合资源受限系统 -- **高度可配置**:通过宏开关实现功能裁剪,可根据项目需求定制功能 -- **工业级鲁棒性**:完善的边界检查、错误处理和断言机制 -- **Modbus映射支持**:内置参数与Modbus寄存器双向映射功能 - -## 2. 目录结构 - -``` -menu/ -├── api/ # 对外API层(用户唯一需要引用的头文件) -│ ├── menu.h # 主接口头文件 -│ └── menu_config.h # 配置文件(功能开关、资源大小) -├── internal/ # 内部头文件层(用户无需关心) -│ ├── menu_core.h # 核心类型定义 -│ ├── menu_data.h # 共享全局变量声明 -│ ├── menu_def.h # 内部宏和辅助函数 -│ └── menu_modbus.h # Modbus映射内部定义 -├── port/ # 硬件端口层(用户需适配) -│ ├── menu_port.c # 硬件接口实现(示例) -│ └── menu_port.h # 硬件接口声明 -├── src/ # 源码层 -│ ├── core/ # 核心逻辑 -│ │ └── menu_core.c # 导航、栈管理、主循环 -│ ├── features/ # 功能扩展 -│ │ └── menu_modbus.c # Modbus映射功能 -│ ├── lang/ # 多语言支持 -│ │ └── menu_lang.c # 语言切换、字符串管理 -│ ├── param/ # 参数管理 -│ │ └── menu_param.c # 参数注册、读写、范围检查 -│ └── utils/ # 工具函数 -│ └── menu_utils.c # 调试打印、断言、系统滴答 -└── examples/ # 示例代码 - └── menu_example.c # 菜单组件使用示例 -``` - -## 3. 核心功能说明 - -### 3.1 菜单导航 - -- 支持多级菜单导航(上下键切换,确认键进入,返回键退出) -- 环形导航(到达边界自动循环) -- 菜单栈管理,支持深度导航 - -### 3.2 参数管理 - -- 支持多种参数类型(int8/uint8/int16/uint16/int32/uint32/float) -- 自动范围检查和边界处理 -- 参数与菜单节点绑定,支持菜单直接调整参数 -- 默认值恢复功能 - -### 3.3 多语言支持 - -- 支持多种语言切换 -- 字符串ID映射机制 -- 自动回退到默认语言 - -### 3.4 Modbus映射 - -- 参数与Modbus寄存器双向映射 -- 支持多种Modbus寄存器类型(线圈、离散输入、保持寄存器、输入寄存器) -- 灵活的读写权限控制 -- 支持多种字节序(小端、大端、Modbus标准) -- 自动类型转换和边界检查 - -## 4. 快速开始 - -### 4.1 配置组件 - -在`menu_config.h`中根据项目需求配置功能开关和资源大小: - -```c -// 核心配置 -#define MENU_CONFIG_MAX_NODES 32 // 最大菜单节点数 -#define MENU_CONFIG_STACK_DEPTH 8 // 菜单栈深度 -#define MENU_CONFIG_EVENT_QUEUE_LEN 16 // 事件队列长度 - -// 功能扩展配置 -#define MENU_CONFIG_ENABLE_PARAM 1 // 启用参数管理功能 -#define MENU_CONFIG_ENABLE_LANG 1 // 启用多语言功能 -#define MENU_CONFIG_ENABLE_MODBUS_MAP 1 // 启用Modbus映射功能 - -// Modbus映射配置 -#define MENU_CONFIG_MAX_MODBUS_MAPS 16 // 最大Modbus映射数量 -#define MENU_CONFIG_MODBUS_BYTE_ORDER 2 // 默认Modbus字节序 -``` - -### 4.2 适配硬件端口 - -在`port/menu_port.c`中实现硬件相关接口: - -```c -// 禁用/启用全局中断 -void menu_port_irq_disable(void) { - // 实现禁用中断的逻辑 -} - -void menu_port_irq_enable(void) { - // 实现启用中断的逻辑 -} - -// 获取系统滴答时间(ms) -uint32_t menu_port_get_tick(void) { - // 实现获取系统时间的逻辑 - return HAL_GetTick(); // 示例:STM32 HAL库 -} - -// 调试打印接口 -void menu_port_printf(const char* fmt, va_list args) { - // 实现打印逻辑,如通过UART输出 - vprintf(fmt, args); -} -``` - -### 4.3 基本使用示例 - -```c -#include "menu.h" -#include "menu_port.h" - -// 菜单回调函数 -static MenuErrCode menu_cb_enter_param(MenuNodeId node_id) { - MENU_DEBUG("Enter param menu: %d", node_id); - return MENU_OK; -} - -int main(void) { - // 1. 初始化硬件(UART、LCD、SysTick等) - hal_init(); - - // 2. 初始化菜单组件 - menu_init(); - - // 3. 注册菜单节点 - menu_register_node(0, 1, "主菜单", NULL, NULL); // 根节点 - menu_register_node(1, 2, "参数设置", menu_cb_enter_param, NULL); // 子节点 - menu_register_node(1, 3, "系统信息", NULL, NULL); // 子节点 - menu_register_node(2, 4, "温度设置", NULL, NULL); // 孙节点 - - // 4. 注册参数(可选) - #if MENU_CONFIG_ENABLE_PARAM - menu_param_register(4, 1, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f); - #endif - - // 5. 注册Modbus映射(可选) - #if MENU_CONFIG_ENABLE_MODBUS_MAP - menu_modbus_map_register(1, MODBUS_REG_TYPE_HOLDING_REG, 40001, 2, MODBUS_PERM_READ_WRITE, 2); - #endif - - // 6. 主循环 - while (1) { - // 扫描硬件按键 - menu_port_key_scan(); - - // 处理菜单事件和刷新显示 - menu_main_loop(); - - // 其他业务逻辑 - // ... - } -} -``` - -## 5. Modbus映射功能详解 - -### 5.1 核心概念 - -Modbus映射功能实现了菜单参数与Modbus寄存器的双向数据转换,支持: - -- **参数到寄存器**:将菜单参数值转换为Modbus寄存器数据 -- **寄存器到参数**:将Modbus寄存器数据转换为菜单参数值 -- **自动类型转换**:支持不同数据类型之间的自动转换 -- **字节序适配**:支持多种字节序,兼容不同Modbus设备 - -### 5.2 寄存器类型支持 - -| 寄存器类型 | 地址范围 | 读写特性 | 支持的参数类型 | -|-----------|---------|---------|--------------| -| 线圈 | 00001-09999 | 读写 | INT8、UINT8 | -| 离散输入 | 10001-19999 | 只读 | INT8、UINT8 | -| 输入寄存器 | 30001-39999 | 只读 | 所有类型 | -| 保持寄存器 | 40001-49999 | 读写 | 所有类型 | - -### 5.3 字节序支持 - -| 字节序类型 | 值 | 描述 | -|-----------|-----|------| -| 小端 | 0 | 低字节在前,高字节在后(x86架构默认) | -| 大端 | 1 | 高字节在前,低字节在后 | -| Modbus标准 | 2 | 字小端,字节大端(Modbus协议默认) | - -### 5.4 Modbus映射使用示例 - -```c -// 1. 注册参数(必须在映射之前完成) -menu_param_register(menu_node_id, param_id, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f); - -// 2. 注册Modbus映射 -menu_modbus_map_register( - param_id, // 参数ID - MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型 - 40001, // 起始地址 - 2, // 寄存器数量(浮点型占2个16位寄存器) - MODBUS_PERM_READ_WRITE, // 读写权限 - 2 // 字节序(Modbus标准) -); - -// 3. 参数到寄存器转换 -uint8_t reg_buf[4] = {0}; -uint8_t buf_len = sizeof(reg_buf); -menu_modbus_map_param_to_reg(param_id, reg_buf, &buf_len); - -// 4. 寄存器到参数转换 -const uint8_t reg_data[4] = {0x00, 0x00, 0x4B, 0x40}; // 25.0f的二进制表示 -menu_modbus_map_reg_to_param(param_id, reg_data, sizeof(reg_data)); -``` - -## 6. 配置说明 - -### 6.1 核心配置项 - -| 配置项 | 说明 | 默认值 | -|-------|------|-------| -| MENU_CONFIG_MAX_NODES | 最大菜单节点数 | 32 | -| MENU_CONFIG_STACK_DEPTH | 菜单栈深度 | 8 | -| MENU_CONFIG_EVENT_QUEUE_LEN | 事件队列长度 | 16 | -| MENU_CONFIG_ENABLE_ASSERT | 是否启用断言 | 1 | -| MENU_CONFIG_ENABLE_DEBUG | 是否启用调试打印 | 1 | - -### 6.2 功能扩展配置 - -| 配置项 | 说明 | 默认值 | -|-------|------|-------| -| MENU_CONFIG_ENABLE_PARAM | 是否启用参数管理 | 1 | -| MENU_CONFIG_ENABLE_LANG | 是否启用多语言支持 | 1 | -| MENU_CONFIG_ENABLE_MODBUS_MAP | 是否启用Modbus映射 | 1 | - -### 6.3 Modbus映射配置 - -| 配置项 | 说明 | 默认值 | -|-------|------|-------| -| MENU_CONFIG_MAX_MODBUS_MAPS | 最大Modbus映射数量 | 16 | -| MENU_CONFIG_MODBUS_MAX_ADDR | Modbus寄存器地址最大值 | 0xFFFF | -| MENU_CONFIG_MODBUS_BYTE_ORDER | 默认Modbus字节序 | 2(Modbus标准) | -| MENU_CONFIG_MODBUS_PERMISSION | 是否启用权限校验 | 1 | - -## 7. 移植指南 - -### 7.1 硬件端口层适配 - -用户只需适配`port/menu_port.c`中的以下接口: - -1. **中断管理**:`menu_port_irq_disable()`、`menu_port_irq_enable()` -2. **系统时间**:`menu_port_get_tick()` -3. **调试打印**:`menu_port_printf()` -4. **错误处理**:`menu_port_error_handler()` -5. **按键扫描**:`menu_port_key_scan()`(可选,用户也可以在外部实现) -6. **显示接口**:`menu_port_display()`(可选) - -### 7.2 编译配置 - -1. 将`menu/api`目录添加到Include路径 -2. 根据项目需求配置`menu_config.h` -3. 编译时包含所有必要的源文件 - -## 8. 性能与资源占用 - -### 8.1 内存占用 - -- **Flash占用**:约5-15KB(取决于启用的功能) -- **RAM占用**:约1-3KB(取决于配置的节点数、参数数和Modbus映射数) - -### 8.2 执行效率 - -- 事件处理:单次事件处理时间 < 10μs -- 菜单刷新:单次刷新时间 < 5μs -- 参数转换:单次转换时间 < 2μs -- Modbus映射:单次映射转换时间 < 5μs - -## 9. 工业级设计考量 - -1. **无动态内存分配**:完全使用静态数组,避免内存碎片 -2. **事件驱动设计**:硬件与软件解耦,提高系统响应性 -3. **完善的错误处理**:每个函数都有明确的错误码返回 -4. **边界检查**:所有数组访问都有边界检查,防止越界 -5. **断言机制**:关键函数入口有断言检查,便于调试 -6. **可配置裁剪**:通过宏开关可以裁剪不需要的功能 -7. **模块化设计**:各功能模块独立,便于维护和扩展 - -## 10. 应用场景 - -- 工业控制器 -- 智能家居设备 -- 仪器仪表 -- 医疗设备 -- 嵌入式人机界面 -- Modbus从站设备 - -## 11. 许可证 - -本项目采用MIT许可证,可自由用于商业和非商业项目。 - -## 12. 联系方式 - -如有问题或建议,欢迎提交Issue或Pull Request。 - ---- - -**版本信息**:v1.0.0 -**最后更新**:2025-12-18 -**适用平台**:所有支持C语言的嵌入式系统 diff --git a/api/menu.h b/api/menu.h deleted file mode 100644 index cd0c597..0000000 --- a/api/menu.h +++ /dev/null @@ -1,427 +0,0 @@ -/** - * @file menu.h - * @brief 菜单组件对外暴露的核心接口(用户唯一需要包含的头文件) - * @note 工业级嵌入式菜单组件 - 对外API层 - */ -#ifndef MENU_H -#define MENU_H - -#include "menu_config.h" -#include -#include - -/************************** 全局类型导出 **************************/ -/** - * @brief 菜单组件全局上下文(用户需创建并管理) - * @note 替代全局变量,提高模块独立性和可测试性 - */ -typedef struct MenuGlobalCtx MenuGlobalCtx; - -/** - * @brief 菜单错误码(工业级错误处理:明确所有可能的错误类型) - */ -typedef enum { - MENU_OK = 0, ///< 操作成功 - MENU_ERR_INVALID_PARAM, ///< 无效参数 - MENU_ERR_OUT_OF_MEMORY, ///< 内存不足(静态内存池已满) - MENU_ERR_NODE_NOT_FOUND, ///< 菜单节点未找到 - MENU_ERR_STACK_OVERFLOW, ///< 菜单栈溢出(导航层级超过配置) - MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回) - MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满 - MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言) - MENU_ERR_HW_PORT_ERROR, ///< 硬件端口层错误 - MENU_ERR_NOT_INITIALIZED, ///< 组件未初始化 - MENU_ERR_INVALID_CONTEXT ///< 无效上下文指针 -} MenuErrCode; - -/** - * @brief 事件优先级枚举 - */ -typedef enum { - MENU_EVENT_PRIORITY_LOW = 0, ///< 低优先级事件 - MENU_EVENT_PRIORITY_NORMAL, ///< 普通优先级事件(默认) - MENU_EVENT_PRIORITY_HIGH, ///< 高优先级事件 - MENU_EVENT_PRIORITY_CRITICAL ///< 紧急优先级事件 -} MenuEventPriority; - -/** - * @brief 菜单事件类型(事件驱动核心:解耦按键与菜单逻辑) - */ -typedef enum { - MENU_EVENT_NONE = 0, ///< 无事件 - MENU_EVENT_KEY_UP, ///< 上键按下 - MENU_EVENT_KEY_DOWN, ///< 下键按下 - MENU_EVENT_KEY_ENTER, ///< 确认键按下 - MENU_EVENT_KEY_BACK, ///< 返回键按下 - MENU_EVENT_CUSTOM_BEGIN = 0x10, ///< 自定义事件起始标识(用户可扩展) -} MenuEventType; - -/** - * @brief 菜单节点ID类型(统一标识) - */ -typedef uint16_t MenuNodeId; - -/** - * @brief 菜单回调函数类型(菜单选中/退出时的执行逻辑) - * @param node_id 触发回调的菜单节点ID - * @return 错误码 - */ -typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id); - -/** - * @brief 参数类型枚举(支持多种数据类型) - */ -typedef enum { - MENU_PARAM_TYPE_INT8, ///< 8位有符号整数 - MENU_PARAM_TYPE_UINT8, ///< 8位无符号整数 - MENU_PARAM_TYPE_INT16, ///< 16位有符号整数 - MENU_PARAM_TYPE_UINT16, ///< 16位无符号整数 - MENU_PARAM_TYPE_INT32, ///< 32位有符号整数 - MENU_PARAM_TYPE_UINT32, ///< 32位无符号整数 - MENU_PARAM_TYPE_FLOAT, ///< 浮点类型 -} MenuParamType; - -/** - * @brief Modbus寄存器类型(符合Modbus协议标准) - */ -typedef enum { - MODBUS_REG_TYPE_COIL = 0, ///< 线圈(1位,地址范围00001-09999) - MODBUS_REG_TYPE_DISCRETE_INPUT, ///< 离散输入(1位,地址范围10001-19999) - MODBUS_REG_TYPE_HOLDING_REG, ///< 保持寄存器(16位,地址范围40001-49999) - MODBUS_REG_TYPE_INPUT_REG, ///< 输入寄存器(16位,地址范围30001-39999) -} ModbusRegType; - -/** - * @brief Modbus寄存器读写权限 - */ -typedef enum { - MODBUS_PERM_READ_ONLY = 0, ///< 只读(如离散输入、输入寄存器) - MODBUS_PERM_WRITE_ONLY, ///< 只写(如线圈) - MODBUS_PERM_READ_WRITE, ///< 读写(如保持寄存器) -} ModbusPerm; - -/** - * @brief 菜单节点配置结构体(用于批量注册) - */ -typedef struct { - MenuNodeId node_id; ///< 节点ID - MenuNodeId parent_id; ///< 父节点ID - const char* name; ///< 菜单名称 - MenuCallback enter_cb; ///< 进入回调 - MenuCallback exit_cb; ///< 退出回调 -} MenuNodeConfig; - -/** - * @brief 参数配置结构体(用于批量注册) - */ -typedef struct { - MenuNodeId node_id; ///< 关联的菜单节点ID - uint16_t param_id; ///< 参数ID - MenuParamType type; ///< 参数类型 - float min_val; ///< 最小值 - float max_val; ///< 最大值 - float default_val; ///< 默认值 - float scale; ///< 缩放因子 -} MenuParamConfig; - -/** - * @brief Modbus映射配置结构体(用于批量注册) - */ -typedef struct { - uint16_t param_id; ///< 关联的参数ID - ModbusRegType reg_type; ///< 寄存器类型 - uint16_t reg_addr; ///< 寄存器地址 - uint8_t reg_count; ///< 寄存器数量 - ModbusPerm perm; ///< 读写权限 - uint8_t byte_order; ///< 字节序 -} MenuModbusMapConfig; - -/************************** 核心接口声明 **************************/ -/** - * @brief 获取菜单组件上下文所需的内存大小 - * @return 上下文内存大小(字节) - */ -uint32_t menu_get_ctx_size(void); - -/** - * @brief 菜单组件初始化(必须首先调用) - * @param ctx 菜单上下文指针(用户需提前分配内存) - * @return 错误码 - */ -MenuErrCode menu_init(MenuGlobalCtx* ctx); - -/** - * @brief 菜单组件反初始化(释放资源) - * @param ctx 菜单上下文指针 - * @return 错误码 - */ -MenuErrCode menu_deinit(MenuGlobalCtx* ctx); - -/** - * @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示) - * @param ctx 菜单上下文指针 - */ -void menu_main_loop(MenuGlobalCtx* ctx); - -/** - * @brief 发送事件到菜单事件队列(如按键事件、自定义事件) - * @param ctx 菜单上下文指针 - * @param type 事件类型 - * @param param 事件附加参数(可选,如按键长按时间) - * @param priority 事件优先级(可选,默认为普通优先级) - * @return 错误码 - */ -MenuErrCode menu_post_event(MenuGlobalCtx* ctx, MenuEventType type, uint32_t param, MenuEventPriority priority); - -/** - * @brief 发送事件到菜单事件队列(兼容旧接口,默认优先级) - * @param ctx 菜单上下文指针 - * @param type 事件类型 - * @param param 事件附加参数 - * @return 错误码 - */ -#define menu_post_event(ctx, type, param) menu_post_event((ctx), (type), (param), MENU_EVENT_PRIORITY_NORMAL) - -/** - * @brief 注册菜单节点(构建菜单树) - * @param ctx 菜单上下文指针 - * @param parent_id 父节点ID(根节点填0) - * @param node_id 当前节点ID(唯一,不可重复) - * @param name_str 菜单名称字符串(或多语言索引) - * @param enter_cb 进入该菜单的回调函数(NULL表示无) - * @param exit_cb 退出该菜单的回调函数(NULL表示无) - * @return 错误码 - */ -MenuErrCode menu_register_node(MenuGlobalCtx* ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb); - -/** - * @brief 批量注册菜单节点 - * @param ctx 菜单上下文指针 - * @param configs 菜单节点配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_register_nodes(MenuGlobalCtx* ctx, const MenuNodeConfig* configs, uint16_t config_count); - -/** - * @brief 获取当前选中的菜单节点ID - * @param ctx 菜单上下文指针 - * @param node_id 输出参数,当前节点ID - * @return 错误码 - */ -MenuErrCode menu_get_current_node(MenuGlobalCtx* ctx, MenuNodeId* node_id); - -/** - * @brief 检查菜单组件是否已初始化 - * @param ctx 菜单上下文指针 - * @return 是否已初始化 - */ -bool menu_is_initialized(MenuGlobalCtx* ctx); - -#if MENU_CONFIG_ENABLE_MEM_MONITOR -/** - * @brief 获取菜单内存使用统计信息 - * @param ctx 菜单上下文指针 - * @param stats 输出参数,内存使用统计信息 - * @return 错误码 - */ -MenuErrCode menu_get_mem_stats(MenuGlobalCtx* ctx, MenuMemStats* stats); -#endif // MENU_CONFIG_ENABLE_MEM_MONITOR - -/************************** 核心类型定义 **************************/ - -/************************** 功能扩展接口声明(可裁剪) **************************/ -#if MENU_CONFIG_ENABLE_PARAM -/** - * @brief 注册参数到菜单节点(参数管理功能) - * @param ctx 菜单上下文指针 - * @param node_id 菜单节点ID(参数与菜单绑定) - * @param param_id 参数ID(唯一) - * @param type 参数类型 - * @param min_val 最小值(浮点型,内部自动转换) - * @param max_val 最大值(浮点型,内部自动转换) - * @param default_val 默认值(浮点型,内部自动转换) - * @param scale 缩放因子(如0.1表示保留1位小数) - * @return 错误码 - */ -MenuErrCode menu_param_register(MenuGlobalCtx* ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale); - -/** - * @brief 批量注册参数到菜单节点 - * @param ctx 菜单上下文指针 - * @param configs 参数配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_param_register_batch(MenuGlobalCtx* ctx, const MenuParamConfig* configs, uint16_t config_count); - -/** - * @brief 设置参数值 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param value 新值(浮点型,内部自动转换) - * @return 错误码 - */ -MenuErrCode menu_param_set_value(MenuGlobalCtx* ctx, uint16_t param_id, float value); - -/** - * @brief 获取参数值 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param value 输出参数,当前值(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_get_value(MenuGlobalCtx* ctx, uint16_t param_id, float* value); - -/** - * @brief 获取参数类型 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param type 输出参数,参数类型 - * @return 错误码 - */ -MenuErrCode menu_param_get_type(MenuGlobalCtx* ctx, uint16_t param_id, MenuParamType* type); - -#endif // MENU_CONFIG_ENABLE_PARAM - -#if MENU_CONFIG_ENABLE_LANG -/** - * @brief 语言字符串结构体(用于批量注册和动态加载) - */ -typedef struct { - uint16_t str_id; ///< 字符串ID - uint8_t lang_id; ///< 语言ID - const char* str; ///< 语言字符串 -} MenuLangStr; - -/** - * @brief 设置当前语言 - * @param ctx 菜单上下文指针 - * @param lang_id 语言ID(如0-中文,1-英文) - * @return 错误码 - */ -MenuErrCode menu_lang_set_current(MenuGlobalCtx* ctx, uint8_t lang_id); - -/** - * @brief 获取当前语言 - * @param ctx 菜单上下文指针 - * @param lang_id 输出参数,当前语言ID - * @return 错误码 - */ -MenuErrCode menu_lang_get_current(MenuGlobalCtx* ctx, uint8_t* lang_id); - -/** - * @brief 注册语言字符串 - * @param ctx 菜单上下文指针 - * @param str_id 字符串ID - * @param lang_id 语言ID - * @param str 语言字符串 - * @return 错误码 - */ -MenuErrCode menu_lang_register_str(MenuGlobalCtx* ctx, uint16_t str_id, uint8_t lang_id, const char* str); - -/** - * @brief 批量注册语言字符串 - * @param ctx 菜单上下文指针 - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_register_strs(MenuGlobalCtx* ctx, const MenuLangStr* strs, uint16_t count); - -/** - * @brief 动态加载语言包 - * @param ctx 菜单上下文指针 - * @param lang_id 语言ID - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_load_pack(MenuGlobalCtx* ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count); - -/** - * @brief 获取支持的最大语言数量 - * @return 最大语言数量 - */ -uint8_t menu_lang_get_max_langs(void); -#endif // MENU_CONFIG_ENABLE_LANG - -/************************** Modbus映射功能(启用时有效) **************************/ -#if MENU_CONFIG_ENABLE_MODBUS_MAP - -/** - * @brief Modbus映射结构体(对外只读,内部初始化) - */ -typedef struct { - uint16_t param_id; ///< 关联的参数ID - ModbusRegType reg_type; ///< 寄存器类型 - uint16_t reg_addr; ///< 寄存器起始地址(协议地址,如40001) - uint8_t reg_count; ///< 占用寄存器数量(如32位浮点占2个16位寄存器) - ModbusPerm perm; ///< 读写权限 - uint8_t byte_order; ///< 字节序(0-小端,1-大端,2-Modbus标准) -} ModbusMap; - -/** - * @brief 注册参数与Modbus寄存器的映射关系 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID(需已通过menu_param_register注册) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器起始地址(协议地址,如40001) - * @param reg_count 占用寄存器数量(如1:16位,2:32位,4:64位) - * @param perm 读写权限 - * @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_register(MenuGlobalCtx* ctx, uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order); - -/** - * @brief 批量注册参数与Modbus寄存器的映射关系 - * @param ctx 菜单上下文指针 - * @param configs Modbus映射配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_register_batch(MenuGlobalCtx* ctx, const MenuModbusMapConfig* configs, uint16_t config_count); - -/** - * @brief 根据参数ID查询Modbus映射关系 - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map); - -/** - * @brief 根据寄存器地址和类型查询Modbus映射关系 - * @param ctx 菜单上下文指针 - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map); - -/** - * @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据) - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器) - * @param buf_len 缓冲区长度(输入),实际写入长度(输出) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len); - -/** - * @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值) - * @param ctx 菜单上下文指针 - * @param param_id 参数ID - * @param reg_buf 输入参数,寄存器数据缓冲区 - * @param buf_len 缓冲区长度 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len); - -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP - -#endif // MENU_H \ No newline at end of file diff --git a/api/menu_config.h b/api/menu_config.h deleted file mode 100644 index b8e11e7..0000000 --- a/api/menu_config.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @file menu_config.h - * @brief 菜单组件用户配置文件(工业级:集中管理所有可配置项) - * @note 用户可根据项目需求修改此文件 - */ -#ifndef MENU_CONFIG_H -#define MENU_CONFIG_H - -#include -#include - -/************************** 核心配置 **************************/ -/** - * @brief 最大菜单节点数(静态内存,根据项目调整) - */ -#define MENU_CONFIG_MAX_NODES 32 - -/** - * @brief 菜单栈深度(最大导航层级,如根→子→孙,深度为3) - */ -#define MENU_CONFIG_STACK_DEPTH 8 - -/** - * @brief 事件队列长度(存储按键/自定义事件,避免丢失) - */ -#define MENU_CONFIG_EVENT_QUEUE_LEN 16 - -/** - * @brief 是否启用断言(调试时开启,发布时关闭) - */ -#define MENU_CONFIG_ENABLE_ASSERT 1 - -/** - * @brief 是否启用调试打印(调试时开启,发布时关闭) - */ -#define MENU_CONFIG_ENABLE_DEBUG 0 - -/** - * @brief 是否启用内存监控(调试时开启,发布时关闭) - */ -#define MENU_CONFIG_ENABLE_MEM_MONITOR 1 - -/** - * @brief 内存使用统计结构体 - */ -typedef struct { - uint16_t total_nodes; ///< 总菜单节点数 - uint16_t used_nodes; ///< 已使用菜单节点数 - uint16_t total_params; ///< 总参数数 - uint16_t used_params; ///< 已使用参数数 - uint16_t total_modbus_maps; ///< 总Modbus映射数 - uint16_t used_modbus_maps; ///< 已使用Modbus映射数 - uint16_t event_queue_len; ///< 事件队列长度 - uint16_t event_queue_used; ///< 事件队列已使用长度 -} MenuMemStats; - -/************************** 功能扩展配置(可裁剪) **************************/ -/** - * @brief 是否启用参数管理功能 - */ -#define MENU_CONFIG_ENABLE_PARAM 1 - -/** - * @brief 是否启用多语言功能 - */ -#define MENU_CONFIG_ENABLE_LANG 1 - -/** - * @brief 最大参数数量(启用参数管理时有效) - */ -#define MENU_CONFIG_MAX_PARAMS 16 - -/** - * @brief 支持的最大语言数量(启用多语言时有效) - */ -#define MENU_CONFIG_MAX_LANGS 2 - -/************************** 硬件无关配置 **************************/ -/** - * @brief 事件处理超时时间(ms,防止事件队列阻塞) - */ -#define MENU_CONFIG_EVENT_TIMEOUT 100 - -/** - * @brief 菜单刷新间隔(ms,控制显示刷新频率) - */ -#define MENU_CONFIG_REFRESH_INTERVAL 50 - -/************************** Modbus映射功能配置(可裁剪) **************************/ -/** - * @brief 是否启用参数-Modbus寄存器映射功能 - */ -#define MENU_CONFIG_ENABLE_MODBUS_MAP 1 - -/** - * @brief 最大Modbus映射数量(静态内存,根据项目调整) - */ -#define MENU_CONFIG_MAX_MODBUS_MAPS 16 - -/** - * @brief Modbus寄存器地址最大值(根据实际Modbus从站配置) - */ -#define MENU_CONFIG_MODBUS_MAX_ADDR 0x0FFF - -/** - * @brief 默认Modbus字节序(0-小端,1-大端,2-字小端字节大端(Modbus标准)) - */ -#define MENU_CONFIG_MODBUS_BYTE_ORDER 2 - -/** - * @brief 是否启用Modbus映射权限校验(与参数权限联动) - */ -#define MENU_CONFIG_MODBUS_PERMISSION 1 - -#endif // MENU_CONFIG_H \ No newline at end of file diff --git a/examples/menu_example.c b/examples/menu_example.c deleted file mode 100644 index 5c18fcd..0000000 --- a/examples/menu_example.c +++ /dev/null @@ -1,373 +0,0 @@ -/** - * @file menu_example.c - * @brief 菜单组件完整使用示例(包含Modbus映射功能演示) - * @note 工业级嵌入式菜单组件 - 使用示例 - */ - -#include -#include "menu.h" -#include "menu_port.h" -#include "menu_def.h" - -/************************** 示例配置 **************************/ -// 菜单节点ID定义 -#define MENU_ROOT_ID 1 // 根节点 -#define MENU_PARAM_ID 2 // 参数设置节点 -#define MENU_MODBUS_ID 3 // Modbus设置节点 -#define MENU_TEMP_ID 4 // 温度参数节点 -#define MENU_HUMID_ID 5 // 湿度参数节点 -#define MENU_PRESS_ID 6 // 压力参数节点 - -// 参数ID定义 -#define PARAM_TEMP_ID 1 // 温度参数ID -#define PARAM_HUMID_ID 2 // 湿度参数ID -#define PARAM_PRESS_ID 3 // 压力参数ID - -// Modbus寄存器地址定义 -#define REG_TEMP_ADDR 40001 // 温度寄存器地址 -#define REG_HUMID_ADDR 40003 // 湿度寄存器地址 -#define REG_PRESS_ADDR 40005 // 压力寄存器地址 - -/************************** 全局变量 **************************/ -// 模拟Modbus寄存器缓冲区 -uint8_t g_modbus_reg_buf[256] = {0}; - -// 菜单上下文 -static uint8_t* menu_ctx_buf = NULL; -static MenuGlobalCtx* g_menu_ctx = NULL; - -/************************** 菜单回调函数 **************************/ -/** - * @brief 进入参数设置菜单回调 - * @param node_id 菜单节点ID - * @return 错误码 - */ -static MenuErrCode menu_cb_enter_param(MenuNodeId node_id) -{ - (void)node_id; // Suppress unused parameter warning when debug is disabled - MENU_DEBUG("Enter param menu: %d", node_id); - return MENU_OK; -} - -/** - * @brief 退出参数设置菜单回调 - * @param node_id 菜单节点ID - * @return 错误码 - */ -static MenuErrCode menu_cb_exit_param(MenuNodeId node_id) -{ - (void)node_id; // Suppress unused parameter warning when debug is disabled - MENU_DEBUG("Exit param menu: %d", node_id); - return MENU_OK; -} - -/** - * @brief 进入Modbus设置菜单回调 - * @param node_id 菜单节点ID - * @return 错误码 - */ -static MenuErrCode menu_cb_enter_modbus(MenuNodeId node_id) -{ - (void)node_id; // Suppress unused parameter warning when debug is disabled - MENU_DEBUG("Enter modbus menu: %d", node_id); - return MENU_OK; -} - -/************************** Modbus通信演示函数 **************************/ -/** - * @brief 模拟Modbus通信演示 - */ -static void modbus_communication_demo(void) -{ - MENU_DEBUG("=== Modbus Communication Demo Start ==="); - - #if MENU_CONFIG_ENABLE_MODBUS_MAP - - // 1. 模拟参数到寄存器的转换(参数值更新后同步到寄存器) - { - MENU_DEBUG("1. Param to Reg Demo:"); - - // 模拟温度参数变化(如从传感器读取到新的温度值) - float new_temp = 30.5f; - MenuErrCode err = menu_param_set_value(g_menu_ctx, PARAM_TEMP_ID, new_temp); - if (err == MENU_OK) - { - // 将参数值转换到Modbus寄存器缓冲区 - uint8_t buf_len = 4; - uint8_t reg_buf[4] = {0}; - - err = menu_modbus_map_param_to_reg(g_menu_ctx, PARAM_TEMP_ID, reg_buf, &buf_len); - if (err == MENU_OK) - { - MENU_DEBUG(" Temp param %.1f°C to reg data: [0x%02X, 0x%02X, 0x%02X, 0x%02X]", - new_temp, reg_buf[0], reg_buf[1], reg_buf[2], reg_buf[3]); - } - else - { - MENU_DEBUG(" Param to reg failed: %d", err); - } - } - } - - // 2. 模拟寄存器到参数的转换(接收到Modbus写入请求后更新参数) - { - MENU_DEBUG("2. Reg to Param Demo:"); - - // 模拟Modbus写入湿度参数(65.0%) - // 65.0f的二进制表示(大端):0x42820000,对应Modbus标准字节序为0x8242 0x0000 - uint8_t reg_buf[4] = {0x82, 0x42, 0x00, 0x00}; - - // 将Modbus寄存器数据转换为参数值 - MenuErrCode err = menu_modbus_map_reg_to_param(g_menu_ctx, PARAM_HUMID_ID, reg_buf, sizeof(reg_buf)); - if (err == MENU_OK) - { - // 读取转换后的参数值 - float humid_val; - menu_param_get_value(g_menu_ctx, PARAM_HUMID_ID, &humid_val); - MENU_DEBUG(" Reg data [0x82, 0x42, 0x00, 0x00] to humid param: %.1f%%", humid_val); - } - else - { - MENU_DEBUG(" Reg to param failed: %d", err); - } - } - - // 3. 模拟Modbus读取请求处理 - { - MENU_DEBUG("3. Modbus Read Request Demo:"); - - // 模拟Modbus主机读取压力参数 - uint8_t buf_len = 4; - uint8_t reg_buf[4] = {0}; - - // 这里应该先调用menu_modbus_map_param_to_reg将参数转换到寄存器缓冲区 - MenuErrCode err = menu_modbus_map_param_to_reg(g_menu_ctx, PARAM_PRESS_ID, reg_buf, &buf_len); - if (err == MENU_OK) - { - // 然后返回给Modbus主机 - MENU_DEBUG(" Read pressure reg: [0x%02X, 0x%02X, 0x%02X, 0x%02X]", - reg_buf[0], reg_buf[1], reg_buf[2], reg_buf[3]); - } - else - { - MENU_DEBUG(" Read pressure failed: %d", err); - } - } - - // 4. 模拟Modbus写入请求处理 - { - MENU_DEBUG("4. Modbus Write Request Demo:"); - - // 模拟Modbus主机写入温度参数(30.0°C) - // 30.0f的二进制表示(大端):0x41F00000,对应Modbus标准字节序为0xF041 0x0000 - uint8_t reg_buf[4] = {0xF0, 0x41, 0x00, 0x00}; - - // 调用menu_modbus_map_reg_to_param将寄存器数据转换为参数值 - MenuErrCode err = menu_modbus_map_reg_to_param(g_menu_ctx, PARAM_TEMP_ID, reg_buf, sizeof(reg_buf)); - if (err == MENU_OK) - { - // 读取转换后的参数值 - float temp_val; - menu_param_get_value(g_menu_ctx, PARAM_TEMP_ID, &temp_val); - MENU_DEBUG(" Write temp reg: [0xF0, 0x41, 0x00, 0x00] -> temp param: %.1f°C", temp_val); - } - else - { - MENU_DEBUG(" Write temp failed: %d", err); - } - } - - MENU_DEBUG("=== Modbus Communication Demo End ==="); - #endif -} - -/************************** 主函数 **************************/ -int main(void) -{ - MENU_DEBUG("=== Menu Component Example ==="); - - /************************** 1. 初始化硬件 **************************/ - MENU_DEBUG("1. Initialize hardware..."); - // 这里应该初始化硬件,如UART、LCD、SysTick等 - // hal_init(); - - /************************** 2. 初始化菜单组件 **************************/ - MENU_DEBUG("2. Initialize menu component..."); - - // 动态分配菜单上下文缓冲区 - menu_ctx_buf = (uint8_t*)malloc(menu_get_ctx_size()); - if (menu_ctx_buf == NULL) - { - MENU_DEBUG(" Failed to allocate menu context buffer"); - return -1; - } - - // 初始化菜单上下文 - g_menu_ctx = (MenuGlobalCtx*)menu_ctx_buf; - MenuErrCode err = menu_init(g_menu_ctx); - if (err != MENU_OK) - { - MENU_DEBUG(" Menu init failed: %d", err); - free(menu_ctx_buf); - return -1; - } - - /************************** 3. 注册菜单节点 **************************/ - MENU_DEBUG("3. Register menu nodes..."); - - // 注册根节点 - err = menu_register_node(g_menu_ctx, 0, MENU_ROOT_ID, "主菜单", NULL, NULL); - if (err != MENU_OK) - { - MENU_DEBUG(" Register root node failed: %d", err); - return -1; - } - - // 注册参数设置节点 - err = menu_register_node(g_menu_ctx, MENU_ROOT_ID, MENU_PARAM_ID, "参数设置", menu_cb_enter_param, menu_cb_exit_param); - if (err != MENU_OK) - { - MENU_DEBUG(" Register param node failed: %d", err); - return -1; - } - - // 注册Modbus设置节点 - err = menu_register_node(g_menu_ctx, MENU_ROOT_ID, MENU_MODBUS_ID, "Modbus设置", menu_cb_enter_modbus, NULL); - if (err != MENU_OK) - { - MENU_DEBUG(" Register modbus node failed: %d", err); - return -1; - } - - // 注册温度参数节点 - err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_TEMP_ID, "温度设置", NULL, NULL); - if (err != MENU_OK) - { - MENU_DEBUG(" Register temp node failed: %d", err); - return -1; - } - - // 注册湿度参数节点 - err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_HUMID_ID, "湿度设置", NULL, NULL); - if (err != MENU_OK) - { - MENU_DEBUG(" Register humid node failed: %d", err); - return -1; - } - - // 注册压力参数节点 - err = menu_register_node(g_menu_ctx, MENU_PARAM_ID, MENU_PRESS_ID, "压力设置", NULL, NULL); - if (err != MENU_OK) - { - MENU_DEBUG(" Register press node failed: %d", err); - return -1; - } - - /************************** 4. 注册参数 **************************/ - MENU_DEBUG("4. Register parameters..."); - - #if MENU_CONFIG_ENABLE_PARAM - // 注册温度参数(范围:-40.0°C ~ 120.0°C,默认25.0°C,精度0.1°C) - err = menu_param_register(g_menu_ctx, MENU_TEMP_ID, PARAM_TEMP_ID, MENU_PARAM_TYPE_FLOAT, - -40.0f, 120.0f, 25.0f, 0.1f); - if (err != MENU_OK) - { - MENU_DEBUG(" Register temp param failed: %d", err); - return -1; - } - - // 注册湿度参数(范围:0.0% ~ 100.0%,默认50.0%,精度0.1%) - err = menu_param_register(g_menu_ctx, MENU_HUMID_ID, PARAM_HUMID_ID, MENU_PARAM_TYPE_FLOAT, - 0.0f, 100.0f, 50.0f, 0.1f); - if (err != MENU_OK) - { - MENU_DEBUG(" Register humid param failed: %d", err); - return -1; - } - - // 注册压力参数(范围:0.0kPa ~ 1000.0kPa,默认101.3kPa,精度0.1kPa) - err = menu_param_register(g_menu_ctx, MENU_PRESS_ID, PARAM_PRESS_ID, MENU_PARAM_TYPE_FLOAT, - 0.0f, 1000.0f, 101.3f, 0.1f); - if (err != MENU_OK) - { - MENU_DEBUG(" Register press param failed: %d", err); - return -1; - } - #endif - - /************************** 5. 注册Modbus映射 **************************/ - MENU_DEBUG("5. Register Modbus mappings..."); - - #if MENU_CONFIG_ENABLE_MODBUS_MAP - // 注册温度参数到Modbus寄存器映射 - err = menu_modbus_map_register( - g_menu_ctx, - PARAM_TEMP_ID, // 参数ID - MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型 - REG_TEMP_ADDR, // 起始地址 - 2, // 寄存器数量(浮点型占2个16位寄存器) - MODBUS_PERM_READ_WRITE, // 读写权限 - 2 // 字节序(Modbus标准) - ); - if (err != MENU_OK) - { - MENU_DEBUG(" Register temp modbus map failed: %d", err); - return -1; - } - - // 注册湿度参数到Modbus寄存器映射 - err = menu_modbus_map_register( - g_menu_ctx, - PARAM_HUMID_ID, // 参数ID - MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型 - REG_HUMID_ADDR, // 起始地址 - 2, // 寄存器数量 - MODBUS_PERM_READ_WRITE, // 读写权限 - 2 // 字节序(Modbus标准) - ); - if (err != MENU_OK) - { - MENU_DEBUG(" Register humid modbus map failed: %d", err); - return -1; - } - - // 注册压力参数到Modbus寄存器映射 - err = menu_modbus_map_register( - g_menu_ctx, - PARAM_PRESS_ID, // 参数ID - MODBUS_REG_TYPE_HOLDING_REG, // 寄存器类型 - REG_PRESS_ADDR, // 起始地址 - 2, // 寄存器数量 - MODBUS_PERM_READ_WRITE, // 读写权限 - 2 // 字节序(Modbus标准) - ); - if (err != MENU_OK) - { - MENU_DEBUG(" Register press modbus map failed: %d", err); - return -1; - } - #endif - - /************************** 6. 运行Modbus通信演示 **************************/ - MENU_DEBUG("6. Run Modbus communication demo..."); - modbus_communication_demo(); - - /************************** 7. 进入主循环 **************************/ - MENU_DEBUG("7. Enter main loop..."); - while (1) - { - // 处理菜单事件和刷新显示 - menu_main_loop(g_menu_ctx); - - // 其他业务逻辑 - // ... - - // 模拟延时 - // menu_port_delay_ms(10); - } - - // Free the menu context buffer - free(menu_ctx_buf); - - return 0; -} \ No newline at end of file diff --git a/internal/menu_core.h b/internal/menu_core.h deleted file mode 100644 index dacfd36..0000000 --- a/internal/menu_core.h +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @file menu_core.h - * @brief 菜单组件核心类型定义(用户无需关心) - */ -#ifndef MENU_CORE_H -#define MENU_CORE_H - -#include "menu_def.h" -#include -#include - -/************************** 核心数据结构 **************************/ -/** - * @brief 菜单事件结构体(事件队列元素) - */ -typedef struct { - MenuEventType type; ///< 事件类型 - uint32_t param; ///< 事件附加参数 - uint32_t timestamp; ///< 事件产生时间(ms,用于超时处理) - MenuEventPriority priority; ///< 事件优先级 -} MenuEvent; - -/** - * @brief 菜单节点结构体(菜单树的基本单元) - * @note 紧凑设计:使用位域和共用体减少内存占用(工业级嵌入式低内存优化) - */ -typedef struct MenuNode { - MenuNodeId id; ///< 节点ID(唯一) - MenuNodeId parent_id; ///< 父节点ID(根节点为0) - const char* name; ///< 菜单名称(或多语言索引) - MenuCallback enter_cb; ///< 进入回调 - MenuCallback exit_cb; ///< 退出回调 - struct MenuNode* first_child;///< 第一个子节点 - struct MenuNode* next_sibling;///< 下一个兄弟节点 - struct MenuNode* prev_sibling;///< 上一个兄弟节点 - // 位域:减少内存占用(1字节代替多个u8变量) - 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; - -/** - * @brief 菜单栈结构体(管理导航层级) - */ -typedef struct { - MenuNodeId nodes[MENU_CONFIG_STACK_DEPTH]; ///< 栈元素(存储节点ID) - uint8_t top; ///< 栈顶指针 -} MenuStack; - -/** - * @brief 事件队列结构体(环形队列,工业级:避免溢出) - */ -typedef struct { - MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区 - uint8_t head; ///< 入队指针 - uint8_t tail; ///< 出队指针 - uint8_t count; ///< 队列元素数量 -} MenuEventQueue; - -/** - * @brief 菜单状态枚举(状态机核心) - */ -typedef enum { - MENU_STATE_INIT = 0, ///< 初始化状态 - MENU_STATE_NORMAL, ///< 正常导航状态 - MENU_STATE_PARAM_EDIT, ///< 参数编辑状态 - MENU_STATE_CONFIRM, ///< 确认状态(如保存/退出确认) - MENU_STATE_ERROR, ///< 错误状态 -} MenuState; - -/** - * @brief 菜单状态转换结构体 - */ -typedef struct { - MenuState current_state; ///< 当前状态 - MenuEventType event; ///< 触发事件 - MenuState next_state; ///< 下一个状态 - MenuErrCode (*action)(MenuGlobalCtx* global_ctx); ///< 状态转换动作 -} MenuStateTransition; - -/** - * @brief 菜单核心上下文(全局唯一,存储菜单状态) - */ -typedef struct { - MenuNode nodes[MENU_CONFIG_MAX_NODES]; ///< 静态菜单节点池(无动态分配) - MenuStack stack; ///< 菜单导航栈 - MenuEventQueue event_queue; ///< 事件队列 - MenuNodeId current_node_id; ///< 当前选中的节点ID - MenuState current_state; ///< 当前菜单状态 - uint32_t last_refresh_tick; ///< 上次刷新时间(ms) - bool is_initialized; ///< 是否已初始化 -} MenuCoreCtx; - -/************************** 核心内部接口声明 **************************/ -/** - * @brief 查找菜单节点(通过ID) - * @param global_ctx 全局上下文指针 - * @param node_id 节点ID - * @return 节点指针(NULL表示未找到) - */ -MenuNode* menu_core_find_node(MenuGlobalCtx* global_ctx, MenuNodeId node_id); - -/** - * @brief 处理单个菜单事件 - * @param global_ctx 全局上下文指针 - * @param event 事件指针 - * @return 错误码 - */ -MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event); - -/** - * @brief 刷新菜单显示(内部调用,对接port层显示接口) - * @param global_ctx 全局上下文指针 - */ -void menu_core_refresh_display(MenuGlobalCtx* global_ctx); - - - -#endif // MENU_CORE_H \ No newline at end of file diff --git a/internal/menu_data.h b/internal/menu_data.h deleted file mode 100644 index f8aacf3..0000000 --- a/internal/menu_data.h +++ /dev/null @@ -1,62 +0,0 @@ -/** - * @file menu_data.h - * @brief 菜单组件共享全局变量(用户无需关心,内部仅通过接口访问) - */ -#ifndef MENU_DATA_H -#define MENU_DATA_H - -#include "../api/menu.h" -#include "menu_core.h" -#include "menu_modbus.h" -#include "menu_param.h" - -/************************** 共享上下文结构(替代全局变量) **************************/ -/** - * @brief 参数管理上下文(启用参数时有效) - */ -#if MENU_CONFIG_ENABLE_PARAM -typedef struct { - MenuParam params[MENU_CONFIG_MAX_PARAMS]; ///< 参数数组 - uint16_t count; ///< 已注册参数数量 -} MenuParamCtx; -#endif // MENU_CONFIG_ENABLE_PARAM - -/** - * @brief 多语言上下文(启用多语言时有效) - */ -#if MENU_CONFIG_ENABLE_LANG -typedef struct { - MenuLangStr strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS]; ///< 语言字符串数组 - uint8_t count; ///< 已注册字符串数量 - uint8_t current_lang_id; ///< 当前语言ID -} MenuLangCtx; -#endif // MENU_CONFIG_ENABLE_LANG - -/** - * @brief Modbus映射上下文(启用Modbus映射时有效) - */ -#if MENU_CONFIG_ENABLE_MODBUS_MAP -typedef struct { - ModbusMapInternal maps[MENU_CONFIG_MAX_MODBUS_MAPS]; ///< Modbus映射数组 - uint16_t count; ///< 已注册映射数量 -} MenuModbusCtx; -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP - -/** - * @brief 菜单组件全局上下文(替代所有全局变量) - */ -struct MenuGlobalCtx { - MenuCoreCtx core; ///< 核心上下文 -#if MENU_CONFIG_ENABLE_PARAM - MenuParamCtx param; ///< 参数上下文 -#endif -#if MENU_CONFIG_ENABLE_LANG - MenuLangCtx lang; ///< 多语言上下文 -#endif -#if MENU_CONFIG_ENABLE_MODBUS_MAP - MenuModbusCtx modbus; ///< Modbus映射上下文 -#endif - bool is_initialized; ///< 是否已初始化 -}; - -#endif // MENU_DATA_H \ No newline at end of file diff --git a/internal/menu_def.h b/internal/menu_def.h deleted file mode 100644 index 3d91a60..0000000 --- a/internal/menu_def.h +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @file menu_def.h - * @brief 菜单组件内部宏定义和辅助函数(用户无需关心) - */ -#ifndef MENU_DEF_H -#define MENU_DEF_H - -#include "menu_config.h" -#include "menu.h" -#include -#include - -/************************** 内部宏定义 **************************/ -/** - * @brief 断言宏(工业级:调试时检查,发布时忽略) - */ -#if MENU_CONFIG_ENABLE_ASSERT -#define MENU_ASSERT(condition) \ - do { \ - if (!(condition)) { \ - menu_utils_assert_failed(__FILE__, __LINE__); \ - while (1); \ - } \ - } while (0) -#else -#define MENU_ASSERT(condition) ((void)0) -#endif - -/** - * @brief 日志级别定义 - */ -typedef enum { - MENU_LOG_LEVEL_DEBUG, ///< 调试信息 - MENU_LOG_LEVEL_INFO, ///< 普通信息 - MENU_LOG_LEVEL_WARN, ///< 警告信息 - MENU_LOG_LEVEL_ERROR, ///< 错误信息 - MENU_LOG_LEVEL_CRITICAL, ///< 严重错误 -} MenuLogLevel; - -/** - * @brief 调试打印宏(工业级:集中控制调试输出) - */ -#if MENU_CONFIG_ENABLE_DEBUG -#define MENU_DEBUG(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define MENU_INFO(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define MENU_WARN(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define MENU_ERROR(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define MENU_CRITICAL(fmt, ...) menu_utils_log(MENU_LOG_LEVEL_CRITICAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#else -#define MENU_DEBUG(fmt, ...) ((void)0) -#define MENU_INFO(fmt, ...) ((void)0) -#define MENU_WARN(fmt, ...) ((void)0) -#define MENU_ERROR(fmt, ...) ((void)0) -#define MENU_CRITICAL(fmt, ...) ((void)0) -#endif - -/** - * @brief 内存清零宏 - */ -#define MENU_MEM_SET_ZERO(ptr, size) memset((ptr), 0, (size)) - -/** - * @brief 无效ID定义 - */ -#define MENU_INVALID_ID ((MenuNodeId)0xFFFF) - -/** - * @brief 错误处理宏(简化错误检查和返回) - */ -#define MENU_CHECK_ERR(expr) \ - do { \ - MenuErrCode err = (expr); \ - if (err != MENU_OK) { \ - MENU_ERROR("Error at %s:%d: %s", __FILE__, __LINE__, menu_err_code_to_str(err)); \ - return err; \ - } \ - } while (0) - -/************************** 内部辅助函数声明 **************************/ -/** - * @brief 断言失败处理函数 - * @param file 文件名 - * @param line 行号 - */ -void menu_utils_assert_failed(const char* file, uint32_t line); - -/** - * @brief 通用日志函数(对接port层的硬件打印接口) - * @param level 日志级别 - * @param file 文件名 - * @param line 行号 - * @param fmt 格式化字符串 - * @param ... 可变参数 - */ -void menu_utils_log(MenuLogLevel level, const char* file, uint32_t line, const char* fmt, ...); - -/** - * @brief 获取系统滴答时间(ms,对接port层) - * @return 当前滴答时间 - */ -uint32_t menu_utils_get_tick(void); - -/** - * @brief 错误码转换为字符串 - * @param err_code 错误码 - * @return 错误信息字符串 - */ -const char* menu_err_code_to_str(MenuErrCode err_code); - -#endif // MENU_DEF_H \ No newline at end of file diff --git a/internal/menu_modbus.h b/internal/menu_modbus.h deleted file mode 100644 index ba4fd13..0000000 --- a/internal/menu_modbus.h +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @file menu_modbus.h - * @brief 菜单Modbus映射内部头文件(用户无需关心) - * @note 工业级嵌入式菜单组件 - Modbus映射内部定义 - */ -#ifndef MENU_MODBUS_H -#define MENU_MODBUS_H - -#include "menu.h" -#include "menu_def.h" - -/************************** 内部类型定义 **************************/ -/** - * @brief Modbus映射内部结构体(扩展对外结构体,增加内部状态) - */ -typedef struct { - ModbusMap pub; ///< 对外暴露的映射信息(只读) - MenuParamType param_type; ///< 参数类型(缓存,避免重复查找) - uint16_t reg_addr_offset; ///< 寄存器偏移地址(转换后的实际偏移) - bool is_registered; ///< 是否已注册 -} ModbusMapInternal; - -#endif // MENU_MODBUS_H diff --git a/port/menu_port.c b/port/menu_port.c deleted file mode 100644 index cfbb359..0000000 --- a/port/menu_port.c +++ /dev/null @@ -1,288 +0,0 @@ -/** - * @file menu_port.c - * @brief 菜单组件硬件端口层实现(模块化设计,支持运行时配置) - * @note 工业级嵌入式菜单组件 - 硬件端口层 - * @attention 用户需要根据自己的硬件平台修改此文件 - */ -#include "menu_port.h" -#include "menu.h" -#include -#include -#include -#include - -/************************** 静态全局变量 **************************/ -static const MenuPortDriver* g_current_driver = NULL; - -/************************** 默认驱动实现 **************************/ - -/** - * @brief 默认硬件打印接口实现(使用标准C库printf) - * @param fmt 格式化字符串 - * @param args 可变参数列表 - */ -static void default_printf(const char* fmt, va_list args) -{ - vprintf(fmt, args); -} - -/** - * @brief 默认获取系统滴答时间实现(使用标准C库time函数) - * @return 当前系统滴答时间(ms) - */ -static uint32_t default_get_tick(void) -{ - return (uint32_t)time(NULL) * 1000; -} - -/** - * @brief 默认硬件延迟函数实现(使用标准C库sleep函数) - * @param ms 延迟时间(ms) - */ -static void default_delay_ms(uint32_t ms) -{ - sleep(ms / 1000); - usleep((ms % 1000) * 1000); -} - -/** - * @brief 默认菜单显示接口实现(使用标准C库printf) - * @param menu_name 菜单名称字符串 - * @param menu_id 菜单ID - */ -static void default_display(const char* menu_name, uint16_t menu_id) -{ - printf("\r\nCurrent Menu: %s (ID: %d)\r\n", menu_name, menu_id); - printf("Use UP/DOWN keys to navigate, ENTER to select, BACK to return\r\n"); -} - -/** - * @brief 默认按键扫描接口实现(无按键返回) - * @return 当前按键事件 - */ -static MenuEventType default_key_scan(void) -{ - return MENU_EVENT_NONE; -} - -/** - * @brief 默认中断管理接口实现(空操作) - * @param enable 中断使能标志 - */ -static void default_irq_ctrl(bool enable) -{ - (void)enable; - // 默认实现为空,用户需根据硬件平台实现 -} - -/** - * @brief 默认错误处理接口实现(打印错误信息) - * @param err_code 错误码 - */ -static void default_error_handler(MenuErrCode err_code) -{ - fprintf(stderr, "[MENU HW ERROR] %d\r\n", err_code); -} - -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief 默认Modbus硬件发送数据接口实现(模拟成功) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -static MenuErrCode default_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len) -{ - (void)reg_type; - (void)reg_addr; - (void)reg_buf; - (void)buf_len; - return MENU_OK; -} - -/** - * @brief 默认Modbus硬件接收数据接口实现(模拟成功) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -static MenuErrCode default_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len) -{ - (void)reg_type; - (void)reg_addr; - (void)reg_buf; - (void)buf_len; - return MENU_OK; -} -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP - -/** - * @brief 默认硬件驱动结构体(提供基本实现,减少用户工作量) - */ -const MenuPortDriver g_menu_default_driver = { - .printf = default_printf, - .get_tick = default_get_tick, - .delay_ms = default_delay_ms, - .display = default_display, - .key_scan = default_key_scan, - .irq_ctrl = default_irq_ctrl, - .error_handler = default_error_handler, -#if MENU_CONFIG_ENABLE_MODBUS_MAP - .modbus_send = default_modbus_send, - .modbus_receive = default_modbus_receive, -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -}; - -/************************** 端口管理函数 **************************/ - -/** - * @brief 硬件端口初始化接口 - * @param driver 硬件驱动结构体指针 - * @return 错误码 - */ -MenuErrCode menu_port_init(const MenuPortDriver* driver) -{ - if (driver == NULL) - { - // 使用默认驱动 - g_current_driver = &g_menu_default_driver; - } - else - { - // 使用用户提供的驱动,替换为默认驱动中未实现的部分 - g_current_driver = driver; - } - - return MENU_OK; -} - -/** - * @brief 硬件端口反初始化接口 - * @return 错误码 - */ -MenuErrCode menu_port_deinit(void) -{ - g_current_driver = NULL; - return MENU_OK; -} - -/** - * @brief 获取当前硬件驱动 - * @return 当前硬件驱动结构体指针 - */ -const MenuPortDriver* menu_port_get_driver(void) -{ - if (g_current_driver == NULL) - { - // 确保始终有驱动可用 - return &g_menu_default_driver; - } - return g_current_driver; -} - -/************************** 兼容旧接口的实现 **************************/ - -/** - * @brief 硬件打印接口(兼容旧接口) - * @param fmt 格式化字符串 - * @param args 可变参数列表 - */ -void menu_port_printf(const char* fmt, va_list args) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - if (driver->printf != NULL) - { - driver->printf(fmt, args); - } -} - -/** - * @brief 获取系统滴答时间(兼容旧接口) - * @return 当前系统滴答时间(ms) - */ -uint32_t menu_port_get_tick(void) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - if (driver->get_tick != NULL) - { - return driver->get_tick(); - } - return 0; -} - -/** - * @brief 硬件延迟函数(兼容旧接口) - * @param ms 延迟时间(ms) - */ -void menu_port_delay_ms(uint32_t ms) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - if (driver->delay_ms != NULL) - { - driver->delay_ms(ms); - } -} - -/** - * @brief 菜单显示接口(兼容旧接口) - * @param menu_name 菜单名称字符串 - * @param menu_id 菜单ID - */ -void menu_port_display(const char* menu_name, uint16_t menu_id) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - if (driver->display != NULL) - { - driver->display(menu_name, menu_id); - } -} - -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief Modbus硬件发送数据接口(兼容旧接口) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - - #if MENU_CONFIG_ENABLE_MODBUS_MAP - if (driver->modbus_send != NULL) - { - return driver->modbus_send(reg_type, reg_addr, reg_buf, buf_len); - } - #endif // MENU_CONFIG_ENABLE_MODBUS_MAP - - return MENU_OK; -} - -/** - * @brief Modbus硬件接收数据接口(兼容旧接口) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len) -{ - const MenuPortDriver* driver = menu_port_get_driver(); - - #if MENU_CONFIG_ENABLE_MODBUS_MAP - if (driver->modbus_receive != NULL) - { - return driver->modbus_receive(reg_type, reg_addr, reg_buf, buf_len); - } - #endif // MENU_CONFIG_ENABLE_MODBUS_MAP - - return MENU_OK; -} -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP \ No newline at end of file diff --git a/port/menu_port.h b/port/menu_port.h deleted file mode 100644 index 0ebcf53..0000000 --- a/port/menu_port.h +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file menu_port.h - * @brief 菜单组件硬件端口层接口定义(用户需要实现的硬件相关接口) - * @note 工业级嵌入式菜单组件 - 硬件端口层 - */ -#ifndef MENU_PORT_H -#define MENU_PORT_H - -#include -#include - -// 包含菜单核心类型定义 -#include "menu.h" - -/************************** 硬件端口层接口声明 **************************/ -/** - * @brief 硬件端口驱动结构体(模块化设计,支持运行时配置) - */ -typedef struct { - /** - * @brief 硬件打印接口(可变参数版本) - * @param fmt 格式化字符串 - * @param args 可变参数列表 - * @note 用户需要实现此接口,用于调试信息输出 - */ - void (*printf)(const char* fmt, va_list args); - - /** - * @brief 获取系统滴答时间(毫秒级) - * @return 当前系统滴答时间(ms) - * @note 用户需要实现此接口,用于菜单的定时和超时处理 - */ - uint32_t (*get_tick)(void); - - /** - * @brief 硬件延迟函数(毫秒级) - * @param ms 延迟时间(ms) - * @note 用户需要实现此接口,用于必要的延迟操作 - */ - void (*delay_ms)(uint32_t ms); - - /** - * @brief 菜单显示接口 - * @param menu_name 菜单名称字符串 - * @param menu_id 菜单ID - * @note 用户需要实现此接口,用于在硬件上显示菜单内容 - */ - void (*display)(const char* menu_name, uint16_t menu_id); - - /** - * @brief 按键扫描接口(可选) - * @return 当前按键事件(MenuEventType类型) - * @note 用户可以实现此接口,也可以在自己的主循环中调用menu_post_event发送按键事件 - */ - MenuEventType (*key_scan)(void); - - /** - * @brief 中断管理接口(可选) - * @param enable 中断使能标志 - * @note 用户需要实现此接口,用于管理全局中断 - */ - void (*irq_ctrl)(bool enable); - - /** - * @brief 错误处理接口(可选) - * @param err_code 错误码 - * @note 用户可以实现此接口,用于处理硬件错误 - */ - void (*error_handler)(MenuErrCode err_code); - - #if MENU_CONFIG_ENABLE_MODBUS_MAP - /** - * @brief Modbus硬件发送数据接口(可选,如RS485发送) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ - MenuErrCode (*modbus_send)(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); - - /** - * @brief Modbus硬件接收数据接口(可选,如RS485接收) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ - MenuErrCode (*modbus_receive)(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); - #endif // MENU_CONFIG_ENABLE_MODBUS_MAP -} MenuPortDriver; - -/** - * @brief 硬件端口初始化接口 - * @param driver 硬件驱动结构体指针 - * @return 错误码 - * @note 用户需要实现此接口,用于初始化硬件设备 - */ -MenuErrCode menu_port_init(const MenuPortDriver* driver); - -/** - * @brief 硬件端口反初始化接口 - * @return 错误码 - * @note 用户需要实现此接口,用于释放硬件资源 - */ -MenuErrCode menu_port_deinit(void); - -/** - * @brief 获取当前硬件驱动 - * @return 当前硬件驱动结构体指针 - * @note 内部使用,获取当前配置的硬件驱动 - */ -const MenuPortDriver* menu_port_get_driver(void); - -/** - * @brief 硬件打印接口(兼容旧接口) - * @param fmt 格式化字符串 - * @param args 可变参数列表 - */ -void menu_port_printf(const char* fmt, va_list args); - -/** - * @brief 获取系统滴答时间(兼容旧接口) - * @return 当前系统滴答时间(ms) - */ -uint32_t menu_port_get_tick(void); - -/** - * @brief 硬件延迟函数(兼容旧接口) - * @param ms 延迟时间(ms) - */ -void menu_port_delay_ms(uint32_t ms); - -/** - * @brief 菜单显示接口(兼容旧接口) - * @param menu_name 菜单名称字符串 - * @param menu_id 菜单ID - */ -void menu_port_display(const char* menu_name, uint16_t menu_id); - -/** - * @brief 默认硬件驱动结构体(提供基本实现,减少用户工作量) - */ -extern const MenuPortDriver g_menu_default_driver; - -#endif // MENU_PORT_H \ No newline at end of file diff --git a/src/core/menu_core.c b/src/core/menu_core.c deleted file mode 100644 index 39129d9..0000000 --- a/src/core/menu_core.c +++ /dev/null @@ -1,869 +0,0 @@ -/** - * @file menu_core.c - * @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动) - */ -#include "../api/menu.h" -#include "menu_core.h" -#include "menu_data.h" -#include - -/************************** 内部函数声明 **************************/ -static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id); -static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id); -static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id); -static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx); -static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx); -static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx); -static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event); - -/************************** 状态机相关函数声明 **************************/ -static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event); -static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event); -static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event); -static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event); -static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event); - -/************************** 状态转换表定义 **************************/ -static const MenuStateTransition menu_state_transitions[] = { - // 当前状态 触发事件 下一个状态 转换动作 - { MENU_STATE_INIT, MENU_EVENT_NONE, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_NORMAL, MENU_EVENT_KEY_UP, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_NORMAL, MENU_EVENT_KEY_DOWN, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_NORMAL, MENU_EVENT_KEY_ENTER, MENU_STATE_PARAM_EDIT, NULL }, - { MENU_STATE_NORMAL, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_UP, MENU_STATE_PARAM_EDIT, NULL }, - { MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_DOWN, MENU_STATE_PARAM_EDIT, NULL }, - { MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_CONFIRM, MENU_EVENT_KEY_UP, MENU_STATE_CONFIRM, NULL }, - { MENU_STATE_CONFIRM, MENU_EVENT_KEY_DOWN, MENU_STATE_CONFIRM, NULL }, - { MENU_STATE_CONFIRM, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_CONFIRM, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_ERROR, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL }, - { MENU_STATE_ERROR, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL }, -}; - -static const uint8_t menu_state_transition_count = sizeof(menu_state_transitions) / sizeof(menu_state_transitions[0]); - -/** - * @brief 查找菜单节点(通过ID) - * @param global_ctx 全局上下文指针 - * @param node_id 节点ID - * @return 节点指针(NULL表示未找到) - */ -MenuNode* menu_core_find_node(MenuGlobalCtx* global_ctx, MenuNodeId node_id) -{ - MenuCoreCtx* ctx = &global_ctx->core; - - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) - { - if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node_id) - { - return &ctx->nodes[i]; - } - } - - return NULL; -} - -/** - * @brief 菜单栈压入节点 - * @param global_ctx 全局上下文指针 - * @param node_id 节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MENU_ASSERT(ctx != NULL); - - if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH) - { - MENU_DEBUG("Stack overflow: depth %d", MENU_CONFIG_STACK_DEPTH); - return MENU_ERR_STACK_OVERFLOW; - } - - ctx->stack.nodes[ctx->stack.top++] = node_id; - MENU_DEBUG("Stack push: node %d, top %d", node_id, ctx->stack.top); - return MENU_OK; -} - -/** - * @brief 菜单栈弹出节点 - * @param global_ctx 全局上下文指针 - * @param node_id 输出参数,弹出的节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MENU_ASSERT(ctx != NULL && node_id != NULL); - - if (ctx->stack.top == 0) - { - MENU_DEBUG("Stack underflow"); - return MENU_ERR_STACK_UNDERFLOW; - } - - *node_id = ctx->stack.nodes[--ctx->stack.top]; - MENU_DEBUG("Stack pop: node %d, top %d", *node_id, ctx->stack.top); - return MENU_OK; -} - -/** - * @brief 菜单导航到子节点(确认键) - * @param global_ctx 全局上下文指针 - * @param node_id 当前节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id) -{ - MenuNode* node = menu_core_find_node(global_ctx, node_id); - if (node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 执行进入回调 - if (node->enter_cb != NULL) - { - MenuErrCode err = node->enter_cb(node_id); - if (err != MENU_OK) - { - return err; - } - } - - // 如果有子节点,压入栈并选中第一个子节点 - if (node->first_child != NULL) - { - MenuCoreCtx* ctx = &global_ctx->core; - ctx->current_node_id = node->first_child->id; - return menu_core_stack_push(global_ctx, node->first_child->id); - } - - return MENU_OK; -} - -/** - * @brief 菜单导航返回父节点(返回键) - * @param global_ctx 全局上下文指针 - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MenuNodeId current_id = ctx->current_node_id; - MenuNode* current_node = menu_core_find_node(global_ctx, current_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 执行退出回调 - if (current_node->exit_cb != NULL) - { - MenuErrCode err = current_node->exit_cb(current_id); - if (err != MENU_OK) - { - return err; - } - } - - // 弹出栈,回到父节点 - MenuNodeId parent_id = current_node->parent_id; - if (parent_id == 0) // 根节点 - { - ctx->current_node_id = current_id; - return MENU_OK; - } - - MenuNodeId pop_id; - MenuErrCode err = menu_core_stack_pop(global_ctx, &pop_id); - if (err != MENU_OK) - { - return err; - } - - ctx->current_node_id = parent_id; - return MENU_OK; -} - -/** - * @brief 菜单导航上选(上键) - * @param global_ctx 全局上下文指针 - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 切换到上一个兄弟节点 - if (current_node->prev_sibling != NULL) - { - ctx->current_node_id = current_node->prev_sibling->id; - } - else - { - // 循环到最后一个兄弟节点(工业级:友好的交互设计) - MenuNode* last_node = current_node; - while (last_node->next_sibling != NULL) - { - last_node = last_node->next_sibling; - } - ctx->current_node_id = last_node->id; - } - - MENU_DEBUG("Navigate up: current node %d", ctx->current_node_id); - return MENU_OK; -} - -/** - * @brief 菜单导航下选(下键) - * @param global_ctx 全局上下文指针 - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 切换到下一个兄弟节点 - if (current_node->next_sibling != NULL) - { - ctx->current_node_id = current_node->next_sibling->id; - } - else - { - // 循环到第一个兄弟节点 - MenuNode* parent_node = menu_core_find_node(global_ctx, current_node->parent_id); - if (parent_node != NULL && parent_node->first_child != NULL) - { - ctx->current_node_id = parent_node->first_child->id; - } - } - - MENU_DEBUG("Navigate down: current node %d", ctx->current_node_id); - return MENU_OK; -} - -/** - * @brief 处理单个菜单事件(使用状态机) - * @param global_ctx 全局上下文指针 - * @param event 事件指针 - * @return 错误码 - */ -MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event) -{ - MENU_ASSERT(event != NULL); - - MenuErrCode err = MENU_OK; - - // 使用状态机处理事件 - err = menu_state_transition(global_ctx, event->type); - - return err; -} - -/** - * @brief 状态机转换处理函数 - * @param global_ctx 全局上下文指针 - * @param event 触发事件 - * @return 错误码 - */ -static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MenuState current_state = ctx->current_state; - MenuErrCode err = MENU_OK; - - // 查找状态转换 - for (uint8_t i = 0; i < menu_state_transition_count; i++) - { - if (menu_state_transitions[i].current_state == current_state && - menu_state_transitions[i].event == event) - { - // 执行状态转换动作 - switch (current_state) - { - case MENU_STATE_NORMAL: - err = menu_state_action_normal_navigate(global_ctx, event); - break; - case MENU_STATE_PARAM_EDIT: - err = menu_state_action_param_edit(global_ctx, event); - break; - case MENU_STATE_CONFIRM: - err = menu_state_action_confirm(global_ctx, event); - break; - case MENU_STATE_ERROR: - err = menu_state_action_error(global_ctx, event); - break; - default: - break; - } - - // 更新状态 - ctx->current_state = menu_state_transitions[i].next_state; - MENU_DEBUG("State transition: %d -> %d, event: %d", - current_state, ctx->current_state, event); - break; - } - } - - return err; -} - -/** - * @brief 正常导航状态的动作处理 - * @param global_ctx 全局上下文指针 - * @param event 触发事件 - * @return 错误码 - */ -static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event) -{ - MenuErrCode err = MENU_OK; - - switch (event) - { - case MENU_EVENT_KEY_UP: - err = menu_core_navigate_up(global_ctx); - break; - case MENU_EVENT_KEY_DOWN: - err = menu_core_navigate_down(global_ctx); - break; - case MENU_EVENT_KEY_ENTER: - err = menu_core_navigate_enter(global_ctx, global_ctx->core.current_node_id); - break; - case MENU_EVENT_KEY_BACK: - err = menu_core_navigate_back(global_ctx); - break; - default: - break; - } - - return err; -} - -/** - * @brief 参数编辑状态的动作处理 - * @param global_ctx 全局上下文指针 - * @param event 触发事件 - * @return 错误码 - */ -static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event) -{ - MenuErrCode err = MENU_OK; - - // Suppress unused parameter warnings - (void)global_ctx; - (void)event; - - // 这里可以添加参数编辑的具体逻辑 - // 例如:根据上下键调整参数值,确认键保存,返回键取消编辑 - MENU_DEBUG("Param edit state, event: %d", event); - - return err; -} - -/** - * @brief 确认状态的动作处理 - * @param global_ctx 全局上下文指针 - * @param event 触发事件 - * @return 错误码 - */ -static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event) -{ - MenuErrCode err = MENU_OK; - - // Suppress unused parameter warnings - (void)global_ctx; - (void)event; - - // 这里可以添加确认状态的具体逻辑 - // 例如:确认保存设置,确认退出等 - MENU_DEBUG("Confirm state, event: %d", event); - - return err; -} - -/** - * @brief 错误状态的动作处理 - * @param global_ctx 全局上下文指针 - * @param event 触发事件 - * @return 错误码 - */ -static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event) -{ - MenuErrCode err = MENU_OK; - - // Suppress unused parameter warnings - (void)global_ctx; - (void)event; - - // 这里可以添加错误状态的具体逻辑 - // 例如:显示错误信息,等待用户确认 - MENU_DEBUG("Error state, event: %d", event); - - return err; -} - -/** - * @brief 刷新菜单显示(内部调用,对接port层显示接口) - * @param global_ctx 全局上下文指针 - */ -void menu_core_refresh_display(MenuGlobalCtx* global_ctx) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id); - if (current_node == NULL) - { - return; - } - - // 这里应该调用port层的显示接口 - // 例如:menu_port_display(current_node->name, current_node->id); - MENU_DEBUG("Refresh display: current menu %s (ID: %d)", current_node->name, current_node->id); -} - -/** - * @brief 从事件队列获取事件(非阻塞) - * @param global_ctx 全局上下文指针 - * @param event 输出参数,获取到的事件 - * @return 是否获取到事件 - */ -/** - * @brief 获取菜单组件上下文所需的内存大小 - * @return 上下文内存大小(字节) - */ -uint32_t menu_get_ctx_size(void) -{ - return sizeof(MenuGlobalCtx); -} - -/** - * @brief 从事件队列获取事件(非阻塞,优先级队列:高优先级事件先处理) - * @param global_ctx 全局上下文指针 - * @param event 输出参数,获取到的事件 - * @return 是否获取到事件 - */ -static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event) -{ - MenuCoreCtx* ctx = &global_ctx->core; - MENU_ASSERT(ctx != NULL && event != NULL); - - if (ctx->event_queue.count == 0) - { - return false; - } - - // 查找优先级最高的事件 - uint8_t highest_priority_idx = ctx->event_queue.head; - for (uint8_t i = 0; i < ctx->event_queue.count; i++) - { - uint8_t idx = (ctx->event_queue.head + i) % MENU_CONFIG_EVENT_QUEUE_LEN; - if (ctx->event_queue.buffer[idx].priority > ctx->event_queue.buffer[highest_priority_idx].priority) - { - highest_priority_idx = idx; - } - } - - // 复制优先级最高的事件 - *event = ctx->event_queue.buffer[highest_priority_idx]; - - // 将最后一个事件移到被取出的位置 - uint8_t last_idx = (ctx->event_queue.head + ctx->event_queue.count - 1) % MENU_CONFIG_EVENT_QUEUE_LEN; - if (highest_priority_idx != last_idx) - { - ctx->event_queue.buffer[highest_priority_idx] = ctx->event_queue.buffer[last_idx]; - } - - // 更新队列计数 - ctx->event_queue.count--; - - return true; -} - -/** - * @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示) - * @param global_ctx 全局上下文指针 - */ -void menu_main_loop(MenuGlobalCtx* global_ctx) -{ - MENU_ASSERT(global_ctx != NULL); - MenuCoreCtx* ctx = &global_ctx->core; - if (!global_ctx->is_initialized) - { - return; - } - - // 处理事件队列 - MenuEvent event; - while (menu_core_get_event(global_ctx, &event)) - { - // 检查事件是否超时 - if (menu_utils_get_tick() - event.timestamp > MENU_CONFIG_EVENT_TIMEOUT) - { - MENU_DEBUG("Event timeout: type %d, priority %d", event.type, event.priority); - continue; - } - - // 处理事件 - MenuErrCode err = menu_core_handle_event(global_ctx, &event); - if (err != MENU_OK) - { - MENU_DEBUG("Event handle error: %d", err); - } - } - - // 定时刷新显示 - if (menu_utils_get_tick() - ctx->last_refresh_tick >= MENU_CONFIG_REFRESH_INTERVAL) - { - menu_core_refresh_display(global_ctx); - ctx->last_refresh_tick = menu_utils_get_tick(); - } -} - -/** - * @brief 菜单组件初始化(必须首先调用) - * @param global_ctx 全局上下文指针 - * @return 错误码 - */ -MenuErrCode menu_init(MenuGlobalCtx* global_ctx) -{ - if (global_ctx == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 初始化全局上下文 - MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx)); - - // 初始化核心上下文 - MenuCoreCtx* ctx = &global_ctx->core; - MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx)); - ctx->is_initialized = true; - ctx->current_state = MENU_STATE_INIT; // 设置初始状态 - - // 初始化事件队列 - ctx->last_refresh_tick = menu_utils_get_tick(); - - // 初始化其他功能模块(如果启用) -#if MENU_CONFIG_ENABLE_PARAM - global_ctx->param.count = 0; -#endif - -#if MENU_CONFIG_ENABLE_LANG - global_ctx->lang.count = 0; - global_ctx->lang.current_lang_id = 0; // 默认语言ID -#endif - -#if MENU_CONFIG_ENABLE_MODBUS_MAP - global_ctx->modbus.count = 0; -#endif - - global_ctx->is_initialized = true; - - // 初始状态转换:从INIT到NORMAL - MenuEvent init_event = { - .type = MENU_EVENT_NONE, - .param = 0, - .timestamp = menu_utils_get_tick(), - .priority = MENU_EVENT_PRIORITY_NORMAL - }; - menu_state_transition(global_ctx, init_event.type); - - return MENU_OK; -} - -/** - * @brief 菜单组件反初始化(释放资源) - * @param global_ctx 全局上下文指针 - * @return 错误码 - */ -MenuErrCode menu_deinit(MenuGlobalCtx* global_ctx) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_INVALID_PARAM; - } - - // 反初始化各功能模块 - // 目前都是静态内存,无需释放,只需重置状态 - MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx)); - - return MENU_OK; -} - -/** - * @brief 发送事件到菜单事件队列(如按键事件、自定义事件) - * @param global_ctx 全局上下文指针 - * @param type 事件类型 - * @param param 事件附加参数(可选,如按键长按时间) - * @param priority 事件优先级 - * @return 错误码 - */ -// Undefine the macro to avoid conflict with the function definition -#undef menu_post_event - -MenuErrCode menu_post_event(MenuGlobalCtx* global_ctx, MenuEventType type, uint32_t param, MenuEventPriority priority) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - MenuCoreCtx* ctx = &global_ctx->core; - // 检查事件队列是否已满 - if (ctx->event_queue.count >= MENU_CONFIG_EVENT_QUEUE_LEN) - { - return MENU_ERR_EVENT_QUEUE_FULL; - } - - // 入队事件 - uint8_t idx = (ctx->event_queue.head + ctx->event_queue.count) % MENU_CONFIG_EVENT_QUEUE_LEN; - ctx->event_queue.buffer[idx].type = type; - ctx->event_queue.buffer[idx].param = param; - ctx->event_queue.buffer[idx].timestamp = menu_utils_get_tick(); - ctx->event_queue.buffer[idx].priority = priority; - ctx->event_queue.count++; - - MENU_DEBUG("Event posted: type %d, param %lu, priority %d, count %d", - type, param, priority, ctx->event_queue.count); - - return MENU_OK; -} - -/** - * @brief 注册菜单节点(构建菜单树) - * @param global_ctx 全局上下文指针 - * @param parent_id 父节点ID(根节点填0) - * @param node_id 当前节点ID(唯一,不可重复) - * @param name_str 菜单名称字符串(或多语言索引) - * @param enter_cb 进入该菜单的回调函数(NULL表示无) - * @param exit_cb 退出该菜单的回调函数(NULL表示无) - * @return 错误码 - */ -MenuErrCode menu_register_node(MenuGlobalCtx* global_ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || node_id == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuCoreCtx* ctx = &global_ctx->core; - // 查找空闲节点 - MenuNode* new_node = NULL; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) - { - if (!ctx->nodes[i].flags.is_registered) - { - new_node = &ctx->nodes[i]; - break; - } - } - - if (new_node == NULL) - { - return MENU_ERR_OUT_OF_MEMORY; - } - - // 检查节点ID是否已存在 - if (menu_core_find_node(global_ctx, node_id) != NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 初始化新节点 - MENU_MEM_SET_ZERO(new_node, sizeof(MenuNode)); - new_node->id = node_id; - new_node->parent_id = parent_id; - new_node->name = name_str; - new_node->enter_cb = enter_cb; - new_node->exit_cb = exit_cb; - new_node->flags.is_registered = true; - new_node->flags.is_selected = false; - - // 如果是根节点(parent_id为0) - if (parent_id == 0) - { - // 检查是否已有根节点 - if (ctx->current_node_id != 0) - { - // 已有根节点,将新节点作为兄弟节点添加到根节点之后 - MenuNode* root_node = menu_core_find_node(global_ctx, ctx->current_node_id); - while (root_node->next_sibling != NULL) - { - root_node = root_node->next_sibling; - } - root_node->next_sibling = new_node; - new_node->prev_sibling = root_node; - } - else - { - // 第一个根节点,设置为当前节点 - ctx->current_node_id = node_id; - menu_core_stack_push(global_ctx, node_id); - } - } - else - { - // 查找父节点 - MenuNode* parent_node = menu_core_find_node(global_ctx, parent_id); - if (parent_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 将新节点添加到父节点的子节点列表中 - if (parent_node->first_child == NULL) - { - // 父节点还没有子节点,作为第一个子节点 - parent_node->first_child = new_node; - } - else - { - // 已有子节点,添加到最后一个子节点之后 - MenuNode* last_child = parent_node->first_child; - while (last_child->next_sibling != NULL) - { - last_child = last_child->next_sibling; - } - last_child->next_sibling = new_node; - new_node->prev_sibling = last_child; - } - } - - return MENU_OK; -} - -/** - * @brief 获取当前选中的菜单节点ID - * @param global_ctx 全局上下文指针 - * @param node_id 输出参数,当前节点ID - * @return 错误码 - */ -MenuErrCode menu_get_current_node(MenuGlobalCtx* global_ctx, MenuNodeId* node_id) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || node_id == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuCoreCtx* ctx = &global_ctx->core; - *node_id = ctx->current_node_id; - return MENU_OK; -} - -/** - * @brief 批量注册菜单节点 - * @param global_ctx 全局上下文指针 - * @param configs 菜单节点配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_register_nodes(MenuGlobalCtx* global_ctx, const MenuNodeConfig* configs, uint16_t config_count) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuErrCode err = MENU_OK; - for (uint16_t i = 0; i < config_count; i++) - { - err = menu_register_node(global_ctx, configs[i].parent_id, configs[i].node_id, - configs[i].name, configs[i].enter_cb, configs[i].exit_cb); - if (err != MENU_OK) - { - MENU_ERROR("Failed to register menu node %d at index %d: %s", - configs[i].node_id, i, menu_err_code_to_str(err)); - return err; - } - } - - return MENU_OK; -} - -/** - * @brief 检查菜单组件是否已初始化 - * @param global_ctx 全局上下文指针 - * @return 是否已初始化 - */ -bool menu_is_initialized(MenuGlobalCtx* global_ctx) -{ - if (global_ctx == NULL) - { - return false; - } - return global_ctx->is_initialized; -} - -#if MENU_CONFIG_ENABLE_MEM_MONITOR -/** - * @brief 获取菜单内存使用统计信息 - * @param global_ctx 全局上下文指针 - * @param stats 输出参数,内存使用统计信息 - * @return 错误码 - */ -MenuErrCode menu_get_mem_stats(MenuGlobalCtx* global_ctx, MenuMemStats* stats) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || stats == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuCoreCtx* ctx = &global_ctx->core; - - // 初始化统计信息 - MENU_MEM_SET_ZERO(stats, sizeof(MenuMemStats)); - - // 统计菜单节点使用情况 - stats->total_nodes = MENU_CONFIG_MAX_NODES; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) - { - if (ctx->nodes[i].flags.is_registered) - { - stats->used_nodes++; - } - } - - // 统计参数使用情况 - stats->total_params = MENU_CONFIG_MAX_PARAMS; - #if MENU_CONFIG_ENABLE_PARAM - for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++) - { - if (global_ctx->param.params[i].is_registered) - { - stats->used_params++; - } - } - #endif - - // 统计Modbus映射使用情况 - stats->total_modbus_maps = MENU_CONFIG_MAX_MODBUS_MAPS; - #if MENU_CONFIG_ENABLE_MODBUS_MAP - stats->used_modbus_maps = global_ctx->modbus.count; - #endif - - // 统计事件队列使用情况 - stats->event_queue_len = MENU_CONFIG_EVENT_QUEUE_LEN; - stats->event_queue_used = ctx->event_queue.count; - - MENU_DEBUG("Memory stats: nodes %d/%d, params %d/%d, modbus %d/%d, events %d/%d", - stats->used_nodes, stats->total_nodes, - stats->used_params, stats->total_params, - stats->used_modbus_maps, stats->total_modbus_maps, - stats->event_queue_used, stats->event_queue_len); - - return MENU_OK; -} -#endif // MENU_CONFIG_ENABLE_MEM_MONITOR \ No newline at end of file diff --git a/src/features/menu_modbus.c b/src/features/menu_modbus.c deleted file mode 100644 index 2f04bd9..0000000 --- a/src/features/menu_modbus.c +++ /dev/null @@ -1,636 +0,0 @@ -/** - * @file menu_modbus.c - * @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配) - */ -#include "../api/menu.h" -#include "menu_core.h" -#include "menu_data.h" -#include "menu_def.h" -#include "menu_modbus.h" -#include -#include - -#if MENU_CONFIG_ENABLE_MODBUS_MAP - -/************************** 内部辅助函数 **************************/ -/** - * @brief 转换Modbus协议地址到实际偏移地址(如40001→0,00001→0) - * @param reg_type 寄存器类型 - * @param reg_addr 协议地址(如40001) - * @param offset 输出参数,实际偏移地址 - * @return 错误码 - */ -static MenuErrCode menu_modbus_convert_addr(ModbusRegType reg_type, uint16_t reg_addr, uint16_t* offset) -{ - if (offset == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 校验协议地址范围(符合Modbus标准) - switch (reg_type) - { - case MODBUS_REG_TYPE_COIL: - if (reg_addr < 1 || reg_addr > 9999) - { - MENU_DEBUG("Modbus coil addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 1; - break; - case MODBUS_REG_TYPE_DISCRETE_INPUT: - if (reg_addr < 10001 || reg_addr > 19999) - { - MENU_DEBUG("Modbus discrete input addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 10001; - break; - case MODBUS_REG_TYPE_HOLDING_REG: - if (reg_addr < 40001 || reg_addr > 49999) - { - MENU_DEBUG("Modbus holding reg addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 40001; - break; - case MODBUS_REG_TYPE_INPUT_REG: - if (reg_addr < 30001 || reg_addr > 39999) - { - MENU_DEBUG("Modbus input reg addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 30001; - break; - default: - return MENU_ERR_INVALID_PARAM; - } - - // 校验实际偏移地址不超过配置的最大值 - // 注意:MENU_CONFIG_MODBUS_MAX_ADDR 不应超过 uint16_t 的最大值 0xFFFF - if (*offset > (uint16_t)MENU_CONFIG_MODBUS_MAX_ADDR) - { - MENU_DEBUG("Modbus reg offset out of range: %d, max allowed: %d", *offset, MENU_CONFIG_MODBUS_MAX_ADDR); - return MENU_ERR_INVALID_PARAM; - } - - return MENU_OK; -} - -/** - * @brief 校验寄存器类型与参数类型的匹配性(工业级:类型安全) - * @param param_type 参数类型 - * @param reg_type 寄存器类型 - * @return 错误码 - */ -static MenuErrCode menu_modbus_check_type_match(MenuParamType param_type, ModbusRegType reg_type) -{ - // 1位寄存器(线圈、离散输入)只能匹配布尔型/8位整型参数 - if (reg_type == MODBUS_REG_TYPE_COIL || reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT) - { - if (param_type != MENU_PARAM_TYPE_INT8 && param_type != MENU_PARAM_TYPE_UINT8) - { - MENU_DEBUG("Modbus 1-bit reg not match param type: %d", param_type); - return MENU_ERR_INVALID_PARAM; - } - return MENU_OK; - } - - // 16位寄存器(保持、输入)可匹配所有类型(16/32/浮点需多寄存器) - return MENU_OK; -} - -/** - * @brief 字节序转换(适配不同的Modbus字节序,工业级:兼容不同从站) - * @param data 数据缓冲区(16位为单位) - * @param len 数据长度(16位的数量) - * @param byte_order 字节序(0-小端,1-大端,2-Modbus标准(字小端,字节大端)) - * @param is_reverse 是否反向转换(用于读取/写入) - */ -static void menu_modbus_byte_order_convert(uint16_t* data, uint8_t len, uint8_t byte_order, bool is_reverse) -{ - if (data == NULL || len == 0) - { - return; - } - - // 默认使用配置的字节序 - if (byte_order > 2) - { - byte_order = MENU_CONFIG_MODBUS_BYTE_ORDER; - } - - switch (byte_order) - { - case 0: // 小端(低字节在前,高字节在后) - if (is_reverse) - { - for (uint8_t i = 0; i < len; i++) - { - data[i] = (data[i] << 8) | (data[i] >> 8); - } - } - break; - case 1: // 大端(高字节在前,低字节在后) - // 无需转换,Modbus默认是大端 - break; - case 2: // Modbus标准(字小端,字节大端:如32位值0x12345678→0x5678 0x1234) - if (is_reverse) - { - // 读取时:将Modbus顺序转换为主机顺序 - for (uint8_t i = 0; i < len; i += 2) - { - if (i + 1 < len) - { - uint16_t temp = data[i]; - data[i] = data[i + 1]; - data[i + 1] = temp; - } - } - } - else - { - // 写入时:将主机顺序转换为Modbus顺序 - for (uint8_t i = 0; i < len; i += 2) - { - if (i + 1 < len) - { - uint16_t temp = data[i]; - data[i] = data[i + 1]; - data[i + 1] = temp; - } - } - } - break; - default: - break; - } -} - -/** - * @brief 查找Modbus映射(通过参数ID) - * @param ctx 菜单全局上下文 - * @param param_id 参数ID - * @return 映射内部结构体指针(NULL表示未找到) - */ -static ModbusMapInternal* menu_modbus_find_by_param(MenuGlobalCtx* ctx, uint16_t param_id) -{ - if (ctx == NULL) - { - return NULL; - } - - MenuModbusCtx* modbus_ctx = &ctx->modbus; - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (modbus_ctx->maps[i].is_registered && modbus_ctx->maps[i].pub.param_id == param_id) - { - return &modbus_ctx->maps[i]; - } - } - return NULL; -} - -/** - * @brief 查找Modbus映射(通过寄存器类型和地址) - * @param ctx 菜单全局上下文 - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址(协议地址) - * @return 映射内部结构体指针(NULL表示未找到) - */ -static ModbusMapInternal* menu_modbus_find_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr) -{ - if (ctx == NULL) - { - return NULL; - } - - MenuModbusCtx* modbus_ctx = &ctx->modbus; - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (modbus_ctx->maps[i].is_registered && - modbus_ctx->maps[i].pub.reg_type == reg_type && - modbus_ctx->maps[i].pub.reg_addr == reg_addr) - { - return &modbus_ctx->maps[i]; - } - } - return NULL; -} - -/************************** 对外接口实现 **************************/ -MenuErrCode menu_modbus_map_register(MenuGlobalCtx* ctx, uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order) -{ - // 1. 校验参数是否已注册(通过获取参数值间接检查) - float dummy_val; - MenuErrCode err = menu_param_get_value(ctx, param_id, &dummy_val); - if (err != MENU_OK) - { - MENU_DEBUG("Modbus map param %d not found", param_id); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验寄存器地址和转换为偏移 - uint16_t reg_offset; - err = menu_modbus_convert_addr(reg_type, reg_addr, ®_offset); - if (err != MENU_OK) - { - return err; - } - - // 3. 校验寄存器数量(至少1个) - if (reg_count == 0) - { - MENU_DEBUG("Modbus map reg count is zero"); - return MENU_ERR_INVALID_PARAM; - } - - // 4. 由于无法直接获取参数类型,这里跳过类型匹配校验 - // 实际项目中,可以考虑在参数模块中添加获取参数类型的接口 - // err = menu_modbus_check_type_match(param->type, reg_type); - // if (err != MENU_OK) - // { - // return err; - // } - - // 5. 校验读写权限(与寄存器类型联动,如离散输入只能只读) - #if MENU_CONFIG_MODBUS_PERMISSION - if ((reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT || reg_type == MODBUS_REG_TYPE_INPUT_REG) && perm != MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus input reg only support read"); - return MENU_ERR_INVALID_PARAM; - } - if (reg_type == MODBUS_REG_TYPE_COIL && perm == MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus coil not support read only"); - return MENU_ERR_INVALID_PARAM; - } - #endif - - // 6. 检查映射是否已存在 - if (menu_modbus_find_by_param(ctx, param_id) != NULL || menu_modbus_find_by_reg(ctx, reg_type, reg_addr) != NULL) - { - MENU_DEBUG("Modbus map already exists: param %d, reg %d:%d", param_id, reg_type, reg_addr); - return MENU_ERR_INVALID_PARAM; - } - - // 7. 查找空闲的映射位置(从全局上下文获取) - MenuModbusCtx* modbus_ctx = &ctx->modbus; - ModbusMapInternal* map = NULL; - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (!modbus_ctx->maps[i].is_registered) - { - map = &modbus_ctx->maps[i]; - break; - } - } - - if (map == NULL) - { - MENU_DEBUG("Modbus map out of memory: max %d", MENU_CONFIG_MAX_MODBUS_MAPS); - return MENU_ERR_OUT_OF_MEMORY; - } - - // 8. 获取参数类型 - MenuParamType param_type; - err = menu_param_get_type(ctx, param_id, ¶m_type); - if (err != MENU_OK) - { - return err; - } - - // 9. 校验寄存器类型与参数类型匹配 - err = menu_modbus_check_type_match(param_type, reg_type); - if (err != MENU_OK) - { - return err; - } - - // 10. 初始化映射关系 - MENU_MEM_SET_ZERO(map, sizeof(ModbusMapInternal)); - map->pub.param_id = param_id; - map->pub.reg_type = reg_type; - map->pub.reg_addr = reg_addr; - map->pub.reg_count = reg_count; - map->pub.perm = perm; - map->pub.byte_order = byte_order; - map->param_type = param_type; - map->reg_addr_offset = reg_offset; - map->is_registered = true; - - MENU_DEBUG("Modbus map registered: param %d → reg %d:%d (count %d, perm %d)", - param_id, reg_type, reg_addr, reg_count, perm); - return MENU_OK; -} - -MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map) -{ - if (ctx == NULL || map == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - ModbusMapInternal* internal_map = menu_modbus_find_by_param(ctx, param_id); - if (internal_map == NULL) - { - MENU_DEBUG("Modbus map param %d not found", param_id); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 复制对外暴露的信息 - *map = internal_map->pub; - return MENU_OK; -} - -MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map) -{ - if (ctx == NULL || map == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - ModbusMapInternal* internal_map = menu_modbus_find_by_reg(ctx, reg_type, reg_addr); - if (internal_map == NULL) - { - MENU_DEBUG("Modbus map reg %d:%d not found", reg_type, reg_addr); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 复制对外暴露的信息 - *map = internal_map->pub; - return MENU_OK; -} - -MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len) -{ - if (ctx == NULL || reg_buf == NULL || buf_len == NULL || *buf_len == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - // 1. 查找映射关系 - ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id); - if (map == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验读写权限 - #if MENU_CONFIG_MODBUS_PERMISSION - if (map->pub.perm == MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus map param %d is read only", param_id); - return MENU_ERR_HW_PORT_ERROR; // 复用原有错误码 - } - #endif - - // 3. 获取参数值 - float param_val; - MenuErrCode err = menu_param_get_value(ctx, param_id, ¶m_val); - if (err != MENU_OK) - { - MENU_DEBUG("Failed to get param value: %d", err); - return err; - } - - // 4. 转换参数值到寄存器数据(按类型和寄存器数量) - uint16_t* reg_data = (uint16_t*)reg_buf; - uint8_t req_len = map->pub.reg_count; - - // 检查缓冲区长度是否足够 - if (*buf_len < req_len * 2) // 寄存器数据按16位计算,每个16位占2字节 - { - MENU_DEBUG("Modbus reg buf too small: need %d bytes, got %d bytes", req_len * 2, *buf_len); - return MENU_ERR_INVALID_PARAM; - } - - // 清空缓冲区 - memset(reg_data, 0, req_len * 2); - - // 根据参数类型转换 - // 使用之前保存的参数类型,不需要再次调用menu_param_find - switch (map->pub.reg_type) - { - case MODBUS_REG_TYPE_COIL: - case MODBUS_REG_TYPE_DISCRETE_INPUT: - // 1位寄存器:取参数值的最低位 - if (req_len != 1) - { - MENU_DEBUG("Invalid reg count for 1-bit reg: %d, expected 1", req_len); - return MENU_ERR_INVALID_PARAM; - } - reg_data[0] = (uint16_t)(param_val > 0 ? 1 : 0); - break; - case MODBUS_REG_TYPE_HOLDING_REG: - case MODBUS_REG_TYPE_INPUT_REG: - // 16位寄存器:根据参数类型和数量转换 - if (map->param_type == MENU_PARAM_TYPE_INT16 || map->param_type == MENU_PARAM_TYPE_UINT16) - { - if (req_len != 1) - { - MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", req_len); - return MENU_ERR_INVALID_PARAM; - } - reg_data[0] = (uint16_t)param_val; - } - else if (map->param_type == MENU_PARAM_TYPE_INT32 || map->param_type == MENU_PARAM_TYPE_UINT32) - { - if (req_len != 2) - { - MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", req_len); - return MENU_ERR_INVALID_PARAM; - } - uint32_t val = (uint32_t)param_val; - reg_data[0] = (uint16_t)(val & 0xFFFF); - reg_data[1] = (uint16_t)((val >> 16) & 0xFFFF); - } - else if (map->param_type == MENU_PARAM_TYPE_FLOAT) - { - if (req_len != 2) - { - MENU_DEBUG("Invalid reg count for float param: %d, expected 2", req_len); - return MENU_ERR_INVALID_PARAM; - } - // 浮点型转换为32位二进制,占2个16位寄存器 - union { - float f; - uint32_t u32; - } float_val; - float_val.f = param_val; - reg_data[0] = (uint16_t)(float_val.u32 & 0xFFFF); - reg_data[1] = (uint16_t)((float_val.u32 >> 16) & 0xFFFF); - } - else if (map->param_type == MENU_PARAM_TYPE_INT8 || map->param_type == MENU_PARAM_TYPE_UINT8) - { - if (req_len != 1) - { - MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", req_len); - return MENU_ERR_INVALID_PARAM; - } - reg_data[0] = (uint16_t)param_val; - } - break; - default: - MENU_DEBUG("Invalid reg type: %d", map->pub.reg_type); - return MENU_ERR_INVALID_PARAM; - } - - // 5. 字节序转换(写入寄存器前) - menu_modbus_byte_order_convert(reg_data, req_len, map->pub.byte_order, false); - - // 6. 设置实际写入长度 - *buf_len = req_len * 2; - - MENU_DEBUG("Modbus param %d to reg: val %f → reg data len %d", param_id, param_val, *buf_len); - return MENU_OK; -} - -MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len) -{ - if (ctx == NULL || reg_buf == NULL || buf_len == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - // 1. 查找映射关系 - ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id); - if (map == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验读写权限 - #if MENU_CONFIG_MODBUS_PERMISSION - if (map->pub.perm == MODBUS_PERM_WRITE_ONLY) - { - MENU_DEBUG("Modbus map param %d is write only", param_id); - return MENU_ERR_HW_PORT_ERROR; // 复用原有错误码 - } - #endif - - // 3. 校验缓冲区长度 - uint8_t req_len = map->pub.reg_count * 2; - if (buf_len < req_len) - { - MENU_DEBUG("Modbus reg buf too small: need %d, got %d", req_len, buf_len); - return MENU_ERR_INVALID_PARAM; - } - - // 4. 复制寄存器数据并进行字节序转换(读取寄存器后) - uint16_t reg_data[MENU_CONFIG_MAX_MODBUS_MAPS * 2] = {0}; // 足够大的临时缓冲区 - - // 复制数据到临时缓冲区 - memcpy(reg_data, reg_buf, req_len); - - // 字节序转换 - menu_modbus_byte_order_convert(reg_data, map->pub.reg_count, map->pub.byte_order, true); - - // 5. 转换寄存器数据到参数值 - float param_val = 0.0f; - // 使用之前保存的参数类型,不需要再次调用menu_param_find - switch (map->pub.reg_type) - { - case MODBUS_REG_TYPE_COIL: - case MODBUS_REG_TYPE_DISCRETE_INPUT: - // 1位寄存器:0→0,非0→1 - if (map->pub.reg_count != 1) - { - MENU_DEBUG("Invalid reg count for 1-bit reg: %d, expected 1", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - param_val = (reg_data[0] & 0x01) ? 1.0f : 0.0f; - break; - case MODBUS_REG_TYPE_HOLDING_REG: - case MODBUS_REG_TYPE_INPUT_REG: - // 16位寄存器:根据参数类型转换 - if (map->param_type == MENU_PARAM_TYPE_INT8) - { - if (map->pub.reg_count != 1) - { - MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - param_val = (int8_t)reg_data[0]; - } - else if (map->param_type == MENU_PARAM_TYPE_UINT8) - { - if (map->pub.reg_count != 1) - { - MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - param_val = (uint8_t)reg_data[0]; - } - else if (map->param_type == MENU_PARAM_TYPE_INT16) - { - if (map->pub.reg_count != 1) - { - MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - param_val = (int16_t)reg_data[0]; - } - else if (map->param_type == MENU_PARAM_TYPE_UINT16) - { - if (map->pub.reg_count != 1) - { - MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - param_val = (uint16_t)reg_data[0]; - } - else if (map->param_type == MENU_PARAM_TYPE_INT32) - { - if (map->pub.reg_count != 2) - { - MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = (int32_t)val; - } - else if (map->param_type == MENU_PARAM_TYPE_UINT32) - { - if (map->pub.reg_count != 2) - { - MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = val; - } - else if (map->param_type == MENU_PARAM_TYPE_FLOAT) - { - if (map->pub.reg_count != 2) - { - MENU_DEBUG("Invalid reg count for float param: %d, expected 2", map->pub.reg_count); - return MENU_ERR_INVALID_PARAM; - } - union { - float f; - uint32_t u32; - } float_val; - float_val.u32 = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = float_val.f; - } - break; - default: - MENU_DEBUG("Invalid reg type: %d", map->pub.reg_type); - return MENU_ERR_INVALID_PARAM; - } - - // 6. 设置参数值(自动进行范围检查) - MenuErrCode err = menu_param_set_value(ctx, param_id, param_val); - if (err != MENU_OK) - { - return err; - } - - MENU_DEBUG("Modbus reg to param %d: reg data len %d → val %f", param_id, buf_len, param_val); - return MENU_OK; -} - - - -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP \ No newline at end of file diff --git a/src/lang/menu_lang.c b/src/lang/menu_lang.c deleted file mode 100644 index 07eb8c4..0000000 --- a/src/lang/menu_lang.c +++ /dev/null @@ -1,207 +0,0 @@ -/** - * @file menu_lang.c - * @brief 菜单多语言支持模块(可裁剪功能) - */ -#include "menu_core.h" -#include "menu_data.h" - -#if MENU_CONFIG_ENABLE_LANG - -/************************** 内部函数声明 **************************/ -static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id); - -/** - * @brief 查找语言字符串(通过字符串ID和语言ID) - * @param global_ctx 全局上下文指针 - * @param str_id 字符串ID - * @param lang_id 语言ID - * @return 语言字符串(NULL表示未找到) - */ -static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id) -{ - MenuLangCtx* lang_ctx = &global_ctx->lang; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++) - { - if (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id) - { - return lang_ctx->strs[i].str; - } - } - return NULL; -} - -/** - * @brief 注册语言字符串 - * @param global_ctx 全局上下文指针 - * @param str_id 字符串ID - * @param lang_id 语言ID - * @param str 字符串内容 - * @return 错误码 - */ -MenuErrCode menu_lang_register_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id, const char* str) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - if (lang_id >= MENU_CONFIG_MAX_LANGS || str == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuLangCtx* lang_ctx = &global_ctx->lang; - // 查找空闲位置或已存在的字符串条目 - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++) - { - if (!lang_ctx->strs[i].str || (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id)) - { - lang_ctx->strs[i].str_id = str_id; - lang_ctx->strs[i].lang_id = lang_id; - lang_ctx->strs[i].str = str; - return MENU_OK; - } - } - - return MENU_ERR_OUT_OF_MEMORY; -} - -/** - * @brief 获取当前语言的字符串 - * @param global_ctx 全局上下文指针 - * @param str_id 字符串ID - * @return 字符串内容(NULL表示未找到) - */ -const char* menu_lang_get_str(MenuGlobalCtx* global_ctx, uint16_t str_id) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return NULL; - } - - MenuLangCtx* lang_ctx = &global_ctx->lang; - const char* str = menu_lang_find_str(global_ctx, str_id, lang_ctx->current_lang_id); - if (str == NULL) - { - // 如果当前语言未找到,尝试使用默认语言(0) - str = menu_lang_find_str(global_ctx, str_id, 0); - } - return str; -} - -/** - * @brief 设置当前语言 - * @param global_ctx 全局上下文指针 - * @param lang_id 语言ID(如0-中文,1-英文) - * @return 错误码 - */ -MenuErrCode menu_lang_set_current(MenuGlobalCtx* global_ctx, uint8_t lang_id) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - if (lang_id >= MENU_CONFIG_MAX_LANGS) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuLangCtx* lang_ctx = &global_ctx->lang; - lang_ctx->current_lang_id = lang_id; - MENU_DEBUG("Language changed to: %d", lang_id); - - // 语言切换后,刷新菜单显示 - menu_core_refresh_display(global_ctx); - - return MENU_OK; -} - -/** - * @brief 获取当前语言 - * @param global_ctx 全局上下文指针 - * @param lang_id 输出参数,当前语言ID - * @return 错误码 - */ -MenuErrCode menu_lang_get_current(MenuGlobalCtx* global_ctx, uint8_t* lang_id) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || lang_id == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuLangCtx* lang_ctx = &global_ctx->lang; - *lang_id = lang_ctx->current_lang_id; - return MENU_OK; -} - -/** - * @brief 获取支持的最大语言数量 - * @return 最大语言数量 - */ -uint8_t menu_lang_get_max_langs(void) -{ - return MENU_CONFIG_MAX_LANGS; -} - -/** - * @brief 批量注册语言字符串 - * @param global_ctx 全局上下文指针 - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_register_strs(MenuGlobalCtx* global_ctx, const MenuLangStr* strs, uint16_t count) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuErrCode err = MENU_OK; - for (uint16_t i = 0; i < count; i++) - { - err = menu_lang_register_str(global_ctx, strs[i].str_id, strs[i].lang_id, strs[i].str); - if (err != MENU_OK) - { - MENU_ERROR("Failed to register lang str %d at index %d: %s", - strs[i].str_id, i, menu_err_code_to_str(err)); - return err; - } - } - - return MENU_OK; -} - -/** - * @brief 加载语言包(动态加载语言字符串) - * @param global_ctx 全局上下文指针 - * @param lang_id 语言ID - * @param strs 语言字符串数组 - * @param count 字符串数量 - * @return 错误码 - */ -MenuErrCode menu_lang_load_pack(MenuGlobalCtx* global_ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - // 先清除该语言的所有字符串 - MenuLangCtx* lang_ctx = &global_ctx->lang; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++) - { - if (lang_ctx->strs[i].lang_id == lang_id) - { - lang_ctx->strs[i].str_id = 0; - lang_ctx->strs[i].lang_id = 0; - lang_ctx->strs[i].str = NULL; - } - } - - // 再注册新的字符串 - return menu_lang_register_strs(global_ctx, strs, count); -} - -#endif // MENU_CONFIG_ENABLE_LANG \ No newline at end of file diff --git a/src/param/menu_param.c b/src/param/menu_param.c deleted file mode 100644 index 9a0b4e9..0000000 --- a/src/param/menu_param.c +++ /dev/null @@ -1,399 +0,0 @@ -/** - * @file menu_param.c - * @brief 菜单参数管理模块(可裁剪功能) - */ -#include "menu_core.h" -#include "menu_data.h" - -#if MENU_CONFIG_ENABLE_PARAM - -/************************** 内部函数声明 **************************/ -static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id); -static void menu_param_float_to_internal(MenuParam* param, float value); -static float menu_param_internal_to_float(const MenuParam* param); - -/** - * @brief 查找参数(通过参数ID,内部使用) - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @return 参数指针(NULL表示未找到) - */ -static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id) -{ - if (global_ctx == NULL) - { - return NULL; - } - - MenuParamCtx* param_ctx = &global_ctx->param; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++) - { - if (param_ctx->params[i].is_registered && param_ctx->params[i].id == param_id) - { - return ¶m_ctx->params[i]; - } - } - return NULL; -} - -/** - * @brief 将浮点值转换为参数内部值(根据类型) - * @param param 参数指针 - * @param value 浮点值 - * @return 转换后的内部值(通过指针返回) - */ -static void menu_param_float_to_internal(MenuParam* param, float value) -{ - MENU_ASSERT(param != NULL); - - // 确保值在范围内 - if (value < param->min_val) - { - value = param->min_val; - } - else if (value > param->max_val) - { - value = param->max_val; - } - - // 应用缩放因子 - value /= param->scale; - - // 根据参数类型转换 - switch (param->type) - { - case MENU_PARAM_TYPE_INT8: - param->value.i8 = (int8_t)value; - break; - case MENU_PARAM_TYPE_UINT8: - param->value.u8 = (uint8_t)value; - break; - case MENU_PARAM_TYPE_INT16: - param->value.i16 = (int16_t)value; - break; - case MENU_PARAM_TYPE_UINT16: - param->value.u16 = (uint16_t)value; - break; - case MENU_PARAM_TYPE_INT32: - param->value.i32 = (int32_t)value; - break; - case MENU_PARAM_TYPE_UINT32: - param->value.u32 = (uint32_t)value; - break; - case MENU_PARAM_TYPE_FLOAT: - param->value.f = value; - break; - default: - break; - } -} - -/** - * @brief 将参数内部值转换为浮点值(根据类型) - * @param param 参数指针 - * @return 浮点值 - */ -static float menu_param_internal_to_float(const MenuParam* param) -{ - MENU_ASSERT(param != NULL); - - float value = 0.0f; - - // 根据参数类型转换 - switch (param->type) - { - case MENU_PARAM_TYPE_INT8: - value = (float)param->value.i8; - break; - case MENU_PARAM_TYPE_UINT8: - value = (float)param->value.u8; - break; - case MENU_PARAM_TYPE_INT16: - value = (float)param->value.i16; - break; - case MENU_PARAM_TYPE_UINT16: - value = (float)param->value.u16; - break; - case MENU_PARAM_TYPE_INT32: - value = (float)param->value.i32; - break; - case MENU_PARAM_TYPE_UINT32: - value = (float)param->value.u32; - break; - case MENU_PARAM_TYPE_FLOAT: - value = param->value.f; - break; - default: - break; - } - - // 应用缩放因子 - return value * param->scale; -} - -/** - * @brief 注册参数到菜单节点(参数管理功能) - * @param global_ctx 全局上下文指针 - * @param node_id 菜单节点ID(参数与菜单绑定) - * @param param_id 参数ID(唯一) - * @param type 参数类型 - * @param min_val 最小值(浮点型,内部自动转换) - * @param max_val 最大值(浮点型,内部自动转换) - * @param default_val 默认值(浮点型,内部自动转换) - * @param scale 缩放因子(如0.1表示保留1位小数) - * @return 错误码 - */ -MenuErrCode menu_param_register(MenuGlobalCtx* global_ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - if (scale <= 0.0f) - { - return MENU_ERR_INVALID_PARAM; - } - - // 查找菜单节点 - MenuNode* node = menu_core_find_node(global_ctx, node_id); - if (node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 检查参数ID是否已存在 - if (menu_param_find(global_ctx, param_id) != NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 查找空闲参数位置 - MenuParamCtx* param_ctx = &global_ctx->param; - MenuParam* param = NULL; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++) - { - if (!param_ctx->params[i].is_registered) - { - param = ¶m_ctx->params[i]; - break; - } - } - - if (param == NULL) - { - return MENU_ERR_OUT_OF_MEMORY; - } - - // 初始化参数 - MENU_MEM_SET_ZERO(param, sizeof(MenuParam)); - param->id = param_id; - param->type = type; - param->min_val = min_val; - param->max_val = max_val; - param->scale = scale; - param->is_registered = true; - - // 设置默认值 - menu_param_float_to_internal(param, default_val); - // 保存默认值 - menu_param_float_to_internal((MenuParam*)¶m->default_val, default_val); - - // 将参数与菜单节点绑定 - node->param_id = param_id; - - MENU_DEBUG("Param registered: ID=%d, Type=%d, Range=[%0.2f, %0.2f], Default=%0.2f, Scale=%0.2f", - param_id, type, min_val, max_val, default_val, scale); - - return MENU_OK; -} - -/** - * @brief 设置参数值 - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @param value 新值(浮点型,内部自动转换) - * @return 错误码 - */ -MenuErrCode menu_param_set_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float value) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - // 查找参数 - MenuParam* param = menu_param_find(global_ctx, param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 设置新值 - menu_param_float_to_internal(param, value); - - MENU_DEBUG("Param set: ID=%d, Value=%0.2f (Internal: %0.2f)", - param_id, value, menu_param_internal_to_float(param)); - - return MENU_OK; -} - -/** - * @brief 获取参数值 - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @param value 输出参数,当前值(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_get_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float* value) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || value == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 查找参数 - MenuParam* param = menu_param_find(global_ctx, param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 获取当前值 - *value = menu_param_internal_to_float(param); - - MENU_DEBUG("Param get: ID=%d, Value=%0.2f", param_id, *value); - - return MENU_OK; -} - -/** - * @brief 将参数恢复为默认值 - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @return 错误码 - */ -MenuErrCode menu_param_restore_default(MenuGlobalCtx* global_ctx, uint16_t param_id) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - // 查找参数 - MenuParam* param = menu_param_find(global_ctx, param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 恢复默认值 - float default_val = menu_param_internal_to_float((const MenuParam*)¶m->default_val); - return menu_param_set_value(global_ctx, param_id, default_val); -} - -/** - * @brief 增加参数值(用于菜单上下键调整) - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @param step 步长(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_increase(MenuGlobalCtx* global_ctx, uint16_t param_id, float step) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - float current_val = 0.0f; - MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val); - if (err != MENU_OK) - { - return err; - } - - return menu_param_set_value(global_ctx, param_id, current_val + step); -} - -/** - * @brief 减少参数值(用于菜单上下键调整) - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @param step 步长(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_decrease(MenuGlobalCtx* global_ctx, uint16_t param_id, float step) -{ - if (global_ctx == NULL || !global_ctx->is_initialized) - { - return MENU_ERR_NOT_INITIALIZED; - } - - float current_val = 0.0f; - MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val); - if (err != MENU_OK) - { - return err; - } - - return menu_param_set_value(global_ctx, param_id, current_val - step); -} - -/** - * @brief 获取参数类型 - * @param global_ctx 全局上下文指针 - * @param param_id 参数ID - * @param type 输出参数,参数类型 - * @return 错误码 - */ -MenuErrCode menu_param_get_type(MenuGlobalCtx* global_ctx, uint16_t param_id, MenuParamType* type) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || type == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuParam* param = menu_param_find(global_ctx, param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 返回参数类型 - *type = param->type; - return MENU_OK; -} - -/** - * @brief 批量注册参数到菜单节点 - * @param global_ctx 全局上下文指针 - * @param configs 参数配置数组 - * @param config_count 配置数量 - * @return 错误码 - */ -MenuErrCode menu_param_register_batch(MenuGlobalCtx* global_ctx, const MenuParamConfig* configs, uint16_t config_count) -{ - if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuErrCode err = MENU_OK; - for (uint16_t i = 0; i < config_count; i++) - { - err = menu_param_register(global_ctx, configs[i].node_id, configs[i].param_id, - configs[i].type, configs[i].min_val, configs[i].max_val, - configs[i].default_val, configs[i].scale); - if (err != MENU_OK) - { - MENU_ERROR("Failed to register param %d at index %d: %s", - configs[i].param_id, i, menu_err_code_to_str(err)); - return err; - } - } - - return MENU_OK; -} - -#endif // MENU_CONFIG_ENABLE_PARAM \ No newline at end of file diff --git a/src/param/menu_param.h b/src/param/menu_param.h deleted file mode 100644 index 4722050..0000000 --- a/src/param/menu_param.h +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @file menu_param.h - * @brief 菜单参数管理内部类型定义(用户无需关心) - */ -#ifndef MENU_PARAM_H -#define MENU_PARAM_H - -#include "menu_core.h" -#include -#include - -/** - * @brief 参数值共用体(支持多种数据类型) - */ -typedef union { - int8_t i8; ///< 8位有符号整数 - uint8_t u8; ///< 8位无符号整数 - int16_t i16; ///< 16位有符号整数 - uint16_t u16; ///< 16位无符号整数 - int32_t i32; ///< 32位有符号整数 - uint32_t u32; ///< 32位无符号整数 - float f; ///< 浮点数 -} MenuParamValue; - -/** - * @brief 参数结构体(参数管理的核心单元) - */ -typedef struct { - uint16_t id; ///< 参数ID(唯一) - MenuParamType type; ///< 参数类型 - MenuParamValue value; ///< 当前值 - MenuParamValue default_val;///< 默认值 - float min_val; ///< 最小值(浮点表示) - float max_val; ///< 最大值(浮点表示) - float scale; ///< 缩放因子 - bool is_registered : 1; ///< 是否已注册 -} MenuParam; - -#endif // MENU_PARAM_H \ No newline at end of file diff --git a/src/utils/menu_utils.c b/src/utils/menu_utils.c deleted file mode 100644 index 2175657..0000000 --- a/src/utils/menu_utils.c +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @file menu_utils.c - * @brief 菜单工具模块:断言、打印、系统滴答等 - */ -#include "menu_def.h" -#include "menu_port.h" -#include -#include - -/** - * @brief 错误码字符串映射 - */ -static const char* menu_err_str[] = { - "OK", ///< MENU_OK - "Invalid parameter", ///< MENU_ERR_INVALID_PARAM - "Out of memory", ///< MENU_ERR_OUT_OF_MEMORY - "Node not found", ///< MENU_ERR_NODE_NOT_FOUND - "Stack overflow", ///< MENU_ERR_STACK_OVERFLOW - "Stack underflow", ///< MENU_ERR_STACK_UNDERFLOW - "Event queue full", ///< MENU_ERR_EVENT_QUEUE_FULL - "Feature not supported", ///< MENU_ERR_NOT_SUPPORTED - "Hardware port error", ///< MENU_ERR_HW_PORT_ERROR - "Component not initialized", ///< MENU_ERR_NOT_INITIALIZED - "Invalid context pointer" ///< MENU_ERR_INVALID_CONTEXT -}; - -/** - * @brief 断言失败处理函数 - * @param file 文件名 - * @param line 行号 - */ -void menu_utils_assert_failed(const char* file, uint32_t line) -{ - // 调用port层的打印接口输出断言信息 - menu_utils_log(MENU_LOG_LEVEL_CRITICAL, file, line, "Assertion failed"); - - // 可以在这里添加其他处理,如触发硬件异常、保存上下文等 - // 例如:menu_port_trigger_exception(); -} - -/** - * @brief 通用日志函数(对接port层的硬件打印接口) - * @param level 日志级别 - * @param file 文件名 - * @param line 行号 - * @param fmt 格式化字符串 - * @param ... 可变参数 - */ -void menu_utils_log(MenuLogLevel level, const char* file, uint32_t line, const char* fmt, ...) -{ - // Explicitly cast unused parameters to void to suppress warnings - (void)level; - (void)file; - (void)line; - - va_list args; - va_start(args, fmt); - - // Get the driver and use its printf directly - const MenuPortDriver* driver = menu_port_get_driver(); - if (driver != NULL && driver->printf != NULL) - { - // Output the log message - driver->printf(fmt, args); - } - - va_end(args); -} - -/** - * @brief 错误码转换为字符串 - * @param err_code 错误码 - * @return 错误信息字符串 - */ -const char* menu_err_code_to_str(MenuErrCode err_code) -{ - if (err_code < sizeof(menu_err_str) / sizeof(menu_err_str[0])) { - return menu_err_str[err_code]; - } else { - return "Unknown error"; - } -} - -/** - * @brief 调试打印函数(兼容旧接口,对接port层的硬件打印接口) - * @param fmt 格式化字符串 - * @param ... 可变参数 - */ -void menu_utils_printf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - - // 调用port层的可变参数打印接口 - menu_port_printf(fmt, args); - - va_end(args); -} - -/** - * @brief 获取系统滴答时间(ms,对接port层) - * @return 当前滴答时间 - */ -uint32_t menu_utils_get_tick(void) -{ - // 调用port层的系统滴答获取接口 - return menu_port_get_tick(); -} - -/** - * @brief 计算两个32位无符号整数的差值(处理溢出情况) - * @param now 当前时间 - * @param last 上次时间 - * @return 差值(ms) - */ -uint32_t menu_utils_diff_tick(uint32_t now, uint32_t last) -{ - if (now >= last) - { - return now - last; - } - else - { - // 处理溢出情况 - return (UINT32_MAX - last) + now + 1; - } -} - -/** - * @brief 延迟函数(毫秒级,对接port层) - * @param ms 延迟时间(ms) - */ -void menu_utils_delay_ms(uint32_t ms) -{ - // 调用port层的延迟接口 - menu_port_delay_ms(ms); -} \ No newline at end of file diff --git a/test/menu_test.c b/test/menu_test.c deleted file mode 100644 index d33780d..0000000 --- a/test/menu_test.c +++ /dev/null @@ -1,471 +0,0 @@ -/** - * @file menu_test.c - * @brief 菜单组件单元测试 - */ -#include "menu.h" -#include "menu_port.h" -#include -#include - -/************************** 测试用例 **************************/ - -/** - * @brief 测试菜单初始化和反初始化 - */ -void test_menu_init_deinit(void) -{ - printf("Test menu_init_deinit..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 检查初始化状态 - if (!menu_is_initialized(ctx)) - { - printf("FAILED: menu_is_initialized returned false\n"); - return; - } - - // 反初始化菜单 - err = menu_deinit(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_deinit returned %d\n", err); - return; - } - - printf("PASSED\n"); -} - -/** - * @brief 测试菜单节点注册 - */ -void test_menu_register_node(void) -{ - printf("Test menu_register_node..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册菜单节点 - err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL); - if (err != MENU_OK) - { - printf("FAILED: menu_register_node returned %d\n", err); - return; - } - - // 注册子菜单节点 - err = menu_register_node(ctx, 1, 2, "Param Menu", NULL, NULL); - if (err != MENU_OK) - { - printf("FAILED: menu_register_node returned %d\n", err); - return; - } - - // 检查当前节点 - MenuNodeId current_node_id; - err = menu_get_current_node(ctx, ¤t_node_id); - if (err != MENU_OK) - { - printf("FAILED: menu_get_current_node returned %d\n", err); - return; - } - - if (current_node_id != 1) - { - printf("FAILED: current_node_id is %d, expected 1\n", current_node_id); - return; - } - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/** - * @brief 测试批量注册菜单节点 - */ -void test_menu_register_nodes(void) -{ - printf("Test menu_register_nodes..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 定义菜单节点配置 - MenuNodeConfig nodes[] = { - {1, 0, "Main Menu", NULL, NULL}, - {2, 1, "Param Menu", NULL, NULL}, - {3, 1, "Info Menu", NULL, NULL}, - {4, 2, "Temp Param", NULL, NULL} - }; - - // 批量注册菜单节点 - err = menu_register_nodes(ctx, nodes, sizeof(nodes) / sizeof(nodes[0])); - if (err != MENU_OK) - { - printf("FAILED: menu_register_nodes returned %d\n", err); - return; - } - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/** - * @brief 测试事件处理 - */ -void test_menu_event(void) -{ - printf("Test menu_event..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册菜单节点 - err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL); - err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL); - err = menu_register_node(ctx, 1, 3, "Menu 2", NULL, NULL); - - // 发送事件 - err = menu_post_event(ctx, MENU_EVENT_KEY_DOWN, 0, MENU_EVENT_PRIORITY_NORMAL); - if (err != MENU_OK) - { - printf("FAILED: menu_post_event returned %d\n", err); - return; - } - - // 处理事件 - menu_main_loop(ctx); - - // 检查当前节点 - MenuNodeId current_node_id; - err = menu_get_current_node(ctx, ¤t_node_id); - if (err != MENU_OK) - { - printf("FAILED: menu_get_current_node returned %d\n", err); - return; - } - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/** - * @brief 测试内存监控 - */ -void test_menu_mem_stats(void) -{ - printf("Test menu_mem_stats..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册菜单节点 - err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL); - err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL); - - // 获取内存统计信息 - MenuMemStats stats; - err = menu_get_mem_stats(ctx, &stats); - if (err != MENU_OK) - { - printf("FAILED: menu_get_mem_stats returned %d\n", err); - return; - } - - printf("PASSED (nodes: %d/%d, params: %d/%d, events: %d/%d)\n", - stats.used_nodes, stats.total_nodes, - stats.used_params, stats.total_params, - stats.event_queue_used, stats.event_queue_len); - - // 反初始化菜单 - menu_deinit(ctx); -} - -/************************** 多语言支持测试 **************************/ - -/** - * @brief 测试多语言功能 - */ -void test_menu_lang(void) -{ - printf("Test menu_lang..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册语言字符串 - err = menu_lang_register_str(ctx, 1, 0, "中文菜单"); - err = menu_lang_register_str(ctx, 1, 1, "English Menu"); - err = menu_lang_register_str(ctx, 2, 0, "参数设置"); - err = menu_lang_register_str(ctx, 2, 1, "Param Settings"); - - // 设置当前语言为中文 - err = menu_lang_set_current(ctx, 0); - if (err != MENU_OK) - { - printf("FAILED: menu_lang_set_current(0) returned %d\n", err); - return; - } - - // 检查当前语言 - uint8_t current_lang; - err = menu_lang_get_current(ctx, ¤t_lang); - if (err != MENU_OK || current_lang != 0) - { - printf("FAILED: menu_lang_get_current returned %d, expected 0\n", current_lang); - return; - } - - // 批量注册语言字符串测试 - MenuLangStr strs[] = { - {3, 0, "系统信息"}, - {3, 1, "System Info"}, - {4, 0, "关于"}, - {4, 1, "About"} - }; - - err = menu_lang_register_strs(ctx, strs, sizeof(strs) / sizeof(strs[0])); - if (err != MENU_OK) - { - printf("FAILED: menu_lang_register_strs returned %d\n", err); - return; - } - - // 动态加载语言包测试 - MenuLangStr new_pack[] = { - {1, 2, "Español Menú"}, - {2, 2, "Ajustes de Parámetros"}, - {3, 2, "Información del Sistema"}, - {4, 2, "Acerca de"} - }; - - err = menu_lang_load_pack(ctx, 2, new_pack, sizeof(new_pack) / sizeof(new_pack[0])); - if (err != MENU_OK) - { - printf("FAILED: menu_lang_load_pack returned %d\n", err); - return; - } - - // 切换到新语言 - err = menu_lang_set_current(ctx, 2); - if (err != MENU_OK) - { - printf("FAILED: menu_lang_set_current(2) returned %d\n", err); - return; - } - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/************************** 参数管理测试 **************************/ - -/** - * @brief 测试参数管理功能 - */ -void test_menu_param(void) -{ - printf("Test menu_param..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册菜单节点 - err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL); - - // 注册参数 - err = menu_param_register(ctx, 1, 1, MENU_PARAM_TYPE_INT16, 0, 100, 50, 1.0f); - if (err != MENU_OK) - { - printf("FAILED: menu_param_register returned %d\n", err); - return; - } - - // 设置参数值 - err = menu_param_set_value(ctx, 1, 75.0f); - if (err != MENU_OK) - { - printf("FAILED: menu_param_set_value returned %d\n", err); - return; - } - - // 获取参数值 - float value; - err = menu_param_get_value(ctx, 1, &value); - if (err != MENU_OK || (int)value != 75) - { - printf("FAILED: menu_param_get_value returned %.1f, expected 75\n", value); - return; - } - - // 获取参数类型 - MenuParamType type; - err = menu_param_get_type(ctx, 1, &type); - if (err != MENU_OK || type != MENU_PARAM_TYPE_INT16) - { - printf("FAILED: menu_param_get_type returned %d, expected %d\n", type, MENU_PARAM_TYPE_INT16); - return; - } - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/************************** 优先级事件测试 **************************/ - -/** - * @brief 测试优先级事件处理 - */ -void test_menu_priority_event(void) -{ - printf("Test menu_priority_event..."); - - // 分配菜单上下文 - uint8_t menu_ctx_buf[menu_get_ctx_size()]; - MenuGlobalCtx* ctx = (MenuGlobalCtx*)menu_ctx_buf; - - // 初始化菜单 - MenuErrCode err = menu_init(ctx); - if (err != MENU_OK) - { - printf("FAILED: menu_init returned %d\n", err); - return; - } - - // 注册菜单节点 - err = menu_register_node(ctx, 0, 1, "Main Menu", NULL, NULL); - err = menu_register_node(ctx, 1, 2, "Menu 1", NULL, NULL); - err = menu_register_node(ctx, 1, 3, "Menu 2", NULL, NULL); - - // 发送不同优先级的事件 - err = menu_post_event(ctx, MENU_EVENT_KEY_DOWN, 0, MENU_EVENT_PRIORITY_LOW); - err = menu_post_event(ctx, MENU_EVENT_KEY_UP, 0, MENU_EVENT_PRIORITY_HIGH); - err = menu_post_event(ctx, MENU_EVENT_KEY_ENTER, 0, MENU_EVENT_PRIORITY_NORMAL); - - // 处理事件 - menu_main_loop(ctx); - - // 反初始化菜单 - menu_deinit(ctx); - - printf("PASSED\n"); -} - -/************************** 主函数 **************************/ - -/** - * @brief 测试主函数 - */ -int main(void) -{ - printf("Menu Component Unit Tests\n"); - printf("=========================\n"); - - // 测试菜单初始化和反初始化 - test_menu_init_deinit(); - - // 测试菜单节点注册 - test_menu_register_node(); - - // 测试批量注册菜单节点 - test_menu_register_nodes(); - - // 测试事件处理 - test_menu_event(); - - // 测试优先级事件处理 - test_menu_priority_event(); - - // 测试内存监控 - test_menu_mem_stats(); - - // 测试参数管理 -#if MENU_CONFIG_ENABLE_PARAM - test_menu_param(); -#endif - - // 测试多语言支持 -#if MENU_CONFIG_ENABLE_LANG - test_menu_lang(); -#endif - - printf("=========================\n"); - printf("All tests completed!\n"); - - return 0; -} diff --git a/update.md b/update.md deleted file mode 100644 index 4a9f919..0000000 --- a/update.md +++ /dev/null @@ -1,819 +0,0 @@ -你需要在已有的工业级嵌入式菜单组件中,拓展**参数与Modbus寄存器地址的灵活映射功能**:让每个参数能根据自身性质(数据类型、读写权限、业务属性)匹配对应的Modbus寄存器(包括线圈、离散输入、保持寄存器、输入寄存器),支持寄存器地址的配置、映射查询,以及参数值与寄存器数据的双向转换,同时保持与原有组件的解耦和工业级的鲁棒性。 - -以下是基于原有目录结构的详细实现方案,遵循**分层解耦、静态内存、可配置、类型安全**的工业级设计原则。 - ---- - -## 一、设计核心思路(工业级考量) -1. **解耦设计**:Modbus映射模块作为独立的功能扩展层,依赖原有参数模块的接口,不侵入核心逻辑。 -2. **静态内存**:使用静态数组存储Modbus映射关系,避免动态分配,适配嵌入式内存约束。 -3. **类型匹配**:参数类型与Modbus寄存器数据类型(1位、8位、16位、32位、浮点)自动转换,支持大小端配置。 -4. **权限控制**:映射时指定寄存器的读写权限(只读/只写/读写),与参数的权限联动。 -5. **灵活配置**:支持单参数映射单寄存器、单参数映射多寄存器(如32位浮点占2个16位保持寄存器)。 -6. **可裁剪**:通过宏开关控制Modbus映射功能的启用/关闭,不影响原有功能。 - ---- - -## 二、代码实现(按目录结构补充/修改) -### 1. 对外API层(`menu/api/`) -#### 1.1 修改`menu/api/menu_config.h`(添加Modbus映射配置宏) -在原有配置后新增Modbus相关配置,保持可裁剪性: -```c -/************************** Modbus映射功能配置(可裁剪) **************************/ -/** - * @brief 是否启用参数-Modbus寄存器映射功能 - */ -#define MENU_CONFIG_ENABLE_MODBUS_MAP 1 - -/** - * @brief 最大Modbus映射数量(静态内存,根据项目调整) - */ -#define MENU_CONFIG_MAX_MODBUS_MAPS 16 - -/** - * @brief Modbus寄存器地址最大值(根据实际Modbus从站配置) - */ -#define MENU_CONFIG_MODBUS_MAX_ADDR 0xFFFF - -/** - * @brief 默认Modbus字节序(0-小端,1-大端,2-字小端字节大端(Modbus标准)) - */ -#define MENU_CONFIG_MODBUS_BYTE_ORDER 2 - -/** - * @brief 是否启用Modbus映射权限校验(与参数权限联动) - */ -#define MENU_CONFIG_MODBUS_PERMISSION 1 -``` - -#### 1.2 修改`menu/api/menu.h`(添加Modbus映射的对外接口和类型) -在原有内容后新增Modbus相关的类型和接口,作为用户可调用的API: -```c -/************************** Modbus映射功能(启用时有效) **************************/ -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief Modbus寄存器类型(符合Modbus协议标准) - */ -typedef enum { - MODBUS_REG_TYPE_COIL = 0, ///< 线圈(1位,地址范围00001-09999) - MODBUS_REG_TYPE_DISCRETE_INPUT, ///< 离散输入(1位,地址范围10001-19999) - MODBUS_REG_TYPE_HOLDING_REG, ///< 保持寄存器(16位,地址范围40001-49999) - MODBUS_REG_TYPE_INPUT_REG, ///< 输入寄存器(16位,地址范围30001-39999) -} ModbusRegType; - -/** - * @brief Modbus寄存器读写权限 - */ -typedef enum { - MODBUS_PERM_READ_ONLY = 0, ///< 只读(如离散输入、输入寄存器) - MODBUS_PERM_WRITE_ONLY, ///< 只写(如线圈) - MODBUS_PERM_READ_WRITE, ///< 读写(如保持寄存器) -} ModbusPerm; - -/** - * @brief Modbus映射结构体(对外只读,内部初始化) - */ -typedef struct { - uint16_t param_id; ///< 关联的参数ID - ModbusRegType reg_type; ///< 寄存器类型 - uint16_t reg_addr; ///< 寄存器起始地址(协议地址,如40001) - uint8_t reg_count; ///< 占用寄存器数量(如32位浮点占2个16位寄存器) - ModbusPerm perm; ///< 读写权限 - uint8_t byte_order; ///< 字节序(0-小端,1-大端,2-Modbus标准) -} ModbusMap; - -/** - * @brief 注册参数与Modbus寄存器的映射关系 - * @param param_id 参数ID(需已通过menu_param_register注册) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器起始地址(协议地址,如40001) - * @param reg_count 占用寄存器数量(如1:16位,2:32位,4:64位) - * @param perm 读写权限 - * @param byte_order 字节序(可选,默认使用MENU_CONFIG_MODBUS_BYTE_ORDER) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order); - -/** - * @brief 根据参数ID查询Modbus映射关系 - * @param param_id 参数ID - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_param(uint16_t param_id, ModbusMap* map); - -/** - * @brief 根据寄存器地址和类型查询Modbus映射关系 - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param map 输出参数,映射关系结构体 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_query_by_reg(ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map); - -/** - * @brief 将参数值写入对应的Modbus寄存器(双向转换:参数值→寄存器数据) - * @param param_id 参数ID - * @param reg_buf 输出参数,寄存器数据缓冲区(需足够大,如32位占2个16位寄存器) - * @param buf_len 缓冲区长度(输入),实际写入长度(输出) - * @return 错误码 - */ -MenuErrCode menu_modbus_map_param_to_reg(uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len); - -/** - * @brief 将Modbus寄存器数据读取到参数值(双向转换:寄存器数据→参数值) - * @param param_id 参数ID - * @param reg_buf 输入参数,寄存器数据缓冲区 - * @param buf_len 缓冲区长度 - * @return 错误码 - */ -MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len); - -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -### 2. 内部头文件层(`menu/internal/`) -#### 2.1 修改`menu/internal/menu_core.h`(添加Modbus映射的内部类型) -在原有核心类型后新增Modbus映射的内部结构体(比对外结构体更详细,包含数据转换的辅助信息): -```c -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief Modbus映射内部结构体(包含数据转换的辅助信息,用户无需关心) - */ -typedef struct { - ModbusMap pub; ///< 对外暴露的映射信息(兼容外部结构体) - bool is_registered; ///< 是否已注册 - MenuParamType param_type; ///< 关联的参数类型(缓存,避免重复查询) - uint16_t reg_addr_offset; ///< 寄存器地址偏移(协议地址→实际地址,如40001→0) -} ModbusMapInternal; -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -#### 2.2 修改`menu/internal/menu_data.h`(添加Modbus映射的静态全局数据) -在原有共享数据后新增Modbus映射的静态数组(静态内存,无动态分配): -```c -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief Modbus映射上下文(静态全局,仅内部访问) - */ -static ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS]; -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -### 3. 功能扩展层(`menu/src/features/`) -#### 3.1 新增`menu/src/features/menu_modbus.c`(Modbus映射核心逻辑) -实现Modbus映射的注册、查询、数据双向转换,与原有参数模块无缝集成: -```c -/** - * @file menu_modbus.c - * @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配) - */ -#if MENU_CONFIG_ENABLE_MODBUS_MAP -#include "menu.h" -#include "menu_core.h" -#include "menu_data.h" -#include "menu_def.h" -#include -#include - -/************************** 内部辅助函数 **************************/ -/** - * @brief 转换Modbus协议地址到实际偏移地址(如40001→0,00001→0) - * @param reg_type 寄存器类型 - * @param reg_addr 协议地址(如40001) - * @param offset 输出参数,实际偏移地址 - * @return 错误码 - */ -static MenuErrCode menu_modbus_convert_addr(ModbusRegType reg_type, uint16_t reg_addr, uint16_t* offset) -{ - if (offset == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - // 校验协议地址范围(符合Modbus标准) - switch (reg_type) - { - case MODBUS_REG_TYPE_COIL: - if (reg_addr < 1 || reg_addr > 9999) - { - MENU_DEBUG("Modbus coil addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 1; - break; - case MODBUS_REG_TYPE_DISCRETE_INPUT: - if (reg_addr < 10001 || reg_addr > 19999) - { - MENU_DEBUG("Modbus discrete input addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 10001; - break; - case MODBUS_REG_TYPE_HOLDING_REG: - if (reg_addr < 40001 || reg_addr > 49999) - { - MENU_DEBUG("Modbus holding reg addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 40001; - break; - case MODBUS_REG_TYPE_INPUT_REG: - if (reg_addr < 30001 || reg_addr > 39999) - { - MENU_DEBUG("Modbus input reg addr out of range: %d", reg_addr); - return MENU_ERR_INVALID_PARAM; - } - *offset = reg_addr - 30001; - break; - default: - return MENU_ERR_INVALID_PARAM; - } - - // 校验实际偏移地址不超过配置的最大值 - if (*offset > MENU_CONFIG_MODBUS_MAX_ADDR) - { - MENU_DEBUG("Modbus reg offset out of range: %d", *offset); - return MENU_ERR_INVALID_PARAM; - } - - return MENU_OK; -} - -/** - * @brief 校验寄存器类型与参数类型的匹配性(工业级:类型安全) - * @param param_type 参数类型 - * @param reg_type 寄存器类型 - * @return 错误码 - */ -static MenuErrCode menu_modbus_check_type_match(MenuParamType param_type, ModbusRegType reg_type) -{ - // 1位寄存器(线圈、离散输入)只能匹配布尔型/8位整型参数 - if (reg_type == MODBUS_REG_TYPE_COIL || reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT) - { - if (param_type != MENU_PARAM_TYPE_INT8 && param_type != MENU_PARAM_TYPE_UINT8) - { - MENU_DEBUG("Modbus 1-bit reg not match param type: %d", param_type); - return MENU_ERR_INVALID_PARAM; - } - return MENU_OK; - } - - // 16位寄存器(保持、输入)可匹配所有类型(16/32/浮点需多寄存器) - return MENU_OK; -} - -/** - * @brief 字节序转换(适配不同的Modbus字节序,工业级:兼容不同从站) - * @param data 数据缓冲区(16位为单位) - * @param len 数据长度(16位的数量) - * @param byte_order 字节序(0-小端,1-大端,2-Modbus标准(字小端,字节大端)) - * @param is_reverse 是否反向转换(用于读取/写入) - */ -static void menu_modbus_byte_order_convert(uint16_t* data, uint8_t len, uint8_t byte_order, bool is_reverse) -{ - if (data == NULL || len == 0) - { - return; - } - - // 默认使用配置的字节序 - if (byte_order > 2) - { - byte_order = MENU_CONFIG_MODBUS_BYTE_ORDER; - } - - switch (byte_order) - { - case 0: // 小端(低字节在前,高字节在后) - if (is_reverse) - { - for (uint8_t i = 0; i < len; i++) - { - data[i] = (data[i] << 8) | (data[i] >> 8); - } - } - break; - case 1: // 大端(高字节在前,低字节在后) - // 无需转换,Modbus默认是大端 - break; - case 2: // Modbus标准(字小端,字节大端:如32位值0x12345678→0x5678 0x1234) - if (is_reverse) - { - // 读取时:将Modbus顺序转换为主机顺序 - for (uint8_t i = 0; i < len; i += 2) - { - if (i + 1 < len) - { - uint16_t temp = data[i]; - data[i] = data[i + 1]; - data[i + 1] = temp; - } - } - } - else - { - // 写入时:将主机顺序转换为Modbus顺序 - for (uint8_t i = 0; i < len; i += 2) - { - if (i + 1 < len) - { - uint16_t temp = data[i]; - data[i] = data[i + 1]; - data[i + 1] = temp; - } - } - } - break; - default: - break; - } -} - -/** - * @brief 查找Modbus映射(通过参数ID) - * @param param_id 参数ID - * @return 映射内部结构体指针(NULL表示未找到) - */ -static ModbusMapInternal* menu_modbus_find_by_param(uint16_t param_id) -{ - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (s_menu_modbus_maps[i].is_registered && s_menu_modbus_maps[i].pub.param_id == param_id) - { - return &s_menu_modbus_maps[i]; - } - } - return NULL; -} - -/** - * @brief 查找Modbus映射(通过寄存器类型和地址) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址(协议地址) - * @return 映射内部结构体指针(NULL表示未找到) - */ -static ModbusMapInternal* menu_modbus_find_by_reg(ModbusRegType reg_type, uint16_t reg_addr) -{ - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (s_menu_modbus_maps[i].is_registered && - s_menu_modbus_maps[i].pub.reg_type == reg_type && - s_menu_modbus_maps[i].pub.reg_addr == reg_addr) - { - return &s_menu_modbus_maps[i]; - } - } - return NULL; -} - -/************************** 对外接口实现 **************************/ -MenuErrCode menu_modbus_map_register(uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order) -{ - // 1. 校验参数是否已注册 - MenuParam* param = menu_param_find(param_id); - if (param == NULL) - { - MENU_DEBUG("Modbus map param %d not found", param_id); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验寄存器地址和转换为偏移 - uint16_t reg_offset; - MenuErrCode err = menu_modbus_convert_addr(reg_type, reg_addr, ®_offset); - if (err != MENU_OK) - { - return err; - } - - // 3. 校验寄存器数量(至少1个) - if (reg_count == 0) - { - MENU_DEBUG("Modbus map reg count is zero"); - return MENU_ERR_INVALID_PARAM; - } - - // 4. 校验寄存器类型与参数类型匹配 - err = menu_modbus_check_type_match(param->type, reg_type); - if (err != MENU_OK) - { - return err; - } - - // 5. 校验读写权限(与寄存器类型联动,如离散输入只能只读) - #if MENU_CONFIG_MODBUS_PERMISSION - if ((reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT || reg_type == MODBUS_REG_TYPE_INPUT_REG) && perm != MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus input reg only support read"); - return MENU_ERR_INVALID_PARAM; - } - if (reg_type == MODBUS_REG_TYPE_COIL && perm == MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus coil not support read only"); - return MENU_ERR_INVALID_PARAM; - } - #endif - - // 6. 检查映射是否已存在 - if (menu_modbus_find_by_param(param_id) != NULL || menu_modbus_find_by_reg(reg_type, reg_addr) != NULL) - { - MENU_DEBUG("Modbus map already exists: param %d, reg %d:%d", param_id, reg_type, reg_addr); - return MENU_ERR_INVALID_PARAM; - } - - // 7. 查找空闲的映射位置(静态数组) - ModbusMapInternal* map = NULL; - for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++) - { - if (!s_menu_modbus_maps[i].is_registered) - { - map = &s_menu_modbus_maps[i]; - break; - } - } - - if (map == NULL) - { - MENU_DEBUG("Modbus map out of memory: max %d", MENU_CONFIG_MAX_MODBUS_MAPS); - return MENU_ERR_OUT_OF_MEMORY; - } - - // 8. 初始化映射关系 - MENU_MEM_SET_ZERO(map, sizeof(ModbusMapInternal)); - map->pub.param_id = param_id; - map->pub.reg_type = reg_type; - map->pub.reg_addr = reg_addr; - map->pub.reg_count = reg_count; - map->pub.perm = perm; - map->pub.byte_order = byte_order; - map->param_type = param->type; - map->reg_addr_offset = reg_offset; - map->is_registered = true; - - MENU_DEBUG("Modbus map registered: param %d → reg %d:%d (count %d, perm %d)", - param_id, reg_type, reg_addr, reg_count, perm); - return MENU_OK; -} - -MenuErrCode menu_modbus_map_query_by_param(uint16_t param_id, ModbusMap* map) -{ - if (map == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - ModbusMapInternal* internal_map = menu_modbus_find_by_param(param_id); - if (internal_map == NULL) - { - MENU_DEBUG("Modbus map param %d not found", param_id); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 复制对外暴露的信息 - *map = internal_map->pub; - return MENU_OK; -} - -MenuErrCode menu_modbus_map_query_by_reg(ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map) -{ - if (map == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - ModbusMapInternal* internal_map = menu_modbus_find_by_reg(reg_type, reg_addr); - if (internal_map == NULL) - { - MENU_DEBUG("Modbus map reg %d:%d not found", reg_type, reg_addr); - return MENU_ERR_NODE_NOT_FOUND; - } - - // 复制对外暴露的信息 - *map = internal_map->pub; - return MENU_OK; -} - -MenuErrCode menu_modbus_map_param_to_reg(uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len) -{ - if (reg_buf == NULL || buf_len == NULL || *buf_len == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - // 1. 查找映射关系 - ModbusMapInternal* map = menu_modbus_find_by_param(param_id); - if (map == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验读写权限 - #if MENU_CONFIG_MODBUS_PERMISSION - if (map->pub.perm == MODBUS_PERM_READ_ONLY) - { - MENU_DEBUG("Modbus map param %d is read only", param_id); - return MENU_ERR_PARAM_WRITE_DENIED; // 复用原有错误码,或新增 - } - #endif - - // 3. 获取参数值 - float param_val; - MenuErrCode err = menu_param_get_value(param_id, ¶m_val); - if (err != MENU_OK) - { - return err; - } - - // 4. 转换参数值到寄存器数据(按类型和寄存器数量) - uint16_t* reg_data = (uint16_t*)reg_buf; - uint8_t req_len = map->pub.reg_count; - if (req_len > *buf_len / 2) // 寄存器数据按16位计算 - { - MENU_DEBUG("Modbus reg buf too small: need %d, got %d", req_len * 2, *buf_len); - return MENU_ERR_INVALID_PARAM; - } - - // 清空缓冲区 - memset(reg_data, 0, req_len * 2); - - // 根据参数类型转换 - MenuParam* param = menu_param_find(param_id); - switch (map->pub.reg_type) - { - case MODBUS_REG_TYPE_COIL: - case MODBUS_REG_TYPE_DISCRETE_INPUT: - // 1位寄存器:取参数值的最低位 - reg_data[0] = (uint16_t)(param_val > 0 ? 1 : 0); - break; - case MODBUS_REG_TYPE_HOLDING_REG: - case MODBUS_REG_TYPE_INPUT_REG: - // 16位寄存器:根据参数类型和数量转换 - if (param->type == MENU_PARAM_TYPE_INT16 || param->type == MENU_PARAM_TYPE_UINT16) - { - reg_data[0] = (uint16_t)param_val; - } - else if (param->type == MENU_PARAM_TYPE_INT32 || param->type == MENU_PARAM_TYPE_UINT32) - { - uint32_t val = (uint32_t)param_val; - reg_data[0] = (uint16_t)(val & 0xFFFF); - reg_data[1] = (uint16_t)((val >> 16) & 0xFFFF); - } - else if (param->type == MENU_PARAM_TYPE_FLOAT) - { - // 浮点型转换为32位二进制,占2个16位寄存器 - union { - float f; - uint32_t u32; - } float_val; - float_val.f = param_val; - reg_data[0] = (uint16_t)(float_val.u32 & 0xFFFF); - reg_data[1] = (uint16_t)((float_val.u32 >> 16) & 0xFFFF); - } - else if (param->type == MENU_PARAM_TYPE_INT8 || param->type == MENU_PARAM_TYPE_UINT8) - { - reg_data[0] = (uint16_t)param_val; - } - break; - default: - return MENU_ERR_INVALID_PARAM; - } - - // 5. 字节序转换(写入寄存器前) - menu_modbus_byte_order_convert(reg_data, req_len, map->pub.byte_order, false); - - // 6. 设置实际写入长度 - *buf_len = req_len * 2; - - MENU_DEBUG("Modbus param %d to reg: val %f → reg data len %d", param_id, param_val, *buf_len); - return MENU_OK; -} - -MenuErrCode menu_modbus_map_reg_to_param(uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len) -{ - if (reg_buf == NULL || buf_len == 0) - { - return MENU_ERR_INVALID_PARAM; - } - - // 1. 查找映射关系 - ModbusMapInternal* map = menu_modbus_find_by_param(param_id); - if (map == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 2. 校验读写权限 - #if MENU_CONFIG_MODBUS_PERMISSION - if (map->pub.perm == MODBUS_PERM_WRITE_ONLY) - { - MENU_DEBUG("Modbus map param %d is write only", param_id); - return MENU_ERR_PARAM_WRITE_DENIED; - } - #endif - - // 3. 校验缓冲区长度 - uint8_t req_len = map->pub.reg_count * 2; - if (buf_len < req_len) - { - MENU_DEBUG("Modbus reg buf too small: need %d, got %d", req_len, buf_len); - return MENU_ERR_INVALID_PARAM; - } - - // 4. 复制寄存器数据并进行字节序转换(读取寄存器后) - uint16_t reg_data[MENU_CONFIG_MAX_MODBUS_MAPS * 2] = {0}; // 足够大的临时缓冲区 - memcpy(reg_data, reg_buf, req_len); - menu_modbus_byte_order_convert(reg_data, map->pub.reg_count, map->pub.byte_order, true); - - // 5. 转换寄存器数据到参数值 - float param_val = 0.0f; - MenuParam* param = menu_param_find(param_id); - switch (map->pub.reg_type) - { - case MODBUS_REG_TYPE_COIL: - case MODBUS_REG_TYPE_DISCRETE_INPUT: - // 1位寄存器:0→0,非0→1 - param_val = (reg_data[0] & 0x01) ? 1.0f : 0.0f; - break; - case MODBUS_REG_TYPE_HOLDING_REG: - case MODBUS_REG_TYPE_INPUT_REG: - // 16位寄存器:根据参数类型转换 - if (param->type == MENU_PARAM_TYPE_INT8) - { - param_val = (int8_t)reg_data[0]; - } - else if (param->type == MENU_PARAM_TYPE_UINT8) - { - param_val = (uint8_t)reg_data[0]; - } - else if (param->type == MENU_PARAM_TYPE_INT16) - { - param_val = (int16_t)reg_data[0]; - } - else if (param->type == MENU_PARAM_TYPE_UINT16) - { - param_val = (uint16_t)reg_data[0]; - } - else if (param->type == MENU_PARAM_TYPE_INT32) - { - uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = (int32_t)val; - } - else if (param->type == MENU_PARAM_TYPE_UINT32) - { - uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = val; - } - else if (param->type == MENU_PARAM_TYPE_FLOAT) - { - union { - float f; - uint32_t u32; - } float_val; - float_val.u32 = ((uint32_t)reg_data[1] << 16) | reg_data[0]; - param_val = float_val.f; - } - break; - default: - return MENU_ERR_INVALID_PARAM; - } - - // 6. 设置参数值(自动进行范围检查) - MenuErrCode err = menu_param_set_value(param_id, param_val); - if (err != MENU_OK) - { - return err; - } - - MENU_DEBUG("Modbus reg to param %d: reg data len %d → val %f", param_id, buf_len, param_val); - return MENU_OK; -} - -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -### 4. 硬件端口层(`menu/src/port/`) -#### 4.1 可选修改`menu/src/port/menu_port.h`(添加Modbus硬件接口,用户适配) -如果需要对接实际的Modbus硬件(如RS485、TCP),可在port层添加硬件接口,与映射逻辑解耦: -```c -#if MENU_CONFIG_ENABLE_MODBUS_MAP -/** - * @brief Modbus硬件发送数据接口(用户需实现,如RS485发送) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len); - -/** - * @brief Modbus硬件接收数据接口(用户需实现,如RS485接收) - * @param reg_type 寄存器类型 - * @param reg_addr 寄存器地址 - * @param reg_buf 数据缓冲区 - * @param buf_len 数据长度 - * @return 错误码 - */ -MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len); -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -#### 4.2 可选修改`menu/src/port/menu_port.c`(Modbus硬件接口示例实现) -用户根据实际硬件(如STM32的RS485)实现Modbus数据的发送和接收: -```c -#if MENU_CONFIG_ENABLE_MODBUS_MAP -MenuErrCode menu_port_modbus_send(ModbusRegType reg_type, uint16_t reg_addr, const uint8_t* reg_buf, uint8_t buf_len) -{ - // 示例:通过RS485发送Modbus写指令(如写保持寄存器0x0000(40001)) - // 实际需根据Modbus协议实现帧组装、校验、发送 - (void)reg_type; - (void)reg_addr; - (void)reg_buf; - (void)buf_len; - return MENU_OK; // 模拟成功,用户需替换为实际逻辑 -} - -MenuErrCode menu_port_modbus_receive(ModbusRegType reg_type, uint16_t reg_addr, uint8_t* reg_buf, uint8_t buf_len) -{ - // 示例:通过RS485接收Modbus读指令的响应数据 - (void)reg_type; - (void)reg_addr; - (void)reg_buf; - (void)buf_len; - return MENU_OK; // 模拟成功,用户需替换为实际逻辑 -} -#endif // MENU_CONFIG_ENABLE_MODBUS_MAP -``` - -### 5. 使用示例(用户代码) -在原有菜单使用示例中添加Modbus映射的注册和数据交互: -```c -/** - * @brief 菜单组件+Modbus映射使用示例(用户主函数) - */ -#include "menu.h" -#include "menu_port.h" - -// 原有回调函数... - -int main(void) -{ - // 1. 初始化硬件和菜单组件 - // hal_init(); - menu_init(); - - // 2. 注册菜单节点和参数 - menu_register_node(0, 1, "Main Menu", NULL, NULL); - menu_register_node(1, 2, "Temperature", NULL, NULL); - menu_param_register(2, 1, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f); // 温度参数(ID=1) - - // 3. 注册参数与Modbus寄存器的映射(核心新增逻辑) - #if MENU_CONFIG_ENABLE_MODBUS_MAP - // 温度参数(ID=1)→ Modbus保持寄存器40001(地址40001,2个寄存器,读写权限,Modbus标准字节序) - menu_modbus_map_register(1, MODBUS_REG_TYPE_HOLDING_REG, 40001, 2, MODBUS_PERM_READ_WRITE, 2); - #endif - - // 4. 主循环 - while (1) - { - // 原有逻辑:按键扫描、菜单主循环 - menu_port_key_scan(); - menu_main_loop(); - - // 5. Modbus数据交互示例(新增逻辑) - #if MENU_CONFIG_ENABLE_MODBUS_MAP - { - // 示例1:将参数值写入Modbus寄存器并发送 - uint8_t reg_buf[4] = {0}; // 2个16位寄存器,占4字节 - uint8_t buf_len = sizeof(reg_buf); - if (menu_modbus_map_param_to_reg(1, reg_buf, &buf_len) == MENU_OK) - { - // 发送到Modbus硬件(用户需实现menu_port_modbus_send) - menu_port_modbus_send(MODBUS_REG_TYPE_HOLDING_REG, 40001, reg_buf, buf_len); - } - - // 示例2:从Modbus寄存器接收数据并更新参数值 - if (menu_port_modbus_receive(MODBUS_REG_TYPE_HOLDING_REG, 40001, reg_buf, buf_len) == MENU_OK) - { - menu_modbus_map_reg_to_param(1, reg_buf, buf_len); - } - } - #endif - - // 其他业务逻辑 - // ... - } -} -``` - ---- - -## 三、总结 -### 核心关键点回顾 -1. **解耦与可裁剪**:Modbus映射模块作为独立功能扩展层,通过`MENU_CONFIG_ENABLE_MODBUS_MAP`宏控制启用/关闭,不影响原有菜单和参数模块的逻辑。 -2. **静态内存与工业级鲁棒性**:使用静态数组存储映射关系,避免动态分配;加入地址范围校验、类型匹配校验、权限校验、字节序适配,确保工业环境下的稳定性。 -3. **灵活映射与数据转换**:支持单/多寄存器映射、参数类型与寄存器类型自动转换、多种字节序配置,适配不同的Modbus从站设备。 -4. **无缝集成**:与原有参数模块联动,参数值与寄存器数据的双向转换自动触发参数的范围检查,无需额外逻辑。 -5. **硬件解耦**:Modbus映射逻辑与硬件接口分离,用户只需在port层实现Modbus硬件的发送/接收接口,即可快速移植到不同的MCU和Modbus硬件(RS485/TCP)。 - -该扩展功能完全遵循工业级嵌入式组件的设计标准,可满足工业控制器、仪器仪表、智能家居等设备中参数与Modbus寄存器的灵活映射需求。 \ No newline at end of file diff --git a/开发文档.md b/开发文档.md index 4432462..4fb2c31 100644 --- a/开发文档.md +++ b/开发文档.md @@ -1,1555 +1,504 @@ -你需要基于指定的目录结构,实现一个符合**工业级嵌入式组件标准**的通用菜单C语言组件。该组件需满足**高可移植性、模块化、可裁剪、低资源占用、事件驱动解耦**的核心要求,同时适配嵌入式系统的硬件特性(如无操作系统/裸机、有限内存、硬件按键/显示)。 +# 工业级嵌入式菜单组件开发文档 -以下是按照目录结构分步实现的完整代码,包含详细注释和工业级设计考量。 +## 1. 项目概述 ---- +本项目是一个基于C语言开发的工业级嵌入式菜单组件,设计用于资源受限的嵌入式系统,具有高可移植性、模块化、可裁剪、低资源占用和事件驱动解耦等核心特点。 -## 一、整体设计思路(工业级嵌入式核心要点) -1. **分层解耦**:核心逻辑层(与硬件无关)→ 功能扩展层(可裁剪)→ 硬件接口层(用户适配),层间通过接口通信。 -2. **事件驱动**:按键扫描等硬件操作产生事件,菜单核心层消费事件,解耦硬件与业务逻辑。 -3. **静态内存**:使用静态数组/内存池代替动态分配(`malloc/free`),避免内存碎片。 -4. **可配置可裁剪**:通过`menu_config.h`的宏开关控制功能模块(如是否启用参数管理、多语言)和资源大小(如菜单节点数、事件队列长度)。 -5. **鲁棒性**:加入边界检查、错误码返回、断言(`ASSERT`)、状态机管理。 -6. **可移植性**:所有硬件相关代码集中在`port`层,用户只需适配该层即可移植到不同MCU。 +## 2. 架构设计 ---- +### 2.1 分层架构 -## 二、代码实现(按目录结构) +项目采用分层架构设计,将硬件与业务逻辑彻底解耦,便于移植和扩展: -### 1. 对外API层(`menu/api/`) -#### 1.1 `menu/api/menu.h`(用户唯一需要引用的头文件) -```c -/** - * @file menu.h - * @brief 菜单组件对外暴露的核心接口(用户唯一需要包含的头文件) - * @note 工业级嵌入式菜单组件 - 对外API层 - */ -#ifndef MENU_H -#define MENU_H +| 层级 | 主要职责 | 包含模块 | 文件位置 | 用户可定制性 | +|------|----------|----------|----------|--------------| +| **核心层** | 提供菜单组件的核心功能,包括菜单节点管理、事件处理、状态机等 | 菜单核心、事件队列、哈希表索引、栈管理 | src/core/ | 不可定制 | +| **功能扩展层** | 基于核心层扩展的功能模块,用户可根据需求裁剪 | 参数管理、多语言支持、Modbus映射、状态机扩展 | src/features/、src/param/、src/lang/ | 部分可定制 | +| **硬件端口层** | 提供硬件适配接口,对接具体硬件平台 | 硬件驱动抽象、打印接口、系统时间、中断管理 | port/ | 完全可定制 | +| **API层** | 对外提供统一的API接口,屏蔽内部实现细节 | 上下文管理、菜单管理、事件处理、功能扩展接口 | api/ | 不可定制 | -#include "menu_config.h" -#include -#include +### 2.2 模块间依赖关系 -/************************** 全局类型导出 **************************/ -/** - * @brief 菜单错误码(工业级错误处理:明确所有可能的错误类型) - */ -typedef enum { - MENU_OK = 0, ///< 操作成功 - MENU_ERR_INVALID_PARAM, ///< 无效参数 - MENU_ERR_OUT_OF_MEMORY, ///< 内存不足(静态内存池已满) - MENU_ERR_NODE_NOT_FOUND, ///< 菜单节点未找到 - MENU_ERR_STACK_OVERFLOW, ///< 菜单栈溢出(导航层级超过配置) - MENU_ERR_STACK_UNDERFLOW, ///< 菜单栈下溢(已到根节点仍返回) - MENU_ERR_EVENT_QUEUE_FULL, ///< 事件队列已满 - MENU_ERR_NOT_SUPPORTED, ///< 功能未启用(如未开启多语言) - MENU_ERR_HW_PORT_ERROR ///< 硬件端口层错误 -} MenuErrCode; - -/** - * @brief 菜单事件类型(事件驱动核心:解耦按键与菜单逻辑) - */ -typedef enum { - MENU_EVENT_NONE = 0, ///< 无事件 - MENU_EVENT_KEY_UP, ///< 上键按下 - MENU_EVENT_KEY_DOWN, ///< 下键按下 - MENU_EVENT_KEY_ENTER, ///< 确认键按下 - MENU_EVENT_KEY_BACK, ///< 返回键按下 - MENU_EVENT_CUSTOM_BEGIN = 0x10, ///< 自定义事件起始标识(用户可扩展) -} MenuEventType; - -/** - * @brief 菜单节点ID类型(统一标识) - */ -typedef uint16_t MenuNodeId; - -/** - * @brief 菜单回调函数类型(菜单选中/退出时的执行逻辑) - * @param node_id 触发回调的菜单节点ID - * @return 错误码 - */ -typedef MenuErrCode (*MenuCallback)(MenuNodeId node_id); - -/************************** 核心接口声明 **************************/ -/** - * @brief 菜单组件初始化(必须首先调用) - * @return 错误码 - */ -MenuErrCode menu_init(void); - -/** - * @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示) - */ -void menu_main_loop(void); - -/** - * @brief 发送事件到菜单事件队列(如按键事件、自定义事件) - * @param type 事件类型 - * @param param 事件附加参数(可选,如按键长按时间) - * @return 错误码 - */ -MenuErrCode menu_post_event(MenuEventType type, uint32_t param); - -/** - * @brief 注册菜单节点(构建菜单树) - * @param parent_id 父节点ID(根节点填0) - * @param node_id 当前节点ID(唯一,不可重复) - * @param name_str 菜单名称字符串(或多语言索引) - * @param enter_cb 进入该菜单的回调函数(NULL表示无) - * @param exit_cb 退出该菜单的回调函数(NULL表示无) - * @return 错误码 - */ -MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb); - -/** - * @brief 获取当前选中的菜单节点ID - * @param node_id 输出参数,当前节点ID - * @return 错误码 - */ -MenuErrCode menu_get_current_node(MenuNodeId* node_id); - -/************************** 功能扩展接口声明(可裁剪) **************************/ -#if MENU_CONFIG_ENABLE_PARAM -/** - * @brief 参数类型枚举 - */ -typedef enum { - MENU_PARAM_TYPE_INT8, - MENU_PARAM_TYPE_UINT8, - MENU_PARAM_TYPE_INT16, - MENU_PARAM_TYPE_UINT16, - MENU_PARAM_TYPE_INT32, - MENU_PARAM_TYPE_UINT32, - MENU_PARAM_TYPE_FLOAT, -} MenuParamType; - -/** - * @brief 注册参数到菜单节点(参数管理功能) - * @param node_id 菜单节点ID(参数与菜单绑定) - * @param param_id 参数ID(唯一) - * @param type 参数类型 - * @param min_val 最小值(浮点型,内部自动转换) - * @param max_val 最大值(浮点型,内部自动转换) - * @param default_val 默认值(浮点型,内部自动转换) - * @param scale 缩放因子(如0.1表示保留1位小数) - * @return 错误码 - */ -MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale); - -/** - * @brief 设置参数值 - * @param param_id 参数ID - * @param value 新值(浮点型,内部自动转换) - * @return 错误码 - */ -MenuErrCode menu_param_set_value(uint16_t param_id, float value); - -/** - * @brief 获取参数值 - * @param param_id 参数ID - * @param value 输出参数,当前值(浮点型) - * @return 错误码 - */ -MenuErrCode menu_param_get_value(uint16_t param_id, float* value); -#endif // MENU_CONFIG_ENABLE_PARAM - -#if MENU_CONFIG_ENABLE_LANG -/** - * @brief 设置当前语言 - * @param lang_id 语言ID(如0-中文,1-英文) - * @return 错误码 - */ -MenuErrCode menu_lang_set_current(uint8_t lang_id); - -/** - * @brief 获取当前语言 - * @param lang_id 输出参数,当前语言ID - * @return 错误码 - */ -MenuErrCode menu_lang_get_current(uint8_t* lang_id); -#endif // MENU_CONFIG_ENABLE_LANG - -#endif // MENU_H +``` ++------------------+ +| 用户应用层 | ++------------------+ + ↓ ++------------------+ +| API层 | ++------------------+ + ↓ ++------------------+ +| 功能扩展层 | ++------------------+ + ↓ ++------------------+ +| 核心层 | ++------------------+ + ↓ ++------------------+ +| 硬件端口层 | ++------------------+ + ↓ ++------------------+ +| 硬件平台 | ++------------------+ ``` -#### 1.2 `menu/api/menu_config.h`(用户配置文件,可自定义) +## 3. 核心思想 + +### 3.1 无动态内存分配 + +为了保证系统的稳定性和可靠性,避免内存碎片问题,项目采用了完全静态的内存管理方式: + +- 所有菜单节点、事件队列、哈希表等均使用静态数组分配 +- 提供内存大小查询接口,便于用户根据实际需求调整内存配置 +- 每个模块都有明确的内存占用上限,便于资源规划 + +### 3.2 事件驱动解耦 + +采用事件驱动设计,将硬件输入与软件逻辑解耦,提高系统响应性: + +- 硬件事件通过事件队列异步处理,避免阻塞主程序 +- 支持多优先级事件处理,保证关键事件优先处理 +- 事件超时机制,避免无效事件占用系统资源 + +### 3.3 高效节点查找 + +为了提高菜单节点的查找效率,项目采用了哈希表索引技术: + +- 将节点查找时间复杂度从O(n)优化到O(1) +- 采用优化的哈希函数,减少哈希冲突 +- 支持动态注册和注销菜单节点,哈希表自动更新 + +### 3.4 可扩展状态机 + +设计了灵活的状态机框架,支持动态扩展状态转换规则: + +- 内置基础状态转换规则,覆盖常见菜单操作 +- 支持动态注册和注销自定义状态转换规则 +- 状态转换动作可定制,便于扩展菜单行为 +- 支持状态切换和状态查询,便于外部系统集成 + +### 3.5 模块化设计 + +采用模块化设计,各功能模块独立,便于维护和扩展: + +- 每个模块都有清晰的接口定义,模块间低耦合 +- 支持通过宏开关裁剪不需要的功能,减少资源占用 +- 模块化的硬件适配层,便于移植到不同硬件平台 + +## 4. 核心数据结构 + +### 4.1 菜单节点结构体 + ```c -/** - * @file menu_config.h - * @brief 菜单组件用户配置文件(工业级:集中管理所有可配置项) - * @note 用户可根据项目需求修改此文件 - */ -#ifndef MENU_CONFIG_H -#define MENU_CONFIG_H - -/************************** 核心配置 **************************/ -/** - * @brief 最大菜单节点数(静态内存,根据项目调整) - */ -#define MENU_CONFIG_MAX_NODES 32 - -/** - * @brief 菜单栈深度(最大导航层级,如根→子→孙,深度为3) - */ -#define MENU_CONFIG_STACK_DEPTH 8 - -/** - * @brief 事件队列长度(存储按键/自定义事件,避免丢失) - */ -#define MENU_CONFIG_EVENT_QUEUE_LEN 16 - -/** - * @brief 是否启用断言(调试时开启,发布时关闭) - */ -#define MENU_CONFIG_ENABLE_ASSERT 1 - -/** - * @brief 是否启用调试打印(调试时开启,发布时关闭) - */ -#define MENU_CONFIG_ENABLE_DEBUG 1 - -/************************** 功能扩展配置(可裁剪) **************************/ -/** - * @brief 是否启用参数管理功能 - */ -#define MENU_CONFIG_ENABLE_PARAM 1 - -/** - * @brief 是否启用多语言功能 - */ -#define MENU_CONFIG_ENABLE_LANG 1 - -/** - * @brief 最大参数数量(启用参数管理时有效) - */ -#define MENU_CONFIG_MAX_PARAMS 16 - -/** - * @brief 支持的最大语言数量(启用多语言时有效) - */ -#define MENU_CONFIG_MAX_LANGS 2 - -/************************** 硬件无关配置 **************************/ -/** - * @brief 事件处理超时时间(ms,防止事件队列阻塞) - */ -#define MENU_CONFIG_EVENT_TIMEOUT 100 - -/** - * @brief 菜单刷新间隔(ms,控制显示刷新频率) - */ -#define MENU_CONFIG_REFRESH_INTERVAL 50 - -#endif // MENU_CONFIG_H -``` - -### 2. 内部头文件层(`menu/internal/`) -#### 2.1 `menu/internal/menu_def.h`(内部宏、辅助函数) -```c -/** - * @file menu_def.h - * @brief 菜单组件内部宏定义和辅助函数(用户无需关心) - */ -#ifndef MENU_DEF_H -#define MENU_DEF_H - -#include "menu_config.h" -#include "menu.h" -#include -#include - -/************************** 内部宏定义 **************************/ -/** - * @brief 断言宏(工业级:调试时检查,发布时忽略) - */ -#if MENU_CONFIG_ENABLE_ASSERT -#define MENU_ASSERT(condition) \ - do { \ - if (!(condition)) { \ - menu_utils_assert_failed(__FILE__, __LINE__); \ - while (1); \ - } \ - } while (0) -#else -#define MENU_ASSERT(condition) ((void)0) -#endif - -/** - * @brief 调试打印宏(工业级:集中控制调试输出) - */ -#if MENU_CONFIG_ENABLE_DEBUG -#define MENU_DEBUG(fmt, ...) menu_utils_printf("[MENU DEBUG] " fmt "\r\n", ##__VA_ARGS__) -#else -#define MENU_DEBUG(fmt, ...) ((void)0) -#endif - -/** - * @brief 内存清零宏 - */ -#define MENU_MEM_SET_ZERO(ptr, size) memset((ptr), 0, (size)) - -/** - * @brief 无效ID定义 - */ -#define MENU_INVALID_ID ((MenuNodeId)0xFFFF) - -/************************** 内部辅助函数声明 **************************/ -/** - * @brief 断言失败处理函数 - * @param file 文件名 - * @param line 行号 - */ -void menu_utils_assert_failed(const char* file, uint32_t line); - -/** - * @brief 调试打印函数(对接port层的硬件打印接口) - * @param fmt 格式化字符串 - * @param ... 可变参数 - */ -void menu_utils_printf(const char* fmt, ...); - -/** - * @brief 获取系统滴答时间(ms,对接port层) - * @return 当前滴答时间 - */ -uint32_t menu_utils_get_tick(void); - -#endif // MENU_DEF_H -``` - -#### 2.2 `menu/internal/menu_core.h`(核心类型定义) -```c -/** - * @file menu_core.h - * @brief 菜单组件核心类型定义(用户无需关心) - */ -#ifndef MENU_CORE_H -#define MENU_CORE_H - -#include "menu_def.h" -#include -#include - -/************************** 核心数据结构 **************************/ -/** - * @brief 菜单事件结构体(事件队列元素) - */ -typedef struct { - MenuEventType type; ///< 事件类型 - uint32_t param; ///< 事件附加参数 - uint32_t timestamp; ///< 事件产生时间(ms,用于超时处理) -} MenuEvent; - -/** - * @brief 菜单节点结构体(菜单树的基本单元) - * @note 紧凑设计:使用位域和共用体减少内存占用(工业级嵌入式低内存优化) - */ typedef struct MenuNode { MenuNodeId id; ///< 节点ID(唯一) MenuNodeId parent_id; ///< 父节点ID(根节点为0) const char* name; ///< 菜单名称(或多语言索引) MenuCallback enter_cb; ///< 进入回调 MenuCallback exit_cb; ///< 退出回调 - struct MenuNode* first_child;///< 第一个子节点 - struct MenuNode* next_sibling;///< 下一个兄弟节点 - struct MenuNode* prev_sibling;///< 上一个兄弟节点 - // 位域:减少内存占用(1字节代替多个u8变量) + 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; ///< 是否被选中 - bool reserved : 6; ///< 保留位 + unsigned int reserved : 6; ///< 保留位 } flags; #if MENU_CONFIG_ENABLE_PARAM uint16_t param_id; ///< 绑定的参数ID(启用参数时有效) #endif } MenuNode; +``` -/** - * @brief 菜单栈结构体(管理导航层级) - */ +**设计亮点**: +- 使用索引代替指针,减少内存占用(32位系统节省12字节/节点,64位系统节省28字节/节点) +- 采用双向链表结构,便于兄弟节点间遍历 +- 使用哈希表索引,提高节点查找效率 +- 位域设计,减少内存占用 + +### 4.2 菜单核心上下文 + +```c typedef struct { - MenuNodeId nodes[MENU_CONFIG_STACK_DEPTH]; ///< 栈元素(存储节点ID) - uint8_t top; ///< 栈顶指针 -} MenuStack; + 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; +``` -/** - * @brief 事件队列结构体(环形队列,工业级:避免溢出) - */ +**设计亮点**: +- 集中管理菜单组件的所有状态和资源 +- 静态数组设计,无动态内存分配 +- 包含所有核心数据结构,便于统一管理 +- 支持状态机扩展,便于功能扩展 + +### 4.3 事件队列结构体 + +```c typedef struct { MenuEvent buffer[MENU_CONFIG_EVENT_QUEUE_LEN]; ///< 队列缓冲区 uint8_t head; ///< 入队指针 uint8_t tail; ///< 出队指针 uint8_t count; ///< 队列元素数量 + uint8_t reserved; ///< 保留字节,用于对齐 } MenuEventQueue; +``` -/** - * @brief 菜单核心上下文(全局唯一,存储菜单状态) - */ +**设计亮点**: +- 环形队列设计,避免内存碎片 +- 支持多优先级事件处理 +- 队列满时自动替换低优先级事件,保证高优先级事件优先处理 +- 事件超时机制,避免无效事件占用资源 + +### 4.4 状态转换结构体 + +```c typedef struct { - MenuNode nodes[MENU_CONFIG_MAX_NODES]; ///< 静态菜单节点池(无动态分配) - MenuStack stack; ///< 菜单导航栈 - MenuEventQueue event_queue; ///< 事件队列 - MenuNodeId current_node_id; ///< 当前选中的节点ID - uint32_t last_refresh_tick; ///< 上次刷新时间(ms) - bool is_initialized; ///< 是否已初始化 -} MenuCoreCtx; - -/************************** 核心内部接口声明 **************************/ -/** - * @brief 初始化菜单核心上下文 - * @return 错误码 - */ -MenuErrCode menu_core_ctx_init(void); - -/** - * @brief 获取菜单核心上下文(内部唯一访问入口) - * @return 菜单核心上下文指针 - */ -MenuCoreCtx* menu_core_get_ctx(void); - -/** - * @brief 处理单个菜单事件 - * @param event 事件指针 - * @return 错误码 - */ -MenuErrCode menu_core_handle_event(const MenuEvent* event); - -/** - * @brief 刷新菜单显示(内部调用,对接port层显示接口) - */ -void menu_core_refresh_display(void); - -#endif // MENU_CORE_H + MenuState current_state; ///< 当前状态 + MenuEventType event; ///< 触发事件 + MenuState next_state; ///< 下一个状态 + MenuErrCode (*action)(MenuGlobalCtx* global_ctx); ///< 状态转换动作 + const char* desc; ///< 状态转换描述(用于调试) +} MenuStateTransition; ``` -#### 2.3 `menu/internal/menu_data.h`(共享全局变量,内部使用) +**设计亮点**: +- 灵活的状态转换规则,支持动态注册 +- 状态转换动作可定制,便于扩展菜单行为 +- 支持状态转换描述,便于调试 + +## 5. 核心算法 + +### 5.1 哈希表算法 + +**功能**:用于快速查找菜单节点,将节点查找时间复杂度从O(n)优化到O(1) + +**实现原理**: + ```c -/** - * @file menu_data.h - * @brief 菜单组件共享全局变量(用户无需关心,内部仅通过接口访问) - */ -#ifndef MENU_DATA_H -#define MENU_DATA_H +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); +} +``` -#include "menu_core.h" +**设计亮点**: +- 采用优化的混合哈希算法,减少冲突 +- 结合旋转和异或操作,提高哈希分布的均匀性 +- 适合嵌入式系统的高效计算,执行速度快 +- 支持动态添加和删除节点,哈希表自动更新 -/************************** 共享全局变量(静态,内部可见) **************************/ -/** - * @brief 菜单核心上下文(静态全局,仅内部访问) - */ -static MenuCoreCtx s_menu_core_ctx; +### 5.2 事件队列算法 -/** - * @brief 参数管理上下文(启用参数时有效,静态全局) - */ -#if MENU_CONFIG_ENABLE_PARAM +**功能**:实现基于优先级的事件处理机制,支持多优先级事件,队列满时自动替换低优先级事件 + +**实现原理**: +- 采用环形队列设计,避免内存碎片 +- 支持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. 硬件适配层 + +**功能**:提供硬件适配接口,对接具体硬件平台 + +**实现原理**: +- 定义统一的硬件驱动结构体,包含各种硬件接口 +- 支持运行时配置硬件驱动,便于动态切换 +- 屏蔽不同硬件平台的差异,提高组件的可移植性 + +**硬件接口**: + +```c typedef struct { - uint16_t id; ///< 参数ID - MenuParamType type; ///< 参数类型 - float min_val; ///< 最小值 - float max_val; ///< 最大值 - float scale; ///< 缩放因子 - union { ///< 共用体:减少内存占用 - int8_t i8; - uint8_t u8; - int16_t i16; - uint16_t u16; - int32_t i32; - uint32_t u32; - float f; - } value; ///< 当前值 - union { ///< 默认值 - int8_t i8; - uint8_t u8; - int16_t i16; - uint16_t u16; - int32_t i32; - uint32_t u32; - float f; - } default_val; - bool is_registered; ///< 是否已注册 -} MenuParam; - -static MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS]; -#endif // MENU_CONFIG_ENABLE_PARAM - -/** - * @brief 多语言上下文(启用多语言时有效,静态全局) - */ -#if MENU_CONFIG_ENABLE_LANG -typedef struct { - const char* str; ///< 语言字符串 - uint16_t str_id; ///< 字符串ID - uint8_t lang_id; ///< 语言ID -} MenuLangStr; - -static MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS]; -static uint8_t s_current_lang_id = 0; -#endif // MENU_CONFIG_ENABLE_LANG - -#endif // MENU_DATA_H + 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; ``` -### 3. 源码核心层(`menu/src/core/`) -#### 3.1 `menu/src/core/menu_core.c`(导航、栈管理、主循环) -```c -/** - * @file menu_core.c - * @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动) - */ -#include "menu_core.h" -#include "menu_data.h" -#include +**关键函数**: +- `menu_port_init`:硬件端口初始化 +- `menu_port_deinit`:硬件端口反初始化 -/** - * @brief 查找菜单节点(通过ID) - * @param node_id 节点ID - * @return 节点指针(NULL表示未找到) - */ -static MenuNode* menu_core_find_node(MenuNodeId node_id) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) - { - if (ctx->nodes[i].is_registered && ctx->nodes[i].id == node_id) - { - return &ctx->nodes[i]; - } - } - return NULL; -} +## 8. 配置管理 -/** - * @brief 菜单栈压入节点 - * @param node_id 节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_stack_push(MenuNodeId node_id) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MENU_ASSERT(ctx != NULL); +**功能**:提供灵活的配置选项,便于用户根据需求定制功能 - if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH) - { - MENU_DEBUG("Stack overflow: depth %d", MENU_CONFIG_STACK_DEPTH); - return MENU_ERR_STACK_OVERFLOW; - } +**实现原理**: +- 所有配置项集中在`menu_config.h`文件中 +- 支持通过宏开关裁剪不需要的功能 +- 支持调整资源大小,如最大节点数、栈深度、事件队列长度等 +- 支持调试开关,便于开发和调试 - ctx->stack.nodes[ctx->stack.top++] = node_id; - MENU_DEBUG("Stack push: node %d, top %d", node_id, ctx->stack.top); - return MENU_OK; -} +**核心配置项**: -/** - * @brief 菜单栈弹出节点 - * @param node_id 输出参数,弹出的节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_stack_pop(MenuNodeId* node_id) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MENU_ASSERT(ctx != NULL && node_id != NULL); +| 配置项 | 说明 | 默认值 | +|-------|------|-------| +| 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 | - if (ctx->stack.top == 0) - { - MENU_DEBUG("Stack underflow"); - return MENU_ERR_STACK_UNDERFLOW; - } +## 9. 工业级设计考量 - *node_id = ctx->stack.nodes[--ctx->stack.top]; - MENU_DEBUG("Stack pop: node %d, top %d", *node_id, ctx->stack.top); - return MENU_OK; -} +### 9.1 可靠性设计 -/** - * @brief 菜单导航到子节点(确认键) - * @param node_id 当前节点ID - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_enter(MenuNodeId node_id) -{ - MenuNode* node = menu_core_find_node(node_id); - if (node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } +- **无动态内存分配**:完全使用静态数组,避免内存碎片和内存泄漏 +- **完善的错误处理**:每个函数都有明确的错误码返回,便于调试和错误定位 +- **边界检查**:所有数组访问都有边界检查,防止越界访问 +- **断言机制**:关键函数入口有断言检查,便于调试 +- **事件超时处理**:避免无效事件占用系统资源 - // 执行进入回调 - if (node->enter_cb != NULL) - { - MenuErrCode err = node->enter_cb(node_id); - if (err != MENU_OK) - { - return err; - } - } +### 9.2 性能优化 - // 如果有子节点,压入栈并选中第一个子节点 - if (node->first_child != NULL) - { - MenuCoreCtx* ctx = menu_core_get_ctx(); - ctx->current_node_id = node->first_child->id; - return menu_core_stack_push(node->first_child->id); - } +- **高效的哈希表算法**:将节点查找时间复杂度从O(n)优化到O(1) +- **位域设计**:减少内存占用,提高缓存命中率 +- **索引代替指针**:减少内存占用,提高系统兼容性 +- **事件驱动设计**:提高系统响应性,减少CPU占用 +- **模块化设计**:支持功能裁剪,减少资源占用 - return MENU_OK; -} +### 9.3 可移植性设计 -/** - * @brief 菜单导航返回父节点(返回键) - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_back(void) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MenuNodeId current_id = ctx->current_node_id; - MenuNode* current_node = menu_core_find_node(current_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } +- **硬件抽象层**:屏蔽不同硬件平台的差异,提高组件的可移植性 +- **标准C语言实现**:不依赖特定的编译器或操作系统 +- **灵活的配置选项**:支持根据不同硬件平台调整资源大小 +- **模块化设计**:各功能模块独立,便于移植和扩展 - // 执行退出回调 - if (current_node->exit_cb != NULL) - { - MenuErrCode err = current_node->exit_cb(current_id); - if (err != MENU_OK) - { - return err; - } - } +### 9.4 可维护性设计 - // 弹出栈,回到父节点 - MenuNodeId parent_id = current_node->parent_id; - if (parent_id == 0) // 根节点 - { - ctx->current_node_id = current_id; - return MENU_OK; - } +- **清晰的代码结构**:采用分层架构和模块化设计,代码结构清晰 +- **完善的文档**:提供详细的API文档和开发文档 +- **统一的代码风格**:采用清晰的命名规范和注释风格 +- **调试支持**:提供丰富的调试打印和日志功能 +- **错误定位机制**:每个错误都有明确的错误码和错误信息 - MenuNodeId pop_id; - MenuErrCode err = menu_core_stack_pop(&pop_id); - if (err != MENU_OK) - { - return err; - } +## 10. 应用场景 - ctx->current_node_id = parent_id; - return MENU_OK; -} +本菜单组件适用于各种资源受限的嵌入式系统,特别是工业控制领域,如: -/** - * @brief 菜单导航上选(上键) - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_up(void) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MenuNode* current_node = menu_core_find_node(ctx->current_node_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } +- 工业控制器 +- 智能家居设备 +- 仪器仪表 +- 医疗设备 +- 嵌入式人机界面 +- Modbus从站设备 - // 切换到上一个兄弟节点 - if (current_node->prev_sibling != NULL) - { - ctx->current_node_id = current_node->prev_sibling->id; - } - else - { - // 循环到最后一个兄弟节点(工业级:友好的交互设计) - MenuNode* last_node = current_node; - while (last_node->next_sibling != NULL) - { - last_node = last_node->next_sibling; - } - ctx->current_node_id = last_node->id; - } +## 11. 开发流程 - MENU_DEBUG("Navigate up: current node %d", ctx->current_node_id); - return MENU_OK; -} +### 11.1 硬件适配 -/** - * @brief 菜单导航下选(下键) - * @return 错误码 - */ -static MenuErrCode menu_core_navigate_down(void) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MenuNode* current_node = menu_core_find_node(ctx->current_node_id); - if (current_node == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } +1. 根据目标硬件平台,实现`menu_port.c`中的硬件接口 +2. 配置`menu_config.h`中的资源大小和功能开关 +3. 编译和测试硬件适配层 - // 切换到下一个兄弟节点 - if (current_node->next_sibling != NULL) - { - ctx->current_node_id = current_node->next_sibling->id; - } - else - { - // 循环到第一个兄弟节点 - MenuNode* first_node = current_node; - while (first_node->prev_sibling != NULL) - { - first_node = first_node->prev_sibling; - } - ctx->current_node_id = first_node->id; - } +### 11.2 菜单设计 - MENU_DEBUG("Navigate down: current node %d", ctx->current_node_id); - return MENU_OK; -} +1. 设计菜单结构,确定菜单节点的层级关系 +2. 注册菜单节点,构建菜单树 +3. 注册参数(可选) +4. 注册Modbus映射(可选) +5. 注册自定义状态转换规则(可选) -/************************** 外部接口实现 **************************/ -MenuErrCode menu_core_ctx_init(void) -{ - MenuCoreCtx* ctx = menu_core_get_ctx(); - MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx)); - ctx->is_initialized = true; - MENU_DEBUG("Core context initialized"); - return MENU_OK; -} +### 11.3 应用集成 -MenuCoreCtx* menu_core_get_ctx(void) -{ - return &s_menu_core_ctx; -} +1. 在应用程序中初始化菜单组件 +2. 在主循环中调用`menu_main_loop`处理事件 +3. 调用`menu_post_event`发送硬件事件(如按键事件) +4. 根据需要调用其他API接口,实现自定义功能 -MenuErrCode menu_core_handle_event(const MenuEvent* event) -{ - if (event == NULL || event->type == MENU_EVENT_NONE) - { - return MENU_ERR_INVALID_PARAM; - } +## 12. 测试与验证 - MenuErrCode err = MENU_OK; - switch (event->type) - { - case MENU_EVENT_KEY_UP: - err = menu_core_navigate_up(); - break; - case MENU_EVENT_KEY_DOWN: - err = menu_core_navigate_down(); - break; - case MENU_EVENT_KEY_ENTER: - err = menu_core_navigate_enter(menu_core_get_ctx()->current_node_id); - break; - case MENU_EVENT_KEY_BACK: - err = menu_core_navigate_back(); - break; - default: - MENU_DEBUG("Unsupported event type: %d", event->type); - err = MENU_ERR_NOT_SUPPORTED; - break; - } +### 12.1 单元测试 - // 处理事件后刷新显示 - if (err == MENU_OK) - { - menu_core_refresh_display(); - } +项目提供了单元测试框架,位于`test/menu_test.c`,包含以下测试用例: - return err; -} +- 菜单初始化和反初始化测试 +- 菜单节点注册和删除测试 +- 菜单导航测试 +- 事件队列测试 +- 参数管理测试 +- 多语言支持测试 +- Modbus映射测试 -void menu_core_refresh_display(void) -{ - // 对接port层的显示接口(用户需在port层实现) - MenuCoreCtx* ctx = menu_core_get_ctx(); - MenuNode* current_node = menu_core_find_node(ctx->current_node_id); - if (current_node != NULL) - { - MENU_DEBUG("Refresh display: %s (node %d)", current_node->name, current_node->id); - // 调用port层的显示函数:menu_port_display(current_node->name, current_node->id); - } -} -``` +### 12.2 集成测试 -#### 3.2 `menu/src/core/menu_event.c`(事件队列处理,解耦按键与菜单) -```c -/** - * @file menu_event.c - * @brief 菜单事件队列:存储和处理事件(工业级:环形队列,线程安全(裸机下关中断)) - */ -#include "menu_core.h" -#include "menu_data.h" -#include "menu_def.h" -#include +- 硬件适配测试:验证硬件接口的正确性 +- 功能测试:验证各个功能模块的正确性 +- 性能测试:测试菜单组件的执行效率和资源占用 +- 可靠性测试:测试菜单组件在各种异常情况下的表现 -/** - * @brief 入队事件(内部函数,裸机下需关中断保护) - * @param queue 事件队列指针 - * @param event 事件指针 - * @return 错误码 - */ -static MenuErrCode menu_event_enqueue(MenuEventQueue* queue, const MenuEvent* event) -{ - if (queue == NULL || event == NULL) - { - return MENU_ERR_INVALID_PARAM; - } +### 12.3 现场测试 - if (queue->count >= MENU_CONFIG_EVENT_QUEUE_LEN) - { - MENU_DEBUG("Event queue full: len %d", MENU_CONFIG_EVENT_QUEUE_LEN); - return MENU_ERR_EVENT_QUEUE_FULL; - } +- 在目标硬件平台上进行测试,验证菜单组件的实际运行效果 +- 测试各种用户操作场景,确保菜单组件的易用性和稳定性 +- 测试长时间运行情况下的稳定性 - // 裸机下关中断(工业级:防止中断中入队导致数据错乱) - menu_port_irq_disable(); +## 13. 版本管理 - queue->buffer[queue->head] = *event; - queue->head = (queue->head + 1) % MENU_CONFIG_EVENT_QUEUE_LEN; - queue->count++; +| 版本 | 发布日期 | 主要变更 | +|------|----------|----------| +| v1.0.0 | 2025-12-19 | 初始版本,包含核心功能 | - menu_port_irq_enable(); +## 14. 总结 - return MENU_OK; -} +本项目是一个设计精良的工业级嵌入式菜单组件,具有高可靠性、高性能、高可移植性和高可扩展性等特点。采用分层架构、事件驱动设计、哈希表索引、可扩展状态机等先进技术,适合各种资源受限的嵌入式系统。 -/** - * @brief 出队事件(内部函数,裸机下需关中断保护) - * @param queue 事件队列指针 - * @param event 输出参数,事件指针 - * @return 错误码 - */ -static MenuErrCode menu_event_dequeue(MenuEventQueue* queue, MenuEvent* event) -{ - if (queue == NULL || event == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - if (queue->count == 0) - { - return MENU_OK; // 队列为空,无事件 - } - - // 裸机下关中断 - menu_port_irq_disable(); - - *event = queue->buffer[queue->tail]; - queue->tail = (queue->tail + 1) % MENU_CONFIG_EVENT_QUEUE_LEN; - queue->count--; - - menu_port_irq_enable(); - - return MENU_OK; -} - -/************************** 外部接口实现 **************************/ -MenuErrCode menu_post_event(MenuEventType type, uint32_t param) -{ - if (!menu_core_get_ctx()->is_initialized) - { - MENU_DEBUG("Menu not initialized"); - return MENU_ERR_INVALID_PARAM; - } - - MenuEvent event = {0}; - event.type = type; - event.param = param; - event.timestamp = menu_utils_get_tick(); - - MenuCoreCtx* ctx = menu_core_get_ctx(); - return menu_event_enqueue(&ctx->event_queue, &event); -} - -void menu_main_loop(void) -{ - if (!menu_core_get_ctx()->is_initialized) - { - return; - } - - MenuCoreCtx* ctx = menu_core_get_ctx(); - uint32_t current_tick = menu_utils_get_tick(); - - // 处理事件队列 - MenuEvent event; - while (menu_event_dequeue(&ctx->event_queue, &event) == MENU_OK) - { - // 检查事件是否超时(工业级:避免处理过期事件) - if (current_tick - event.timestamp > MENU_CONFIG_EVENT_TIMEOUT) - { - MENU_DEBUG("Event timeout: type %d", event.type); - continue; - } - - // 处理事件 - menu_core_handle_event(&event); - } - - // 定期刷新显示(控制刷新频率) - if (current_tick - ctx->last_refresh_tick >= MENU_CONFIG_REFRESH_INTERVAL) - { - menu_core_refresh_display(); - ctx->last_refresh_tick = current_tick; - } -} -``` - -#### 3.3 `menu/src/core/menu_utils.c`(工具函数:调试、断言、内存操作) -```c -/** - * @file menu_utils.c - * @brief 菜单工具函数:调试打印、断言、系统滴答(对接port层) - */ -#include "menu_def.h" -#include "menu_port.h" -#include - -/************************** 外部接口实现 **************************/ -void menu_utils_assert_failed(const char* file, uint32_t line) -{ - MENU_DEBUG("Assert failed: %s:%d", file, line); - // 对接port层的错误处理(如点亮错误LED) - menu_port_error_handler(); -} - -void menu_utils_printf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - // 对接port层的打印接口(如UART、LCD) - menu_port_printf(fmt, args); - va_end(args); -} - -uint32_t menu_utils_get_tick(void) -{ - // 对接port层的系统滴答(如SysTick) - return menu_port_get_tick(); -} -``` - -### 4. 源码功能扩展层(`menu/src/features/`) -#### 4.1 `menu/src/features/menu_param.c`(参数管理功能) -```c -/** - * @file menu_param.c - * @brief 菜单参数管理:注册、设置、获取(工业级:类型安全,范围检查) - */ -#if MENU_CONFIG_ENABLE_PARAM -#include "menu.h" -#include "menu_core.h" -#include "menu_data.h" -#include "menu_def.h" -#include - -/** - * @brief 查找参数(通过ID) - * @param param_id 参数ID - * @return 参数指针(NULL表示未找到) - */ -static MenuParam* menu_param_find(uint16_t param_id) -{ - for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++) - { - if (s_menu_params[i].is_registered && s_menu_params[i].id == param_id) - { - return &s_menu_params[i]; - } - } - return NULL; -} - -/** - * @brief 转换浮点值到参数类型的实际值 - * @param type 参数类型 - * @param value 浮点值 - * @param scale 缩放因子 - * @param out 输出参数,实际值(共用体指针) - */ -static void menu_param_convert_float_to_value(MenuParamType type, float value, float scale, void* out) -{ - float scaled = value * scale; - switch (type) - { - case MENU_PARAM_TYPE_INT8: - ((MenuParam*)out)->value.i8 = (int8_t)scaled; - break; - case MENU_PARAM_TYPE_UINT8: - ((MenuParam*)out)->value.u8 = (uint8_t)scaled; - break; - case MENU_PARAM_TYPE_INT16: - ((MenuParam*)out)->value.i16 = (int16_t)scaled; - break; - case MENU_PARAM_TYPE_UINT16: - ((MenuParam*)out)->value.u16 = (uint16_t)scaled; - break; - case MENU_PARAM_TYPE_INT32: - ((MenuParam*)out)->value.i32 = (int32_t)scaled; - break; - case MENU_PARAM_TYPE_UINT32: - ((MenuParam*)out)->value.u32 = (uint32_t)scaled; - break; - case MENU_PARAM_TYPE_FLOAT: - ((MenuParam*)out)->value.f = scaled; - break; - default: - break; - } -} - -/** - * @brief 转换参数类型的实际值到浮点值 - * @param type 参数类型 - * @param in 实际值(共用体指针) - * @param scale 缩放因子 - * @return 浮点值 - */ -static float menu_param_convert_value_to_float(MenuParamType type, const void* in, float scale) -{ - float value = 0.0f; - switch (type) - { - case MENU_PARAM_TYPE_INT8: - value = (float)((MenuParam*)in)->value.i8 / scale; - break; - case MENU_PARAM_TYPE_UINT8: - value = (float)((MenuParam*)in)->value.u8 / scale; - break; - case MENU_PARAM_TYPE_INT16: - value = (float)((MenuParam*)in)->value.i16 / scale; - break; - case MENU_PARAM_TYPE_UINT16: - value = (float)((MenuParam*)in)->value.u16 / scale; - break; - case MENU_PARAM_TYPE_INT32: - value = (float)((MenuParam*)in)->value.i32 / scale; - break; - case MENU_PARAM_TYPE_UINT32: - value = (float)((MenuParam*)in)->value.u32 / scale; - break; - case MENU_PARAM_TYPE_FLOAT: - value = ((MenuParam*)in)->value.f / scale; - break; - default: - break; - } - return value; -} - -/************************** 外部接口实现 **************************/ -MenuErrCode menu_param_register(MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale) -{ - if (scale <= 0.0f || min_val > max_val) - { - return MENU_ERR_INVALID_PARAM; - } - - // 检查参数ID是否已存在 - if (menu_param_find(param_id) != NULL) - { - MENU_DEBUG("Param ID %d already registered", param_id); - return MENU_ERR_INVALID_PARAM; - } - - // 查找空闲参数位置 - MenuParam* param = NULL; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++) - { - if (!s_menu_params[i].is_registered) - { - param = &s_menu_params[i]; - break; - } - } - - if (param == NULL) - { - return MENU_ERR_OUT_OF_MEMORY; - } - - // 初始化参数 - MENU_MEM_SET_ZERO(param, sizeof(MenuParam)); - param->id = param_id; - param->type = type; - param->min_val = min_val; - param->max_val = max_val; - param->scale = scale; - - // 设置默认值 - menu_param_convert_float_to_value(type, default_val, scale, param); - param->default_val = param->value; - - param->is_registered = true; - - // 绑定参数到菜单节点 - MenuNode* node = menu_core_find_node(node_id); - if (node != NULL) - { - node->param_id = param_id; - } - - MENU_DEBUG("Param %d registered to node %d, type %d", param_id, node_id, type); - return MENU_OK; -} - -MenuErrCode menu_param_set_value(uint16_t param_id, float value) -{ - MenuParam* param = menu_param_find(param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 范围检查(工业级:参数有效性验证) - if (value < param->min_val || value > param->max_val) - { - MENU_DEBUG("Param %d out of range: %f (min %f, max %f)", param_id, value, param->min_val, param->max_val); - return MENU_ERR_INVALID_PARAM; - } - - // 转换并设置值 - menu_param_convert_float_to_value(param->type, value, param->scale, param); - MENU_DEBUG("Param %d set to %f", param_id, value); - return MENU_OK; -} - -MenuErrCode menu_param_get_value(uint16_t param_id, float* value) -{ - if (value == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuParam* param = menu_param_find(param_id); - if (param == NULL) - { - return MENU_ERR_NODE_NOT_FOUND; - } - - // 转换为浮点值 - *value = menu_param_convert_value_to_float(param->type, param, param->scale); - MENU_DEBUG("Param %d get: %f", param_id, *value); - return MENU_OK; -} -#endif // MENU_CONFIG_ENABLE_PARAM -``` - -#### 4.2 `menu/src/features/menu_lang.c`(多语言功能) -```c -/** - * @file menu_lang.c - * @brief 菜单多语言:语言切换、字符串映射(工业级:可扩展) - */ -#if MENU_CONFIG_ENABLE_LANG -#include "menu.h" -#include "menu_data.h" -#include "menu_def.h" -#include - -/** - * @brief 注册语言字符串 - * @param str_id 字符串ID(与菜单节点ID绑定) - * @param lang_id 语言ID - * @param str 字符串 - * @return 错误码 - */ -static MenuErrCode menu_lang_register_str(uint16_t str_id, uint8_t lang_id, const char* str) -{ - if (str == NULL || lang_id >= MENU_CONFIG_MAX_LANGS) - { - return MENU_ERR_INVALID_PARAM; - } - - // 查找空闲位置 - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++) - { - if (s_menu_lang_strs[i].str == NULL) - { - s_menu_lang_strs[i].str_id = str_id; - s_menu_lang_strs[i].lang_id = lang_id; - s_menu_lang_strs[i].str = str; - return MENU_OK; - } - } - - return MENU_ERR_OUT_OF_MEMORY; -} - -/** - * @brief 获取语言字符串 - * @param str_id 字符串ID - * @param lang_id 语言ID - * @return 字符串指针(NULL表示未找到) - */ -static const char* menu_lang_get_str(uint16_t str_id, uint8_t lang_id) -{ - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++) - { - if (s_menu_lang_strs[i].str_id == str_id && s_menu_lang_strs[i].lang_id == lang_id) - { - return s_menu_lang_strs[i].str; - } - } - return NULL; -} - -/************************** 外部接口实现 **************************/ -MenuErrCode menu_lang_set_current(uint8_t lang_id) -{ - if (lang_id >= MENU_CONFIG_MAX_LANGS) - { - return MENU_ERR_INVALID_PARAM; - } - - s_current_lang_id = lang_id; - MENU_DEBUG("Current language set to %d", lang_id); - return MENU_OK; -} - -MenuErrCode menu_lang_get_current(uint8_t* lang_id) -{ - if (lang_id == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - *lang_id = s_current_lang_id; - return MENU_OK; -} - -// 扩展:菜单节点注册时自动绑定多语言字符串 -MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb) -{ - // 调用核心的节点注册逻辑(此处简化,实际需结合menu_core.c的节点注册) - // ... - - // 注册默认语言字符串(lang_id=0) - menu_lang_register_str(node_id, 0, name_str); - return MENU_OK; -} -#endif // MENU_CONFIG_ENABLE_LANG -``` - -#### 4.3 `menu/src/features/menu_bind.c`(菜单节点与功能绑定) -```c -/** - * @file menu_bind.c - * @brief 菜单节点与功能绑定:回调、参数、事件(工业级:灵活绑定) - */ -#include "menu.h" -#include "menu_core.h" -#include "menu_data.h" -#include "menu_def.h" -#include - -/************************** 外部接口实现 **************************/ -MenuErrCode menu_register_node(MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb) -{ - if (name_str == NULL || node_id == 0) // 0为根节点,不可用 - { - return MENU_ERR_INVALID_PARAM; - } - - MenuCoreCtx* ctx = menu_core_get_ctx(); - if (!ctx->is_initialized) - { - return MENU_ERR_INVALID_PARAM; - } - - // 检查节点ID是否已存在 - if (menu_core_find_node(node_id) != NULL) - { - MENU_DEBUG("Node ID %d already registered", node_id); - return MENU_ERR_INVALID_PARAM; - } - - // 查找空闲节点位置 - MenuNode* node = NULL; - for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++) - { - if (!ctx->nodes[i].is_registered) - { - node = &ctx->nodes[i]; - break; - } - } - - if (node == NULL) - { - return MENU_ERR_OUT_OF_MEMORY; - } - - // 初始化节点 - MENU_MEM_SET_ZERO(node, sizeof(MenuNode)); - node->id = node_id; - node->parent_id = parent_id; - node->name = name_str; - node->enter_cb = enter_cb; - node->exit_cb = exit_cb; - node->flags.is_registered = true; - - // 关联到父节点的子节点链表(工业级:链表管理,高效遍历) - if (parent_id != 0) - { - MenuNode* parent_node = menu_core_find_node(parent_id); - if (parent_node != NULL) - { - if (parent_node->first_child == NULL) - { - parent_node->first_child = node; - } - else - { - MenuNode* last_child = parent_node->first_child; - while (last_child->next_sibling != NULL) - { - last_child = last_child->next_sibling; - } - last_child->next_sibling = node; - node->prev_sibling = last_child; - } - } - } - else - { - // 根节点,设置为当前节点并压入栈 - ctx->current_node_id = node_id; - menu_core_stack_push(node_id); - } - - MENU_DEBUG("Node %d registered (parent %d): %s", node_id, parent_id, name_str); - return MENU_OK; -} - -MenuErrCode menu_get_current_node(MenuNodeId* node_id) -{ - if (node_id == NULL) - { - return MENU_ERR_INVALID_PARAM; - } - - MenuCoreCtx* ctx = menu_core_get_ctx(); - *node_id = ctx->current_node_id; - return MENU_OK; -} -``` - -### 5. 源码硬件端口层(`menu/src/port/`) -#### 5.1 `menu/src/port/menu_port.h`(硬件端口层头文件) -```c -/** - * @file menu_port.h - * @brief 菜单组件硬件端口层头文件(用户需适配的接口) - */ -#ifndef MENU_PORT_H -#define MENU_PORT_H - -#include "menu_config.h" -#include -#include - -/************************** 硬件端口接口声明(用户需实现) **************************/ -/** - * @brief 禁用全局中断(裸机下保护临界区) - */ -void menu_port_irq_disable(void); - -/** - * @brief 启用全局中断 - */ -void menu_port_irq_enable(void); - -/** - * @brief 获取系统滴答时间(ms) - * @return 当前滴答时间 - */ -uint32_t menu_port_get_tick(void); - -/** - * @brief 调试打印接口(如UART、LCD) - * @param fmt 格式化字符串 - * @param args 可变参数列表 - */ -void menu_port_printf(const char* fmt, va_list args); - -/** - * @brief 错误处理接口(如点亮错误LED、重启) - */ -void menu_port_error_handler(void); - -/** - * @brief 硬件按键扫描接口(用户需在外部循环中调用,产生事件) - * @note 扫描结果通过menu_post_event发送到菜单事件队列 - */ -void menu_port_key_scan(void); - -/** - * @brief 显示接口(可选,对接menu_core_refresh_display) - * @param str 显示字符串 - * @param node_id 菜单节点ID - */ -void menu_port_display(const char* str, MenuNodeId node_id); - -#endif // MENU_PORT_H -``` - -#### 5.2 `menu/src/port/menu_port.c`(硬件端口层实现,用户需适配) -```c -/** - * @file menu_port.c - * @brief 菜单组件硬件端口层实现(示例:基于STM32,用户需根据MCU修改) - * @note 工业级:所有硬件相关代码集中在此,便于移植 - */ -#include "menu_port.h" -#include "menu.h" -// 引入MCU相关头文件(示例:STM32) -// #include "stm32f1xx_hal.h" - -/************************** 示例实现(用户需根据硬件修改) **************************/ -void menu_port_irq_disable(void) -{ - // 示例:STM32禁用全局中断 - // __disable_irq(); -} - -void menu_port_irq_enable(void) -{ - // 示例:STM32启用全局中断 - // __enable_irq(); -} - -uint32_t menu_port_get_tick(void) -{ - // 示例:STM32获取SysTick时间(ms) - // return HAL_GetTick(); - static uint32_t tick = 0; - return tick++; // 模拟,用户需替换为实际硬件滴答 -} - -void menu_port_printf(const char* fmt, va_list args) -{ - // 示例:STM32通过UART打印(如USART1) - // vprintf(fmt, args); // 需重定向fputc到UART - (void)fmt; - (void)args; // 模拟,用户需替换为实际打印逻辑 -} - -void menu_port_error_handler(void) -{ - // 示例:点亮错误LED - // HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_SET); -} - -void menu_port_key_scan(void) -{ - // 示例:扫描硬件按键(上、下、确认、返回) - // 实际需根据硬件电路读取GPIO状态,此处为模拟 - static uint8_t key_state = 0; - key_state++; - if (key_state % 10 == 0) - { - menu_post_event(MENU_EVENT_KEY_DOWN, 0); // 模拟下键事件 - } - else if (key_state % 20 == 0) - { - menu_post_event(MENU_EVENT_KEY_UP, 0); // 模拟上键事件 - } - else if (key_state % 30 == 0) - { - menu_post_event(MENU_EVENT_KEY_ENTER, 0); // 模拟确认键事件 - } - else if (key_state % 40 == 0) - { - menu_post_event(MENU_EVENT_KEY_BACK, 0); // 模拟返回键事件 - key_state = 0; - } -} - -void menu_port_display(const char* str, MenuNodeId node_id) -{ - // 示例:LCD显示字符串 - // lcd_display_str(0, 0, str); - (void)str; - (void)node_id; // 模拟,用户需替换为实际显示逻辑 -} -``` - -### 6. 菜单组件初始化与使用示例(用户代码) -```c -/** - * @brief 菜单组件使用示例(用户主函数) - */ -#include "menu.h" -#include "menu_port.h" - -// 菜单回调函数示例 -static MenuErrCode menu_cb_enter_param(MenuNodeId node_id) -{ - MENU_DEBUG("Enter param menu: %d", node_id); - return MENU_OK; -} - -static MenuErrCode menu_cb_exit_param(MenuNodeId node_id) -{ - MENU_DEBUG("Exit param menu: %d", node_id); - return MENU_OK; -} - -int main(void) -{ - // 1. 初始化硬件(如UART、LCD、SysTick) - // hal_init(); - - // 2. 初始化菜单组件 - menu_init(); - - // 3. 注册菜单节点(构建菜单树) - menu_register_node(0, 1, "Main Menu", NULL, NULL); // 根节点 - menu_register_node(1, 2, "Parameter Setting", menu_cb_enter_param, menu_cb_exit_param); // 子节点1 - menu_register_node(1, 3, "System Info", NULL, NULL); // 子节点2 - menu_register_node(2, 4, "Temperature", NULL, NULL); // 孙节点1 - - // 4. 注册参数(启用参数管理时) - #if MENU_CONFIG_ENABLE_PARAM - menu_param_register(4, 1, MENU_PARAM_TYPE_FLOAT, 0.0f, 100.0f, 25.0f, 1.0f); // 温度参数 - #endif - - // 5. 主循环 - while (1) - { - // 扫描硬件按键(产生事件) - menu_port_key_scan(); - - // 菜单主循环(处理事件、刷新显示) - menu_main_loop(); - - // 其他业务逻辑 - // ... - } -} - -// 菜单组件初始化实现(用户可放在menu.c中,或主函数中) -MenuErrCode menu_init(void) -{ - MenuErrCode err = menu_core_ctx_init(); - if (err != MENU_OK) - { - return err; - } - - MENU_DEBUG("Menu component initialized"); - return MENU_OK; -} -``` - ---- - -## 三、总结 -### 核心关键点回顾 -1. **工业级架构设计**:采用**核心层+功能扩展层+硬件端口层**的分层架构,所有硬件相关代码集中在`port`层,移植性极强。 -2. **事件驱动解耦**:按键扫描产生事件,菜单核心层消费事件,彻底解耦硬件与业务逻辑,符合嵌入式开发最佳实践。 -3. **静态内存管理**:使用静态数组/内存池代替动态分配,避免内存碎片,满足嵌入式系统的内存约束。 -4. **可配置可裁剪**:通过`menu_config.h`的宏开关控制功能模块和资源大小,可根据项目需求裁剪功能,减少资源占用。 -5. **鲁棒性保障**:加入断言、边界检查、错误码返回、事件超时处理,确保组件在工业环境下稳定运行。 - -该菜单组件完全符合工业级嵌入式组件的标准,可直接应用于各类嵌入式设备(如工业控制器、智能家居、仪器仪表),并支持灵活的功能扩展和硬件适配。 \ No newline at end of file +通过本开发文档,开发者可以全面了解菜单组件的架构设计、核心思想、数据算法和使用方法,便于快速集成和定制开发。 \ No newline at end of file