Utilice el protocolo de comunicación ModbusTcp en Qt para leer y escribir bobinas/registros de retención de PLC (números con signo de 32 bits)


Prefacio

Este artículo describe principalmente el uso del módulo Modbus de Qt para comunicarse con ModbusTcp para realizar la lectura y escritura de los registros de bobina y registros de retención del PLC. No explicaré demasiado sobre el contenido del protocolo Modbus basado en TCP/IP. Consulte el artículo de referencia. para más detalles. . En el ejemplo de este artículo, la clase QModbusTcpClient se usa como cliente Modbus (estación maestra) y el PLC se usa como estación esclava. Encapsula su propia clase MyModbus. Espero que pueda ayudar a todos. Si hay algún error , puedes criticarlos y corregirlos.

Efecto del proyecto
Por favor agregue la descripción de la imagen.


提示:以下是本篇文章正文内容,下面案例可供参考

1. Introducción del módulo Modbus

1. Aquí he implementado mi propia encapsulación de clase MyModbus, utilizando el método del submódulo pri, que también facilita la reutilización de este módulo en el futuro. El
módulo Modbus se introduce en pri:
MyModbus.pri

QT   += serialbus serialport

Agregue archivos de encabezado relevantes a la clase MyModbus

#include <QModbusTcpClient>
#include <QModbusDataUnit>

2. Conexión de dispositivos Modbus

1. La conexión ModbusTcp solo necesita configurar los parámetros de conexión IP+Puerto

//判断当前连接状态是否为断开状态
if(myClient->state() != QModbusDevice::ConnectedState)
{
    
    
    //配置ModbusTcp的连接参数IP+Port
    myClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,ip);
    myClient->setConnectionParameter(QModbusDevice::NetworkPortParameter,port);
    myClient->connectDevice();
}

3. Lectura de datos de cada registro.

1. Hay 4 tipos de objetos de operación en Modbus, todos los cuales se pueden leer: bobinas, entradas discretas, registros de retención y registros de entrada.

//读取modbus设备各寄存器数据
//typeNum:1_线圈 2_离散输入 3_保持 4_输入
bool MyModbus::readModbusData(int typeNum,int startAdd,quint16 numbers)
{
    
    
    if(myClient->state() != QModbusDevice::ConnectedState)
    {
    
    
        return false;
    }

    //确定寄存器类型
    QModbusDataUnit ReadUnit;
    if(typeNum == 1)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::Coils,startAdd,numbers);
    }
    else if(typeNum == 2)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::DiscreteInputs,startAdd,numbers);
    }
    else if(typeNum == 3)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAdd,numbers);
    }
    else if(typeNum == 4)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::InputRegisters,startAdd,numbers);
    }
    else
    {
    
    
        LOGDEBUG<<"读取寄存器类型错误";
        return false;
    }
    LOGDEBUG<<"readModbusData typeNum:"<<typeNum;

    //多读
    if(auto *reply = myClient->sendReadRequest(ReadUnit,1))
    {
    
    
        if(!reply->isFinished())
        {
    
    
            if((typeNum == 1) || (typeNum == 2))
            {
    
    
                QObject::connect(reply,&QModbusReply::finished,this,&MyModbus::slot_readReadyCoils);   //读取线圈
            }
            if((typeNum == 3) || (typeNum == 4))
            {
    
    
                QObject::connect(reply,&QModbusReply::finished,this,&MyModbus::slot_readReadyRegisters);   //读取寄存器
            }
            //reply->deleteLater();
            return true;
        }
        else
        {
    
    
            reply->deleteLater();
            return false;
        }
    }
    else
    {
    
    
        LOGDEBUG<<"读取错误:" + myClient->errorString();
        return false;
    }
}

4. Escritura de datos en cada registro

1. Sólo se pueden escribir dos de los cuatro objetos operativos en Modbus: bobinas y registros de retención.

