QT中的Modbus

modbus在qt中分为服务器与客户端,再细分为串口和TCP,如图:

服务器与客户端:

客户端分为串口与TCP:

服务器分为串口与TCP:

添加相应模块:

添加相应类:

QModbusRtuSerialMaster/QModbusTcpClient/QModbusRtuSerialSlave/QModbusTcpServer

以QModbusRtuSerialMaster为例子:

构建对象:

初始化:

找不到就向父类找,这里是qmodbusdevice有一个定义链接参数的函数,自行查阅手册看下详细

这个函数形参分为对象和值,对象如图:

有串口的名字、校验位、波特率、数据位、停止位;还有网口的端口号与IP地址

值得自己添加。

#ifndef MODBUS_H
#define MODBUS_H

#include <QWidget>
#include <QModbusRtuSerialMaster>

namespace Ui {
class ModBus;
}

class ModBus : public QWidget
{
    Q_OBJECT

public:
    explicit ModBus(QWidget *parent = nullptr);
    ~ModBus();

private:
    Ui::ModBus *ui;
    QModbusRtuSerialMaster *m_modbusrtumaster;
    
};

#endif // MODBUS_H

初始化:

//初始化
    m_modbusrtumaster = new QModbusRtuSerialMaster(this);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM3");
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::EvenParity);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, 9600);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);

记得添加类:

#include <QModbusDevice>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTcpServer>
#include <QTcpSocket>

接下来就是读写,首先是数据单元:

modbus协议可以了解下,不知道的以为也可以用ai搜索下,以下为ai查询结果:

QModbusDataUnit 类在Qt的Modbus模块中是用来表示Modbus协议中不同类型数据单元的。以下是这几个枚举值的区别:

  1. QModbusDataUnit::Invalid

    • 这是一个无效的数据单元类型,通常用于表示未定义或错误的状态。
  2. QModbusDataUnit::DiscreteInputs

    • 表示Modbus协议中的离散输入寄存器(DI, Discrete Inputs)。这些寄存器用于读取远程设备上的数字输入状态,如开关、传感器等的开/关状态,每个输入占用1个字节,只能读取,不能写入。
  3. QModbusDataUnit::Coils

    • 表示Modbus协议中的线圈(Coils)。线圈类似于离散输入,但它们是可以读写的数字输出,也是用来表示开/关状态。每个线圈也占用1个字节。
  4. QModbusDataUnit::InputRegisters

    • 输入寄存器(IR, Input Registers)用于读取远程设备上的模拟量输入或更复杂的状态信息,每个寄存器通常占用2个字节,也是只读的。
  5. QModbusDataUnit::HoldingRegisters

    • 持续寄存器(HR, Holding Registers)既可以读取也可以写入,通常用于存储远程设备的设定值、计数值或其它需要保持的数据。每个寄存器也占用2个字节。

在Modbus协议中,这些数据单元类型对应于不同的功能码(Function Codes),例如:

  • 功能码02用于读取输入寄存器(InputRegisters)。
  • 功能码01用于读取线圈状态(Coils)。
  • 功能码04用于读取离散输入状态(DiscreteInputs)。
  • 功能码03和16用于读取和写入持续寄存器(HoldingRegisters)。

如果是用来与PLC通信的话,一般用的最多的是QModbusDataUnit::DiscreteInputs与QModbusDataUnit::InputRegisters。像西门子的PLC,模拟量一般留两个字节,开关量一个字节。

这里我们做测试选个持续的寄存器QModbusDataUnit::HoldingRegisters。

//链接设备
    if(m_modbusrtumaster->state() != QModbusRtuSerialMaster::ConnectedState)
    {
        m_modbusrtumaster->connectDevice();
    }
    
    //起始位
    QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, ui->lineEdit_startaddr->text().toInt(), 1);
    //发送请求响应
    QModbusReply *reply = m_modbusrtumaster->sendReadRequest(unit, 2);

然后就是响应了,数据内容在响应里面:

//发送请求响应
    QModbusReply *reply = m_modbusrtumaster->sendReadRequest(unit, 1);
    if(reply && !reply->isFinished())
    {
        connect(reply, &QModbusReply::finished, this, [=]()
        {
            QModbusReply *reply_1 = qobject_cast<QModbusReply *>(sender());//等同于reply_1 = reply
            if(reply_1)
            {
               QModbusDataUnit unit_1 = reply_1->result();//数据存入unit_1
               reply_1->deleteLater();
               
               QVector<quint16> data = unit_1.values();
               QString s;
               Q_FOREACH(quint16 i, data)
               {
                   s.append(QString::number(i)).append(" ");
               }
               ui->lineEdit_readwrite->setText(s);
            }
        });
    }

源码如下:

头文件:

#ifndef MODBUS_H
#define MODBUS_H

#include <QWidget>
#include <QModbusRtuSerialMaster>

