本页内容

Modbus 自定义命令

该示例演示了如何处理自定义 Modbus 功能码。

该示例在单个应用程序中同时充当 Modbus 客户端和服务器。它们之间的连接通过 Modbus TCP 建立。该示例用于发送和接收自定义 Modbus 请求,并根据请求和响应调整其内部状态。

本示例的主要目的是提供一些演示代码,展示如何实现能够处理自定义 Modbus 功能码的 Modbus 客户端或 Modbus 服务器。

包含服务器和客户端寄存器表以及自定义功能代码 0x41(读取)和 0x42(写入)的 Modbus 自定义命令窗口

用户定义的 Modbus 功能码

Modbus协议支持1 - 127 (0x01 - 0x7F HEX) 范围内的功能码。大多数功能码都有明确的定义并公开记录在案。不过,有两个范围可用于用户自定义功能,分别是65 - 72 (0x41 - 48 HEX) 和100 - 110 (0x64 - 0x6E HEX) 。用户可以从这些范围内选择功能码,并以某种自定义方式进行处理。

本应用程序使用功能代码65 (0x41 HEX) 来实现CustomRead 命令,并使用功能代码66 (0x42 HEX) 来实现CustomWrite 命令。在此示例中,这些自定义命令仅用于读取和写入Holding Registers 。

发送自定义 Modbus 命令

自定义 Modbus 命令通过QModbusClient::sendRawRequest() 方法发送。该方法需要生成一个QModbusRequest 对象,其中包含所需的函数代码以及将编码为QByteArray 的参数列表:

    QModbusDataUnit unit {
        QModbusDataUnit::HoldingRegisters,
        ui->startAddress->value(),
        qMin(ui->numberOfRegisters->currentText().toUShort(),
            quint16(10 - ui->startAddress->value()))// 不要超过 10 个条目
    };

   for(qsizetype i= 0,total=unit.valueCount(); i<total;++i)
        unit.setValue(i, m_model->m_registers[i+unit.startAddress()]);

    constquint8 byteCount=quint8(unit.valueCount()* 2);
    QModbusRequest writeRequest {
        QModbusPdu::FunctionCode(ModbusClient::CustomWrite),
        quint16(unit.startAddress()),
        quint16(unit.valueCount()),byteCount,unit.values()
    };

QModbusClient::sendRawRequest() 方法会返回一个 `QModbusReply ` 对象,可用于像往常一样检查错误:

    if (auto *reply = m_client.sendRawRequest(writeRequest, ui->serverAddress->value())) {
        if (!reply->isFinished()) {
            connect(reply, &QModbusReply::finished, this, [this, reply]() {
                if (reply->error() == QModbusDevice::ProtocolError) {
                    statusBar()->showMessage(tr("Write response error: %1 (Modbus exception: 0x%2)")
                        .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16),
                        5000);
                } else if (reply->error() != QModbusDevice::NoError) {
                    statusBar()->showMessage(tr("Write response error: %1 (code: 0x%2)").
                        arg(reply->errorString()).arg(reply->error(), -1, 16), 5000);
                }

                reply->deleteLater();
                }
            );
        }
    } else {
        statusBar()->showMessage(tr("Write error: ") + m_client.errorString(), 5000);
    }

自定义 Modbus 服务器

自定义服务器继承自QModbusTcpServer 类。它重写了QModbusServer::processPrivateRequest() 方法。

class ModbusServer : public QModbusTcpServer
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(ModbusServer)

public:
    ModbusServer(QObject *parent = nullptr);

private:
    QModbusResponse processPrivateRequest(const QModbusPdu &request) override;
};

当接收到包含自定义功能代码的命令时,基类服务器会调用processPrivateRequest()方法。

自定义实现通过生成一个包含请求寄存器值的QModbusResponse 来处理CustomRead 命令:

    if (ModbusClient::CustomRead == request.functionCode()) {
        quint16 startAddress, count;
        request.decodeData(&startAddress, &count);

        QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, startAddress, count);
        if (!data(&unit)) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::IllegalDataAddress);
        }
        return QModbusResponse(request.functionCode(), startAddress, quint8(count * 2), unit.values());
    }

