Qt中Modbus协议

一、Qt Modbus模块概述

Qt提供了Qt SerialBus模块来支持工业总线协议,包括Modbus协议。

主要类介绍

1. QModbusClient


QModbusClient
 是一个基类,用于表示 Modbus 客户端(主站),具体的网络协议由派生类(如 
QModbusTcpClient
 和 
QModbusRtuSerialPort
)实现。

方法 (Methods) 

下表列出了 
QModbusClient
 及 
QModbusDevice
 的主要方法。

方法 (函数签名) 所属类 描述

QModbusReply * sendReadRequest(const QModbusDataUnit &read, int serverAddress)
QModbusClient 发送一个读取请求。返回一个 
QModbusReply
 对象用于跟踪请求状态和结果。

QModbusReply * sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送一个单个写入请求(如写多个线圈或保持寄存器)。

QModbusReply * sendWriteRequest(QModbusRequest::WriteRegisterModel model, const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送一个特定类型的写入请求(例如,预置单个寄存器 0x06,预置多个寄存器 0x10)。

int numberOfRetries() const
QModbusClient 获取请求失败后的重试次数。

void setNumberOfRetries(int number)
QModbusClient 设置请求失败后的重试次数。

uint timeout() const
QModbusClient 获取等待响应的超时时间(毫秒)。

void setTimeout(uint newTimeout)
QModbusClient 设置等待响应的超时时间(毫秒)。


bool connectDevice()
 | QModbusDevice | 尝试连接并打开 Modbus 设备。这是一个异步操作。 |

void disconnectDevice()
 | QModbusDevice | 断开并关闭设备连接。 |

bool state() const
 | QModbusDevice | 返回设备当前状态 (
QModbusDevice::ConnectedState
 或 
QModbusDevice::UnconnectedState
)。 |

QModbusDevice::Error error() const
 | QModbusDevice | 返回最后发生的错误类型。 |

QString errorString() const
 | QModbusDevice | 返回最后发生错误的人类可读描述。 |

信号 (Signals) 

下表列出了 
QModbusClient
 及 
QModbusDevice
 的主要信号。

信号 (函数签名) 所属类 描述

void errorOccurred(QModbusDevice::Error error)
QModbusDevice 当设备发生错误时发出。

void stateChanged(QModbusDevice::State state)
QModbusDevice 当设备状态改变时发出(例如,从断开连接到已连接)。
关键属性 (Properties)

这些属性通常通过对应的 getter 和 setter 方法访问。

属性 类型 描述 访问函数

numberOfRetries

int
操作失败后重试的次数。
numberOfRetries()

setNumberOfRetries()

timeout

uint
等待操作完成的最大毫秒数。
timeout()

setTimeout()

connectionParameter

QVariant
设置连接参数(如串口名、波特率等)。
setConnectionParameter()

connectionParameter()

2. QModbusServer


QModbusServer
 用于表示 Modbus 服务器(从站),它管理着内部的数据区(线圈、离散输入、保持寄存器、输入寄存器),并响应来自客户端的请求。

方法 (Methods) 

下表列出了 
QModbusServer
 及 
QModbusDevice
 的主要方法。

方法 (函数签名) 所属类 描述
数据操作相关

bool setData(QModbusDataUnit::RegisterType table, quint16 address, quint16 data)
QModbusServer 在指定的数据表中设置单个数据值。

bool setData(QModbusDataUnit::RegisterType table, quint16 address, const QVector<quint16> &data)
QModbusServer 在指定的数据表中设置多个数据值。

quint16 data(QModbusDataUnit::RegisterType table, quint16 address, bool *ok = nullptr) const
QModbusServer 从指定的数据表中获取单个数据值。

QVector<quint16> data(QModbusDataUnit::RegisterType table, quint16 address, quint16 number = 1) const
QModbusServer 从指定的数据表中获取多个数据值。
服务器配置相关

QModbusServer::Option option(QModbusServer::Option option) const
QModbusServer 获取一个服务器选项的值。

bool setOption(QModbusServer::Option option, const QVariant &value)
QModbusServer 设置一个服务器选项。

int serverAddress() const
QModbusServer 获取服务器的从站地址。

void setServerAddress(int serverAddress)
QModbusServer 设置服务器的从站地址。

bool isBroadcastReplyEnabled() const
QModbusServer (Qt 5.15+) 返回是否对广播请求发送回复。

void setBroadcastReplyEnabled(bool broadcastReply)
QModbusServer (Qt 5.15+) 设置是否对广播请求发送回复。
设备基础操作

bool connectDevice()
QModbusDevice 尝试连接并打开 Modbus 设备。

void disconnectDevice()
QModbusDevice 断开并关闭设备连接。

bool state() const
QModbusDevice 返回设备当前状态 (
ConnectedState
 或 
UnconnectedState
)。

QModbusDevice::Error error() const
QModbusDevice 返回最后发生的错误类型。

QString errorString() const
QModbusDevice 返回最后发生错误的人类可读描述。
信号 (Signals) 

下表列出了 
QModbusServer
 及 
QModbusDevice
 的主要信号。

信号 (函数签名) 所属类 描述
数据变化相关

void dataWritten(QModbusDataUnit::RegisterType table, quint16 address, quint16 size)
QModbusServer 当客户端写入数据到服务器时发出。这是最常用的信号,用于感知外部控制。

void dataRead(QModbusDataUnit::RegisterType table, quint16 address, quint16 size)
QModbusServer 当客户端从服务器读取数据时发出。
设备状态相关

void errorOccurred(QModbusDevice::Error error)
QModbusDevice 当设备发生错误时发出。

void stateChanged(QModbusDevice::State state)
QModbusDevice 当设备状态改变时发出(例如,从断开连接到已连接)。
关键属性 (Properties) 和选项 (Options) 
属性
属性 类型 描述 访问函数

serverAddress

int
服务器的从站地址(1-247)。
serverAddress()

setServerAddress()
服务器选项 (QModbusServer::Option)

通过 
setOption
 和 
option
 方法设置和获取。

选项 类型 描述

DiagnosticRegister

quint16
设置 Modbus 诊断寄存器值。

DeviceIdentification

QModbusServer::DeviceIdentificationMap
设置设备标识信息,用于响应功能码 0x2B 0x0E。

ExceptionStatusOffset

quint8
设置异常状态字节的偏移量(用于某些自定义功能)。

3. QModbusRtuSerialMaster


QModbusRtuSerialMaster
 是 
QModbusClient
 的一个具体实现,用于通过串行端口(RS-232/RS-485) 以 RTU(Remote Terminal Unit)编码方式与 Modbus 从设备进行通信。

方法 (Methods) 


QModbusRtuSerialMaster
 本身没有引入大量新的公有方法。它的主要功能继承自 
QModbusClient
 和 
QModbusDevice
。其特殊性在于通过 
QModbusDevice::setConnectionParameter
 来设置串口特有的参数。

方法 (函数签名) 所属类 描述
构造与连接

explicit QModbusRtuSerialMaster(QObject *parent = nullptr)
QModbusRtuSerialMaster 构造函数。

bool connectDevice()
QModbusDevice 尝试连接并打开串口。这是启动通信的关键步骤。

void disconnectDevice()
QModbusDevice 断开并关闭串口连接。
请求发送 (继承自 QModbusClient)

QModbusReply * sendReadRequest(const QModbusDataUnit &read, int serverAddress)
QModbusClient 发送读取请求(功能码 0x01, 0x02, 0x03, 0x04)。

QModbusReply * sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送写入请求(功能码 0x05, 0x06, 0x0F, 0x10)。

QModbusReply * sendWriteRequest(QModbusRequest::WriteRegisterModel model, const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送特定模式的写入请求。
参数配置 (继承自 QModbusClient)

int numberOfRetries() const
QModbusClient 获取操作失败后的重试次数。

void setNumberOfRetries(int number)
QModbusClient 设置操作失败后的重试次数。

uint timeout() const
QModbusClient 获取等待响应的超时时间(毫秒)。

void setTimeout(uint newTimeout)
QModbusClient 设置等待响应的超时时间(毫秒)。
设备状态 (继承自 QModbusDevice)

QModbusDevice::State state() const
QModbusDevice 返回设备当前状态(
ConnectedState
 或 
UnconnectedState
)。

QModbusDevice::Error error() const
QModbusDevice 返回最后发生的错误类型。

QString errorString() const
QModbusDevice 返回最后发生错误的人类可读描述。
连接参数设置 (关键)

void setConnectionParameter(int parameter, const QVariant &value)
QModbusDevice 用于设置所有连接参数,包括串口特有的参数
信号 (Signals) 

信号全部继承自父类,没有新增信号。

信号 (函数签名) 所属类 描述

void errorOccurred(QModbusDevice::Error error)
QModbusDevice 当设备发生错误时发出(如串口打开失败、通信超时等)。

void stateChanged(QModbusDevice::State state)
QModbusDevice 当设备连接状态改变时发出(例如,串口成功打开或断开)。
关键属性 (Properties) 和连接参数


QModbusRtuSerialMaster
 的核心在于通过 
setConnectionParameter
 设置以下串口参数。

连接参数 (Connection Parameters)
参数 (Parameter) 类型 描述 必需

QModbusDevice::SerialPortNameParameter

QString
串口名称(例如:
"COM1"

"COM3"
 on Windows;
"/dev/ttyS0"

"/dev/ttyUSB0"
 on Linux)。

QModbusDevice::SerialParityParameter

QSerialPort::Parity
校验位
QSerialPort::NoParity

QSerialPort::EvenParity

QSerialPort::OddParity
)。

QModbusDevice::SerialBaudRateParameter

qint32
波特率(例如:
9600

19200

115200
)。

QModbusDevice::SerialDataBitsParameter

QSerialPort::DataBits
数据位
QSerialPort::Data5

Data6

Data7

Data8
)。

