一、Qt Modbus模块概述
Qt提供了Qt SerialBus模块来支持工业总线协议,包括Modbus协议。
主要类介绍
1. QModbusClient
是一个基类,用于表示 Modbus 客户端(主站),具体的网络协议由派生类(如
QModbusClient 和
QModbusTcpClient)实现。
QModbusRtuSerialPort
方法 (Methods)
下表列出了 及
QModbusClient 的主要方法。
QModbusDevice
| 方法 (函数签名) | 所属类 | 描述 |
|---|---|---|
|
QModbusClient | 发送一个读取请求。返回一个 对象用于跟踪请求状态和结果。 |
|
QModbusClient | 发送一个单个写入请求(如写多个线圈或保持寄存器)。 |
|
QModbusClient | 发送一个特定类型的写入请求(例如,预置单个寄存器 0x06,预置多个寄存器 0x10)。 |
|
QModbusClient | 获取请求失败后的重试次数。 |
|
QModbusClient | 设置请求失败后的重试次数。 |
|
QModbusClient | 获取等待响应的超时时间(毫秒)。 |
|
QModbusClient | 设置等待响应的超时时间(毫秒)。 |
| | QModbusDevice | 尝试连接并打开 Modbus 设备。这是一个异步操作。 |
bool connectDevice()
| | QModbusDevice | 断开并关闭设备连接。 |
void disconnectDevice()
| | QModbusDevice | 返回设备当前状态 (
bool state() const 或
QModbusDevice::ConnectedState)。 |
QModbusDevice::UnconnectedState
| | QModbusDevice | 返回最后发生的错误类型。 |
QModbusDevice::Error error() const
| | QModbusDevice | 返回最后发生错误的人类可读描述。 |
QString errorString() const
信号 (Signals)
下表列出了 及
QModbusClient 的主要信号。
QModbusDevice
| 信号 (函数签名) | 所属类 | 描述 |
|---|---|---|
|
QModbusDevice | 当设备发生错误时发出。 |
|
QModbusDevice | 当设备状态改变时发出(例如,从断开连接到已连接)。 |
关键属性 (Properties)
这些属性通常通过对应的 getter 和 setter 方法访问。
| 属性 | 类型 | 描述 | 访问函数 |
|---|---|---|---|
|
|
操作失败后重试的次数。 | , |
|
|
等待操作完成的最大毫秒数。 | , |
|
|
设置连接参数(如串口名、波特率等)。 | , |
2. QModbusServer
用于表示 Modbus 服务器(从站),它管理着内部的数据区(线圈、离散输入、保持寄存器、输入寄存器),并响应来自客户端的请求。
QModbusServer
方法 (Methods)
下表列出了 及
QModbusServer 的主要方法。
QModbusDevice
| 方法 (函数签名) | 所属类 | 描述 |
|---|---|---|
| 数据操作相关 | ||
|
QModbusServer | 在指定的数据表中设置单个数据值。 |
|
QModbusServer | 在指定的数据表中设置多个数据值。 |
|
QModbusServer | 从指定的数据表中获取单个数据值。 |
|
QModbusServer | 从指定的数据表中获取多个数据值。 |
| 服务器配置相关 | ||
|
QModbusServer | 获取一个服务器选项的值。 |
|
QModbusServer | 设置一个服务器选项。 |
|
QModbusServer | 获取服务器的从站地址。 |
|
QModbusServer | 设置服务器的从站地址。 |
|
QModbusServer (Qt 5.15+) | 返回是否对广播请求发送回复。 |
|
QModbusServer (Qt 5.15+) | 设置是否对广播请求发送回复。 |
| 设备基础操作 | ||
|
QModbusDevice | 尝试连接并打开 Modbus 设备。 |
|
QModbusDevice | 断开并关闭设备连接。 |
|
QModbusDevice | 返回设备当前状态 ( 或 )。 |
|
QModbusDevice | 返回最后发生的错误类型。 |
|
QModbusDevice | 返回最后发生错误的人类可读描述。 |
信号 (Signals)
下表列出了 及
QModbusServer 的主要信号。
QModbusDevice
| 信号 (函数签名) | 所属类 | 描述 |
|---|---|---|
| 数据变化相关 | ||
|
QModbusServer | 当客户端写入数据到服务器时发出。这是最常用的信号,用于感知外部控制。 |
|
QModbusServer | 当客户端从服务器读取数据时发出。 |
| 设备状态相关 | ||
|
QModbusDevice | 当设备发生错误时发出。 |
|
QModbusDevice | 当设备状态改变时发出(例如,从断开连接到已连接)。 |
关键属性 (Properties) 和选项 (Options)
属性
| 属性 | 类型 | 描述 | 访问函数 |
|---|---|---|---|
|
|
服务器的从站地址(1-247)。 | , |
服务器选项 (QModbusServer::Option)
通过 和
setOption 方法设置和获取。
option
| 选项 | 类型 | 描述 |
|---|---|---|
|
|
设置 Modbus 诊断寄存器值。 |
|
|
设置设备标识信息,用于响应功能码 0x2B 0x0E。 |
|
|
设置异常状态字节的偏移量(用于某些自定义功能)。 |
3. QModbusRtuSerialMaster
是
QModbusRtuSerialMaster 的一个具体实现,用于通过串行端口(RS-232/RS-485) 以 RTU(Remote Terminal Unit)编码方式与 Modbus 从设备进行通信。
QModbusClient
方法 (Methods)
本身没有引入大量新的公有方法。它的主要功能继承自
QModbusRtuSerialMaster 和
QModbusClient。其特殊性在于通过
QModbusDevice 来设置串口特有的参数。
QModbusDevice::setConnectionParameter
| 方法 (函数签名) | 所属类 | 描述 |
|---|---|---|
| 构造与连接 | ||
|
QModbusRtuSerialMaster | 构造函数。 |
|
QModbusDevice | 尝试连接并打开串口。这是启动通信的关键步骤。 |
|
QModbusDevice | 断开并关闭串口连接。 |
| 请求发送 (继承自 QModbusClient) | ||
|
QModbusClient | 发送读取请求(功能码 0x01, 0x02, 0x03, 0x04)。 |
|
QModbusClient | 发送写入请求(功能码 0x05, 0x06, 0x0F, 0x10)。 |
|
QModbusClient | 发送特定模式的写入请求。 |
| 参数配置 (继承自 QModbusClient) | ||
|
QModbusClient | 获取操作失败后的重试次数。 |
|
QModbusClient | 设置操作失败后的重试次数。 |
|
QModbusClient | 获取等待响应的超时时间(毫秒)。 |
|
QModbusClient | 设置等待响应的超时时间(毫秒)。 |
| 设备状态 (继承自 QModbusDevice) | ||
|
QModbusDevice | 返回设备当前状态( 或 )。 |
|
QModbusDevice | 返回最后发生的错误类型。 |
|
QModbusDevice | 返回最后发生错误的人类可读描述。 |
| 连接参数设置 (关键) | ||
|
QModbusDevice | 用于设置所有连接参数,包括串口特有的参数。 |
信号 (Signals)
信号全部继承自父类,没有新增信号。
| 信号 (函数签名) | 所属类 | 描述 |
|---|---|---|
|
QModbusDevice | 当设备发生错误时发出(如串口打开失败、通信超时等)。 |
|
QModbusDevice | 当设备连接状态改变时发出(例如,串口成功打开或断开)。 |
关键属性 (Properties) 和连接参数
的核心在于通过
QModbusRtuSerialMaster 设置以下串口参数。
setConnectionParameter
连接参数 (Connection Parameters)
| 参数 (Parameter) | 类型 | 描述 | 必需 |
|---|---|---|---|
|
|
串口名称(例如:, on Windows;, on Linux)。 |
是 |
|
|
校验位(, , )。 |
是 |
|
|
波特率(例如:, , )。 |
是 |
|
|
数据位(, , , )。 |
是 |
|
|
停止位(, )。 |
是 |
RTU 特定参数
| 参数 | 类型 | 描述 | 默认值 |
|---|---|---|---|
|
|
帧间延时(微秒)。在 RTU 模式下,这是帧与帧之间必须保持静默的时间。对于某些特殊设备,可能需要调整此值。 | 计算得出 |
4. QModbusTcpClient
是
QModbusTcpClient 的一个具体实现,用于通过 TCP/IP 网络 与 Modbus TCP 服务器(从站)进行通信。它使用标准的 Socket 连接,无需关心底层的 TCP 包处理。
QModbusClient
方法 (Methods)
本身没有引入大量新的公有方法。它的主要功能继承自
QModbusTcpClient 和
QModbusClient。其特殊性在于通过
QModbusDevice 来设置网络特有的参数。
QModbusDevice::setConnectionParameter
| 方法 (函数签名) | 所属类 | 描述 |
|---|---|---|
| 构造与连接 | ||
|
QModbusTcpClient | 构造函数。 |
|
QModbusDevice | 发起连接,连接到指定的服务器。这是一个异步操作。 |
|
QModbusDevice | 断开与服务器的连接。 |
| 请求发送 (继承自 QModbusClient) | ||
|
QModbusClient | 发送读取请求(功能码 0x01, 0x02, 0x03, 0x04)。注意:在 Modbus TCP 中, 通常被视为 Unit Identifier,许多设备将其忽略或固定为 0xFF/0x00,请查阅设备手册。 |
|
QModbusClient | 发送写入请求(功能码 0x05, 0x06, 0x0F, 0x10)。 |
|
QModbusClient | 发送特定模式的写入请求。 |
| 参数配置 (继承自 QModbusClient) | ||
|
QModbusClient | 获取操作失败后的重试次数。 |
|
QModbusClient | 设置操作失败后的重试次数。 |
|
QModbusClient | 获取等待响应的超时时间(毫秒)。 |
|
QModbusClient | 设置等待响应的超时时间(毫秒)。 |
| 设备状态 (继承自 QModbusDevice) | ||
|
QModbusDevice | 返回设备当前状态( 或 )。 |
|
QModbusDevice | 返回最后发生的错误类型。 |
|
QModbusDevice | 返回最后发生错误的人类可读描述。 |
| 连接参数设置 (关键) | ||
|
QModbusDevice | 用于设置所有连接参数,包括网络特有的参数。 |
信号 (Signals)
信号全部继承自父类,没有新增信号。
| 信号 (函数签名) | 所属类 | 描述 |
|---|---|---|
|
QModbusDevice | 当连接或通信过程中发生错误时发出(如连接被拒绝、网络错误、超时等)。 |
|
QModbusDevice | 当连接状态改变时发出(例如,从”正在连接”变为”已连接”,或连接断开)。 |
关键属性 (Properties) 和连接参数
的核心在于通过
QModbusTcpClient 设置以下网络参数。
setConnectionParameter
连接参数 (Connection Parameters)
| 参数 (Parameter) | 类型 | 描述 | 必需 | 默认值 |
|---|---|---|---|---|
|
|
服务器的 IP 地址或主机名(例如:, )。 |
是 | – |
|
|
服务器监听的端口号。 | 否 | 502 (Modbus TCP 标准端口) |
|
|
帧超时时间(微秒)。在 TCP 模式下,这是接收一个完整 Modbus 帧的最大等待时间。通常不需要修改。 | 否 | 计算得出 |
Qt Modbus 模块的各个类关系图
类关系
text
QObject
│
├── QModbusDevice (抽象基类)
│ │
│ ├── QModbusClient (抽象基类)
│ │ │
│ │ ├── QModbusRtuSerialMaster
│ │ │
│ │ └── QModbusTcpClient
│ │
│ └── QModbusServer (抽象基类)
│ │
│ ├── QModbusRtuSerialSlave
│ │
│ └── QModbusTcpServer
│
├── QModbusReply
│
├── QModbusDataUnit
│
├── QModbusRequest
│
├── QModbusResponse
│
└── QModbusExceptionResponse
详细类关系说明
1. 核心设备类层次
text
QModbusDevice (所有Modbus设备的基类)
├── QModbusClient (主站/客户端基类)
│ ├── QModbusRtuSerialMaster (RTU串行主站)
│ └── QModbusTcpClient (TCP客户端)
│
└── QModbusServer (从站/服务器基类)
├── QModbusRtuSerialSlave (RTU串行从站)
└── QModbusTcpServer (TCP服务器)
2. 数据传输相关类
text
QModbusDataUnit (数据单元)
├── 包含: RegisterType (寄存器类型)
├── 包含: 起始地址、数据值
└── 被: QModbusClient、QModbusServer、QModbusReply 使用
3. 请求-响应处理流程
text
QModbusClient
↓ (发送请求)
QModbusRequest / QModbusDataUnit
↓ (返回)
QModbusReply
↓ (包含结果)
QModbusDataUnit / QModbusResponse
↓ (异常时)
QModbusExceptionResponse
典型通信流程
客户端通信流程
text
QModbusTcpClient/QModbusRtuSerialMaster
↓ connectDevice()
建立连接 → stateChanged(ConnectedState)
↓ sendReadRequest/sendWriteRequest()
创建 QModbusReply
↓ 请求完成
QModbusReply::finished() 信号
↓ 处理结果
reply->result() (QModbusDataUnit)
服务器通信流程
text
QModbusTcpServer/QModbusRtuSerialSlave
↓ connectDevice()
开始监听
↓ 客户端请求到达
自动处理请求
↓ 数据被写入
发出 dataWritten() 信号
↓ 应用程序响应
更新内部数据或执行操作
基本使用方法
在项目文件中添加
qmake
QT += serialbus
创建Modbus TCP客户端
cpp
#include <QModbusTcpClient>
#include <QModbusDataUnit>
// 创建TCP客户端
QModbusTcpClient *modbusDevice = new QModbusTcpClient(this);
// 设置连接参数
modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "127.0.0.1");//从站 IP 地址
// 连接设备
if (!modbusDevice->connectDevice()) {
qDebug() << "连接失败:" << modbusDevice->errorString();
}
创建Modbus RTU主站
cpp
#include <QModbusRtuSerialMaster>
QModbusRtuSerialMaster *modbusDevice = new QModbusRtuSerialMaster(this);
modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1");// Windows
// modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "/dev/ttyUSB0"); // Linux
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
if (!modbusDevice->connectDevice()) {
qDebug() << "连接失败:" << modbusDevice->errorString();
}
读取保持寄存器
cpp
void readHoldingRegisters()
{
if (!modbusDevice)
return;
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 10);
if (auto *reply = modbusDevice->sendReadRequest(readUnit, 1)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
for (uint i = 0; i < unit.valueCount(); i++) {
qDebug() << "寄存器" << unit.startAddress() + i
<< ":" << unit.value(i);
}
} else {
qDebug() << "读取错误:" << reply->errorString();
}
reply->deleteLater();
});
} else {
delete reply;
}
} else {
qDebug() << "读取请求失败";
}
}
写入单个寄存器
cpp
void writeSingleRegister(int address, quint16 value)
{
if (!modbusDevice)
return;
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, address, 1);
writeUnit.setValue(0, value);
if (auto *reply = modbusDevice->sendWriteRequest(writeUnit, 1)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() != QModbusDevice::NoError) {
qDebug() << "写入错误:" << reply->errorString();
} else {
qDebug() << "写入成功";
}
reply->deleteLater();
});
} else {
delete reply;
}
}
}
创建Modbus服务器
cpp
#include <QModbusTcpServer>
QModbusTcpServer *modbusServer = new QModbusTcpServer(this);
// 设置服务器参数
modbusServer->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
// 设置数据
QModbusDataUnitMap reg;
reg.insert(QModbusDataUnit::HoldingRegisters,
QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 0, 100));
modbusServer->setMap(reg);
// 启动服务器
if (!modbusServer->connectDevice()) {
qDebug() << "服务器启动失败:" << modbusServer->errorString();
}
错误处理
cpp
connect(modbusDevice, &QModbusClient::errorOccurred, [](QModbusDevice::Error error) {
qDebug() << "Modbus错误:" << error;
});
connect(modbusDevice, &QModbusClient::stateChanged, [](QModbusDevice::State state) {
qDebug() << "状态改变:" << state;
});
二、QModbusClient使用
是 Qt 框架中用于 Modbus 通信的客户端类,作为主站向从站设备发送请求。
QModbusClient
核心概念
角色:主站,发起请求
协议支持:Modbus TCP 和 Modbus RTU
异步操作:基于信号槽的异步通信
基本使用步骤
1. 包含头文件和模块
在 文件中:
.pro
pro
QT += core gui serialbus
在代码中:
cpp
#include <QModbusTcpClient> // TCP 客户端 #include <QModbusRtuSerialMaster> // RTU 客户端 #include <QModbusDataUnit> #include <QModbusReply> #include <QDebug>
2. 创建客户端实例
Modbus TCP 客户端:
cpp
QModbusTcpClient *modbusClient = new QModbusTcpClient(this); modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "192.168.1.100"); modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
Modbus RTU 客户端:
cpp
QModbusRtuSerialMaster *modbusClient = new QModbusRtuSerialMaster(this); modbusClient->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1"); modbusClient->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600); modbusClient->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8); modbusClient->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity); modbusClient->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
3. 配置连接参数
cpp
// 设置超时和重试次数
modbusClient->setTimeout(1000); // 1秒超时
modbusClient->setNumberOfRetries(3); // 重试3次
// 连接状态监控
connect(modbusClient, &QModbusClient::stateChanged, [](QModbusDevice::State state) {
qDebug() << "State changed:" << state;
});
// 错误处理
connect(modbusClient, &QModbusDevice::errorOccurred, [](QModbusDevice::Error error) {
qDebug() << "Error occurred:" << error;
});
4. 建立连接
cpp
if (!modbusClient->connectDevice()) {
qDebug() << "Connect failed:" << modbusClient->errorString();
return;
}
qDebug() << "Connected successfully!";
5. 发送读取请求
读取保持寄存器:
cpp
void readHoldingRegisters(QModbusClient *client, int slaveAddress)
{
// 创建数据单元:保持寄存器,起始地址0,读取10个寄存器
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 10);
// 发送读取请求
if (auto *reply = client->sendReadRequest(readUnit, slaveAddress)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
handleReadReply(reply);
});
} else {
reply->deleteLater(); // 立即删除已完成的回复
}
} else {
qDebug() << "Read request failed:" << client->errorString();
}
}
void handleReadReply(QModbusReply *reply)
{
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qDebug() << "Read successful - Start address:" << unit.startAddress();
for (int i = 0; i < unit.valueCount(); ++i) {
qDebug() << "Address" << unit.startAddress() + i << ":" << unit.value(i);
}
} else {
qDebug() << "Read error:" << reply->errorString();
}
reply->deleteLater(); // 重要:必须删除回复对象
}
读取线圈:
cpp
void readCoils(QModbusClient *client, int slaveAddress)
{
QModbusDataUnit readUnit(QModbusDataUnit::Coils, 0, 8); // 读取8个线圈
if (auto *reply = client->sendReadRequest(readUnit, slaveAddress)) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
for (int i = 0; i < unit.valueCount(); ++i) {
bool value = static_cast<bool>(unit.value(i));
qDebug() << "Coil" << unit.startAddress() + i << ":" << value;
}
}
reply->deleteLater();
});
}
}
6. 发送写入请求
写入单个寄存器:
cpp
void writeSingleRegister(QModbusClient *client, int slaveAddress)
{
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 0, QVector<quint16>{ 1234 });
if (auto *reply = client->sendWriteRequest(writeUnit, slaveAddress)) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
qDebug() << "Write successful!";
} else {
qDebug() << "Write error:" << reply->errorString();
}
reply->deleteLater();
});
} else {
qDebug() << "Write request failed:" << client->errorString();
}
}
写入多个线圈:
cpp
void writeMultipleCoils(QModbusClient *client, int slaveAddress)
{
QVector<quint16> values;
values << 1 << 0 << 1 << 1 << 0; // 5个线圈的值
QModbusDataUnit writeUnit(QModbusDataUnit::Coils, 0, values);
if (auto *reply = client->sendWriteRequest(writeUnit, slaveAddress)) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
qDebug() << "Multiple coils write successful!";
}
reply->deleteLater();
});
}
}
7. 完整示例
cpp
class ModbusManager : public QObject
{
Q_OBJECT
public:
ModbusManager(QObject *parent = nullptr) : QObject(parent) {
setupClient();
}
~ModbusManager() {
if (modbusClient) {
modbusClient->disconnectDevice();
delete modbusClient;
}
}
private:
void setupClient() {
// 创建 TCP 客户端
modbusClient = new QModbusTcpClient(this);
modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, "127.0.0.1");
modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
modbusClient->setTimeout(1000);
connect(modbusClient, &QModbusClient::stateChanged, this, &ModbusManager::onStateChanged);
connect(modbusClient, &QModbusDevice::errorOccurred, this, &ModbusManager::onErrorOccurred);
if (modbusClient->connectDevice()) {
qDebug() << "Modbus client connected";
}
}
void onStateChanged(QModbusDevice::State state) {
qDebug() << "Client state:" << state;
if (state == QModbusDevice::ConnectedState) {
// 连接成功后读取数据
readHoldingRegisters(1); // 从站地址1
}
}
void onErrorOccurred(QModbusDevice::Error error) {
qDebug() << "Modbus error:" << error << "-" << modbusClient->errorString();
}
void readHoldingRegisters(int slaveAddress) {
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 5);
if (auto *reply = modbusClient->sendReadRequest(readUnit, slaveAddress)) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
qDebug() << "Read" << unit.valueCount() << "registers:";
for (int i = 0; i < unit.valueCount(); ++i) {
qDebug() << " Register" << i << ":" << unit.value(i);
}
}
reply->deleteLater();
});
}
}
private:
QModbusClient *modbusClient = nullptr;
};
重要注意事项
内存管理: 对象必须在
QModbusReply 信号后调用
finished()
deleteLater()
错误处理:始终检查 和客户端错误状态
reply->error()
线程安全: 不是线程安全的
QModbusClient
地址映射:使用从0开始的地址(线圈00001 = 地址0)
连接状态:在发送请求前检查连接状态
常用数据类型
– 线圈(读/写)
QModbusDataUnit::Coils
– 离散输入(只读)
QModbusDataUnit::DiscreteInputs
– 输入寄存器(只读)
QModbusDataUnit::InputRegisters
– 保持寄存器(读/写)
QModbusDataUnit::HoldingRegisters
三、QModbusServer使用
是用于创建 Modbus 从站(服务器)的类,负责响应主站的请求。
QModbusServer
核心概念
角色:从站(服务器),响应主站请求
数据存储:维护四种类型的数据表
自动响应:自动处理主站的读/写请求
基本使用步骤
1. 包含头文件和模块
在 文件中:
.pro
pro
QT += core gui serialbus
在代码中:
cpp
#include <QModbusTcpServer> // TCP 服务器 #include <QModbusRtuSerialSlave> // RTU 服务器 #include <QModbusDataUnit> #include <QDebug>
2. 创建服务器实例
Modbus TCP 服务器:
cpp
QModbusTcpServer *modbusServer = new QModbusTcpServer(this); modbusServer->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
Modbus RTU 服务器:
cpp
QModbusRtuSerialSlave *modbusServer = new QModbusRtuSerialSlave(this); modbusServer->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1"); modbusServer->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600); modbusServer->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8); modbusServer->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity); modbusServer->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
3. 配置服务器参数
cpp
// 设置从站地址
modbusServer->setServerAddress(1);
// 设置数据映射(定义数据表大小)
QModbusDataUnitMap map;
map.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 100 }); // 100个线圈
map.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 100 }); // 100个离散输入
map.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 50 }); // 50个输入寄存器
map.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 50 }); // 50个保持寄存器
modbusServer->setMap(map);
// 设置超时
modbusServer->setTimeout(1000);
4. 初始化数据值
cpp
// 初始化线圈值
for (int i = 0; i < 10; ++i) {
modbusServer->setData(QModbusDataUnit::Coils, i, (i % 2 == 0)); // 交替设置 true/false
}
// 初始化保持寄存器
for (int i = 0; i < 10; ++i) {
modbusServer->setData(QModbusDataUnit::HoldingRegisters, i, i * 100);
}
// 初始化输入寄存器(模拟只读数据)
for (int i = 0; i < 10; ++i) {
modbusServer->setData(QModbusDataUnit::InputRegisters, i, i * 50);
}
// 设置离散输入
modbusServer->setData(QModbusDataUnit::DiscreteInputs, 0, true);
modbusServer->setData(QModbusDataUnit::DiscreteInputs, 1, false);
5. 连接信号槽监控请求
cpp
// 监控数据写入事件
connect(modbusServer, &QModbusServer::dataWritten,
this, &MyModbusServer::onDataWritten);
// 监控状态变化
connect(modbusServer, &QModbusServer::stateChanged,
this, &MyModbusServer::onStateChanged);
// 错误处理
connect(modbusServer, &QModbusDevice::errorOccurred,
this, &MyModbusServer::onErrorOccurred);
6. 启动服务器
cpp
if (!modbusServer->connectDevice()) {
qDebug() << "Server start failed:" << modbusServer->errorString();
return false;
}
qDebug() << "Modbus server started successfully!";
qDebug() << "Server address:" << modbusServer->serverAddress();
qDebug() << "Listening on port: 502"; // 对于 TCP
完整示例代码
Modbus TCP 服务器完整示例
cpp
class ModbusTcpServer : public QObject
{
Q_OBJECT
public:
ModbusTcpServer(QObject *parent = nullptr) : QObject(parent) {
setupServer();
}
~ModbusTcpServer() {
if (modbusServer) {
modbusServer->disconnectDevice();
delete modbusServer;
}
}
private slots:
void onDataWritten(QModbusDataUnit::RegisterType table, int address, int size) {
qDebug() << "Data written - Table:" << table
<< "Address:" << address << "Size:" << size;
// 读取被写入的数据
for (int i = address; i < address + size; ++i) {
quint16 value = modbusServer->data(table, i);
qDebug() << " Address" << i << "=" << value;
}
// 如果是保持寄存器被写入,可以触发相应操作
if (table == QModbusDataUnit::HoldingRegisters) {
handleHoldingRegisterWrite(address, size);
}
}
void onStateChanged(QModbusDevice::State state) {
qDebug() << "Server state changed:" << state;
if (state == QModbusDevice::ConnectedState) {
qDebug() << "Server is now connected and ready";
} else if (state == QModbusDevice::UnconnectedState) {
qDebug() << "Server disconnected";
}
}
void onErrorOccurred(QModbusDevice::Error error) {
qDebug() << "Server error:" << error << "-" << modbusServer->errorString();
}
private:
void setupServer() {
// 创建 TCP 服务器
modbusServer = new QModbusTcpServer(this);
// 设置连接参数
modbusServer->setConnectionParameter(QModbusDevice::NetworkPortParameter, 502);
// 设置服务器地址
modbusServer->setServerAddress(1);
// 设置数据映射
setupDataMap();
// 初始化数据
initializeData();
// 连接信号槽
connect(modbusServer, &QModbusServer::dataWritten,
this, &ModbusTcpServer::onDataWritten);
connect(modbusServer, &QModbusServer::stateChanged,
this, &ModbusTcpServer::onStateChanged);
connect(modbusServer, &QModbusDevice::errorOccurred,
this, &ModbusTcpServer::onErrorOccurred);
// 启动服务器
if (modbusServer->connectDevice()) {
qDebug() << "Modbus TCP Server started successfully on port 502";
} else {
qDebug() << "Failed to start server:" << modbusServer->errorString();
}
}
void setupDataMap() {
QModbusDataUnitMap map;
map.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 200 });
map.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 200 });
map.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 100 });
map.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 100 });
modbusServer->setMap(map);
}
void initializeData() {
// 初始化线圈
for (int i = 0; i < 20; i += 2) {
modbusServer->setData(QModbusDataUnit::Coils, i, true);
}
// 初始化离散输入
modbusServer->setData(QModbusDataUnit::DiscreteInputs, 0, true);
modbusServer->setData(QModbusDataUnit::DiscreteInputs, 1, false);
// 初始化输入寄存器(模拟传感器数据)
for (int i = 0; i < 20; ++i) {
modbusServer->setData(QModbusDataUnit::InputRegisters, i, 1000 + i);
}
// 初始化保持寄存器
for (int i = 0; i < 20; ++i) {
modbusServer->setData(QModbusDataUnit::HoldingRegisters, i, i * 10);
}
qDebug() << "Server data initialized";
}
void handleHoldingRegisterWrite(int address, int size) {
qDebug() << "Holding registers" << address << "to" << address + size - 1 << "were modified";
// 这里可以添加业务逻辑,比如:
// - 控制硬件设备
// - 更新内部状态
// - 触发其他操作
// 示例:如果地址0被修改,执行特定操作
if (address == 0) {
quint16 value = modbusServer->data(QModbusDataUnit::HoldingRegisters, 0);
qDebug() << "Command register set to:" << value;
executeCommand(value);
}
}
void executeCommand(quint16 command) {
// 根据命令值执行相应操作
switch (command) {
case 1:
qDebug() << "Execute: Start operation";
break;
case 2:
qDebug() << "Execute: Stop operation";
break;
case 3:
qDebug() << "Execute: Reset system";
break;
default:
qDebug() << "Execute: Unknown command" << command;
break;
}
}
private:
QModbusServer *modbusServer = nullptr;
};
Modbus RTU 服务器示例
cpp
class ModbusRtuServer : public QObject
{
Q_OBJECT
public:
bool startRtuServer(const QString &portName) {
modbusServer = new QModbusRtuSerialSlave(this);
// 设置串口参数
modbusServer->setConnectionParameter(QModbusDevice::SerialPortNameParameter, portName);
modbusServer->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
modbusServer->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
modbusServer->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
modbusServer->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
// 设置从站地址
modbusServer->setServerAddress(1);
// 设置数据映射
QModbusDataUnitMap map;
map.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 16 });
map.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 8 });
modbusServer->setMap(map);
// 初始化数据
modbusServer->setData(QModbusDataUnit::Coils, 0, true);
modbusServer->setData(QModbusDataUnit::HoldingRegisters, 0, 1234);
// 连接信号
connect(modbusServer, &QModbusServer::dataWritten,
this, &ModbusRtuServer::onDataWritten);
return modbusServer->connectDevice();
}
private slots:
void onDataWritten(QModbusDataUnit::RegisterType table, int address, int size) {
qDebug() << "RTU Server - Data written at address" << address;
}
private:
QModbusServer *modbusServer = nullptr;
};
数据访问方法
读取数据
cpp
// 读取单个值
quint16 value = modbusServer->data(QModbusDataUnit::HoldingRegisters, 0);
// 读取多个值
QVector<quint16> values;
for (int i = 0; i < 10; ++i) {
values.append(modbusServer->data(QModbusDataUnit::HoldingRegisters, i));
}
写入数据
cpp
// 写入单个值
modbusServer->setData(QModbusDataUnit::Coils, 0, true);
// 写入多个值
for (int i = 0; i < 5; ++i) {
modbusServer->setData(QModbusDataUnit::HoldingRegisters, i, i * 100);
}
重要注意事项
数据类型权限:
线圈:读/写
离散输入:只读
输入寄存器:只读
保持寄存器:读/写
地址范围:使用从0开始的地址
线程安全: 不是线程安全的
QModbusServer
性能考虑:对于高频数据更新,考虑使用 的重载版本批量更新
setData()
错误处理:始终检查 的返回值
connectDevice()
资源清理:在析构时调用
disconnectDevice()
四、Qt Modbus RTU 详细介绍
Modbus RTU 协议特点
物理层: RS-232/RS-485
数据传输: 二进制格式
校验: CRC16 校验
地址范围: 1-247
基本配置
项目配置
qmake
QT += serialbus serialport
创建RTU主站
cpp
#include <QModbusRtuSerialMaster>
#include <QSerialPort>
class ModbusRTUManager : public QObject
{
Q_OBJECT
private:
QModbusRtuSerialMaster *modbusDevice;
public:
ModbusRTUManager(QObject *parent = nullptr) : QObject(parent)
{
modbusDevice = new QModbusRtuSerialMaster(this);
setupConnection();
}
private:
void setupConnection()
{
// 设置连接参数
modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1");
modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::EvenParity);
modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
// 设置超时和重试
modbusDevice->setTimeout(1000);
modbusDevice->setNumberOfRetries(3);
// 连接信号
connect(modbusDevice, &QModbusClient::errorOccurred, this, &ModbusRTUManager::onErrorOccurred);
connect(modbusDevice, &QModbusClient::stateChanged, this, &ModbusRTUManager::onStateChanged);
}
};
完整的RTU主站实现
cpp
#include <QModbusRtuSerialMaster>
#include <QModbusDataUnit>
#include <QSerialPort>
#include <QDebug>
class ModbusRTUMaster : public QObject
{
Q_OBJECT
public:
explicit ModbusRTUMaster(QObject *parent = nullptr);
~ModbusRTUMaster();
bool connectDevice(const QString &portName,
QSerialPort::BaudRate baudRate = QSerialPort::Baud9600,
QSerialPort::DataBits dataBits = QSerialPort::Data8,
QSerialPort::Parity parity = QSerialPort::NoParity,
QSerialPort::StopBits stopBits = QSerialPort::OneStop);
void disconnectDevice();
bool isConnected() const;
// 读取功能码
void readCoils(int slaveAddress, int startAddress, int quantity);
void readDiscreteInputs(int slaveAddress, int startAddress, int quantity);
void readHoldingRegisters(int slaveAddress, int startAddress, int quantity);
void readInputRegisters(int slaveAddress, int startAddress, int quantity);
// 写入功能码
void writeSingleCoil(int slaveAddress, int address, bool value);
void writeSingleRegister(int slaveAddress, int address, quint16 value);
void writeMultipleRegisters(int slaveAddress, int startAddress, const QVector<quint16> &values);
signals:
void connectionStatusChanged(bool connected);
void dataRead(QModbusDataUnit data);
void errorOccurred(const QString &error);
private slots:
void onReadFinished();
void onWriteFinished();
private:
QModbusRtuSerialMaster *m_modbusDevice;
void setupConnections();
};
实现文件
cpp
ModbusRTUMaster::ModbusRTUMaster(QObject *parent)
: QObject(parent)
, m_modbusDevice(new QModbusRtuSerialMaster(this))
{
setupConnections();
}
ModbusRTUMaster::~ModbusRTUMaster()
{
disconnectDevice();
}
bool ModbusRTUMaster::connectDevice(const QString &portName,
QSerialPort::BaudRate baudRate,
QSerialPort::DataBits dataBits,
QSerialPort::Parity parity,
QSerialPort::StopBits stopBits)
{
if (m_modbusDevice->state() != QModbusDevice::UnconnectedState) {
disconnectDevice();
}
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, portName);
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudRate);
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits);
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, parity);
m_modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits);
m_modbusDevice->setTimeout(1000);
m_modbusDevice->setNumberOfRetries(3);
bool result = m_modbusDevice->connectDevice();
emit connectionStatusChanged(result);
return result;
}
void ModbusRTUMaster::disconnectDevice()
{
if (m_modbusDevice) {
m_modbusDevice->disconnectDevice();
}
}
bool ModbusRTUMaster::isConnected() const
{
return m_modbusDevice && (m_modbusDevice->state() == QModbusDevice::ConnectedState);
}
void ModbusRTUMaster::setupConnections()
{
connect(m_modbusDevice, &QModbusClient::errorOccurred, this, [this](QModbusDevice::Error error) {
emit errorOccurred(m_modbusDevice->errorString());
});
connect(m_modbusDevice, &QModbusClient::stateChanged, this, [this](QModbusDevice::State state) {
emit connectionStatusChanged(state == QModbusDevice::ConnectedState);
});
}
// 读取保持寄存器
void ModbusRTUMaster::readHoldingRegisters(int slaveAddress, int startAddress, int quantity)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, startAddress, quantity);
if (auto *reply = m_modbusDevice->sendReadRequest(readUnit, slaveAddress)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onReadFinished);
} else {
delete reply;
}
} else {
emit errorOccurred("读取请求发送失败");
}
}
// 写入多个寄存器
void ModbusRTUMaster::writeMultipleRegisters(int slaveAddress, int startAddress, const QVector<quint16> &values)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, startAddress, values);
if (auto *reply = m_modbusDevice->sendWriteRequest(writeUnit, slaveAddress)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusRTUMaster::onWriteFinished);
} else {
delete reply;
}
} else {
emit errorOccurred("写入请求发送失败");
}
}
void ModbusRTUMaster::onReadFinished()
{
auto *reply = qobject_cast<QModbusReply *>(sender());
if (!reply)
return;
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
emit dataRead(unit);
} else {
emit errorOccurred(reply->errorString());
}
reply->deleteLater();
}
void ModbusRTUMaster::onWriteFinished()
{
auto *reply = qobject_cast<QModbusReply *>(sender());
if (!reply)
return;
if (reply->error() != QModbusDevice::NoError) {
emit errorOccurred(reply->errorString());
}
reply->deleteLater();
}
使用示例
cpp
// 创建主站实例
ModbusRTUMaster master;
// 连接设备
if (master.connectDevice("COM3", QSerialPort::Baud19200,
QSerialPort::Data8,
QSerialPort::EvenParity,
QSerialPort::OneStop)) {
qDebug() << "连接成功";
}
// 读取寄存器
master.readHoldingRegisters(1, 0, 10); // 从站地址1,起始地址0,读取10个寄存器
// 写入寄存器
QVector<quint16> values = {100, 200, 300};
master.writeMultipleRegisters(1, 0, values);
// 处理读取到的数据
connect(&master, &ModbusRTUMaster::dataRead, [](QModbusDataUnit data) {
for (int i = 0; i < data.valueCount(); ++i) {
qDebug() << "地址" << data.startAddress() + i
<< ":" << data.value(i);
}
});
常用串口参数配置
cpp
// 不同设备的常见配置
struct SerialConfig {
QString portName;
QSerialPort::BaudRate baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::Parity parity;
QSerialPort::StopBits stopBits;
};
// 常见配置示例
SerialConfig commonConfigs[] = {
{"COM1", QSerialPort::Baud9600, QSerialPort::Data8, QSerialPort::NoParity, QSerialPort::OneStop},
{"COM1", QSerialPort::Baud19200, QSerialPort::Data8, QSerialPort::EvenParity, QSerialPort::OneStop},
{"COM1", QSerialPort::Baud38400, QSerialPort::Data8, QSerialPort::NoParity, QSerialPort::TwoStop}
};
错误处理建议
cpp
// 错误处理示例
connect(&master, &ModbusRTUMaster::errorOccurred, [](const QString &error) {
qDebug() << "Modbus错误:" << error;
// 根据错误类型进行相应处理
if (error.contains("timeout", Qt::CaseInsensitive)) {
// 超时处理
} else if (error.contains("connection", Qt::CaseInsensitive)) {
// 连接错误处理
}
});
五、Qt Modbus ASCII详细介绍
Modbus ASCII 协议特点
物理层: RS-232/RS-485
数据传输: ASCII 文本格式(十六进制ASCII字符)
起始符: (0x3A)
:
结束符: (0x0D 0x0A)
CR LF
校验: LRC 纵向冗余校验
地址范围: 0-247
Qt 中的 Modbus ASCII 支持
注意: Qt 官方 Modbus 模块主要支持 RTU 和 TCP,ASCII 模式需要自定义实现。
自定义 Modbus ASCII 实现
项目配置
qmake
QT += serialport core
Modbus ASCII 主站类头文件
cpp
#ifndef MODBUSASCIIMASTER_H
#define MODBUSASCIIMASTER_H
#include <QObject>
#include <QSerialPort>
#include <QTimer>
#include <QQueue>
class ModbusASCIIMaster : public QObject
{
Q_OBJECT
public:
explicit ModbusASCIIMaster(QObject *parent = nullptr);
~ModbusASCIIMaster();
// 连接管理
bool connectDevice(const QString &portName,
qint32 baudRate = 9600,
QSerialPort::DataBits dataBits = QSerialPort::Data8,
QSerialPort::Parity parity = QSerialPort::NoParity,
QSerialPort::StopBits stopBits = QSerialPort::OneStop);
void disconnectDevice();
bool isConnected() const;
// 读取功能
void readCoils(quint8 slaveAddress, quint16 startAddress, quint16 quantity);
void readDiscreteInputs(quint8 slaveAddress, quint16 startAddress, quint16 quantity);
void readHoldingRegisters(quint8 slaveAddress, quint16 startAddress, quint16 quantity);
void readInputRegisters(quint8 slaveAddress, quint16 startAddress, quint16 quantity);
// 写入功能
void writeSingleCoil(quint8 slaveAddress, quint16 address, bool value);
void writeSingleRegister(quint8 slaveAddress, quint16 address, quint16 value);
void writeMultipleRegisters(quint8 slaveAddress, quint16 startAddress, const QVector<quint16> &values);
// 配置
void setTimeout(int milliseconds);
void setRetryCount(int count);
signals:
void connectionStatusChanged(bool connected);
void dataReceived(quint8 slaveAddress, quint8 functionCode, const QVector<quint16> &data);
void errorOccurred(const QString &error);
void requestTimeout(quint8 slaveAddress, quint8 functionCode);
private slots:
void onReadyRead();
void onTimeout();
void onError(QSerialPort::SerialPortError error);
private:
// 串口通信
QSerialPort *m_serialPort;
QByteArray m_receiveBuffer;
// 超时和重试
QTimer *m_timer;
int m_timeout;
int m_retryCount;
int m_currentRetry;
// 请求队列
struct ModbusRequest {
quint8 slaveAddress;
quint8 functionCode;
QByteArray pdu; // Protocol Data Unit
QByteArray fullFrame;
};
QQueue<ModbusRequest> m_requestQueue;
ModbusRequest m_currentRequest;
// ASCII 编解码
QByteArray encodeFrame(quint8 slaveAddress, quint8 functionCode, const QByteArray &data);
bool decodeFrame(const QByteArray &frame, quint8 &slaveAddress, quint8 &functionCode, QByteArray &data);
// LRC 校验
quint8 calculateLRC(const QByteArray &data);
bool verifyLRC(const QByteArray &data, quint8 receivedLRC);
// PDU 构建
QByteArray buildReadPDU(quint8 functionCode, quint16 startAddress, quint16 quantity);
QByteArray buildWriteSinglePDU(quint8 functionCode, quint16 address, quint16 value);
QByteArray buildWriteMultiplePDU(quint16 startAddress, const QVector<quint16> &values);
// 响应处理
void processResponse(const QByteArray &frame);
void processReadResponse(quint8 slaveAddress, quint8 functionCode, const QByteArray &data);
void processWriteResponse(quint8 slaveAddress, quint8 functionCode, const QByteArray &data);
// 请求管理
void sendNextRequest();
void retryCurrentRequest();
void completeCurrentRequest(bool success);
};
#endif // MODBUSASCIIMASTER_H
实现文件
cpp
#include "ModbusASCIIMaster.h"
#include <QDebug>
#include <QThread>
ModbusASCIIMaster::ModbusASCIIMaster(QObject *parent)
: QObject(parent)
, m_serialPort(new QSerialPort(this))
, m_timer(new QTimer(this))
, m_timeout(1000)
, m_retryCount(3)
, m_currentRetry(0)
{
// 设置定时器
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &ModbusASCIIMaster::onTimeout);
// 连接串口信号
connect(m_serialPort, &QSerialPort::readyRead, this, &ModbusASCIIMaster::onReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &ModbusASCIIMaster::onError);
}
ModbusASCIIMaster::~ModbusASCIIMaster()
{
disconnectDevice();
}
bool ModbusASCIIMaster::connectDevice(const QString &portName, qint32 baudRate,
QSerialPort::DataBits dataBits,
QSerialPort::Parity parity,
QSerialPort::StopBits stopBits)
{
if (m_serialPort->isOpen()) {
disconnectDevice();
}
m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(baudRate);
m_serialPort->setDataBits(dataBits);
m_serialPort->setParity(parity);
m_serialPort->setStopBits(stopBits);
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (m_serialPort->open(QIODevice::ReadWrite)) {
m_receiveBuffer.clear();
emit connectionStatusChanged(true);
return true;
} else {
emit errorOccurred(QString("无法打开串口 %1: %2")
.arg(portName)
.arg(m_serialPort->errorString()));
return false;
}
}
void ModbusASCIIMaster::disconnectDevice()
{
if (m_serialPort->isOpen()) {
m_serialPort->close();
m_receiveBuffer.clear();
m_requestQueue.clear();
m_timer->stop();
emit connectionStatusChanged(false);
}
}
bool ModbusASCIIMaster::isConnected() const
{
return m_serialPort->isOpen();
}
// LRC 校验计算
quint8 ModbusASCIIMaster::calculateLRC(const QByteArray &data)
{
quint8 lrc = 0;
for (char byte : data) {
lrc += static_cast<quint8>(byte);
}
return static_cast<quint8>(-static_cast<qint8>(lrc));
}
bool ModbusASCIIMaster::verifyLRC(const QByteArray &data, quint8 receivedLRC)
{
return calculateLRC(data) == receivedLRC;
}
// ASCII 编码
QByteArray ModbusASCIIMaster::encodeFrame(quint8 slaveAddress, quint8 functionCode, const QByteArray &data)
{
// 构建 PDU
QByteArray pdu;
pdu.append(static_cast<char>(slaveAddress));
pdu.append(static_cast<char>(functionCode));
pdu.append(data);
// 计算 LRC
quint8 lrc = calculateLRC(pdu);
// 转换为 ASCII HEX
QByteArray asciiFrame = ":";
for (char byte : pdu) {
asciiFrame.append(QByteArray::number(static_cast<quint8>(byte), 16).right(2).toUpper());
}
asciiFrame.append(QByteArray::number(lrc, 16).right(2).toUpper());
asciiFrame.append("
");
return asciiFrame;
}
// ASCII 解码
bool ModbusASCIIMaster::decodeFrame(const QByteArray &frame, quint8 &slaveAddress,
quint8 &functionCode, QByteArray &data)
{
// 检查帧格式
if (frame.length() < 11 || // 最小帧长度 :AABBCCDDEE
!frame.startsWith(':') ||
!frame.endsWith("
")) {
return false;
}
// 提取 HEX 数据(去掉:和
)
QByteArray hexData = frame.mid(1, frame.length() - 3);
if (hexData.length() % 2 != 0) {
return false;
}
// HEX 转二进制
QByteArray binaryData;
for (int i = 0; i < hexData.length(); i += 2) {
bool ok;
quint8 byte = static_cast<quint8>(hexData.mid(i, 2).toInt(&ok, 16));
if (!ok) return false;
binaryData.append(static_cast<char>(byte));
}
// 分离数据和 LRC
if (binaryData.length() < 3) return false; // 至少需要地址+功能码+LRC
QByteArray messageData = binaryData.left(binaryData.length() - 1);
quint8 receivedLRC = static_cast<quint8>(binaryData.at(binaryData.length() - 1));
// 验证 LRC
if (!verifyLRC(messageData, receivedLRC)) {
return false;
}
// 提取地址和功能码
slaveAddress = static_cast<quint8>(messageData.at(0));
functionCode = static_cast<quint8>(messageData.at(1));
data = messageData.mid(2);
return true;
}
// 构建读取 PDU
QByteArray ModbusASCIIMaster::buildReadPDU(quint8 functionCode, quint16 startAddress, quint16 quantity)
{
QByteArray pdu;
pdu.append(static_cast<char>(startAddress >> 8)); // 地址高字节
pdu.append(static_cast<char>(startAddress & 0xFF)); // 地址低字节
pdu.append(static_cast<char>(quantity >> 8)); // 数量高字节
pdu.append(static_cast<char>(quantity & 0xFF)); // 数量低字节
return pdu;
}
// 构建写入单个寄存器 PDU
QByteArray ModbusASCIIMaster::buildWriteSinglePDU(quint8 functionCode, quint16 address, quint16 value)
{
QByteArray pdu;
pdu.append(static_cast<char>(address >> 8)); // 地址高字节
pdu.append(static_cast<char>(address & 0xFF)); // 地址低字节
pdu.append(static_cast<char>(value >> 8)); // 值高字节
pdu.append(static_cast<char>(value & 0xFF)); // 值低字节
return pdu;
}
// 构建写入多个寄存器 PDU
QByteArray ModbusASCIIMaster::buildWriteMultiplePDU(quint16 startAddress, const QVector<quint16> &values)
{
QByteArray pdu;
pdu.append(static_cast<char>(startAddress >> 8));
pdu.append(static_cast<char>(startAddress & 0xFF));
pdu.append(static_cast<char>(values.size() >> 8));
pdu.append(static_cast<char>(values.size() & 0xFF));
pdu.append(static_cast<char>(values.size() * 2)); // 字节数
for (quint16 value : values) {
pdu.append(static_cast<char>(value >> 8));
pdu.append(static_cast<char>(value & 0xFF));
}
return pdu;
}
// 读取保持寄存器
void ModbusASCIIMaster::readHoldingRegisters(quint8 slaveAddress, quint16 startAddress, quint16 quantity)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
if (quantity < 1 || quantity > 125) {
emit errorOccurred("读取数量必须在1-125之间");
return;
}
QByteArray pdu = buildReadPDU(0x03, startAddress, quantity);
QByteArray frame = encodeFrame(slaveAddress, 0x03, pdu);
ModbusRequest request;
request.slaveAddress = slaveAddress;
request.functionCode = 0x03;
request.pdu = pdu;
request.fullFrame = frame;
m_requestQueue.enqueue(request);
if (m_requestQueue.size() == 1) {
sendNextRequest();
}
}
// 写入多个寄存器
void ModbusASCIIMaster::writeMultipleRegisters(quint8 slaveAddress, quint16 startAddress, const QVector<quint16> &values)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
if (values.isEmpty() || values.size() > 123) {
emit errorOccurred("写入数量必须在1-123之间");
return;
}
QByteArray pdu = buildWriteMultiplePDU(startAddress, values);
QByteArray frame = encodeFrame(slaveAddress, 0x10, pdu);
ModbusRequest request;
request.slaveAddress = slaveAddress;
request.functionCode = 0x10;
request.pdu = pdu;
request.fullFrame = frame;
m_requestQueue.enqueue(request);
if (m_requestQueue.size() == 1) {
sendNextRequest();
}
}
// 发送请求
void ModbusASCIIMaster::sendNextRequest()
{
if (m_requestQueue.isEmpty()) {
return;
}
m_currentRequest = m_requestQueue.dequeue();
m_currentRetry = 0;
qint64 bytesWritten = m_serialPort->write(m_currentRequest.fullFrame);
if (bytesWritten == m_currentRequest.fullFrame.size()) {
m_timer->start(m_timeout);
m_serialPort->flush();
} else {
emit errorOccurred("发送数据失败");
sendNextRequest();
}
}
// 超时处理
void ModbusASCIIMaster::onTimeout()
{
if (m_currentRetry < m_retryCount) {
m_currentRetry++;
retryCurrentRequest();
} else {
emit requestTimeout(m_currentRequest.slaveAddress, m_currentRequest.functionCode);
completeCurrentRequest(false);
}
}
// 重试当前请求
void ModbusASCIIMaster::retryCurrentRequest()
{
qint64 bytesWritten = m_serialPort->write(m_currentRequest.fullFrame);
if (bytesWritten == m_currentRequest.fullFrame.size()) {
m_timer->start(m_timeout);
m_serialPort->flush();
} else {
completeCurrentRequest(false);
}
}
// 完成当前请求
void ModbusASCIIMaster::completeCurrentRequest(bool success)
{
m_timer->stop();
if (!success) {
emit errorOccurred("请求失败");
}
sendNextRequest();
}
// 数据接收处理
void ModbusASCIIMaster::onReadyRead()
{
m_receiveBuffer.append(m_serialPort->readAll());
// 查找完整的帧
int startIndex = m_receiveBuffer.indexOf(':');
while (startIndex != -1) {
int endIndex = m_receiveBuffer.indexOf("
", startIndex);
if (endIndex != -1) {
// 提取完整帧
QByteArray frame = m_receiveBuffer.mid(startIndex, endIndex - startIndex + 2);
processResponse(frame);
// 移除已处理的数据
m_receiveBuffer.remove(0, endIndex + 2);
startIndex = m_receiveBuffer.indexOf(':');
} else {
break;
}
}
}
// 处理响应
void ModbusASCIIMaster::processResponse(const QByteArray &frame)
{
quint8 slaveAddress, functionCode;
QByteArray data;
if (!decodeFrame(frame, slaveAddress, functionCode, data)) {
emit errorOccurred("帧格式错误或LRC校验失败");
return;
}
// 检查异常响应
if (functionCode & 0x80) {
if (data.size() >= 1) {
quint8 exceptionCode = static_cast<quint8>(data.at(0));
emit errorOccurred(QString("从站异常响应: 功能码 %1, 异常码 %2")
.arg(functionCode & 0x7F)
.arg(exceptionCode));
}
completeCurrentRequest(false);
return;
}
// 正常响应处理
switch (functionCode) {
case 0x03: // 读保持寄存器
case 0x04: // 读输入寄存器
processReadResponse(slaveAddress, functionCode, data);
break;
case 0x10: // 写多个寄存器
processWriteResponse(slaveAddress, functionCode, data);
break;
default:
emit dataReceived(slaveAddress, functionCode, QVector<quint16>());
break;
}
completeCurrentRequest(true);
}
// 处理读取响应
void ModbusASCIIMaster::processReadResponse(quint8 slaveAddress, quint8 functionCode, const QByteArray &data)
{
if (data.size() < 1) {
emit errorOccurred("响应数据长度错误");
return;
}
quint8 byteCount = static_cast<quint8>(data.at(0));
if (data.size() != byteCount + 1) {
emit errorOccurred("响应数据长度不匹配");
return;
}
QVector<quint16> registers;
for (int i = 1; i < data.size(); i += 2) {
if (i + 1 < data.size()) {
quint16 value = (static_cast<quint8>(data.at(i)) << 8) | static_cast<quint8>(data.at(i + 1));
registers.append(value);
}
}
emit dataReceived(slaveAddress, functionCode, registers);
}
// 处理写入响应
void ModbusASCIIMaster::processWriteResponse(quint8 slaveAddress, quint8 functionCode, const QByteArray &data)
{
// 写入响应通常包含地址和数量
if (data.size() >= 4) {
QVector<quint16> result;
result.append((static_cast<quint8>(data.at(0)) << 8) | static_cast<quint8>(data.at(1)));
result.append((static_cast<quint8>(data.at(2)) << 8) | static_cast<quint8>(data.at(3)));
emit dataReceived(slaveAddress, functionCode, result);
}
}
// 错误处理
void ModbusASCIIMaster::onError(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError) {
emit errorOccurred(m_serialPort->errorString());
if (error == QSerialPort::ResourceError) {
disconnectDevice();
}
}
}
void ModbusASCIIMaster::setTimeout(int milliseconds)
{
m_timeout = milliseconds;
}
void ModbusASCIIMaster::setRetryCount(int count)
{
m_retryCount = count;
}
使用示例
cpp
// 创建 Modbus ASCII 主站
ModbusASCIIMaster master;
// 连接设备
if (master.connectDevice("COM1", 9600,
QSerialPort::Data7, // ASCII 模式常用 7 数据位
QSerialPort::EvenParity,
QSerialPort::OneStop)) {
qDebug() << "Modbus ASCII 连接成功";
}
// 设置超时和重试
master.setTimeout(2000);
master.setRetryCount(2);
// 读取保持寄存器
master.readHoldingRegisters(1, 0, 10); // 从站1,地址0开始读10个寄存器
// 写入多个寄存器
QVector<quint16> values = {100, 200, 300, 400};
master.writeMultipleRegisters(1, 0, values);
// 处理接收到的数据
connect(&master, &ModbusASCIIMaster::dataReceived,
[](quint8 slaveAddress, quint8 functionCode, const QVector<quint16> &data) {
qDebug() << "从站" << slaveAddress << "功能码" << functionCode << "数据:";
for (int i = 0; i < data.size(); ++i) {
qDebug() << "寄存器" << i << ":" << data[i];
}
});
// 错误处理
connect(&master, &ModbusASCIIMaster::errorOccurred, [](const QString &error) {
qDebug() << "Modbus ASCII 错误:" << error;
});
常用配置参数
cpp
// Modbus ASCII 典型配置
struct ASCIIConfig {
QString portName;
qint32 baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::Parity parity;
QSerialPort::StopBits stopBits;
};
// 常见配置
ASCIIConfig commonConfigs[] = {
{"COM1", 9600, QSerialPort::Data7, QSerialPort::EvenParity, QSerialPort::OneStop},
{"COM1", 19200, QSerialPort::Data7, QSerialPort::EvenParity, QSerialPort::OneStop},
{"COM1", 38400, QSerialPort::Data7, QSerialPort::EvenParity, QSerialPort::OneStop}
};
六、Qt Modbus TCP详细介绍
Modbus TCP 协议特点
传输层: TCP/IP
端口号: 默认 502
协议标识: 0x0000 (Modbus协议)
单元标识: 从站地址(兼容串行链路)
无校验: 依赖TCP的可靠性
Qt Modbus TCP 官方支持
项目配置
qmake
QT += serialbus
完整的 Modbus TCP 主站实现
头文件
cpp
#ifndef MODBUSTCPMASTER_H
#define MODBUSTCPMASTER_H
#include <QObject>
#include <QModbusTcpClient>
#include <QModbusDataUnit>
#include <QModbusReply>
#include <QTimer>
#include <QHostAddress>
class ModbusTcpMaster : public QObject
{
Q_OBJECT
public:
explicit ModbusTcpMaster(QObject *parent = nullptr);
~ModbusTcpMaster();
// 连接管理
bool connectToDevice(const QString &host, quint16 port = 502);
bool connectToDevice(const QHostAddress &host, quint16 port = 502);
void disconnectFromDevice();
bool isConnected() const;
// 设备配置
void setTimeout(int milliseconds);
void setRetryCount(int count);
void setUnitId(int unitId);
// 读取功能
void readCoils(int startAddress, int quantity, int unitId = -1);
void readDiscreteInputs(int startAddress, int quantity, int unitId = -1);
void readHoldingRegisters(int startAddress, int quantity, int unitId = -1);
void readInputRegisters(int startAddress, int quantity, int unitId = -1);
// 写入功能
void writeSingleCoil(int address, bool value, int unitId = -1);
void writeSingleRegister(int address, quint16 value, int unitId = -1);
void writeMultipleCoils(int startAddress, const QVector<bool> &values, int unitId = -1);
void writeMultipleRegisters(int startAddress, const QVector<quint16> &values, int unitId = -1);
// 设备信息读取
void readDeviceIdentification(int unitId = -1, quint8 objectId = 0x00);
signals:
void connectionStatusChanged(bool connected);
void dataRead(QModbusDataUnit data);
void writeCompleted(QModbusDataUnit data);
void errorOccurred(const QString &error);
void deviceInfoReceived(const QMap<quint8, QString> &deviceInfo);
private slots:
void onStateChanged(QModbusDevice::State state);
void onErrorOccurred(QModbusDevice::Error error);
void onReadReady();
void onWriteReady();
private:
QModbusTcpClient *m_modbusDevice;
int m_timeout;
int m_retryCount;
int m_defaultUnitId;
void setupConnections();
int getUnitId(int unitId) const;
};
#endif // MODBUSTCPMASTER_H
实现文件
cpp
#include "ModbusTcpMaster.h"
#include <QDebug>
#include <QModbusDeviceIdentification>
ModbusTcpMaster::ModbusTcpMaster(QObject *parent)
: QObject(parent)
, m_modbusDevice(new QModbusTcpClient(this))
, m_timeout(1000)
, m_retryCount(3)
, m_defaultUnitId(1)
{
setupConnections();
}
ModbusTcpMaster::~ModbusTcpMaster()
{
disconnectFromDevice();
}
void ModbusTcpMaster::setupConnections()
{
connect(m_modbusDevice, &QModbusClient::stateChanged,
this, &ModbusTcpMaster::onStateChanged);
connect(m_modbusDevice, &QModbusClient::errorOccurred,
this, &ModbusTcpMaster::onErrorOccurred);
}
bool ModbusTcpMaster::connectToDevice(const QString &host, quint16 port)
{
return connectToDevice(QHostAddress(host), port);
}
bool ModbusTcpMaster::connectToDevice(const QHostAddress &host, quint16 port)
{
if (m_modbusDevice->state() != QModbusDevice::UnconnectedState) {
disconnectFromDevice();
}
m_modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, host.toString());
m_modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
m_modbusDevice->setTimeout(m_timeout);
m_modbusDevice->setNumberOfRetries(m_retryCount);
bool result = m_modbusDevice->connectDevice();
if (!result) {
emit errorOccurred(QString("连接失败: %1").arg(m_modbusDevice->errorString()));
}
return result;
}
void ModbusTcpMaster::disconnectFromDevice()
{
if (m_modbusDevice) {
m_modbusDevice->disconnectDevice();
}
}
bool ModbusTcpMaster::isConnected() const
{
return m_modbusDevice && (m_modbusDevice->state() == QModbusDevice::ConnectedState);
}
void ModbusTcpMaster::setTimeout(int milliseconds)
{
m_timeout = milliseconds;
if (m_modbusDevice) {
m_modbusDevice->setTimeout(m_timeout);
}
}
void ModbusTcpMaster::setRetryCount(int count)
{
m_retryCount = count;
if (m_modbusDevice) {
m_modbusDevice->setNumberOfRetries(m_retryCount);
}
}
void ModbusTcpMaster::setUnitId(int unitId)
{
m_defaultUnitId = unitId;
}
int ModbusTcpMaster::getUnitId(int unitId) const
{
return (unitId == -1) ? m_defaultUnitId : unitId;
}
// 读取保持寄存器
void ModbusTcpMaster::readHoldingRegisters(int startAddress, int quantity, int unitId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
if (quantity < 1 || quantity > 125) {
emit errorOccurred("读取数量必须在1-125之间");
return;
}
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, startAddress, quantity);
int targetUnitId = getUnitId(unitId);
if (auto *reply = m_modbusDevice->sendReadRequest(readUnit, targetUnitId)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusTcpMaster::onReadReady);
} else {
delete reply;
}
} else {
emit errorOccurred("读取请求发送失败");
}
}
// 读取输入寄存器
void ModbusTcpMaster::readInputRegisters(int startAddress, int quantity, int unitId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
QModbusDataUnit readUnit(QModbusDataUnit::InputRegisters, startAddress, quantity);
int targetUnitId = getUnitId(unitId);
if (auto *reply = m_modbusDevice->sendReadRequest(readUnit, targetUnitId)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusTcpMaster::onReadReady);
} else {
delete reply;
}
} else {
emit errorOccurred("读取请求发送失败");
}
}
// 写入多个寄存器
void ModbusTcpMaster::writeMultipleRegisters(int startAddress, const QVector<quint16> &values, int unitId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
if (values.isEmpty() || values.size() > 123) {
emit errorOccurred("写入数量必须在1-123之间");
return;
}
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, startAddress, values);
int targetUnitId = getUnitId(unitId);
if (auto *reply = m_modbusDevice->sendWriteRequest(writeUnit, targetUnitId)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusTcpMaster::onWriteReady);
} else {
delete reply;
}
} else {
emit errorOccurred("写入请求发送失败");
}
}
// 写入单个寄存器
void ModbusTcpMaster::writeSingleRegister(int address, quint16 value, int unitId)
{
QVector<quint16> values;
values.append(value);
writeMultipleRegisters(address, values, unitId);
}
// 读取线圈
void ModbusTcpMaster::readCoils(int startAddress, int quantity, int unitId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
QModbusDataUnit readUnit(QModbusDataUnit::Coils, startAddress, quantity);
int targetUnitId = getUnitId(unitId);
if (auto *reply = m_modbusDevice->sendReadRequest(readUnit, targetUnitId)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusTcpMaster::onReadReady);
} else {
delete reply;
}
} else {
emit errorOccurred("读取线圈请求发送失败");
}
}
// 写入多个线圈
void ModbusTcpMaster::writeMultipleCoils(int startAddress, const QVector<bool> &values, int unitId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
QVector<quint16> coilValues;
for (bool value : values) {
coilValues.append(value ? 0xFF00 : 0x0000);
}
QModbusDataUnit writeUnit(QModbusDataUnit::Coils, startAddress, coilValues);
int targetUnitId = getUnitId(unitId);
if (auto *reply = m_modbusDevice->sendWriteRequest(writeUnit, targetUnitId)) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, &ModbusTcpMaster::onWriteReady);
} else {
delete reply;
}
} else {
emit errorOccurred("写入线圈请求发送失败");
}
}
// 读取设备信息
void ModbusTcpMaster::readDeviceIdentification(int unitId, quint8 objectId)
{
if (!isConnected()) {
emit errorOccurred("设备未连接");
return;
}
auto *reply = m_modbusDevice->sendReadDeviceIdentification(getUnitId(unitId), objectId);
if (reply) {
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
QModbusDeviceIdentification deviceInfo = reply->result();
QMap<quint8, QString> infoMap;
// 获取所有可用的对象ID
for (quint8 id = 0x00; id <= 0xFF; ++id) {
if (deviceInfo.contains(id)) {
infoMap[id] = deviceInfo[id];
}
}
emit deviceInfoReceived(infoMap);
} else {
emit errorOccurred(QString("读取设备信息失败: %1").arg(reply->errorString()));
}
reply->deleteLater();
});
} else {
delete reply;
}
} else {
emit errorOccurred("设备信息请求发送失败");
}
}
void ModbusTcpMaster::onReadReady()
{
auto *reply = qobject_cast<QModbusReply *>(sender());
if (!reply)
return;
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
emit dataRead(unit);
} else if (reply->error() == QModbusDevice::ProtocolError) {
QModbusPdu::ExceptionCode exceptionCode = reply->rawResult().exceptionCode();
emit errorOccurred(QString("Modbus协议错误: %1").arg(exceptionCode));
} else {
emit errorOccurred(QString("读取错误: %1").arg(reply->errorString()));
}
reply->deleteLater();
}
void ModbusTcpMaster::onWriteReady()
{
auto *reply = qobject_cast<QModbusReply *>(sender());
if (!reply)
return;
if (reply->error() == QModbusDevice::NoError) {
const QModbusDataUnit unit = reply->result();
emit writeCompleted(unit);
} else if (reply->error() == QModbusDevice::ProtocolError) {
QModbusPdu::ExceptionCode exceptionCode = reply->rawResult().exceptionCode();
emit errorOccurred(QString("Modbus协议错误: %1").arg(exceptionCode));
} else {
emit errorOccurred(QString("写入错误: %1").arg(reply->errorString()));
}
reply->deleteLater();
}
void ModbusTcpMaster::onStateChanged(QModbusDevice::State state)
{
bool connected = (state == QModbusDevice::ConnectedState);
emit connectionStatusChanged(connected);
if (state == QModbusDevice::ClosingState) {
qDebug() << "Modbus TCP连接正在关闭";
} else if (state == QModbusDevice::UnconnectedState) {
qDebug() << "Modbus TCP连接已断开";
}
}
void ModbusTcpMaster::onErrorOccurred(QModbusDevice::Error error)
{
if (error != QModbusDevice::NoError) {
emit errorOccurred(m_modbusDevice->errorString());
}
}
使用示例
基本使用
cpp
#include "ModbusTcpMaster.h"
#include <QCoreApplication>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
ModbusTcpMaster master;
// 连接信号
QObject::connect(&master, &ModbusTcpMaster::connectionStatusChanged,
[](bool connected) {
qDebug() << "连接状态:" << (connected ? "已连接" : "已断开");
});
QObject::connect(&master, &ModbusTcpMaster::dataRead,
[](QModbusDataUnit data) {
qDebug() << "读取到数据 - 类型:" << data.registerType()
<< "起始地址:" << data.startAddress()
<< "数量:" << data.valueCount();
for (int i = 0; i < data.valueCount(); ++i) {
qDebug() << "地址" << data.startAddress() + i
<< ":" << data.value(i);
}
});
QObject::connect(&master, &ModbusTcpMaster::errorOccurred,
[](const QString &error) {
qDebug() << "错误:" << error;
});
// 连接到设备
if (master.connectToDevice("192.168.1.100", 502)) {
qDebug() << "正在连接...";
}
// 延迟执行读写操作
QTimer::singleShot(2000, [&master]() {
if (master.isConnected()) {
// 读取保持寄存器
master.readHoldingRegisters(0, 10);
// 写入寄存器
QVector<quint16> values = {100, 200, 300, 400};
master.writeMultipleRegisters(10, values);
// 读取设备信息
master.readDeviceIdentification(1, 0x00);
}
});
return app.exec();
}
高级使用 – 多设备管理
cpp
class ModbusTcpManager : public QObject
{
Q_OBJECT
public:
explicit ModbusTcpManager(QObject *parent = nullptr) : QObject(parent) {}
void addDevice(const QString &name, const QString &host, quint16 port = 502)
{
auto *master = new ModbusTcpMaster(this);
m_devices[name] = master;
connect(master, &ModbusTcpMaster::dataRead, this,
[this, name](QModbusDataUnit data) {
emit deviceDataReceived(name, data);
});
connect(master, &ModbusTcpMaster::errorOccurred, this,
[this, name](const QString &error) {
emit deviceError(name, error);
});
master->connectToDevice(host, port);
}
void readFromDevice(const QString &name, int startAddress, int quantity)
{
if (m_devices.contains(name)) {
m_devices[name]->readHoldingRegisters(startAddress, quantity);
}
}
void writeToDevice(const QString &name, int startAddress, const QVector<quint16> &values)
{
if (m_devices.contains(name)) {
m_devices[name]->writeMultipleRegisters(startAddress, values);
}
}
signals:
void deviceDataReceived(const QString &deviceName, QModbusDataUnit data);
void deviceError(const QString &deviceName, const QString &error);
private:
QMap<QString, ModbusTcpMaster*> m_devices;
};
在Qt Widgets应用中使用
cpp
#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#include <QSpinBox>
#include <QLabel>
class ModbusTcpWindow : public QMainWindow
{
Q_OBJECT
public:
ModbusTcpWindow(QWidget *parent = nullptr) : QMainWindow(parent)
{
setupUI();
setupConnections();
}
private slots:
void onConnectClicked()
{
QString host = hostEdit->text();
quint16 port = static_cast<quint16>(portSpin->value());
if (master.connectToDevice(host, port)) {
logEdit->append("正在连接到 " + host + ":" + QString::number(port));
}
}
void onReadClicked()
{
int address = addressSpin->value();
int quantity = quantitySpin->value();
master.readHoldingRegisters(address, quantity);
logEdit->append(QString("读取请求: 地址=%1, 数量=%2").arg(address).arg(quantity));
}
void onDataRead(QModbusDataUnit data)
{
QString message = QString("读取响应: 类型=%1, 起始地址=%2, 数量=%3")
.arg(data.registerType())
.arg(data.startAddress())
.arg(data.valueCount());
logEdit->append(message);
for (int i = 0; i < data.valueCount(); ++i) {
logEdit->append(QString(" 地址 %1: %2")
.arg(data.startAddress() + i)
.arg(data.value(i)));
}
}
private:
ModbusTcpMaster master;
QLineEdit *hostEdit;
QSpinBox *portSpin;
QSpinBox *addressSpin;
QSpinBox *quantitySpin;
QPushButton *connectButton;
QPushButton *readButton;
QTextEdit *logEdit;
void setupUI()
{
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QVBoxLayout *layout = new QVBoxLayout(centralWidget);
// 连接设置
QHBoxLayout *connectLayout = new QHBoxLayout();
connectLayout->addWidget(new QLabel("主机:"));
hostEdit = new QLineEdit("127.0.0.1");
connectLayout->addWidget(hostEdit);
connectLayout->addWidget(new QLabel("端口:"));
portSpin = new QSpinBox();
portSpin->setRange(1, 65535);
portSpin->setValue(502);
connectLayout->addWidget(portSpin);
connectButton = new QPushButton("连接");
connectLayout->addWidget(connectButton);
layout->addLayout(connectLayout);
// 读写设置
QHBoxLayout *readLayout = new QHBoxLayout();
readLayout->addWidget(new QLabel("地址:"));
addressSpin = new QSpinBox();
addressSpin->setRange(0, 65535);
readLayout->addWidget(addressSpin);
readLayout->addWidget(new QLabel("数量:"));
quantitySpin = new QSpinBox();
quantitySpin->setRange(1, 125);
quantitySpin->setValue(10);
readLayout->addWidget(quantitySpin);
readButton = new QPushButton("读取");
readLayout->addWidget(readButton);
layout->addLayout(readLayout);
// 日志
logEdit = new QTextEdit();
logEdit->setReadOnly(true);
layout->addWidget(logEdit);
}
void setupConnections()
{
connect(connectButton, &QPushButton::clicked, this, &ModbusTcpWindow::onConnectClicked);
connect(readButton, &QPushButton::clicked, this, &ModbusTcpWindow::onReadClicked);
connect(&master, &ModbusTcpMaster::dataRead, this, &ModbusTcpWindow::onDataRead);
connect(&master, &ModbusTcpMaster::errorOccurred, this,
[this](const QString &error) {
logEdit->append("错误: " + error);
});
}
};
常见配置和错误处理
cpp
// 配置多个设备
ModbusTcpManager manager;
manager.addDevice("PLC1", "192.168.1.100", 502);
manager.addDevice("PLC2", "192.168.1.101", 502);
manager.addDevice("HMI", "192.168.1.102", 502);
// 错误处理策略
connect(&master, &ModbusTcpMaster::errorOccurred, [](const QString &error) {
if (error.contains("Connection refused", Qt::CaseInsensitive)) {
qDebug() << "连接被拒绝,检查目标设备是否运行";
} else if (error.contains("timeout", Qt::CaseInsensitive)) {
qDebug() << "请求超时,检查网络连接";
} else if (error.contains("Unknown", Qt::CaseInsensitive)) {
qDebug() << "未知错误,检查Modbus设备配置";
}
});