处理CustomWrite 命令包括从接收到的QModbusPdu 中提取新值、执行实际的值更新,以及返回包含实际已更新寄存器的QModbusResponse :

    if (ModbusClient::CustomWrite == request.functionCode()) {
        quint8 byteCount;
        quint16 startAddress, numberOfRegisters;
        request.decodeData(&startAddress, &numberOfRegisters, &byteCount);

        if (byteCount % 2 != 0) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::IllegalDataValue);
        }

        const QByteArray pduData = request.data().remove(0, WriteHeaderSize);
        QDataStream stream(pduData);

        QList<quint16> values;
        for (int i = 0; i < numberOfRegisters; i++) {
            quint16 tmp;
            stream >> tmp;
            values.append(tmp);
        }

        if (!writeData({QModbusDataUnit::HoldingRegisters, startAddress, values})) {
            return QModbusExceptionResponse(request.functionCode(),
                QModbusExceptionResponse::ServerDeviceFailure);
        }

        return QModbusResponse(request.functionCode(), startAddress, numberOfRegisters);
    }

自定义 Modbus 客户端

该自定义客户端继承自QModbusTcpClient 类。它重写了QModbusClient::processPrivateResponse()方法。

class ModbusClient : public QModbusTcpClient
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(ModbusClient)

public:
    ModbusClient(QObject *parent = nullptr);

    static constexpr QModbusPdu::FunctionCode CustomRead {QModbusPdu::FunctionCode(0x41)};
    static constexpr QModbusPdu::FunctionCode CustomWrite {QModbusPdu::FunctionCode(0x42)};

private:
    bool processPrivateResponse(const QModbusResponse &response, QModbusDataUnit *data) override;
};

基类客户端通过调用processPrivateResponse()方法,使用自定义功能代码来处理服务器的响应。

自定义实现通过CustomRead 和CustomWrite 功能代码处理响应:

bool ModbusClient::processPrivateResponse(const QModbusResponse &response, QModbusDataUnit *data)
{
    if (!response.isValid())
        return QModbusClient::processPrivateResponse(response, data);

    if (CustomRead == response.functionCode())
        return collateBytes(response, data);

    if (CustomWrite == response.functionCode())
        return collateMultipleValues(response, data);
    return QModbusClient::processPrivateResponse(response, data);
}

CustomRead 响应的处理方式是解码提供的QModbusPdu ,并提取所请求寄存器的值:

static bool collateBytes(const QModbusPdu &response, QModbusDataUnit *data)
{
    if (response.dataSize() < MinimumReadResponseSize)
        return false;

    quint16 address; quint8 byteCount;
    response.decodeData(&address, &byteCount);

    if (byteCount % 2 != 0)
        return false;

    if (data) {
        QDataStream stream(response.data().remove(0, 3));

        QList<quint16> values;
        const quint8 itemCount = byteCount / 2;
        for (int i = 0; i < itemCount; i++) {
            quint16 tmp;
            stream >> tmp;
            values.append(tmp);
        }
        *data = {QModbusDataUnit::HoldingRegisters, address, values};
    }
    return true;
}

CustomWrite 的响应处理方式是直接验证响应参数:

static bool collateMultipleValues(const QModbusPdu &response, QModbusDataUnit *data)
{
    if (response.dataSize() != WriteResponseSize)
        return false;

    quint16 address, count;
    response.decodeData(&address, &count);

    if (count < 1 || count > 10)
        return false;

    if (data)
        *data = {QModbusDataUnit::HoldingRegisters, address, count};
    return true;
}

运行示例

您可以从以下位置运行该示例:

此示例无法与其他应用程序结合使用。一旦示例启动,它只能在应用程序内部交换自定义 Modbus 命令。客户端与服务器之间的所有交互均采用 Modbus TCP 协议。

示例项目 @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.