QModbusDevice::SerialStopBitsParameter

QSerialPort::StopBits
停止位
QSerialPort::OneStop

QSerialPort::TwoStop
)。
RTU 特定参数
参数 类型 描述 默认值

interFrameDelay

int
帧间延时(微秒)。在 RTU 模式下,这是帧与帧之间必须保持静默的时间。对于某些特殊设备,可能需要调整此值。 计算得出

4. QModbusTcpClient


QModbusTcpClient
 是 
QModbusClient
 的一个具体实现,用于通过 TCP/IP 网络 与 Modbus TCP 服务器(从站)进行通信。它使用标准的 Socket 连接,无需关心底层的 TCP 包处理。

方法 (Methods) 


QModbusTcpClient
 本身没有引入大量新的公有方法。它的主要功能继承自 
QModbusClient
 和 
QModbusDevice
。其特殊性在于通过 
QModbusDevice::setConnectionParameter
 来设置网络特有的参数。

方法 (函数签名) 所属类 描述
构造与连接

explicit QModbusTcpClient(QObject *parent = nullptr)
QModbusTcpClient 构造函数。

bool connectDevice()
QModbusDevice 发起连接,连接到指定的服务器。这是一个异步操作。

void disconnectDevice()
QModbusDevice 断开与服务器的连接。
请求发送 (继承自 QModbusClient)

