增加modbus绑定
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -86,3 +86,4 @@ dkms.conf
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
build/*
|
||||||
|
|||||||
@ -32,6 +32,7 @@ include_directories(
|
|||||||
src/param
|
src/param
|
||||||
src/lang
|
src/lang
|
||||||
src/utils
|
src/utils
|
||||||
|
src/features
|
||||||
port
|
port
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ set(SOURCES
|
|||||||
src/param/menu_param.c
|
src/param/menu_param.c
|
||||||
src/lang/menu_lang.c
|
src/lang/menu_lang.c
|
||||||
src/utils/menu_utils.c
|
src/utils/menu_utils.c
|
||||||
|
src/features/menu_modbus.c
|
||||||
port/menu_port.c
|
port/menu_port.c
|
||||||
examples/menu_example.c
|
examples/menu_example.c
|
||||||
)
|
)
|
||||||
|
|||||||
320
README.md
320
README.md
@ -1,2 +1,320 @@
|
|||||||
# menu
|
# 工业级嵌入式菜单组件
|
||||||
|
|
||||||
|
## 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语言的嵌入式系统
|
||||||
|
|||||||
91
api/menu.h
91
api/menu.h
@ -131,6 +131,15 @@ MenuErrCode menu_param_set_value(uint16_t param_id, float value);
|
|||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
MenuErrCode menu_param_get_value(uint16_t param_id, float* value);
|
MenuErrCode menu_param_get_value(uint16_t param_id, float* value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数类型
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param type 输出参数,参数类型
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type);
|
||||||
|
|
||||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_LANG
|
#if MENU_CONFIG_ENABLE_LANG
|
||||||
@ -149,4 +158,86 @@ MenuErrCode menu_lang_set_current(uint8_t lang_id);
|
|||||||
MenuErrCode menu_lang_get_current(uint8_t* lang_id);
|
MenuErrCode menu_lang_get_current(uint8_t* lang_id);
|
||||||
#endif // MENU_CONFIG_ENABLE_LANG
|
#endif // MENU_CONFIG_ENABLE_LANG
|
||||||
|
|
||||||
|
/************************** 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
|
||||||
|
|
||||||
#endif // MENU_H
|
#endif // MENU_H
|
||||||
@ -64,4 +64,30 @@
|
|||||||
*/
|
*/
|
||||||
#define MENU_CONFIG_REFRESH_INTERVAL 50
|
#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
|
#endif // MENU_CONFIG_H
|
||||||
@ -1,302 +1,339 @@
|
|||||||
/**
|
/**
|
||||||
* @file menu_example.c
|
* @file menu_example.c
|
||||||
* @brief 菜单组件使用示例
|
* @brief 菜单组件完整使用示例(包含Modbus映射功能演示)
|
||||||
* @note 工业级嵌入式菜单组件 - 使用示例
|
* @note 工业级嵌入式菜单组件 - 使用示例
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "menu_port.h"
|
#include "menu_port.h"
|
||||||
#include <stdio.h>
|
#include "menu_def.h"
|
||||||
|
|
||||||
// 条件编译:处理不同平台的键盘输入函数
|
/************************** 示例配置 **************************/
|
||||||
#if defined(_WIN32)
|
// 菜单节点ID定义
|
||||||
#include <conio.h> // Windows平台
|
#define MENU_ROOT_ID 1 // 根节点
|
||||||
#else
|
#define MENU_PARAM_ID 2 // 参数设置节点
|
||||||
// MinGW或其他平台的模拟实现
|
#define MENU_MODBUS_ID 3 // Modbus设置节点
|
||||||
#include <termios.h>
|
#define MENU_TEMP_ID 4 // 温度参数节点
|
||||||
#include <unistd.h>
|
#define MENU_HUMID_ID 5 // 湿度参数节点
|
||||||
#include <fcntl.h>
|
#define MENU_PRESS_ID 6 // 压力参数节点
|
||||||
|
|
||||||
int _kbhit(void)
|
// 参数ID定义
|
||||||
{
|
#define PARAM_TEMP_ID 1 // 温度参数ID
|
||||||
struct termios oldt, newt;
|
#define PARAM_HUMID_ID 2 // 湿度参数ID
|
||||||
int ch;
|
#define PARAM_PRESS_ID 3 // 压力参数ID
|
||||||
int oldf;
|
|
||||||
|
|
||||||
tcgetattr(STDIN_FILENO, &oldt);
|
// Modbus寄存器地址定义
|
||||||
newt = oldt;
|
#define REG_TEMP_ADDR 40001 // 温度寄存器地址
|
||||||
newt.c_lflag &= ~(ICANON | ECHO);
|
#define REG_HUMID_ADDR 40003 // 湿度寄存器地址
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
#define REG_PRESS_ADDR 40005 // 压力寄存器地址
|
||||||
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
||||||
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
|
||||||
|
|
||||||
ch = getchar();
|
/************************** 全局变量 **************************/
|
||||||
|
// 模拟Modbus寄存器缓冲区
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
uint8_t g_modbus_reg_buf[256] = {0};
|
||||||
fcntl(STDIN_FILENO, F_SETFL, oldf);
|
|
||||||
|
|
||||||
if(ch != EOF)
|
|
||||||
{
|
|
||||||
ungetc(ch, stdin);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int _getch(void)
|
|
||||||
{
|
|
||||||
struct termios oldt, newt;
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
tcgetattr(STDIN_FILENO, &oldt);
|
|
||||||
newt = oldt;
|
|
||||||
newt.c_lflag &= ~(ICANON | ECHO);
|
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
||||||
|
|
||||||
ch = getchar();
|
|
||||||
|
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
||||||
|
|
||||||
return ch;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/************************** 菜单节点ID定义 **************************/
|
|
||||||
#define MENU_ID_MAIN 100 ///< 主菜单
|
|
||||||
#define MENU_ID_SETTINGS 200 ///< 设置菜单
|
|
||||||
#define MENU_ID_DISPLAY 201 ///< 显示设置
|
|
||||||
#define MENU_ID_LANGUAGE 202 ///< 语言设置
|
|
||||||
#define MENU_ID_PARAMETER 300 ///< 参数设置
|
|
||||||
#define MENU_ID_ABOUT 400 ///< 关于
|
|
||||||
|
|
||||||
/************************** 参数ID定义 **************************/
|
|
||||||
#define PARAM_ID_BRIGHTNESS 1000 ///< 亮度参数
|
|
||||||
#define PARAM_ID_CONTRAST 1001 ///< 对比度参数
|
|
||||||
|
|
||||||
/************************** 菜单回调函数 **************************/
|
/************************** 菜单回调函数 **************************/
|
||||||
/**
|
/**
|
||||||
* @brief 主菜单进入回调
|
* @brief 进入参数设置菜单回调
|
||||||
* @param node_id 菜单节点ID
|
* @param node_id 菜单节点ID
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_main(MenuNodeId node_id)
|
static MenuErrCode menu_cb_enter_param(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
(void)node_id; // 未使用的参数
|
MENU_DEBUG("Enter param menu: %d", node_id);
|
||||||
printf("Enter Main Menu\r\n");
|
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置菜单进入回调
|
* @brief 退出参数设置菜单回调
|
||||||
* @param node_id 菜单节点ID
|
* @param node_id 菜单节点ID
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_settings(MenuNodeId node_id)
|
static MenuErrCode menu_cb_exit_param(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
(void)node_id; // 未使用的参数
|
MENU_DEBUG("Exit param menu: %d", node_id);
|
||||||
printf("Enter Settings Menu\r\n");
|
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 显示设置进入回调
|
* @brief 进入Modbus设置菜单回调
|
||||||
* @param node_id 菜单节点ID
|
* @param node_id 菜单节点ID
|
||||||
* @return 错误码
|
* @return 错误码
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_display(MenuNodeId node_id)
|
static MenuErrCode menu_cb_enter_modbus(MenuNodeId node_id)
|
||||||
{
|
{
|
||||||
(void)node_id; // 未使用的参数
|
MENU_DEBUG("Enter Modbus menu: %d", node_id);
|
||||||
printf("Enter Display Settings\r\n");
|
|
||||||
return MENU_OK;
|
return MENU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 语言设置进入回调
|
|
||||||
* @param node_id 菜单节点ID
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
static MenuErrCode menu_cb_language(MenuNodeId node_id)
|
|
||||||
{
|
|
||||||
(void)node_id; // 未使用的参数
|
|
||||||
printf("Enter Language Settings\r\n");
|
|
||||||
return MENU_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 参数设置进入回调
|
* @brief 模拟Modbus通信流程
|
||||||
* @param node_id 菜单节点ID
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
*/
|
||||||
static MenuErrCode menu_cb_parameter(MenuNodeId node_id)
|
static void modbus_communication_demo(void)
|
||||||
{
|
{
|
||||||
(void)node_id; // 未使用的参数
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
printf("Enter Parameter Settings\r\n");
|
MENU_DEBUG("=== Modbus Communication Demo ===");
|
||||||
return MENU_OK;
|
|
||||||
|
// 1. 模拟参数到寄存器的转换(参数修改后更新寄存器)
|
||||||
|
{
|
||||||
|
MENU_DEBUG("1. Param to Reg Demo:");
|
||||||
|
|
||||||
|
// 设置温度参数为28.5
|
||||||
|
menu_param_set_value(PARAM_TEMP_ID, 28.5f);
|
||||||
|
|
||||||
|
// 将温度参数转换到Modbus寄存器
|
||||||
|
uint8_t buf_len = 4;
|
||||||
|
uint8_t reg_buf[4] = {0};
|
||||||
|
MenuErrCode err = menu_modbus_map_param_to_reg(PARAM_TEMP_ID, reg_buf, &buf_len);
|
||||||
|
if (err == MENU_OK)
|
||||||
|
{
|
||||||
|
MENU_DEBUG(" Temp param (28.5f) to reg: [0x%02X, 0x%02X, 0x%02X, 0x%02X]",
|
||||||
|
reg_buf[0], reg_buf[1], reg_buf[2], reg_buf[3]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MENU_DEBUG(" Param to reg failed: %d", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 2. 模拟寄存器到参数的转换(接收到Modbus写入请求后更新参数)
|
||||||
* @brief 关于菜单进入回调
|
|
||||||
* @param node_id 菜单节点ID
|
|
||||||
* @return 错误码
|
|
||||||
*/
|
|
||||||
static MenuErrCode menu_cb_about(MenuNodeId node_id)
|
|
||||||
{
|
{
|
||||||
(void)node_id; // 未使用的参数
|
MENU_DEBUG("2. Reg to Param Demo:");
|
||||||
printf("Enter About Menu\r\n");
|
|
||||||
printf("Industrial Menu Component v1.0\r\n");
|
// 模拟Modbus写入湿度参数(65.0%)
|
||||||
printf("Highly Portable & Modular\r\n");
|
// 65.0f的二进制表示(大端):0x42820000,对应Modbus标准字节序为0x8242 0x0000
|
||||||
return MENU_OK;
|
uint8_t reg_buf[4] = {0x82, 0x42, 0x00, 0x00};
|
||||||
|
|
||||||
|
// 将Modbus寄存器数据转换为参数值
|
||||||
|
MenuErrCode err = menu_modbus_map_reg_to_param(PARAM_HUMID_ID, reg_buf, sizeof(reg_buf));
|
||||||
|
if (err == MENU_OK)
|
||||||
|
{
|
||||||
|
// 读取转换后的参数值
|
||||||
|
float humid_val;
|
||||||
|
menu_param_get_value(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读取请求处理
|
||||||
/**
|
|
||||||
* @brief 按键扫描函数(Windows平台示例)
|
|
||||||
* @return 按键事件类型
|
|
||||||
*/
|
|
||||||
static MenuEventType key_scan(void)
|
|
||||||
{
|
{
|
||||||
if (_kbhit())
|
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(PARAM_PRESS_ID, reg_buf, &buf_len);
|
||||||
|
if (err == MENU_OK)
|
||||||
{
|
{
|
||||||
int key = _getch();
|
// 然后返回给Modbus主机
|
||||||
switch (key)
|
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
|
||||||
{
|
{
|
||||||
case 'w':
|
MENU_DEBUG(" Read pressure failed: %d", err);
|
||||||
case 'W':
|
|
||||||
case 72: // 上箭头
|
|
||||||
return MENU_EVENT_KEY_UP;
|
|
||||||
case 's':
|
|
||||||
case 'S':
|
|
||||||
case 80: // 下箭头
|
|
||||||
return MENU_EVENT_KEY_DOWN;
|
|
||||||
case 13: // 回车键
|
|
||||||
return MENU_EVENT_KEY_ENTER;
|
|
||||||
case 'b':
|
|
||||||
case 'B':
|
|
||||||
case 27: // ESC键
|
|
||||||
return MENU_EVENT_KEY_BACK;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MENU_EVENT_NONE;
|
|
||||||
|
// 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(PARAM_TEMP_ID, reg_buf, sizeof(reg_buf));
|
||||||
|
if (err == MENU_OK)
|
||||||
|
{
|
||||||
|
// 读取转换后的参数值
|
||||||
|
float temp_val;
|
||||||
|
menu_param_get_value(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)
|
int main(void)
|
||||||
{
|
{
|
||||||
printf("Industrial Menu Component Example\r\n");
|
MENU_DEBUG("=== Menu Component Example ===");
|
||||||
printf("==================================\r\n");
|
|
||||||
printf("Controls:\r\n");
|
|
||||||
printf(" W/Up Arrow - Up\r\n");
|
|
||||||
printf(" S/Down Arrow - Down\r\n");
|
|
||||||
printf(" Enter - Enter\r\n");
|
|
||||||
printf(" B/ESC - Back\r\n");
|
|
||||||
printf(" Q - Quit\r\n");
|
|
||||||
printf("==================================\r\n\r\n");
|
|
||||||
|
|
||||||
/************************** 初始化菜单组件 **************************/
|
/************************** 1. 初始化硬件 **************************/
|
||||||
|
MENU_DEBUG("1. Initialize hardware...");
|
||||||
|
// 这里应该初始化硬件,如UART、LCD、SysTick等
|
||||||
|
// hal_init();
|
||||||
|
|
||||||
|
/************************** 2. 初始化菜单组件 **************************/
|
||||||
|
MENU_DEBUG("2. Initialize menu component...");
|
||||||
MenuErrCode err = menu_init();
|
MenuErrCode err = menu_init();
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Menu initialization failed: %d\r\n", err);
|
MENU_DEBUG(" Menu init failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
printf("Menu initialized successfully\r\n\r\n");
|
|
||||||
|
|
||||||
/************************** 注册菜单节点 **************************/
|
/************************** 3. 注册菜单节点 **************************/
|
||||||
// 注册主菜单
|
MENU_DEBUG("3. Register menu nodes...");
|
||||||
err = menu_register_node(0, MENU_ID_MAIN, "Main Menu", menu_cb_main, NULL);
|
|
||||||
|
// 注册根节点
|
||||||
|
err = menu_register_node(0, MENU_ROOT_ID, "主菜单", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Main Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register root node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册设置菜单
|
// 注册参数设置节点
|
||||||
err = menu_register_node(MENU_ID_MAIN, MENU_ID_SETTINGS, "Settings", menu_cb_settings, NULL);
|
err = menu_register_node(MENU_ROOT_ID, MENU_PARAM_ID, "参数设置", menu_cb_enter_param, menu_cb_exit_param);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Settings Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register param node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册显示设置菜单
|
// 注册Modbus设置节点
|
||||||
err = menu_register_node(MENU_ID_SETTINGS, MENU_ID_DISPLAY, "Display Settings", menu_cb_display, NULL);
|
err = menu_register_node(MENU_ROOT_ID, MENU_MODBUS_ID, "Modbus设置", menu_cb_enter_modbus, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Display Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register modbus node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册语言设置菜单
|
// 注册温度参数节点
|
||||||
err = menu_register_node(MENU_ID_SETTINGS, MENU_ID_LANGUAGE, "Language", menu_cb_language, NULL);
|
err = menu_register_node(MENU_PARAM_ID, MENU_TEMP_ID, "温度设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Language Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register temp node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册参数设置菜单
|
// 注册湿度参数节点
|
||||||
err = menu_register_node(MENU_ID_MAIN, MENU_ID_PARAMETER, "Parameters", menu_cb_parameter, NULL);
|
err = menu_register_node(MENU_PARAM_ID, MENU_HUMID_ID, "湿度设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Parameter Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register humid node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册关于菜单
|
// 注册压力参数节点
|
||||||
err = menu_register_node(MENU_ID_MAIN, MENU_ID_ABOUT, "About", menu_cb_about, NULL);
|
err = menu_register_node(MENU_PARAM_ID, MENU_PRESS_ID, "压力设置", NULL, NULL);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register About Menu failed: %d\r\n", err);
|
MENU_DEBUG(" Register press node failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************** 注册参数 **************************/
|
/************************** 4. 注册参数 **************************/
|
||||||
|
MENU_DEBUG("4. Register parameters...");
|
||||||
|
|
||||||
#if MENU_CONFIG_ENABLE_PARAM
|
#if MENU_CONFIG_ENABLE_PARAM
|
||||||
// 注册亮度参数(范围0-100,默认50,步长1)
|
// 注册温度参数(范围:-40.0°C ~ 120.0°C,默认25.0°C,精度0.1°C)
|
||||||
err = menu_param_register(MENU_ID_DISPLAY, PARAM_ID_BRIGHTNESS,
|
err = menu_param_register(MENU_TEMP_ID, PARAM_TEMP_ID, MENU_PARAM_TYPE_FLOAT,
|
||||||
MENU_PARAM_TYPE_UINT8, 0.0f, 100.0f, 50.0f, 1.0f);
|
-40.0f, 120.0f, 25.0f, 0.1f);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Brightness Param failed: %d\r\n", err);
|
MENU_DEBUG(" Register temp param failed: %d", err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册对比度参数(范围0-100,默认50,步长1)
|
// 注册湿度参数(范围:0.0% ~ 100.0%,默认50.0%,精度0.1%)
|
||||||
err = menu_param_register(MENU_ID_DISPLAY, PARAM_ID_CONTRAST,
|
err = menu_param_register(MENU_HUMID_ID, PARAM_HUMID_ID, MENU_PARAM_TYPE_FLOAT,
|
||||||
MENU_PARAM_TYPE_UINT8, 0.0f, 100.0f, 50.0f, 1.0f);
|
0.0f, 100.0f, 50.0f, 0.1f);
|
||||||
if (err != MENU_OK)
|
if (err != MENU_OK)
|
||||||
{
|
{
|
||||||
printf("Register Contrast Param failed: %d\r\n", err);
|
MENU_DEBUG(" Register humid param failed: %d", err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册压力参数(范围:0.0kPa ~ 1000.0kPa,默认101.3kPa,精度0.1kPa)
|
||||||
|
err = menu_param_register(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;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
printf("Menu nodes registered successfully\r\n\r\n");
|
/************************** 5. 注册Modbus映射 **************************/
|
||||||
|
MENU_DEBUG("5. Register Modbus mappings...");
|
||||||
|
|
||||||
/************************** 主循环 **************************/
|
// 注册温度参数到Modbus寄存器映射
|
||||||
|
err = menu_modbus_map_register(
|
||||||
|
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(
|
||||||
|
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(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************** 6. 运行Modbus通信演示 **************************/
|
||||||
|
MENU_DEBUG("6. Run Modbus communication demo...");
|
||||||
|
modbus_communication_demo();
|
||||||
|
|
||||||
|
/************************** 7. 进入主循环 **************************/
|
||||||
|
MENU_DEBUG("7. Enter main loop...");
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
// 扫描按键
|
// 处理菜单事件和刷新显示
|
||||||
MenuEventType event = key_scan();
|
|
||||||
if (event != MENU_EVENT_NONE)
|
|
||||||
{
|
|
||||||
// 发送按键事件到菜单组件
|
|
||||||
menu_post_event(event, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理菜单主循环
|
|
||||||
menu_main_loop();
|
menu_main_loop();
|
||||||
|
|
||||||
// 检查退出条件
|
// 其他业务逻辑
|
||||||
if (_kbhit() && _getch() == 'q')
|
// ...
|
||||||
{
|
|
||||||
printf("\r\nQuit program\r\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 轻微延迟,避免CPU占用过高
|
// 模拟延时
|
||||||
menu_port_delay_ms(10);
|
menu_port_delay_ms(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -105,4 +105,6 @@ MenuErrCode menu_core_handle_event(const MenuEvent* event);
|
|||||||
*/
|
*/
|
||||||
void menu_core_refresh_display(void);
|
void menu_core_refresh_display(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // MENU_CORE_H
|
#endif // MENU_CORE_H
|
||||||
@ -6,6 +6,7 @@
|
|||||||
#define MENU_DATA_H
|
#define MENU_DATA_H
|
||||||
|
|
||||||
#include "menu_core.h"
|
#include "menu_core.h"
|
||||||
|
#include "menu_modbus.h"
|
||||||
|
|
||||||
/************************** 共享全局变量(声明,内部可见) **************************/
|
/************************** 共享全局变量(声明,内部可见) **************************/
|
||||||
/**
|
/**
|
||||||
@ -61,4 +62,11 @@ extern MenuLangStr s_menu_lang_strs[MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANG
|
|||||||
extern uint8_t s_current_lang_id;
|
extern uint8_t s_current_lang_id;
|
||||||
#endif // MENU_CONFIG_ENABLE_LANG
|
#endif // MENU_CONFIG_ENABLE_LANG
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus映射上下文(启用Modbus映射时有效,全局)
|
||||||
|
*/
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
extern ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS];
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
|
||||||
#endif // MENU_DATA_H
|
#endif // MENU_DATA_H
|
||||||
23
internal/menu_modbus.h
Normal file
23
internal/menu_modbus.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
@ -96,3 +96,44 @@ MenuEventType menu_port_key_scan(void)
|
|||||||
return MENU_EVENT_NONE;
|
return MENU_EVENT_NONE;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
|
/**
|
||||||
|
* @brief Modbus硬件发送数据接口示例实现
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器地址
|
||||||
|
* @param reg_buf 数据缓冲区
|
||||||
|
* @param buf_len 数据长度
|
||||||
|
* @return 错误码
|
||||||
|
* @note 用户需要根据实际硬件(如RS485、TCP)实现此接口
|
||||||
|
*/
|
||||||
|
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; // 模拟成功,用户需替换为实际逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Modbus硬件接收数据接口示例实现
|
||||||
|
* @param reg_type 寄存器类型
|
||||||
|
* @param reg_addr 寄存器地址
|
||||||
|
* @param reg_buf 数据缓冲区
|
||||||
|
* @param buf_len 数据长度
|
||||||
|
* @return 错误码
|
||||||
|
* @note 用户需要根据实际硬件(如RS485、TCP)实现此接口
|
||||||
|
*/
|
||||||
|
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
|
||||||
@ -47,4 +47,28 @@ void menu_port_display(const char* menu_name, uint16_t menu_id);
|
|||||||
*/
|
*/
|
||||||
// MenuEventType menu_port_key_scan(void);
|
// MenuEventType menu_port_key_scan(void);
|
||||||
|
|
||||||
|
#include "menu.h"
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
#endif // MENU_PORT_H
|
#endif // MENU_PORT_H
|
||||||
629
src/features/menu_modbus.c
Normal file
629
src/features/menu_modbus.c
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
/**
|
||||||
|
* @file menu_modbus.c
|
||||||
|
* @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配)
|
||||||
|
*/
|
||||||
|
#include "menu.h"
|
||||||
|
#include "menu_core.h"
|
||||||
|
#include "menu_data.h"
|
||||||
|
#include "menu_def.h"
|
||||||
|
#include "menu_modbus.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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 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. 校验参数是否已注册(通过获取参数值间接检查)
|
||||||
|
float dummy_val;
|
||||||
|
MenuErrCode err = menu_param_get_value(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(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. 获取参数类型
|
||||||
|
MenuParamType param_type;
|
||||||
|
err = menu_param_get_type(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(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_HW_PORT_ERROR; // 复用原有错误码
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 3. 获取参数值
|
||||||
|
float param_val;
|
||||||
|
MenuErrCode err = menu_param_get_value(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(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_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}; // 足够大的临时缓冲区
|
||||||
|
|
||||||
|
// 检查缓冲区长度是否足够
|
||||||
|
if (buf_len < req_len)
|
||||||
|
{
|
||||||
|
MENU_DEBUG("Modbus reg buf too small: need %d bytes, got %d bytes", req_len, buf_len);
|
||||||
|
return MENU_ERR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制数据到临时缓冲区
|
||||||
|
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(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义Modbus映射静态数组
|
||||||
|
ModbusMapInternal s_menu_modbus_maps[MENU_CONFIG_MAX_MODBUS_MAPS];
|
||||||
|
|
||||||
|
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||||
@ -11,7 +11,7 @@
|
|||||||
MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS];
|
MenuParam s_menu_params[MENU_CONFIG_MAX_PARAMS];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查找参数(通过参数ID)
|
* @brief 查找参数(通过参数ID,内部使用)
|
||||||
* @param param_id 参数ID
|
* @param param_id 参数ID
|
||||||
* @return 参数指针(NULL表示未找到)
|
* @return 参数指针(NULL表示未找到)
|
||||||
*/
|
*/
|
||||||
@ -299,4 +299,29 @@ MenuErrCode menu_param_decrease(uint16_t param_id, float step)
|
|||||||
return menu_param_set_value(param_id, current_val - step);
|
return menu_param_set_value(param_id, current_val - step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数类型
|
||||||
|
* @param param_id 参数ID
|
||||||
|
* @param type 输出参数,参数类型
|
||||||
|
* @return 错误码
|
||||||
|
*/
|
||||||
|
MenuErrCode menu_param_get_type(uint16_t param_id, MenuParamType* type)
|
||||||
|
{
|
||||||
|
if (type == NULL)
|
||||||
|
{
|
||||||
|
return MENU_ERR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找参数
|
||||||
|
MenuParam* param = menu_param_find(param_id);
|
||||||
|
if (param == NULL)
|
||||||
|
{
|
||||||
|
return MENU_ERR_NODE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回参数类型
|
||||||
|
*type = param->type;
|
||||||
|
return MENU_OK;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||||
Reference in New Issue
Block a user