ESP32通过Modbus RTU读取多数据类型的技术问题求助
问题:ESP32通过RS485 Modbus RTU读取多字节数据类型失败
当前在ESP32上通过RS485运行Modbus RTU,从Modbus Slave模拟器读取数据。使用调整后的ArduinoModbus库(原库不支持ESP32)可读写二进制和int类型数据,但无法处理float、long、double等多字节数据类型,开发环境为VSCode+PlatformIO。
修改后的read()函数
long ModbusClient::read() { if (_available <= 0) { return -1; } long result = -1; switch (_type) { case COILS: case DISCRETE_INPUTS: result = ((uint8_t*)_values)[_read]; if (result != -1) { _available--; _read++; } break; case HOLDING_REGISTERS: case INPUT_REGISTERS: if (_available >= 2) { result = ((uint16_t*)_values)[_read]; result = (result << 16) | ((uint16_t*)_values)[_read + 1]; if (result != -1) { _available -= 2; _read += 2; } } break; default: break; } return result; }
备选研究函数holdingRegisterRead()
long ModbusClient::holdingRegisterRead(int id, int address) { uint16_t value; modbus_set_slave(_mb, id); if (modbus_read_registers(_mb, address, 1, &value) < 0) { return -1; } return value; }
主循环代码
void loop() { readHoldingRegisterValues(); delay(5000); Serial.println(); } void readHoldingRegisterValues() { Serial.print("Reading Input Register values ... "); // read 10 Input Register values from (slave) id 1, address 0x00 if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x00, 10)) { Serial.print("failed! "); Serial.println(ModbusRTUClient.lastError()); } else { Serial.println("success"); while (ModbusRTUClient.available()) { Serial.print(ModbusRTUClient.read()); Serial.print(' '); } Serial.println(); } }
当前输出结果
1111170744 0 0 0 0
已知Modbus寄存器为16位,float(32位)、double(64位)等类型需要组合多个寄存器读取,求解决方法。
解决方案
核心思路
Modbus多字节数据的解析依赖字节序(大端/小端),需先明确Slave模拟器的字节存储规则,再通过读取连续寄存器后重组数据。
1. 新增原始寄存器读取方法
在ModbusClient类中添加读取单个16位寄存器的函数,方便后续灵活组合多字节数据:
uint16_t ModbusClient::readRegister() { // 校验寄存器类型和可用数量 if (_available <= 0 || (_type != HOLDING_REGISTERS && _type != INPUT_REGISTERS)) { return 0xFFFF; // 返回无效值标记 } uint16_t result = ((uint16_t*)_values)[_read]; _available--; _read++; return result; }
2. 应用层解析多字节数据示例
以读取float类型为例(占2个16位寄存器),假设Slave采用大端序存储(高字节在前):
void readFloatValue() { Serial.print("Reading float from address 0 ... "); // 读取2个连续寄存器 if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x00, 2)) { Serial.print("failed! "); Serial.println(ModbusRTUClient.lastError()); } else { Serial.println("success"); if(ModbusRTUClient.available() >=2){ uint16_t regHigh = ModbusRTUClient.readRegister(); uint16_t regLow = ModbusRTUClient.readRegister(); // 组合成32位原始二进制数据 uint32_t rawData = ((uint32_t)regHigh) << 16 | regLow; // 转换为float类型 float floatValue; memcpy(&floatValue, &rawData, sizeof(floatValue)); Serial.print("Parsed float value: "); Serial.println(floatValue); } } }
3. 扩展支持double类型
double占4个16位寄存器,解析逻辑类似:
void readDoubleValue() { Serial.print("Reading double from address 2 ... "); if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x02, 4)) { Serial.print("failed! "); Serial.println(ModbusRTUClient.lastError()); } else { Serial.println("success"); if(ModbusRTUClient.available() >=4){ uint64_t rawData = 0; // 按大端序拼接4个寄存器 for(int i=0;i<4;i++){ rawData = (rawData << 16) | ModbusRTUClient.readRegister(); } double doubleValue; memcpy(&doubleValue, &rawData, sizeof(doubleValue)); Serial.print("Parsed double value: "); Serial.println(doubleValue); } } }
关键注意事项
- 字节序校验:若解析结果异常,交换寄存器顺序测试(比如大端序改
regLow在前regHigh在后),匹配Slave的实际存储规则。 - 寄存器数量匹配:float需2个寄存器,double需4个,
requestFrom的数量必须对应。 - 安全转换:使用
memcpy进行二进制数据转换,避免直接强制类型转换导致的字节序错误。
内容的提问来源于stack exchange,提问作者Chung Phạm
相关产品推荐
相关产品推荐

