Files
menu/src/features/menu_modbus.c
2025-12-18 22:24:25 +08:00

629 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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→000001→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, &reg_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, &param_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, &param_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