//对modbus设备各寄存器写入数据
//typeNum:1_线圈 2_保持 (这两类寄存器可读可写,其余的只读)
bool MyModbus::writeModbusData(int typeNum,int startAdd,int writeNum)
{
    
    
    if(myClient->state() != QModbusDevice::ConnectedState)
    {
    
    
        return false;
    }

    //确定寄存器类型
    QModbusDataUnit writeUnit;
    if(typeNum == 1)
    {
    
    
        writeUnit = QModbusDataUnit(QModbusDataUnit::Coils,startAdd,1);   //写入一个数据
        writeUnit.setValue(0,writeNum);

        //单写
        //bool ok;
        //quint16 hexData = writeData.toInt(&ok,16);   //转16进制
    }
    else if(typeNum == 2)
    {
    
    
        writeUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAdd,2);   //写入两个数据
        quint16 uData16[2] = {
    
    0};
        uData16[0] = writeNum & 0xffff;
        uData16[1] = (writeNum >> 16) & 0xffff;
        writeUnit.setValue(0,uData16[0]);
        writeUnit.setValue(1,uData16[1]);
        //LOGDEBUG<<"uData16[0]:"<<uData16[0]<<"   uData16[1]:"<<uData16[1]<<"   writeNum:"<<writeNum;
    }
    else
    {
    
    
        LOGDEBUG<<"写入寄存器类型错误";
        return false;
    }
    //LOGDEBUG<<"writeModbusData typeNum:"<<typeNum<<"   writeNum:"<<writeNum;
    if(auto *reply = myClient->sendWriteRequest(writeUnit,1))
    {
    
    
        if(!reply->isFinished())
        {
    
    
            connect(reply,&QModbusReply::finished,this,[reply]()
            {
    
    
                if(reply->error() == QModbusDevice::NoError)
                {
    
    
                    reply->deleteLater();
                    return true;
                }
                else
                {
    
    
                    LOGDEBUG<<"写入返回错误:"<<reply->error();
                    reply->deleteLater();
                    return false;
                }
            });
        }
        else
        {
    
    
            reply->deleteLater();
            return false;
        }
    }
    else
    {
    
    
        LOGDEBUG<<"写入错误:" + myClient->errorString();
        return false;
    }
    return true;
}

5. Ejemplo de código completo

Aquí está el código completo del proyecto de muestra, incluido el programa principal pro y el subarchivo MyModbus.pri
1.ModbusTest.pro

QT   += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

#定义编译选项.QT_DEPRECATED_WARNINGS表示当Qt的某些功能被标记为过时的,那么编译器会发出警告.
DEFINES += QT_DEPRECATED_WARNINGS

#设置字符(MSCV编译器下防止中文乱码)
contains( CONFIG,"msvc" ):QMAKE_CXXFLAGS += /source-charset:utf-8 /execution-charset:utf-8
contains( CONFIG,"msvc" ):QMAKE_CFLAGS +=/source-charset:utf-8 /execution-charset:utf-8

include (./MyModbus/MyModbus.pri)

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${
    
    TARGET}/bin
else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2.MyModbus.pri

QT       += serialbus serialport

HEADERS += \
    $$PWD/mymodbus.h

SOURCES += \
    $$PWD/mymodbus.cpp

3.mymodbus.h

#ifndef MYMODBUS_H
#define MYMODBUS_H

#include <QObject>
#include <QModbusTcpClient>
#include <QModbusDataUnit>
#include <QDebug>

#define LOGDEBUG qDebug()<<__FILE__<<__LINE__

class MyModbus : public QObject
{
    
    
    Q_OBJECT
public:
    explicit MyModbus(QObject *parent = nullptr);
    ~MyModbus();

    void initModbus();

    void connectToModbus(QString ip,int port);

    bool readModbusData(int typeNum,int startAdd,quint16 numbers);
    bool writeModbusData(int typeNum,int startAdd,int writeNum);

signals:
    void signal_stateChanged(bool flag);
    void signal_readCoils(QVector<quint16> vAllData);
    void signal_readRegisters(int resultNum);

private slots:
    void slot_stateChanged();
    void slot_readReadyCoils();
    void slot_readReadyRegisters();

private:
    QModbusTcpClient *myClient;

};
#endif // MYMODBUS_H

