首页 > 其他分享 >Qt PLC Modbus通信

Qt PLC Modbus通信

时间:2023-09-26 09:45:11浏览次数:40  
标签:modbusDevice Qt setEnabled comboBox Modbus ui 串口 reply PLC

项目介绍
有一个项目需要PC和PLC通信,PLC通信协议是Modbus协议。前两天研究了一下,QT源码也有例程,不过源码读了有点懵,参考了别人的博客,实现了一个简单的通信Demo,测试可以对PLC内部寄存器和中间继电器读写。

软件版本和程序总览

QT:5.12.12
Kits:MSVC2017
PLC:信捷XD5

软件预览:

软件介绍

pro文件增加串口和modbus配置

QT       += core gui serialbus serialport

串口配置代码

主要对串口的参数进行配置,包括串口号、波特率、数据位、停止位、校验位、超时时间和重试次数。刷新按钮实现对PC串口的检测,使用QSerialPortInfo::availablePorts();获取串口信息,函数放到初始化中。

 1     QList<QSerialPortInfo> port_list = QSerialPortInfo::availablePorts();
 2     ui->comboBox_com->clear();
 3     foreach(const QSerialPortInfo & info,port_list)
 4     {
 5 //       qDebug() << info.portName(); //串口号 COM1 COM2-----
 6 //       qDebug() << info.systemLocation(); //串口存在的系统位置是个路径
 7 //       qDebug() << info.description();//返回串口描述字符串(如果可用);否则返回空字符串
 8 //       qDebug() << info.manufacturer();//返回串口制造商字符串(如果可用);否则返回空字符串
 9 //       qDebug() << info.serialNumber();//返回串口序列号字符串(如果可用);否则返回空字符串
10        ui->comboBox_com->addItem(info.portName());
11     }

用结构体存储串口设置参数

 1 //串口参数
 2 struct Settings {
 3     QString serialPort;
 4     int parity;
 5     int baud;
 6     int dataBits;
 7     int stopBits;
 8     int responseTime;
 9     int numberOfRetries;
10 };

每次打开串口时重新刷新参数

 1     //初始化串口参数信息
 2     m_settings.serialPort = ui->comboBox_com->currentText();
 3     m_settings.parity = ui->comboBox_parity->currentIndex();
 4     if (m_settings.parity > 0)
 5         m_settings.parity++;
 6     m_settings.baud = ui->comboBox_baud->currentText().toInt();
 7     m_settings.dataBits = ui->comboBox_databits->currentText().toInt();
 8     m_settings.stopBits = ui->comboBox_stopbits->currentText().toInt();
 9     m_settings.responseTime = ui->spinBox_timeout->value();
10     m_settings.numberOfRetries = ui->spinBox_retries->value();

打开串口

先获取设置的串口参数,然后实例化串口设备对象,这里使用串口线连接,使用QModbusRtuSerialMaster,最后设置参数连接串口,连接成功设置按钮状态。

 1     //获取串口数据
 2     getComParameter();
 3     if (modbusDevice)
 4     {
 5         modbusDevice->disconnectDevice();
 6         delete modbusDevice;
 7         modbusDevice = nullptr;
 8     }
 9 
10     modbusDevice = new QModbusRtuSerialMaster(this);
11     connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {
12         qDebug() << "modbus Error:" << modbusDevice->errorString();
13     });
14 
15     //配置串口参数
16     modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,m_settings.serialPort);
17     modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,m_settings.parity);
18     modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,m_settings.baud);
19     modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,m_settings.dataBits);
20     modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,m_settings.stopBits);
21     modbusDevice->setTimeout(m_settings.responseTime);          // 配置请求超时时间
22     modbusDevice->setNumberOfRetries(m_settings.numberOfRetries);     // 配置失败重试次数
23 
24     if(!modbusDevice->connectDevice())
25     {
26         qDebug()<<tr("Connect failed: %1").arg(modbusDevice->errorString());
27     }
28     else
29     {
30         qDebug() << "Modbus open Success!";
31         ui->pushButton_openCom->setEnabled(false);
32         ui->pushButton_closeCom->setEnabled(true);
33         ui->pushButton_refreshCom->setEnabled(false);
34 
35         ui->comboBox_com->setEnabled(false);
36         ui->comboBox_baud->setEnabled(false);
37         ui->comboBox_databits->setEnabled(false);
38         ui->comboBox_stopbits->setEnabled(false);
39         ui->comboBox_parity->setEnabled(false);
40         ui->spinBox_timeout->setEnabled(false);
41         ui->spinBox_retries->setEnabled(false);
42     }