namespace Ui {
class ModBus;
}

class ModBus : public QWidget
{
    Q_OBJECT

public:
    explicit ModBus(QWidget *parent = nullptr);
    ~ModBus();

private slots:
    void on_pushButton_read_clicked();

    void on_pushButton_write_clicked();

private:
    Ui::ModBus *ui;
    QModbusRtuSerialMaster *m_modbusrtumaster;

};

#endif // MODBUS_H

cpp:

#include "modbus.h"
#include "ui_modbus.h"

#include <QModbusDevice>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTcpServer>
#include <QTcpSocket>

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

    //初始化
    m_modbusrtumaster = new QModbusRtuSerialMaster(this);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM3");
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::EvenParity);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, 9600);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
    m_modbusrtumaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
}

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

void ModBus::on_pushButton_read_clicked()
{
    //链接设备
    if(m_modbusrtumaster->state() != QModbusRtuSerialMaster::ConnectedState)
    {
        m_modbusrtumaster->connectDevice();
    }

    //起始位
    QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, ui->lineEdit_startaddr->text().toInt(), 2);//读2个位
    //发送请求响应
    QModbusReply *reply = m_modbusrtumaster->sendReadRequest(unit, 1);
    if(reply && !reply->isFinished())
    {
        connect(reply, &QModbusReply::finished, this, [=]()
        {
            QModbusReply *reply_1 = qobject_cast<QModbusReply *>(sender());//等同于reply_1 = reply
            if(reply_1)
            {
               QModbusDataUnit unit_1 = reply_1->result();//数据存入unit_1
               reply_1->deleteLater();

               QVector<quint16> data = unit_1.values();
               QString s;
               Q_FOREACH(quint16 i, data)
               {
                   s.append(QString::number(i)).append(" ");
               }
               ui->lineEdit_readwrite->setText(s);
            }
        });
    }
}

void ModBus::on_pushButton_write_clicked()
{
    if(m_modbusrtumaster->state() != QModbusRtuSerialMaster::ConnectedState)
    {
        m_modbusrtumaster->connectDevice();
    }

    QVector<quint16> data;
    data << 1 << 89;
    QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters,
                         ui->lineEdit_startaddr->text().toInt(), data);
    m_modbusrtumaster->sendWriteRequest(unit, 1);
}

如果你想在Qt解析Modbus TCP输入状态的线圈,而不使用任何现成的Modbus库,可以按照以下步骤进行: 1. 建立一个TCP连接到Modbus服务器: ```cpp QTcpSocket socket; socket.connectToHost("192.168.1.1", 502); // 修改为你的Modbus服务器IP和端口 if (!socket.waitForConnected(5000)) { qDebug() << "连接到Modbus服务器失败: " << socket.errorString(); return; } ``` 2. 组装一个Modbus TCP请求消息: ```cpp QByteArray request; QDataStream stream(&request, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::BigEndian); // Modbus TCP使用大端字节序 stream << quint16(0x0000) << quint16(0x0000) << quint16(0x0006) << quint16(0x0001) << quint16(0x0000) << quint16(0x0001); // 第一个quint16(0x0000)是事务标识符,第二个quint16(0x0000)是协议标识符,第三个quint16(0x0006)是消息长度,第四个quint16(0x0001)是功能码,第五个quint16(0x0000)是起始地址,第六个quint16(0x0001)是线圈数量 ``` 3. 发送请求消息,并等待响应: ```cpp socket.write(request); if (!socket.waitForBytesWritten(5000)) { qDebug() << "写入Modbus请求消息失败: " << socket.errorString(); return; } QByteArray response; if (!socket.waitForReadyRead(5000)) { qDebug() << "等待Modbus响应消息失败: " << socket.errorString(); return; } response = socket.readAll(); ``` 4. 解析响应消息: ```cpp QDataStream responseStream(response); responseStream.setByteOrder(QDataStream::BigEndian); quint16 transactionIdentifier; quint16 protocolIdentifier; quint16 length; quint8 unitIdentifier; quint8 functionCode; quint8 byteCount; quint8 coilStatus; responseStream >> transactionIdentifier >> protocolIdentifier >> length >> unitIdentifier >> functionCode >> byteCount >> coilStatus; if (functionCode & 0x80) { // 如果功能码的最高位是1,表示响应包含了错误码 quint8 errorCode; responseStream >> errorCode; qDebug() << "Modbus响应错误码: " << errorCode; return; } // 解析线圈状态 for (int i = 0; i < byteCount; i++) { quint8 byte; responseStream >> byte; for (int j = 0; j < 8; j++) { qDebug() << "线圈" << (i * 8 + j) << "状态: " << (byte & (1 << j)); } } ``` 以上就是一个简单的Modbus TCP输入状态的线圈解析示例。注意,这只是一个基础示例,如果需要解析其他类型的Modbus数据,可能需要更复杂的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值