4.mymodbus.cpp

#include "mymodbus.h"

MyModbus::MyModbus(QObject *parent) : QObject(parent)
{
    
    
    this->initModbus();
}

MyModbus::~MyModbus()
{
    
    

}

//初始化
void MyModbus::initModbus()
{
    
    
    myClient = new QModbusTcpClient();
    //connect(myClient,SIGNAL(stateChanged()),this,SLOT(slot_stateChanged()));
    connect(myClient,&QModbusClient::stateChanged,this,&MyModbus::slot_stateChanged);
}

//连接到modbus设备
void MyModbus::connectToModbus(QString ip,int port)
{
    
    
    if(!myClient)
    {
    
    
        return;
    }

    //判断当前连接状态是否为断开状态
    if(myClient->state() != QModbusDevice::ConnectedState)
    {
    
    
        //配置ModbusTcp的连接参数IP+Port
        myClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,ip);
        myClient->setConnectionParameter(QModbusDevice::NetworkPortParameter,port);
        myClient->connectDevice();
    }
    //else
    //{
    
    
    //    myClient->disconnectDevice();
    //}
}

//读取modbus设备各寄存器数据
//typeNum:1_线圈 2_离散输入 3_保持 4_输入
bool MyModbus::readModbusData(int typeNum,int startAdd,quint16 numbers)
{
    
    
    if(myClient->state() != QModbusDevice::ConnectedState)
    {
    
    
        return false;
    }

    //确定寄存器类型
    QModbusDataUnit ReadUnit;
    if(typeNum == 1)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::Coils,startAdd,numbers);
    }
    else if(typeNum == 2)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::DiscreteInputs,startAdd,numbers);
    }
    else if(typeNum == 3)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAdd,numbers);
    }
    else if(typeNum == 4)
    {
    
    
        ReadUnit = QModbusDataUnit(QModbusDataUnit::InputRegisters,startAdd,numbers);
    }
    else
    {
    
    
        LOGDEBUG<<"读取寄存器类型错误";
        return false;
    }
    LOGDEBUG<<"readModbusData typeNum:"<<typeNum;

    //多读
    if(auto *reply = myClient->sendReadRequest(ReadUnit,1))
    {
    
    
        if(!reply->isFinished())
        {
    
    
            if((typeNum == 1) || (typeNum == 2))
            {
    
    
                QObject::connect(reply,&QModbusReply::finished,this,&MyModbus::slot_readReadyCoils);   //读取线圈
            }
            if((typeNum == 3) || (typeNum == 4))
            {
    
    
                QObject::connect(reply,&QModbusReply::finished,this,&MyModbus::slot_readReadyRegisters);   //读取寄存器
            }
            //reply->deleteLater();
            return true;
        }
        else
        {
    
    
            reply->deleteLater();
            return false;
        }
    }
    else
    {
    
    
        LOGDEBUG<<"读取错误:" + myClient->errorString();
        return false;
    }
}

