#include "modbusrtu.h"
#include <QThread>
#include <QDebug>
#include <QElapsedTimer>
// 构造函数:初始化 ModbusRTU 对象
ModbusRTU::ModbusRTU(QObject *parent) : QObject(parent), serialPort(new QSerialPort(this)), deviceAddress(0) {
// 设置默认的串口参数(无校验位、8 数据位、1 停止位)
serialPort->setParity(QSerialPort::NoParity);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setStopBits(QSerialPort::OneStop);
}
// 析构函数:释放资源
ModbusRTU::~ModbusRTU() {
// 如果串口处于打开状态,先关闭串口
if (serialPort->isOpen()) {
serialPort->close();
}
}
// 初始化串口
bool ModbusRTU::initializePort(const QString &portName, uint32_t baudRate) {
serialPort->setPortName(portName);
serialPort->setBaudRate(baudRate);
if (!serialPort->open(QIODevice::ReadWrite)) {
emit errorOccurred(QString("打开失败: %1").arg(serialPort->errorString()));
return false;
}
// 监听串口错误信号
connect(serialPort, &QSerialPort::errorOccurred, this, [this](QSerialPort::SerialPortError error) {
if (error != QSerialPort::NoError) {
emit errorOccurred(QString("运行时错误: %1").arg(serialPort->errorString()));
closePort();
}
});
return true;
}
// 设置 Modbus RTU 从设备地址
void ModbusRTU::setDeviceAddress(uint8_t address) {
deviceAddress = address; // 设置设备地址
}
// 读取保持寄存器(功能码 0x03)
bool ModbusRTU::readHoldingRegisters(uint16_t startAddress, uint16_t quantity, std::vector<uint16_t> &values) {
// 构造请求数据部分:起始地址和寄存器数量
qDebug() << "Reading" << quantity << "holding registers starting at address:" << startAddress;
// 构造 Modbus 请求帧
QByteArray requestData;
requestData.append(static_cast<char>(startAddress >> 8)); // 起始地址高字节
requestData.append(static_cast<char>(startAddress & 0xFF)); // 起始地址低字节
requestData.append(static_cast<char>(quantity >> 8)); // 寄存器数量高字节
requestData.append(static_cast<char>(quantity & 0xFF)); // 寄存器数量低字节
QByteArray request = buildRequest(0x03, requestData); // 功能码 0x03 表示读保持寄存器
qDebug() << "Sending read request:" << request.toHex();
if (!writeRequest(request)) {
qDebug() << "Failed to send read request";
return false;
}
QByteArray response;
if (!readResponseTimeout(6000, response)) { // 增加超时时间
qDebug() << "Timeout while waiting for response";
return false;
}
qDebug() << "Received response:" << response.toHex();
return parseReadResponse(response, values);
}
// 写单个寄存器(功能码 0x06)
bool ModbusRTU::writeSingleRegisterWithAddress(uint8_t address, uint16_t regAddress, uint16_t value) {
qDebug() << "Sending command to address" << address
<< "| Register:" << QString::number(regAddress, 16)
<< "| Value:" << value;
QByteArray requestData;
requestData.append(static_cast<char>(regAddress >> 8)); // 寄存器地址高位
requestData.append(static_cast<char>(regAddress & 0xFF)); // 寄存器地址低位
requestData.append(static_cast<char>(value >> 8)); // 数据高字节
requestData.append(static_cast<char>(value & 0xFF)); // 数据低字节
// 构造完整的 Modbus RTU 请求帧
QByteArray request = buildRequestWithAddress(address, 0x06, requestData); // 功能码 0x06 表示写单个寄存器
qDebug() << "Full command:" << request.toHex();
// 发送请求
return writeRequest(request);
}
// 写多个寄存器(功能码 0x10)
bool ModbusRTU::writeMultipleRegisters(uint16_t startAddress, const std::vector<uint16_t> &values) {
// 构造请求数据部分:起始地址、寄存器数量和值
QByteArray requestData;
requestData.append(static_cast<char>(startAddress >> 8)); // 起始地址高位
requestData.append(static_cast<char>(startAddress & 0xFF)); // 起始地址低位
requestData.append(static_cast<char>((values.size() >> 8) & 0xFF)); // 寄存器数量高位
requestData.append(static_cast<char>(values.size() & 0xFF)); // 寄存器数量低位
requestData.append(static_cast<char>(values.size() * 2)); // 字节计数(每个寄存器 2 字节)
// 添加寄存器值
for (uint16_t value : values) {
requestData.append(static_cast<char>(value >> 8)); // 值高位
requestData.append(static_cast<char>(value & 0xFF)); // 值低位
}
// 构造完整的 Modbus RTU 请求帧
QByteArray request = buildRequest(0x10, requestData); // 功能码 0x10 表示写多个寄存器
// 发送请求
if (writeRequest(request)) {
// 接收响应
QByteArray response;
if (readResponseTimeout(request.size() - (values.size() * 2), response)) { // 响应长度等于请求长度减去寄存器值字节数
// 验证响应是否正确
return verifyResponse(response, 0x10);
}
}
return false;
}
// 计算 CRC-16 校验码
uint16_t ModbusRTU::calculateCRC(const QByteArray &data) {
uint16_t crc = 0xFFFF; // 初始值
for (char c : data) { // 遍历数据中的每一个字节
crc ^= static_cast<uint8_t>(c); // 校验值与当前字节异或
for (int i = 0; i < 8; i++) { // 循环 8 次,处理每一位
if (crc & 0x0001) { // 最低位为 1
crc >>= 1; // 右移一位
crc ^= 0xA001; // 与多项式异或
} else {
crc >>= 1; // 右移一位
}
}
}
return crc; // 返回计算得到的 CRC 校验码
}
// 验证 CRC-16 校验码
bool ModbusRTU::verifyCRC(const QByteArray &data) {
if (data.size() < 2) { // 数据长度不足,无法验证 CRC
return false;
}
// 提取接收到的 CRC 值(低字节在前)
uint16_t receivedCRC = (static_cast<uint8_t>(data[data.size() - 2]) << 8) | static_cast<uint8_t>(data[data.size() - 1]);
// 提取数据部分(不包括 CRC 校验码)
QByteArray dataWithoutCRC = data.mid(0, data.size() - 2);
// 计算 CRC 校验码
uint16_t calculatedCRC = calculateCRC(dataWithoutCRC);
return (receivedCRC == calculatedCRC); // 比较接收到的 CRC 值与计算得到的 CRC 值
}
// 构造 Modbus RTU 请求帧,包含动态设备地址
QByteArray ModbusRTU::buildRequestWithAddress(uint8_t address, uint8_t functionCode, const QByteArray &data) {
QByteArray request;
request.append(static_cast<char>(address)); // 添加设备地址
request.append(static_cast<char>(functionCode)); // 添加功能码
request.append(data); // 添加数据部分
// 计算并添加 CRC 校验码
uint16_t crc = calculateCRC(request);
request.append(static_cast<char>(crc & 0xFF)); // CRC 低字节
request.append(static_cast<char>((crc >> 8) & 0xFF)); // CRC 高字节
return request; // 返回完整的请求帧
}
// 构造 Modbus RTU 请求帧
QByteArray ModbusRTU::buildRequest(uint8_t functionCode, const QByteArray &data) {
QByteArray request;
request.append(static_cast<char>(deviceAddress)); // 添加设备地址
request.append(static_cast<char>(functionCode)); // 添加功能码
request.append(data); // 添加数据部分
// 计算并添加 CRC 校验码
uint16_t crc = calculateCRC(request);
request.append(static_cast<char>(crc & 0xFF)); // CRC 低字节
request.append(static_cast<char>((crc >> 8) & 0xFF)); // CRC 高字节
return request; // 返回完整的请求帧
}
// 发送请求到串口
bool ModbusRTU::writeRequest(const QByteArray &request) {
if (!serialPort->isOpen()) {
emit errorOccurred("Serial port not open");
return false;
}
// 清空缓冲区
serialPort->flush();
serialPort->clear();
qDebug()<<request;
clearBuffers(); // 发送前清理缓冲区
// 发送请求
qint64 bytesWritten = serialPort->write(request);
if (bytesWritten != request.size()) {
emit errorOccurred(QString("Error writing to port: %1").arg(serialPort->errorString()));
return false;
}
// 等待数据发送完成
if (!serialPort->waitForBytesWritten(1000)) { // 等待发送完成
emit errorOccurred(QString("Timeout writing to port: %1").arg(serialPort->errorString()));
return false;
}
clearBuffers(); // 发送后清理缓冲区
return true;
}
// 从串口读取响应
bool ModbusRTU::readResponseTimeout(int expectedBytes, QByteArray &response) {
response.clear();
QElapsedTimer timer;
timer.start();
// 总超时时间设为 2000 毫秒
while (timer.elapsed() < 2000) {
if (serialPort->waitForReadyRead(500)) {
response += serialPort->readAll();
qDebug() << "Received partial data:" << response.toHex();
// 检查是否已接收足够字节
if (response.size() >= expectedBytes) { // 使用 expectedBytes 参数
qDebug() << "Full response received:" << response.toHex();
return true;
}
} else {
qDebug() << "Partial timeout, current received bytes:" << response.size();
}
}
qDebug() << "Total timeout, received:" << response.toHex();
return false;
}
// 验证响应是否正确
bool ModbusRTU::verifyResponse(const QByteArray &response, uint8_t functionCode) {
if (response.size() < 8) { // 响应长度过短
emit errorOccurred("Invalid response length"); // 发送错误信号
return false;
}
// 检查设备地址是否匹配
if (response[0] != static_cast<char>(deviceAddress)) {
emit errorOccurred("Invalid device address in response"); // 发送错误信号
return false;
}
// 检查功能码是否匹配或是否为异常代码
if (response[1] != static_cast<char>(functionCode + 0x80)) { // 不是异常代码
if (response[1] != static_cast<char>(functionCode)) { // 功能码不匹配
emit errorOccurred("Invalid function code in response"); // 发送错误信号
return false;
}
}
// 验证 CRC 校验码
if (!verifyCRC(response)) {
emit errorOccurred("Invalid CRC in response"); // 发送错误信号
return false;
}
emit registersWritten(true); // 发送寄存器写入成功的信号
return true; // 响应验证成功
}
// 解析读取寄存器的响应
bool ModbusRTU::parseReadResponse(const QByteArray &response, std::vector<uint16_t> &values) {
if (response.size() < 8) { // 响应长度过短
emit errorOccurred("Invalid response length"); // 发送错误信号
return false;
}
int byteCount = response[2]; // 读取字节计数
values.clear(); // 清空寄存器值容器
// 提取寄存器值
for (int i = 0; i < byteCount / 2; ++i) {
uint16_t value = static_cast<uint16_t>(response[3 + i * 2] << 8) | static_cast<uint16_t>(response[3 + i * 2 + 1]);
values.push_back(value); // 将寄存器值添加到容器中
}
emit registersRead(true); // 发送寄存器读取成功的信号
return true; // 响应解析成功
}
// 判断是否打开串口
bool ModbusRTU::isOpen() const {
return serialPort->isOpen();
}
// 清理串口缓冲区
void ModbusRTU::clearBuffers() {
if (serialPort->isOpen()) {
serialPort->clear(); // 清除输入和输出缓冲区
QThread::msleep(100); // 暂停 100 毫秒
}
}
//关闭串口
void ModbusRTU::closePort() {
if (serialPort->isOpen()) {
serialPort->close();
}
}
Received partial data: "0103020000b844"
Partial timeout, current received bytes: 7
14:45:58: 程序异常结束。
14:45:58: The process was ended forcefully.
Avatar
已完成推理
检查ModbusRTU通信异常
从你提供的日志和代码来看,程序在读取寄存器时遇到问题,可能导致崩溃的原因如下:
数据接收不完整:readResponseTimeout 方法在读取数据时,可能只接收到部分数据(如日志中显示的 7 字节),而后续的解析逻辑需要完整的数据帧。
超时时间设置不足:readResponseTimeout 方法中的超时时间可能过短,导致在数据未完全接收前就返回,引发后续错误。
未正确处理异常:在数据解析或校验过程中,可能未正确处理异常情况,导致程序崩溃。
以下是修正后的代码,重点改进了数据接收和错误处理逻辑:
修正后的 modbusrtu.cpp
cpp
复制
#include "modbusrtu.h"
#include <QThread>
#include <QDebug>
#include <QElapsedTimer>
// 构造函数:初始化 ModbusRTU 对象
ModbusRTU::ModbusRTU(QObject *parent) : QObject(parent), serialPort(new QSerialPort(this)), deviceAddress(0) {
// 设置默认的串口参数(无校验位、8 数据位、1 停止位)
serialPort->setParity(QSerialPort::NoParity);
serialPort->setDataBits(QSerialPort::Data8);
serialPort->setStopBits(QSerialPort::OneStop);
}
// 析构函数:释放资源
ModbusRTU::~ModbusRTU() {
// 如果串口处于打开状态,先关闭串口
if (serialPort->isOpen()) {
serialPort->close();
}
}
// 初始化串口
bool ModbusRTU::initializePort(const QString &portName, uint32_t baudRate) {
serialPort->setPortName(portName);
serialPort->setBaudRate(baudRate);
if (!serialPort->open(QIODevice::ReadWrite)) {
emit errorOccurred(QString("打开失败: %1").arg(serialPort->errorString()));
return false;
}
// 监听串口错误信号
connect(serialPort, &QSerialPort::errorOccurred, this, [this](QSerialPort::SerialPortError error) {
if (error != QSerialPort::NoError) {
emit errorOccurred(QString("运行时错误: %1").arg(serialPort->errorString()));
closePort();
}
});
return true;
}
// 设置 Modbus RTU 从设备地址
void ModbusRTU::setDeviceAddress(uint8_t address) {
deviceAddress = address; // 设置设备地址
}
// 读取保持寄存器(功能码 0x03)
bool ModbusRTU::readHoldingRegisters(uint16_t startAddress, uint16_t quantity, std::vector<uint16_t> &values) {
qDebug() << "Reading" << quantity << "holding registers starting at address:" << startAddress;
// 构造请求数据部分:起始地址和寄存器数量
QByteArray requestData;
requestData.append(static_cast<char>(startAddress >> 8)); // 起始地址高字节
requestData.append(static_cast<char>(startAddress & 0xFF)); // 起始地址低字节
requestData.append(static_cast<char>(quantity >> 8)); // 寄存器数量高字节
requestData.append(static_cast<char>(quantity & 0xFF)); // 寄存器数量低字节
QByteArray request = buildRequest(0x03, requestData); // 功能码 0x03 表示读保持寄存器
qDebug() << "Sending read request:" << request.toHex();
if (!writeRequest(request)) {
qDebug() << "Failed to send read request";
return false;
}
QByteArray response;
int expectedBytes = 5 + 2 * quantity; // 预期的响应字节数
if (!readResponseTimeout(expectedBytes, response)) { // 等待完整数据
qDebug() << "Timeout while waiting for response";
return false;
}
qDebug() << "Received response:" << response.toHex();
return parseReadResponse(response, values);
}
// 读取响应并等待完整数据
bool ModbusRTU::readResponseTimeout(int expectedBytes, QByteArray &response) {
QElapsedTimer timer;
timer.start();
response.clear();
while (timer.elapsed() < 2000) { // 总超时时间设为 2000 毫秒
if (serialPort->waitForReadyRead(500)) {
response += serialPort->readAll();
qDebug() << "Received partial data:" << response.toHex();
// 检查是否已接收足够字节
if (response.size() >= expectedBytes) {
qDebug() << "Full response received:" << response.toHex();
return true;
}
} else {
qDebug() << "Partial timeout, current received bytes:" << response.size();
}
}
qDebug() << "Total timeout, received:" << response.toHex();
return false;
}
// 解析读取寄存器的响应
bool ModbusRTU::parseReadResponse(const QByteArray &response, std::vector<uint16_t> &values) {
if (response.size() < 5) { // 响应长度过短
emit errorOccurred("Invalid response length");
return false;
}
if (response[1] != 0x03) { // 功能码 0x03 表示读保持寄存器
emit errorOccurred("Invalid function code in response");
return false;
}
int byteCount = response[2];
int numValues = byteCount / 2;
values.clear();
values.reserve(numValues);
for (int i = 0; i < numValues; ++i) {
if (3 + i * 2 + 1 >= response.size()) { // 数据不足
emit errorOccurred("Incomplete data in response");
return false;
}
uint16_t value = (static_cast<uint16_t>(response[3 + i * 2]) << 8) | response[4 + i * 2];
values.push_back(value);
}
emit registersRead(true);
return true;
}
// 写单个寄存器(功能码 0x06)
bool ModbusRTU::writeSingleRegisterWithAddress(uint8_t address, uint16_t regAddress, uint16_t value) {
qDebug() << "Sending command to address" << address
<< "| Register:" << QString::number(regAddress, 16)
<< "| Value:" << value;
QByteArray requestData;
requestData.append(static_cast<char>(regAddress >> 8)); // 寄存器地址高位
requestData.append(static_cast<char>(regAddress & 0xFF)); // 寄存器地址低位
requestData.append(static_cast<char>(value >> 8)); // 数据高字节
requestData.append(static_cast<char>(value & 0xFF)); // 数据低字节
// 构造完整的 Modbus RTU 请求帧
QByteArray request = buildRequestWithAddress(address, 0x06, requestData); // 功能码 0x06 表示写单个寄存器
qDebug() << "Full command:" << request.toHex();
// 发送请求
return writeRequest(request);
}
// 写多个寄存器(功能码 0x10)
bool ModbusRTU::writeMultipleRegisters(uint16_t startAddress, const std::vector<uint16_t> &values) {
// 构造请求数据部分:起始地址、寄存器数量和值
QByteArray requestData;
requestData.append(static_cast<char>(startAddress >> 8)); // 起始地址高位
requestData.append(static_cast<char>(startAddress & 0xFF)); // 起始地址低位
requestData.append(static_cast<char>((values.size() >> 8) & 0xFF)); // 寄存器数量高位
requestData.append(static_cast<char>(values.size() & 0xFF)); // 寄存器数量低位
requestData.append(static_cast<char>(values.size() * 2)); // 字节计数(每个寄存器 2 字节)
// 添加寄存器值
for (uint16_t value : values) {
requestData.append(static_cast<char>(value >> 8)); // 值高位
requestData.append(static_cast<char>(value & 0xFF)); // 值低位
}
// 构造完整的 Modbus RTU 请求帧
QByteArray request = buildRequest(0x10, requestData); // 功能码 0x10 表示写多个寄存器
// 发送请求
if (writeRequest(request)) {
// 接收响应
QByteArray response;
if (readResponseTimeout(request.size() - (values.size() * 2), response)) { // 响应长度等于请求长度减去寄存器值字节数
// 验证响应是否正确
return verifyResponse(response, 0x10);
}
}
return false;
}
// 计算 CRC-16 校验码
uint16_t ModbusRTU::calculateCRC(const QByteArray &data) {
uint16_t crc = 0xFFFF; // 初始值
for (char c : data) { // 遍历数据中的每一个字节
crc ^= static_cast<uint8_t>(c); // 校验值与当前字节异或
for (int i = 0; i < 8; i++) { // 循环 8 次,处理每一位
if (crc & 0x0001) { // 最低位为 1
crc >>= 1; // 右移一位
crc ^= 0xA001; // 与多项式异或
} else {
crc >>= 1; // 右移一位
}
}
}
return crc; // 返回计算得到的 CRC 校验码
}
// 验证 CRC-16 校验码
bool ModbusRTU::verifyCRC(const QByteArray &data) {
if (data.size() < 2) { // 数据长度不足,无法验证 CRC
return false;
}
// 提取接收到的 CRC 值(低字节在前)
uint16_t receivedCRC = (static_cast<uint8_t>(data[data.size() - 2]) << 8) | static_cast<uint8_t>(data[data.size() - 1]);
// 提取数据部分(不包括 CRC 校验码)
QByteArray dataWithoutCRC = data.mid(0, data.size() - 2);
// 计算 CRC 校验码
uint16_t calculatedCRC = calculateCRC(dataWithoutCRC);
return (receivedCRC == calculatedCRC); // 比较接收到的 CRC 值与计算得到的 CRC 值
}
// 构造 Modbus RTU 请求帧,包含动态设备地址
QByteArray ModbusRTU::buildRequestWithAddress(uint8_t address, uint8_t functionCode, const QByteArray &data) {
QByteArray request;
request.append(static_cast<char>(address)); // 添加设备地址
request.append(static_cast<char>(functionCode)); // 添加功能码
request.append(data); // 添加数据部分
// 计算并添加 CRC 校验码
uint16_t crc = calculateCRC(request);
request.append(static_cast<char>(crc & 0xFF)); // CRC 低字节
request.append(static_cast<char>((crc >> 8) & 0xFF)); // CRC 高字节
return request; // 返回完整的请求帧
}
// 构造 Modbus RTU 请求帧
QByteArray ModbusRTU::buildRequest(uint8_t functionCode, const QByteArray &data) {
QByteArray request;
request.append(static_cast<char>(deviceAddress)); // 添加设备地址
request.append(static_cast<char>(functionCode)); // 添加功能码
request.append(data); // 添加数据部分
// 计算并添加 CRC 校验码
uint16_t crc = calculateCRC(request);
request.append(static_cast<char>(crc & 0xFF)); // CRC 低字节
request.append(static_cast<char>((crc >> 8) & 0xFF)); // CRC 高字节
return request; // 返回完整的请求帧
}
// 发送请求到串口
bool ModbusRTU::writeRequest(const QByteArray &request) {
if (!serialPort->isOpen()) {
emit errorOccurred("Serial port not open");
return false;
}
// 清空缓冲区
serialPort->flush();
serialPort->clear();
clearBuffers(); // 发送前清理缓冲区
// 发送请求
qint64 bytesWritten = serialPort->write(request);
if (bytesWritten != request.size()) {
emit errorOccurred(QString("Error writing to port: %1").arg(serialPort->errorString()));
return false;
}
// 等待数据发送完成
if (!serialPort->waitForBytesWritten(1000)) { // 等待发送完成
emit errorOccurred(QString("Timeout writing to port: %1").arg(serialPort->errorString()));
return false;
}
clearBuffers(); // 发送后清理缓冲区
return true;
}
// 验证响应是否正确
bool ModbusRTU::verifyResponse(const QByteArray &response, uint8_t functionCode) {
if (response.size() < 8) { // 响应长度过短
emit errorOccurred("Invalid response length");
return false;
}
// 检查设备地址是否匹配
if (response[0] != static_cast<char>(deviceAddress)) {
emit errorOccurred("Invalid device address in response");
return false;
}
// 检查功能码是否匹配或是否为异常代码
if (response[1] != static_cast<char>(functionCode + 0x80)) { // 不是异常代码
if (response[1] != static_cast<char>(functionCode)) { // 功能码不匹配
emit errorOccurred("Invalid function code in response");
return false;
}
}
// 验证 CRC 校验码
if (!verifyCRC(response)) {
emit errorOccurred("Invalid CRC in response");
return false;
}
emit registersWritten(true);
return true;
}
// 判断是否打开串口
bool ModbusRTU::isOpen() const {
return serialPort->isOpen();
}
// 清理串口缓冲区
void ModbusRTU::clearBuffers() {
if (serialPort->isOpen()) {
serialPort->clear(); // 清除输入和输出缓冲区
QThread::msleep(100); // 暂停 100 毫秒
}
}
// 关闭串口
void ModbusRTU::closePort() {
if (serialPort->isOpen()) {
serialPort->close();
}
}
改进点
数据接收超时处理:
在 readResponseTimeout 方法中,使用 QElapsedTimer 精准控制超时时间,确保在数据接收不完整时不会提前退出。
数据完整性校验:
在 parseReadResponse 方法中,检查数据长度是否足够,避免因数据不完整导致程序崩溃。
错误处理与信号:
通过 emit errorOccurred 发送错误信号,用户可以在界面中捕获并处理错误,提高用户体验。
代码健壮性:
增加对特殊场景的处理,如数据不足、CRC校验失败等,确保程序在各种环境下都能稳定运行。
使用示例
cpp
复制
void MainWindow::on_btnRead_clicked()
{
if (!m_valve->isOpen()) {
QMessageBox::warning(this, "警告", "请先打开串口连接");
return;
}
std::vector<uint16_t> values;
if (m_valve->readHoldingRegisters(0x0001, 1, values)) {
ui->txtLog->append(QString("当前压力:%1 kPa").arg(values[0]));
ui->txtRead->setText(QString::number(values[0]));
} else {
ui->txtLog->append("读取失败,请重试");
}
}
错误排查
串口参数:
确保串口名称、波特率、数据位、停止位和校验位与设备一致。
如果设备手册中没有明确说明,可能需要尝试不同的波特率(如 19200、38400 等)。
设备地址:
确保设置的设备地址与设备的实际地址一致。
数据格式:
确保发送和接收的数据格式符合 Modbus RTU 协议规范。
硬件问题:
检查串口线是否连接牢固,尝试更换串口线或设备。
如果问题仍未解决,请提供更多详细信息,例如设备的手册、串口调试工具的输出等,我将进一步协助你排查问题。
modbus读取数据超时
于 2025-03-04 14:57:09 首次发布