Qt下使用ModbusTcp通信协议进行PLC线圈/保持寄存器的读写(32位有符号数)
时间:2024-04-27 11:15:34 来源:网络cs 作者:往北 栏目:卖家故事 阅读:
系列文章目录
提示:这里是该系列文章的所有文章的目录
第一章:Qt下使用ModbusTcp通信协议进行PLC线圈/保持寄存器的读写(32位有符号数)
第二章:Qt下使用modbus-c库实现PLC线圈/保持寄存器的读写
文章目录
系列文章目录前言一、引入Modbus模块二、Modbus设备的连接三、各寄存器数据的读取四、各寄存器数据的写入五、示例完整代码总结
前言
本文主要讲述了使用Qt的Modbus模块来进行ModbusTcp的通信,实现对PLC的线圈寄存器和保持寄存器的读写,基于TCP/IP的Modbus协议的内容我就不做过多解释了,详见参考文章。在本文示例中采用QModbusTcpClient类作为Modbus客户端(主站),PLC作为从站,封装了一个自己的MyModbus类,希望可以帮助到大家,如有错误之处,欢迎大家批评指正。
项目效果
提示:以下是本篇文章正文内容,下面案例可供参考
一、引入Modbus模块
1.这里我实现了自己的MyModbus类的封装,使用了pri子模块的方式,也是方便日后进行此模块的复用
pri中引入Modbus模块:
MyModbus.pri
QT += serialbus serialport
MyModbus类中添加相关头文件
#include <QModbusTcpClient>#include <QModbusDataUnit>
二、Modbus设备的连接
1.ModbusTcp的连接只需要配置好连接参数IP+Port
//判断当前连接状态是否为断开状态if(myClient->state() != QModbusDevice::ConnectedState){ //配置ModbusTcp的连接参数IP+Port myClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter,ip); myClient->setConnectionParameter(QModbusDevice::NetworkPortParameter,port); myClient->connectDevice();}
三、各寄存器数据的读取
1.Modbus中有4种操作对象,这4种都能进行读取操作:线圈、离散输入、保持寄存器、输入寄存器
//读取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; }}
四、各寄存器数据的写入
1.Modbus中4种操作对象只有这2种能进行写入操作:线圈 、保持寄存器
//对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;}
五、示例完整代码
这里是示例项目的完整代码,包含pro主程序和MyModbus.pri子文件
1.ModbusTest.pro
QT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11#定义编译选项.QT_DEPRECATED_WARNINGS表示当Qt的某些功能被标记为过时的,那么编译器会发出警告.DEFINES += QT_DEPRECATED_WARNINGS#设置字符(MSCV编译器下防止中文乱码)contains( CONFIG,"msvc" ):QMAKE_CXXFLAGS += /source-charset:utf-8 /execution-charset:utf-8contains( CONFIG,"msvc" ):QMAKE_CFLAGS +=/source-charset:utf-8 /execution-charset:utf-8include (./MyModbus/MyModbus.pri)SOURCES += \ main.cpp \ widget.cppHEADERS += \ widget.hFORMS += \ widget.ui# Default rules for deployment.qnx: target.path = /tmp/$${TARGET}/binelse: unix:!android: target.path = /opt/$${TARGET}/bin!isEmpty(target.path): INSTALLS += target
2.MyModbus.pri
QT += serialbus serialportHEADERS += \ $$PWD/mymodbus.hSOURCES += \ $$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_OBJECTpublic: 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_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: 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.main.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
总结
基于Qt使用这个ModbusTcp协议进行通信,还是比较简单的,首先就是确定下寄存器的类型,直接使用相关的函数进行读写。需要注意的一点是在进行保持寄存器读写的时候,我们PLC某个地址上的数是个16位的数,所以在进行32位有符号数读写的时候,要考虑两个相邻地址的组合,文中就对此进行了处理,这种情况下PLC那边也别忘记进行相应的设置哈。
hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。
参考博客:
QT下的Modbus TCP 通讯
C++与PLC通过Modbus TCP协议进行PLC内部寄存器的值的读取/写入总结
本文链接:https://www.kjpai.cn/gushi/2024-04-27/162822.html,文章来源:网络cs,作者:往北,版权归作者所有,如需转载请注明来源和作者,否则将追究法律责任!
下一篇:返回列表