//对modbus设备各寄存器写入数据
//typeNum:1_线圈 2_保持 (这两类寄存器可读可写,其余的只读)
bool MyModbus::writeModbusData(int typeNum,int startAdd,int writeNum)
{
    
    
    if(myClient->state() != QModbusDevice::ConnectedState)
    {
    
    
        return false;
    }

    //确定寄存器类型
    QModbusDataUnit writeUnit;
    if(typeNum == 1)
    {
    
    
        writeUnit = QModbusDataUnit(QModbusDataUnit::Coils,startAdd,1);   //写入一个数据
        writeUnit.setValue(0,writeNum);

        //单写
        //bool ok;
        //quint16 hexData = writeData.toInt(&ok,16);   //转16进制
    }
    else if(typeNum == 2)
    {
    
    
        writeUnit = QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAdd,2);   //写入两个数据
        quint16 uData16[2] = {
    
    0};
        uData16[0] = writeNum & 0xffff;
        uData16[1] = (writeNum >> 16) & 0xffff;
        writeUnit.setValue(0,uData16[0]);
        writeUnit.setValue(1,uData16[1]);
        //LOGDEBUG<<"uData16[0]:"<<uData16[0]<<"   uData16[1]:"<<uData16[1]<<"   writeNum:"<<writeNum;
    }
    else
    {
    
    
        LOGDEBUG<<"写入寄存器类型错误";
        return false;
    }
    //LOGDEBUG<<"writeModbusData typeNum:"<<typeNum<<"   writeNum:"<<writeNum;
    if(auto *reply = myClient->sendWriteRequest(writeUnit,1))
    {
    
    
        if(!reply->isFinished())
        {
    
    
            connect(reply,&QModbusReply::finished,this,[reply]()
            {
    
    
                if(reply->error() == QModbusDevice::NoError)
                {
    
    
                    reply->deleteLater();
                    return true;
                }
                else
                {
    
    
                    LOGDEBUG<<"写入返回错误:"<<reply->error();
                    reply->deleteLater();
                    return false;
                }
            });
        }
        else
        {
    
    
            reply->deleteLater();
            return false;
        }
    }
    else
    {
    
    
        LOGDEBUG<<"写入错误:" + myClient->errorString();
        return false;
    }
    return true;
}

//监听TCP连接的状态,若状态发生改变,发出对应的信号
void MyModbus::slot_stateChanged()
{
    
    
    LOGDEBUG<<myClient->state();
    if(myClient->state() == QModbusDevice::ConnectedState)
    {
    
    
        emit signal_stateChanged(true);
    }
    else if(myClient->state() == QModbusDevice::UnconnectedState)
    {
    
    
        emit signal_stateChanged(false);
    }
}

//接收到读取线圈/离散输入寄存器请求后执行的槽函数
void MyModbus::slot_readReadyCoils()
{
    
    
    QVector<quint16> vAllData;
    QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
    if(!reply)
    {
    
    
        LOGDEBUG<<"读取线圈/离散输入寄存器错误";
        return;
    }
    if(reply->error() == QModbusDevice::NoError)
    {
    
    
        const QModbusDataUnit unit = reply->result();
        vAllData = unit.values();
        emit signal_readCoils(vAllData);
    }
    else
    {
    
    
        LOGDEBUG<<"线圈/离散输入寄存器回复错误:"<<reply->error();
    }
    reply->deleteLater();
}

//接收到读取保持/输入寄存器请求后执行的槽函数
void MyModbus::slot_readReadyRegisters()
{
    
    
    QModbusReply *reply = qobject_cast<QModbusReply *>(sender());
    if(!reply)
    {
    
    
        LOGDEBUG<<"读取保持/输入寄存器错误";
        return;
    }
    if(reply->error() == QModbusDevice::NoError)
    {
    
    
        const QModbusDataUnit unit = reply->result();
        auto valueList = unit.values();
        int nSize = valueList.size();
        if(nSize == 2)
        {
    
    
            quint16 uData16[2] = {
    
    0};
            uData16[0] = valueList[0];
            uData16[1] = valueList[1];
            int resultNum = uData16[0] | (uData16[1] << 16);
            //LOGDEBUG<<"uData16[0]:"<<uData16[0]<<"   uData16[1]:"<<uData16[1]<<"   resultNum:"<<resultNum;
            emit signal_readRegisters(resultNum);
        }
        else
        {
    
    
            LOGDEBUG<<"保持寄存器返回数据错误,个数:"<<nSize;
        }
    }
    else
    {
    
    
        LOGDEBUG<<"保持/输入寄存器回复错误:"<<reply->error();
    }
    reply->deleteLater();
}

