記事ディレクトリ
序文
この記事では主に、Qt の Modbus モジュールを使用して ModbusTcp と通信し、PLC のコイルレジスタや保持レジスタの読み書きを実現する方法について説明します。TCP/IP をベースにした Modbus プロトコルの内容については、あまり説明しません。参考記事を参照してください。詳細については。この記事の例では、QModbusTcpClient クラスを Modbus クライアント (マスター局) として使用し、PLC をスレーブ局として使用し、独自の MyModbus クラスをカプセル化しています。 、批判や修正は大歓迎です。
事業効果
提示:以下是本篇文章正文内容,下面案例可供参考
1. Modbusモジュールの紹介
1. ここでは、pri サブモジュール メソッドを使用して、独自の MyModbus クラスのカプセル化を実装しました。これにより、将来のこのモジュールの再利用も容易になります。Modbus モジュールは、pri に導入されています
:
MyModbus.pri
QT += serialbus serialport
関連するヘッダー ファイルを MyModbus クラスに追加します。
#include <QModbusTcpClient>
#include <QModbusDataUnit>
2. 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();
}
3. 各レジスタからのデータの読み出し
1. Modbus にはコイル、ディスクリート入力、保持レジスタ、入力レジスタの 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;
}
}
4. 各レジスタへのデータの書き込み
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;
}
5. 完全なコードのサンプル
これは、pro メイン プログラムと 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.ウィジェット.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.ウィジェット.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.ウィジェット.ui
要約する
この ModbusTcp プロトコルを使用して Qt ベースで通信するのは比較的簡単で、まずレジスタの種類を決定し、関連する関数を直接使用して読み書きします。注意すべき点の 1 つは、保持レジスタを読み書きする場合、PLC の特定のアドレスの数値は 16 ビットの数値であるため、32 ビットの符号付き数値を読み書きする場合は、2 つの隣接するアドレスの組み合わせを考慮する必要があることです。 , この記事ではこれについて取り上げていますが、この場合はPLC側でも対応する設定を忘れずに行ってください。
こんにちは:
一緒に学び、一緒に進歩しましょう。関連する質問がまだある場合は、ディスカッションのためにコメント領域にメッセージを残すことができます。
参考ブログ:
QT 通信における Modbus TCP
C++ と Modbus TCP プロトコルを介した PLC による PLC 内部レジスタの値の読み取り/書き込み まとめ