前言:最近要编一个modbus的客户端,利用TCP网络通信。在编写之前我脑海中的默认想法是:
1、建立一个客户端Client上位机,与设备通信,可以下发命令,也可以读取参数。————但是最开始我坚定不移的认为,客户端是从站。于是我写好基本的连接代码后,就尝试与modbus主站模拟器连接,怎么都连不上。我都疯魔了,代码我检查了数十遍。
然后我才开始搜索主从站,服务器,客户端定义,恍然大悟。
我的错误想法:想当然的以为,服务器Sever是老大,当然是主站,老大当然是服务器;客户端Client听着就比较小,当然是从站。
我一直在用客户端(主站)连接另一个客户端(主站),能连上才有鬼!!!!
重点:
客户端(Client),是主站(Master)
服务器(Server),是从站(Slave)
对于上位机,看具体协议要求,上位机自己没有主从站之分。如果拿到的通信协议让你建立Sever那你就是服务器,是从站;如果是要求你建立Client,那就是主站。
2、modbus简介
modbus通信时候,有多个功能码,刚开始可以了解一下。用qt开发的时候,完全没必要关注这些功能码,qt自己打包的modbus类,建立好实例后,例如:QModbusRtuSerialMaster modbusMaster,接下来就用该实例来发送读取请求、和写入请求。这个阶段,只需要关注,你需要操作的地址是什么:
1、线圈寄存器---------Coils-----地址1~9999
2、Discreteinputs(InputStatus)---离散输入(输入线圈、离散寄存器)------地址10001~19999
3、HoldingRegisters---------保持寄存器-------------地址40001~49999
4、InputRegisters----------输入寄存器地址:30001~39999
这里面,是我在查资料过程中确认的,尤其是第二种,不同的地方,叫法不一。
红色部分,是qt中的名称,在编码过程中,是那个寄存器,到时候就用那个。
目录
在前期学习中,就开始根据协议手搓通讯程序。编写过程中对modebus协议理解总是不到位,后来不断查资料,才知道qt有实现好的modebus类不用自己从头开始根据协议手搓。不过手搓过程虽然显得蠢,对协议理解还是有帮助。后来利用Qt中有的modebus类就得心应手了。
总的来说和有两中模式,一种是modeBus串口通信,一种是modeBus网口通信,都用QT封装好的类实现。
modebus通讯协议理解推荐:详解Modbus通信协议---清晰易懂-CSDN博客
本文实现modeBus串口通信,末尾有网口RTU介绍,掌握一种之后,第二种就不难。
环境,VS2015,qt5.9.6.
一、新建项目,模块包含。
新建qt widgets项目,包含需要用到的两个类,在属性中添加,然后构建程序,重新扫描就行。
然后,ui界面布局如下。我们的程序逻辑是,点击“查询”按钮——获取“设备ID”、“模式”、“起始地址”、“长度”,对设备发起读取请求,然后接收设备反馈,在界面显示。
这时候需要用到一个modebus设备模拟器,推荐:modbus模拟器(可模拟Master和Slave)_modbus模拟器,modbus模拟资源-CSDN文库
二、代码
.h头文件
#pragma once
#include <QtWidgets/QWidget>
#include "ui_myModeBus.h"
#include <QSerialPort>
#include <QtSerialBus/QtSerialBus>
#include <QtSerialBus/QModbusDataUnit>
#include <QtSerialBus/qmodbusrtuserialmaster.h>
#include <qserialportinfo.h>
#include <unordered_map> //非排序图
#include <qmessagebox.h>
class myModeBus : public QWidget
{
Q_OBJECT
public:
myModeBus(QWidget *parent = Q_NULLPTR);
private slots:
void on_pushButton_conn_clicked();
void on_pushButton_query_clicked();
void query_out();
void on_lineEdit_data1_returnPressed();
void on_lineEdit_data2_returnPressed();
void on_lineEdit_data3_returnPressed();
void set_data();
private:
Ui::myModeBusClass ui;
QModbusRtuSerialMaster modbusMaster;//主机类
//波特率
std::unordered_map<int, QSerialPort::BaudRate> baudRateMap =
{
{ 0, QSerialPort::Baud1200 },{ 1, QSerialPort::Baud2400 },{ 2, QSerialPort::Baud4800 },{ 3, QSerialPort::Baud9600 },
{ 4, QSerialPort::Baud19200 },{ 5, QSerialPort::Baud38400 },{ 6, QSerialPort::Baud57600 },{ 7, QSerialPort::Baud115200 }
};
//数据位
std::unordered_map<int, QSerialPort::DataBits>dataMap =
{
{ 0,QSerialPort::Data5 },{ 1,QSerialPort::Data6 },{ 2,QSerialPort::Data7 },{ 3,QSerialPort::Data8 }
};
//
//校验位
std::unordered_map<int, QSerialPort::Parity>parityMap =
{
{ 0,QSerialPort::NoParity },{ 1,QSerialPort::OddParity },{ 2,QSerialPort::EvenParity }
};
//停止位
std::unordered_map<int, QSerialPort::StopBits>stopMap =
{
{ 0,QSerialPort::OneStop },{ 1,QSerialPort::OneAndHalfStop },{ 2,QSerialPort::TwoStop }
};
//状态
std::unordered_map<int,QModbusDataUnit::RegisterType>RegMap=
{ //线圈 //离散输入 //保持寄存器 输入寄存器
{0,QModbusDataUnit::Coils},{ 1,QModbusDataUnit::DiscreteInputs },{ 2,QModbusDataUnit::HoldingRegisters },{ 3,QModbusDataUnit::InputRegisters }
};
};
.cpp文件
#include "myModeBus.h"
myModeBus::myModeBus(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
ui.comboBox_baudrate->addItem(QString::number(1200));
ui.comboBox_baudrate->addItem(QString::number(2400));
ui.comboBox_baudrate->addItem(QString::number(4800));
ui.comboBox_baudrate->addItem(QString::number(9600));
ui.comboBox_baudrate->addItem(QString::number(19200));
ui.comboBox_baudrate->addItem(QString::number(38400));
ui.comboBox_baudrate->addItem(QString::number(57600));
ui.comboBox_baudrate->addItem(QString::number(115200));
ui.comboBox_baudrate->setCurrentIndex(3);
QStringList dataList = (QStringList() << QString::fromLocal8Bit("5位") << QString::fromLocal8Bit("6位") << QString::fromLocal8Bit("7位") << QString::fromLocal8Bit("8位"));
ui.comboBox_datasize->addItems(dataList);
ui.comboBox_datasize->setCurrentIndex(3);
QStringList checklist = (QStringList() << QString::fromLocal8Bit("无校验") << QString::fromLocal8Bit("奇校验") << QString::fromLocal8Bit("偶校验"));
ui.comboBox_check->addItems(checklist);
ui.comboBox_check->setCurrentIndex(1);
QStringList stoplist = (QStringList() << QString::fromLocal8Bit("1位") << QString::fromLocal8Bit("1.5位") << QString::fromLocal8Bit("2位"));
ui.comboBox_stop->addItems(stoplist);
ui.comboBox_stop->setCurrentIndex(0);
QStringList modlist = (QStringList() << QString("Coils") << QString("DiscreteInputs") << QString("HoldingRegisters") << QString("InputRegisters"));
ui.comboBox_mode->addItems(modlist);
ui.comboBox_mode->setCurrentIndex(0);
QStringList portNameList;
QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
ui.comboBox_com->clear();
for (int i = 0; i < serialPortInfos.count(); i++)
{
QSerialPort serial;
serial.setPort(serialPortInfos[i]);
if (serial.open(QIODevice::ReadWrite))
{
portNameList.append(serial.portName());
serial.close();
}
}
ui.comboBox_com->addItems(portNameList);
ui.comboBox_com->setCurrentIndex(0);
}
void myModeBus::on_pushButton_conn_clicked()
{
QString comstr = ui.comboBox_com->currentText();
modbusMaster.setConnectionParameter(QModbusDevice::SerialPortNameParameter, comstr); // 设置串口名称
int baudindex = ui.comboBox_baudrate->currentIndex();
modbusMaster.setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudRateMap[baudindex]);
int dataindex = ui.comboBox_datasize->currentIndex();
modbusMaster.setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataMap[dataindex]); // 设置数据位
int parityindex = ui.comboBox_check->currentIndex();
modbusMaster.setConnectionParameter(QModbusDevice::SerialParityParameter, parityMap[parityindex]); // 设置奇校验位
int stopindex = ui.comboBox_stop->currentIndex();
modbusMaster.setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopMap[stopindex]); // 设置停止位
if (modbusMaster.state() != QModbusDevice::ConnectedState)
{
if (!modbusMaster.connectDevice())
{
qDebug() << "Failed to connect to Modbus device!";
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("连接"), QString::fromLocal8Bit("确认"));
ui.pushButton_conn->setText(QString::fromLocal8Bit("断开"));
}
}
else
{
QMessageBox::information(this, QString::fromLocal8Bit("断开"), QString::fromLocal8Bit("确认"));
ui.pushButton_conn->setText(QString::fromLocal8Bit("连接"));
modbusMaster.disconnectDevice();
}
}
void myModeBus::on_pushButton_query_clicked()
{
int moIndex = ui.comboBox_mode->currentIndex();
int deviceID = ui.lineEdit_Deviceaddress->text().toInt();
int address = ui.lineEdit_address->text().toInt();
int length = ui.lineEdit_length->text().toInt();
QModbusDataUnit readUnit(RegMap[moIndex], address-1, length); // 读取保持寄存器,起始地址为100,长度读3
auto *reply1 = modbusMaster.sendReadRequest(readUnit, deviceID);//获取温度值
if (reply1)
{
// 等待读取完成
connect(reply1, &QModbusReply::finished, this, &myModeBus::query_out);//
}
else
{
qDebug() << "Failed to send Modbus read request!";
}
}
void myModeBus::query_out()
{
ui.textEdit_receive->clear();
auto reply = qobject_cast<QModbusReply *>(sender());
//这是 QObject 类的一个方法。在Qt的信号和槽机制中,当一个信号被触发时,sender() 方法就提供了这样的功能,
//它返回触发信号的对象的指针(如果可用)
if (!reply)
return;
if (reply->error() == QModbusDevice::NoError)
{// 处理读取到的数据
const auto values = reply->result().values();
int value;
for (int i = 0; i < values.size(); i++)
{
QString str = QString("<%1>:%2").arg(ui.lineEdit_address->text().toInt()+i).arg(values[i]);
ui.textEdit_receive->append(str);
}
}
else
{
qDebug() << "Read error:" << reply->errorString();
}
reply->deleteLater();
}
void myModeBus::on_lineEdit_data1_returnPressed()
{
set_data();
}
void myModeBus::on_lineEdit_data2_returnPressed()
{
set_data();
}
void myModeBus::on_lineEdit_data3_returnPressed()
{
set_data();
}
void myModeBus::set_data()
{
bool ok;
QVector<quint16> data;
//data.resize(3);
quint16 data1 = ui.lineEdit_data1->text().toUShort(&ok);
if(ok)
data.append(data1);
quint16 data2 = ui.lineEdit_data2->text().toUShort(&ok);
if(ok)
data.append(data2);
quint16 data3 = ui.lineEdit_data3->text().toUShort(&ok);
if(ok)
data.append(data3);
qDebug() << "write1:"<< data;
int moIndex = ui.comboBox_mode->currentIndex();
int deviceID = ui.lineEdit_Deviceaddress->text().toInt();
int address = ui.lineEdit_address->text().toInt();
int length = ui.lineEdit_length->text().toInt();
QModbusDataUnit writeUnit(RegMap[moIndex], address-1, length); // 写入保持寄存器0,
writeUnit.setValues(data);
if (auto *reply2 = modbusMaster.sendWriteRequest(writeUnit, deviceID))
{
// 等待写入完成
if (!reply2->isFinished())
{
QEventLoop loop;
QObject::connect(reply2, &QModbusReply::finished, &loop, &QEventLoop::quit);
loop.exec();
}
// 检查写入结果
if (reply2->error() == QModbusDevice::NoError)
{
qDebug() << "Write request completed successfully!";
}
else
{
qDebug() << "Write error:" << reply2->errorString();
}
// 释放Modbus回复对象
reply2->deleteLater();
}
else
{
qDebug() << "Failed to send Modbus write request!";
}
}
三、实现效果
3.1保持寄存器数据写入
3.2读取寄存器
四、modebus RTU网口通信
网口与串口通信没有很大的区别,就是在连接时候,使用QModbusTcpClient,类建立一个私有成员。
client = new QModbusTcpClient(this);
然后在ui界面放置一个编辑框用于输入网口:端口
如100.10.0.00:502,冒号前是ip,冒号后是端口。
然后,点击按钮,其中的参数设置如下。连接完成之后,寄存器的读写与串口没有大的区别。就是在
const QUrl url = QUrl::fromUserInput(ui.lineEdit_IPaddress->text());
client->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
client->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
client->setTimeout(500);
client->setNumberOfRetries(3);
结束语:代码已经全部放在文中了,没有详细解释,项目正常建立的话,程序应该可以正常运行。有疑问的话,可以评论,我会回复的。