关闭串口

断开串口连接,析构串口设备对象,设置按钮状态。

 1     if (!modbusDevice)
 2         return;
 3     modbusDevice->disconnectDevice();
 4     delete modbusDevice;
 5     modbusDevice = nullptr;
 6 
 7     qDebug() << "Modbus close Success!";
 8     ui->pushButton_openCom->setEnabled(true);
 9     ui->pushButton_closeCom->setEnabled(false);
10     ui->pushButton_refreshCom->setEnabled(true);
11 
12     ui->comboBox_com->setEnabled(true);
13     ui->comboBox_baud->setEnabled(true);
14     ui->comboBox_databits->setEnabled(true);
15     ui->comboBox_stopbits->setEnabled(true);
16     ui->comboBox_parity->setEnabled(true);
17     ui->spinBox_timeout->setEnabled(true);
18     ui->spinBox_retries->setEnabled(true);

写串口数据

设置一个下拉框来选择读写的数据存储类型,例如线圈或者寄存器。读写数据的时候按照需要选择
这里只用测试了Coils和Holding Registers,其他两个没有测试。

1     //值类型
2     ui->comboBox_valueType->addItem(tr("Coils"), QModbusDataUnit::Coils);
3     ui->comboBox_valueType->addItem(tr("Discrete Inputs"), QModbusDataUnit::DiscreteInputs);
4     ui->comboBox_valueType->addItem(tr("Input Registers"), QModbusDataUnit::InputRegisters);
5     ui->comboBox_valueType->addItem(tr("Holding Registers"), QModbusDataUnit::HoldingRegisters);

写串口数据需要对方的设备id,和要写入的寄存器或线圈地址,和写入的数据,
代码支持同时写入多个连续位置,数据按照空格区分,写入的数据是十进制的。对于进制我没有过于追究。

 1    if (!modbusDevice)
 2     {
 3         QMessageBox::information(NULL,  "提示",  "请先连接设备");
 4         return;
 5     }
 6 
 7     //获取要写入的寄存器数据
 8     QList<quint16> values;
 9     QStringList values_list = ui->lineEdit_writeValue->text().split(" ");
10     for(int i = 0 ; i < values_list.size(); i++)
11     {
12         values.append(values_list.at(i).toUInt());
13     }
14     int id = ui->lineEdit_id->text().toInt(); //设备地址
15     int addr = ui->lineEdit_addr->text().toInt(); //寄存器地址
16 
17     //组合写数据帧  table写入的数据类型 寄存器或线圈
18     const auto table =
19         static_cast<QModbusDataUnit::RegisterType> (ui->comboBox_valueType->currentData().toInt());
20     QModbusDataUnit writeUnit = QModbusDataUnit(table,
21                                                 addr, values.size());
22     for(int i=0; i<values.size(); i++)
23     {
24         writeUnit.setValue(i, values.at(i));
25     }
26 
27     //id 发生给slave的ID
28     if (auto *reply = modbusDevice->sendWriteRequest(writeUnit,id))
29     {
30         if (!reply->isFinished())
31         {
32             connect(reply, &QModbusReply::finished, this, [this, reply]()
33             {
34                 if (reply->error() == QModbusDevice::ProtocolError)
35                 {
36                     qDebug() << QString("Write response error: %1 (Mobus exception: 0x%2)")
37                                 .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);
38                 }
39                 else if (reply->error() != QModbusDevice::NoError)
40                 {
41                     qDebug() << QString("Write response error: %1 (code: 0x%2)").
42                                 arg(reply->errorString()).arg(reply->error(), -1, 16);
43                 }
44                 reply->deleteLater();
45             });
46         }
47         else
48         {
49             reply->deleteLater();
50         }
51     }
52     else
53     {
54         qDebug() << QString(("Write error: ") + modbusDevice->errorString());
55     }

