一、Mudbus TCP简介
Modbus TCP 是 Modbus 协议的一个变种,它利用 TCP/IP 网络协议来传输 Modbus 消息。Modbus TCP 是一种广泛应用于工业自动化领域的通信协议,特别适用于远程监控和控制系统之间的数据交换。
-
基于 TCP/IP:
- Modbus TCP 基于标准的 TCP/IP 协议,可以在任何支持 TCP/IP 的网络上传输 Modbus 消息。
- 使用 TCP/IP 协议提供了可靠的传输机制,包括错误检测和重传机制。
-
固定端口:
- Modbus TCP 通常使用固定的端口 502 进行通信。
- 这使得 Modbus TCP 设备更容易在网络中被发现和配置。
-
简化协议:
- Modbus TCP 相比传统的 Modbus RTU 和 Modbus ASCII 协议更加简洁。
- Modbus TCP 消息格式简化了原始的 Modbus 协议报头,仅保留了事务标识符、协议标识符、长度字段和单元标识符。
-
快速响应:
- Modbus TCP 的设计旨在减少网络延迟,提高响应速度。
- 由于 Modbus TCP 消息格式更短,可以更快地进行处理。
-
易于集成:
- Modbus TCP 可以轻松地与其他基于 TCP/IP 的系统集成。
- 支持多种操作系统和平台,便于开发和维护。
二、Mudbus TCP 在Qt中的使用
示例代码:
Modbus TCP Demo for Qt: 我的 Qt Modbus TCP Demo 当前存在一点问题,自动刷新和写入寄存器的效果冲突,但放在此处方便后续使用 (gitee.com)
***.pro
QT += core gui serialbus
slave.ui
![](https://i-blog.csdnimg.cn/direct/babb3bb3fa5843dba1d9c04971929507.png)
slave.h
#ifndef SLAVE_H
#define SLAVE_H
#include <QWidget>
#include <QtSerialBus>
#include <QModbusDataUnit>
#include <QModbusClient>
#include <QString>
#include <QMessageBox>
namespace Ui {
class Slave;
}
class Slave : public QWidget
{
Q_OBJECT
public:
explicit Slave(QWidget *parent = nullptr);
Slave(QString ip,int port);
~Slave();
virtual void timerEvent(QTimerEvent *event);
signals:
private slots:
void onReadyRead();
void closeTCP();
void on_btn_con_clicked();
void on_btn_read_clicked();
void on_btn_write_clicked();
private:
QString ipNum;
int portNum;
QModbusTcpClient tcpClient;
Ui::Slave *ui;
void readU();
};
#endif // SLAVE_H
slave.cpp
#include "slave.h"
#include "ui_slave.h"
Slave::Slave(QWidget *parent) :
QWidget(parent),
ui(new Ui::Slave)
{
ui->setupUi(this);
// this->ui->btn_read->setVisible(false);
// this->ui->btn_write->hide();
}
Slave::Slave(QString ip, int port)
{
this->ipNum = ip;
this->portNum = port;
this->setWindowTitle(" 从机- " + ip +" : "+ port);
}
Slave::~Slave()
{
closeTCP();
delete ui;
}
void Slave::timerEvent(QTimerEvent *event)
{
on_btn_read_clicked();
}
void Slave::onReadyRead()
{
auto reply = qobject_cast<QModbusReply *>(sender());
if(reply->error()==QModbusDevice::NoError){
qDebug()<<" 读取成功 ! ";
reply->deleteLater();
}else{
qDebug()<<" 读取失败! ";
reply->deleteLater();
return;
}
const QModbusDataUnit unit = reply->result();
QString dataStr;
for(int i=0;i<unit.valueCount();i++){
dataStr += QString::number(unit.value(i))+" ";
ui->tab_slave->setItem(i,0,new QTableWidgetItem(QString::number(unit.value(i))));
ui->tab_master->setItem(i,0,new QTableWidgetItem(QString::number(unit.value(i))));
}
qDebug()<<dataStr;
}
void Slave::closeTCP()
{
tcpClient.disconnectDevice();
qDebug()<<" 连接已断开 ";
}
void Slave::on_btn_con_clicked()
{
if(ui->lt_ipNum->text().isEmpty()||ui->lt_portNum->text().isEmpty()){
QMessageBox errorDialog;
errorDialog.setIcon(QMessageBox::Critical); // 设置图标为错误图标
errorDialog.setWindowTitle(" 错误提示 "); // 设置窗口标题
errorDialog.setText(" 请检查ip和port,不可为空 " ); // 设置错误信息文本
errorDialog.exec(); // 显示错误提示窗口并等待用户关闭
return;
}else{
if(tcpClient.state()==QModbusClient::ConnectedState){
tcpClient.disconnectDevice();
}
this->ipNum=ui->lt_ipNum->text();
this->portNum = ui->lt_portNum->text().toInt();
this->setWindowTitle(" 从机-" + ipNum +":"+ QString::number(portNum));
}
tcpClient.setConnectionParameter(QModbusDevice::NetworkAddressParameter,ipNum);
tcpClient.setConnectionParameter(QModbusDevice::NetworkPortParameter,portNum);
tcpClient.setTimeout(300);
if(!tcpClient.connectDevice()){
QMessageBox errorDialog2;
errorDialog2.setIcon(QMessageBox::Critical); // 设置图标为错误图标
errorDialog2.setWindowTitle(" 错误提示 "); // 设置窗口标题
errorDialog2.setText(" 连接失败,请检查ip和port,慢点点击 "); // 设置错误信息文本
errorDialog2.exec(); // 显示错误提示窗口并等待用户关闭
return;
}
else {
qDebug()<<" 连接成功 ";
// startTimer(1000);
// ui->tab_slave->clear();
// ui->tab_master->clear();
}
}
void Slave::on_btn_read_clicked()
{
QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters,0,10);
auto *reply = tcpClient.sendReadRequest(readUnit,1);
connect( reply,&QModbusReply::finished,this,&Slave::onReadyRead);
}
void Slave::on_btn_write_clicked()
{
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters,0,10); // 创建数据单元
// int emptyFlag = 0;
for (int i = 0; i < ui->tab_master->rowCount(); ++i) {
// if(ui->tab_master->item(i,0)->text().isEmpty()){
// emptyFlag++;
// }
writeUnit.setValue(i,ui->tab_master->item(i,0)->text().toInt());// 设置值
}
// if(emptyFlag>0){
// QMessageBox::StandardButton reply;
// reply = QMessageBox::question(nullptr, "警告", "寄存器编辑器包含空选项是否要写入从机寄存器",
// QMessageBox::Yes | QMessageBox::No);
// if (reply == QMessageBox::No) {
// return;
// }
// }
if (auto *reply = tcpClient.sendWriteRequest(writeUnit, 1)) { // 发送写请求
if (!reply->isFinished()) {
connect(reply, &QModbusReply::finished, this, [this, reply]() {
if (reply->error() == QModbusDevice::NoError) {
qDebug() << " 修改成功 ";
on_btn_read_clicked();
} else {
qDebug() << " 修改失败: " << reply->errorString();
}
reply->deleteLater();
});
} else {
// 请求已完成但未完成
delete reply; // 删除回复对象
}
} else {
qDebug() << "发送请求失败:" << tcpClient.errorString();
}
}
三、对Moudbus TCP的补充
//常用操作
#include <QCoreApplication>
#include <QModbusDataUnit>
#include <QModbusReply>
#include <QModbusTcpClient>
#include <QDebug>
// 定义 Modbus TCP 客户端
class ModbusTcpClient : public QModbusTcpClient {
public:
ModbusTcpClient(QObject *parent = nullptr) : QModbusTcpClient(parent) {}
// 读取保持寄存器
bool readHoldingRegisters(quint16 startAddress, quint16 quantity, QModbusDataUnit &result) {
QModbusDataUnit request(QModbusDataUnit::HoldingRegisters, startAddress, quantity);
QModbusReply *reply = sendReadRequest(request, 1); // 设备ID为1
if (!reply) {
qDebug() << "Failed to send read request.";
return false;
}
QObject::connect(reply, &QModbusReply::finished, this, [this, &result, reply]() {
if (reply->error() == QModbusDevice::NoError) {
result = reply->result();
} else {
qDebug() << "Error reading data from Modbus server:" << reply->errorString();
}
delete reply;
});
return true;
}
// 写入单个线圈
bool writeSingleCoil(quint16 coilAddress, bool state) {
QModbusDataUnit request(QModbusDataUnit:: Coil, coilAddress, 1);
request.setValue(0, state ? 0xFF00 : 0x0000);
QModbusReply *reply = sendWriteRequest(request, 1); // 设备ID为1
if (!reply) {
qDebug() << "Failed to send write request.";
return false;
}
QObject::connect(reply, &QModbusReply::finished, this, [reply]() {
if (reply->error() != QModbusDevice::NoError) {
qDebug() << "Error writing data to Modbus server:" << reply->errorString();
}
delete reply;
});
return true;
}
// 写入多个保持寄存器
bool writeMultipleHoldingRegisters(quint16 startAddress, const QList<quint16> &values) {
QModbusDataUnit request(QModbusDataUnit::HoldingRegisters, startAddress, values.size());
for (int i = 0; i < values.size(); ++i) {
request.setValue(i, values[i]);
}
QModbusReply *reply = sendWriteRequest(request, 1); // 设备ID为1
if (!reply) {
qDebug() << "Failed to send write request.";
return false;
}
QObject::connect(reply, &QModbusReply::finished, this, [reply]() {
if (reply->error() != QModbusDevice::NoError) {
qDebug() << "Error writing data to Modbus server:" << reply->errorString();
}
delete reply;
});
return true;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建 Modbus TCP 客户端
ModbusTcpClient client;
// 设置服务器地址和端口
client.setServerAddress(QHostAddress("192.168.1.100"), 502);
// 连接到 Modbus 服务器
if (!client.connectDevice()) {
qDebug() << "Failed to connect to Modbus server.";
return -1;
}
// 定义要读取的寄存器的起始地址和数量
quint16 startAddress = 100;
quint16 quantity = 10;
// 创建一个数据单元来请求读取保持寄存器
QModbusDataUnit result;
// 读取保持寄存器
if (client.readHoldingRegisters(startAddress, quantity, result)) {
qDebug() << "Received data from Modbus server:";
for (quint16 i = 0; i < result.quantityOfValues(); ++i) {
qDebug() << "Register" << startAddress + i << ":" << result.value(i);
}
}
// 写入单个线圈
if (client.writeSingleCoil(200, true)) {
qDebug() << "Wrote single coil to Modbus server.";
}
// 写入多个保持寄存器
QList<quint16> values = {1, 2, 3, 4, 5};
if (client.writeMultipleHoldingRegisters(300, values)) {
qDebug() << "Wrote multiple holding registers to Modbus server.";
}
// 主事件循环
a.exec();
return 0;
}