QModbusReply * sendReadRequest(const QModbusDataUnit &read, int serverAddress)
QModbusClient 发送读取请求(功能码 0x01, 0x02, 0x03, 0x04)。注意:在 Modbus TCP 中,
serverAddress
 通常被视为 Unit Identifier,许多设备将其忽略或固定为 0xFF/0x00,请查阅设备手册。

QModbusReply * sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送写入请求(功能码 0x05, 0x06, 0x0F, 0x10)。

QModbusReply * sendWriteRequest(QModbusRequest::WriteRegisterModel model, const QModbusDataUnit &write, int serverAddress)
QModbusClient 发送特定模式的写入请求。
参数配置 (继承自 QModbusClient)

int numberOfRetries() const
QModbusClient 获取操作失败后的重试次数。

void setNumberOfRetries(int number)
QModbusClient 设置操作失败后的重试次数。

uint timeout() const
QModbusClient 获取等待响应的超时时间(毫秒)。

void setTimeout(uint newTimeout)
QModbusClient 设置等待响应的超时时间(毫秒)。
设备状态 (继承自 QModbusDevice)

QModbusDevice::State state() const
QModbusDevice 返回设备当前状态(
ConnectedState
 或 
UnconnectedState
)。

QModbusDevice::Error error() const
QModbusDevice 返回最后发生的错误类型。

QString errorString() const
QModbusDevice 返回最后发生错误的人类可读描述。
连接参数设置 (关键)

void setConnectionParameter(int parameter, const QVariant &value)
QModbusDevice 用于设置所有连接参数,包括网络特有的参数
信号 (Signals) 

信号全部继承自父类,没有新增信号。

信号 (函数签名) 所属类 描述

void errorOccurred(QModbusDevice::Error error)
QModbusDevice 当连接或通信过程中发生错误时发出(如连接被拒绝、网络错误、超时等)。

void stateChanged(QModbusDevice::State state)
QModbusDevice 当连接状态改变时发出(例如,从”正在连接”变为”已连接”,或连接断开)。
关键属性 (Properties) 和连接参数


QModbusTcpClient
 的核心在于通过 
setConnectionParameter
 设置以下网络参数。

连接参数 (Connection Parameters)
参数 (Parameter) 类型 描述 必需 默认值

QModbusDevice::NetworkAddressParameter

QString
服务器的 IP 地址或主机名(例如:
"192.168.1.100"

"plc.example.com"
)。

QModbusDevice::NetworkPortParameter

int
服务器监听的端口号 502 (Modbus TCP 标准端口)

QModbusDevice::NetworkFrameTimeoutParameter

int
帧超时时间(微秒)。在 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使用


QModbusClient
 是 Qt 框架中用于 Modbus 通信的客户端类,作为主站向从站设备发送请求。

核心概念

角色:主站,发起请求

协议支持: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使用


QModbusServer
 是用于创建 Modbus 从站(服务器)的类,负责响应主站的请求。

核心概念

角色:从站(服务器),响应主站请求

数据存储:维护四种类型的数据表

自动响应:自动处理主站的读/写请求

基本使用步骤

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)

结束符
CR LF
 (0x0D 0x0A)

校验: 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设备配置";
    }
});
© 版权声明

相关文章

暂无评论

none
暂无评论...