这里是对D0寄存器写入100。

读取串口

读串口数据需要对方的设备id,和要读取的寄存器或线圈地址,读取的个数,支持读取连续的多个寄存器或线圈,读取数值按照空格区分。
读数据要先发送数据帧给从机,然后等待从机返回数据。

 1     if (!modbusDevice)
 2     {
 3         QMessageBox::information(NULL,  "提示",  "请先连接设备");
 4         return;
 5     }
 6     //清除读窗口信息
 7     ui->lineEdit_readValue->clear();
 8 
 9     //获取设备信息
10     int id = ui->lineEdit_id->text().toInt(); //设备地址
11     int addr = ui->lineEdit_addr->text().toInt(); //寄存器地址
12     int readNum = ui->lineEdit_readNum->text().toInt(); //读取寄存器个数
13 
14     //组合写数据帧  table写入的数据类型 寄存器或线圈
15     const auto table =
16         static_cast<QModbusDataUnit::RegisterType> (ui->comboBox_valueType->currentData().toInt());
17 
18     QModbusDataUnit readUint = QModbusDataUnit(table,
19                                                      addr, readNum);
20     //读取数据
21     if (auto *reply = modbusDevice->sendReadRequest(readUint, id))
22     {
23         if (!reply->isFinished())
24             connect(reply, &QModbusReply::finished, this, &Widget::readReady);
25         else
26             delete reply;
27     }
28     else
29     {
30         qDebug() << "Read error: " << modbusDevice->errorString();
31     }

在槽函数readReady();获取读到的数据。

 1     auto reply = qobject_cast<QModbusReply *>(sender());
 2     if (!reply)
 3         return;
 4 
 5     if (reply->error() == QModbusDevice::NoError)
 6     {
 7         const QModbusDataUnit unit = reply->result();
 8         if(unit.valueCount() == ui->lineEdit_readNum->text().toUInt())
 9         {
10             QString send_buff;
11             for (uint i = 0; i < unit.valueCount(); i++)
12             {
13                 const QString entry = tr("Address: %1, Value: %2").arg(unit.startAddress() + i)
14                                         .arg(QString::number(unit.value(i),
15                                              unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16));
16                 send_buff.append(QString::number(unit.value(i),
17                                                  unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16) + " ");
18             }
19             //读取的数据
20             ui->lineEdit_readValue->insert(send_buff);
21         }
22     }
23     else if (reply->error() == QModbusDevice::ProtocolError)
24     {
25         qDebug() << QString("Read response error: %1 (Mobus exception: 0x%2)").
26                     arg(reply->errorString()).
27                     arg(reply->rawResult().exceptionCode(), -1, 16);
28     }
29     else
30     {
31         qDebug() << QString("Read response error: %1 (code: 0x%2)").
32                     arg(reply->errorString()).
33                     arg(reply->error(), -1, 16);
34     }
35     reply->deleteLater();

这里读取D0 D1寄存器的值,读取值显示为16进制。

数据帧显示

代码发送数据帧和接收数据帧是没有做显示,这里参考通过重定向打印功能将将我们需要的数据显示到缓冲区显示。使用QLoggingCategory
这部分没什么用,我也没有研究过,就不做说明。

标签:modbusDevice,Qt,setEnabled,comboBox,Modbus,ui,串口,reply,PLC
From: https://www.cnblogs.com/ybqjymy/p/17729402.html