/*
//读取保持/输入寄存器数据的另一种方式,已废弃
//当前数据格式为大端模式,高位存低地址
//判断正负数,以高8位的16进制是否为f判断
int resultNum = 0;
if(QString::number(valueList[1],16).left(1) == "f")   //负数
{
    //判断是否小于-65535,高16位的10进制为65535
    if(valueList[1] == 65535)
    {
        resultNum = valueList[0] - 65536;
    }
    else
    {
        resultNum = (valueList[1] - 65535) * 65536 + (valueList[0] - 65536);
    }
}
else
{
    //判断是否大于65535,高16位的10进制大于0
    if(valueList[1] > 0)
    {
        resultNum = valueList[1] * 65536 + valueList[0];
    }
    else
    {
        resultNum = valueList[0];
    }
}
*/

5.widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
#include "MyModbus/mymodbus.h"

QT_BEGIN_NAMESPACE
namespace Ui {
    
     class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    
    
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void initWidget();

private slots:
    void slot_stateChanged(bool flag);
    void slot_readCoils(QVector<quint16> vAllData);
    void slot_readRegisters(int resultNum);

private slots:
    void on_pb_connect_clicked();
    void on_pb_readM_clicked();
    void on_pb_writeM_clicked();
    void on_pb_readD_clicked();
    void on_pb_writeD_clicked();

private:
    Ui::Widget *ui;

    MyModbus *m_myModsbus;   //MyModbus对象

};
#endif // WIDGET_H

6.widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    
    
    ui->setupUi(this);
    this->initWidget();
}

Widget::~Widget()
{
    
    
    delete ui;
}

void Widget::initWidget()
{
    
    
    //初始化MyModbus对象
    m_myModsbus = new MyModbus();
    connect(m_myModsbus,SIGNAL(signal_stateChanged(bool)),this,SLOT(slot_stateChanged(bool)));
    connect(m_myModsbus,SIGNAL(signal_readCoils(QVector<quint16>)),this,SLOT(slot_readCoils(QVector<quint16>)));
    connect(m_myModsbus,SIGNAL(signal_readRegisters(int)),this,SLOT(slot_readRegisters(int)));

}

void Widget::slot_stateChanged(bool flag)
{
    
    
    if(flag)
    {
    
    
        ui->lb_state->setText("连接成功");
        ui->te_show->appendPlainText(QTime::currentTime().toString("hh:mm:ss: ") + "连接成功");
        QMessageBox::warning(this,"警告","连接成功!");
    }
    else
    {
    
    
        ui->lb_state->setText("连接断开");
        ui->te_show->appendPlainText(QTime::currentTime().toString("hh:mm:ss: ") + "连接断开");
        QMessageBox::warning(this,"警告","连接断开!");
    }
}

void Widget::slot_readCoils(QVector<quint16> vAllData)
{
    
    
    LOGDEBUG<<"readCoils size:"<<vAllData.size();
    for(int i=0;i<vAllData.size();i++)
    {
    
    
        LOGDEBUG<<"i:"<<vAllData[i];

        ui->te_show->appendPlainText(QTime::currentTime().toString("hh:mm:ss: ") + "读取M区返回数据:" + QString::number(vAllData[i]));
        ui->le_dataM->setText(QString::number(vAllData[0]));
    }
}

void Widget::slot_readRegisters(int resultNum)
{
    
    
    LOGDEBUG<<"resultNum:"<<resultNum;

    ui->te_show->appendPlainText(QTime::currentTime().toString("hh:mm:ss: ") + "读取D区返回数据:" + QString::number(resultNum));
    ui->le_dataD->setText(QString::number(resultNum));
}

void Widget::on_pb_connect_clicked()
{
    
    
    QString ip = ui->le_ip->text();
    int port = ui->le_port->text().toInt();
    LOGDEBUG<<"ip:"<<ip<<"   port:"<<port;
    ui->te_show->appendPlainText(QTime::currentTime().toString("hh:mm:ss: ") + "ip:" + ip + " port:" + QString::number(port));

    //连接到modbus设备
    m_myModsbus->connectToModbus(ip,port);
}

void Widget::on_pb_readM_clicked()
{
    
    
    int startAdd = ui->le_addressM->text().toInt();
    LOGDEBUG<<"startAdd:"<<startAdd;

    if(!m_myModsbus->readModbusData(1,startAdd,1))
    {
    
    
        QMessageBox::warning(this,"警告","M区数据读取失败!");
    }
}