相关文章

  • Qt 5——对象树及对象树容易出现的内存问题
    对象模型(对象树) 在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。QObject是以对象树的形式组织起来的。当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是parent,也就是父对象指针。这相当于,在......
  • PyQt5
    PyQt5是一个用于创建图形用户界面(GUI)应用程序的Python库。它是Python编程语言与Qt应用程序框架的绑定,允许开发人员使用Python语言来创建跨平台的桌面应用程序。Qt是一个功能强大且广泛使用的C++库,用于开发图形界面和应用程序功能。以下是关于PyQt5的一些重要信息和功能:1.跨平台性......
  • Qt 5.12.9 + VS 2019配置并实现与三菱Q系列PLC通讯(1)软件的安装
    本人最近配置了QT5.12.9+VS2019,并实现了与三菱Q系列PLC通讯并实现数据交互的基本功能,在这个对中间遇到的一些问题和过程进行文字说明,以后大家有用到相关功能的话可以避免一些不必要的问题~需要安装的软件有三个:QT5.12.9、VS2019、MXComponetS4.19QT安装首先是对......
  • Qt窗口和视口解析(转)
    目录坐标变换流程世界坐标、窗口坐标和设备坐标窗口和视口世界变换和窗口视口变换QWidget、QGraphicsItem、QGraphicsView绘图窗口与视口绘图测试 坐标变换流程  QPainter.drawRect(QRectF)绘制图形传入的是世界坐标,而后经过变换矩形变为窗口坐标,最后经过窗口-视......
  • Qt/C++音视频开发56-udp推流和拉流/组播和单播推流
    一、前言之前已经实现了rtsp/rtmp推流,rtsp/rtmp/hls/flv/ws-flv/webrtc等拉流,这种一般都需要依赖一个独立的流媒体服务程序,有没有一种更便捷的方式不需要这种依赖,然后又能实现推拉流呢,当然有的那就是udpp推流,其中udp推流还可以是组播或者单播推流,组播一般会选择224.0.0.1这个地址......
  • pyqt5-QTreeWidgetItem
    QTreeWidgetItem树节点项。QTreeWidgetItem(strings:Iterable[str],type:int=QTreeWidgetItem.Type)创建节点时,必须是Iterable[str],表示一行中各列的文本 1、子节点child(self,index:int)->QTreeWidgetItem获取某节点的某子节点childCount(self)->int获......
  • pyqt5-QTreeWidget
    QTreeWidget树组件。1、顶级项addTopLevelItem(self,item:QTreeWidgetItem)末尾添加单个顶级项addTopLevelItems(self,items:Iterable[QTreeWidgetItem])末尾批量添加顶级项insertTopLevelItem(self,index:int,item:QTreeWidgetItem)指定索引插入单个顶级项......
  • QT
    QT讲得好几乎没有,都是过时的那一套,没有讲qml,现在在跟的这门课程不知如何,黑马QT太简单,适合快速过一遍有个印象大丙太杂,而且讲的太细,不如看书,有黑马基础看其实也行c++QTc++中高级,设计c++技术栈的都有讲,就是太细太高端,讲的很好但听起来很吃力,有讲QT以及最后的QT项目,作为简历项目......
  • Qt之QMessageBox的用法
    一、QMessageBox::informationQMessageBox::information 用于创建一个信息对话框,通常用于向用户显示一些重要的信息或通知。这个函数的用法很简单,它接受几个参数来配置对话框的内容和行为,并且通常以模态方式显示对话框,阻塞程序的执行,直到用户关闭对话框。QMessageBox::informa......
  • Clion中使用QtCharts导致进程直接退出
    解决办法:除了需要Qt6Charts.dll外,还需要引入QtOpenGL.dll、QtOpenGLWidgets.dll。最近在看H264编码相关的实现,一头雾水。顺便写了个二进制读取文件的小工具,方便分析码流。地址:BinaryView:使用二进制查看文件,(gitee.com)贴一下CMakeList:find_package(Qt6COMPONENTS......