void Widget::on_pb_writeM_clicked()
{
    
    
    int startAdd = ui->le_addressM->text().toInt();
    int writeNum = ui->le_dataM->text().toInt();
    LOGDEBUG<<"startAdd:"<<startAdd<<"   writeNum:"<<writeNum;

    //单写
    if(!m_myModsbus->writeModbusData(1,startAdd,writeNum))
    {
    
    
        QMessageBox::warning(this,"警告","M区数据写入失败!");
    }
}

void Widget::on_pb_readD_clicked()
{
    
    
    int startAdd = ui->le_addressD->text().toInt();
    LOGDEBUG<<"startAdd:"<<startAdd;

    if(!m_myModsbus->readModbusData(3,startAdd,2))
    {
    
    
        QMessageBox::warning(this,"警告","D区数据读取失败!");
    }
}

void Widget::on_pb_writeD_clicked()
{
    
    
    int startAdd = ui->le_addressD->text().toInt();
    int writeNum = ui->le_dataD->text().toInt();;
    LOGDEBUG<<"startAdd:"<<startAdd<<"   writeNum:"<<writeNum;

    //进行写入寄存器数据的处理
    if(!m_myModsbus->writeModbusData(2,startAdd,writeNum))
    {
    
    
        QMessageBox::warning(this,"警告","D区数据写入失败!");
    }
}

7.principal.cpp

#include "widget.h"
#include <QApplication>
#include <QMutex>

//程序输出日志
void outputMessage(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
    
    
    static QMutex mutex;
    mutex.lock();

    //初始化log文件夹
    QString logFilePath = QCoreApplication::applicationDirPath() + "/LogFile/";
    QDir dstDir(logFilePath);
    if(!dstDir.exists())
    {
    
    
        if(!dstDir.mkpath(logFilePath))
        {
    
    
            LOGDEBUG<<"程序输出日志创建失败!";
        }
        else
        {
    
    
            LOGDEBUG<<"程序输出日志创建成功!";
        }
    }

    //获取输出内容
    QString debugMsg;
    if(type == QtDebugMsg)
    {
    
    
        QString debugDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
        debugMsg = QString("%1\n%2%3").arg(debugDateTime).arg(msg).arg(context.function);
    }

    //保存文件
    QString curDate = QDate::currentDate().toString("yyyyMMdd");
    QString logFile = logFilePath + "log_" + curDate + ".txt";
    QFile file(logFile);
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream textStream(&file);
    textStream << debugMsg << "\n\n";
    file.flush();
    file.close();

    mutex.unlock();
}

int main(int argc, char *argv[])
{
    
    
    QApplication a(argc, argv);
    qInstallMessageHandler(outputMessage);
    Widget w;
    w.show();
    return a.exec();
}

8.widget.ui
Por favor agregue la descripción de la imagen.


Resumir

Utilizar este protocolo ModbusTcp para comunicarse basado en Qt es relativamente sencillo, lo primero es determinar el tipo de registro y utilizar directamente las funciones relevantes para leer y escribir. Una cosa a tener en cuenta es que al leer y escribir registros de retención, el número en una determinada dirección de nuestro PLC es un número de 16 bits, por lo que al leer y escribir números con signo de 32 bits, se debe considerar la combinación de dos direcciones adyacentes. , este artículo se ha ocupado de esto, en este caso no olvide realizar los ajustes correspondientes en el lado del PLC.


Hola:
aprendamos juntos y progresemos juntos. Si aún tiene preguntas relacionadas, puede dejar un mensaje en el área de comentarios para discutirlo.

Blog de referencia:
Modbus TCP bajo comunicación QT
C++ y PLC a través del protocolo Modbus TCP para leer/escribir el valor del registro interno del PLC Resumen

Supongo que te gusta

Origin blog.csdn.net/XCJandLL/article/details/132251669
Recomendado
Clasificación