当前位置: 首页 > news >正文

基于Qt开发的ModbusTcp主站软件开发教程​——从协议原理到工业级实现

目录

      • 第一章 环境配置与库集成
        • 1. 安装Qt与Modbus模块
        • 2. 第三方库兼容性(备选方案)
      • 第二章 Modbus TCP协议与Qt类解析
        • 1. 协议核心要点
        • 2. Qt关键类说明
      • 第三章 主站连接管理与通信初始化
        • 1. 连接建立与断线重连
        • 2. 请求超时与响应机制
      • 第四章 数据读写操作实战
        • 1. 读取保持寄存器(功能码0x03)
        • 2. 写入多个寄存器(功能码0x10)
      • 第五章 异常处理与调试技巧
        • 1. 常见错误排查
        • 2. 启用调试模式
      • 第六章 应用实例
        • 1. 功能设计
        • 2. 完整代码
      • 📝 七、常见问题与解决方案
      • 八、总结

成果展示

第一章 环境配置与库集成

1. 安装Qt与Modbus模块
- 使用 Qt 5.13+,通过Qt Maintenance Tool安装组件:- Qt SerialBus(基础通信)- Qt Modbus(协议支持)
- 项目配置文件(.pro)添加依赖:
QT += serialbus network  
2. 第三方库兼容性(备选方案)
- 若需使用libmodbus库(跨平台兼容性更强):
LIBS += -L$$PWD/libmodbus/lib -lmodbus  # 编译后库文件路径
INCLUDEPATH += $$PWD/libmodbus/include   # 头文件路径
- Windows编译libmodbus需安装MSYS2并配置镜像源加速编译。

第二章 Modbus TCP协议与Qt类解析

1. 协议核心要点
- 帧结构:MBAP头(7字节) + PDU(功能码+数据)
字段字节数说明
事务标识符2请求/响应匹配(如00 01)
协议标识符2固定00 00(Modbus TCP)
长度2PDU部分字节数
单元标识符1从站地址(默认1)
  • 常用功能码:
    • 0x03读保持寄存器、0x10写多寄存器、0x06写单寄存器
2. Qt关键类说明
- 主站核心类:QModbusTcpClient
QModbusTcpClient *client = new QModbusTcpClient(this);
client->setConnectionParameter(QModbusDevice::NetworkAddress, "192.168.1.100"); // 从站IP
client->setConnectionParameter(QModbusDevice::NetworkPort, 502); // 默认端口

第三章 主站连接管理与通信初始化

1. 连接建立与断线重连
if (!client->connectDevice()) {qDebug() << "连接失败:" << client->errorString();
} else {// 监听连接状态变化connect(client, &QModbusClient::stateChanged, [](QModbusDevice::State state) {if (state == QModbusDevice::ConnectedState) qDebug() << "连接成功";if (state == QModbusDevice::UnconnectedState) client->connectDevice(); // 自动重连});
}
2. 请求超时与响应机制
- 设置超时时间(单位毫秒):
client->setTimeout(3000);  // 3秒无响应视为失败

第四章 数据读写操作实战

1. 读取保持寄存器(功能码0x03)
auto *reply = client->sendReadRequest(QModbusDataUnit(QModbusDataUnit::HoldingRegisters, 0, 10),  // 从地址0读10个寄存器1  // 从站地址
);
connect(reply, &QModbusReply::finished, [=]() {if (reply->error() == QModbusDevice::NoError) {const QModbusDataUnit data = reply->result();for (int i = 0; i < data.valueCount(); ++i) {quint16 value = data.value(i);qDebug() << "寄存器" << i << "值:" << value;}}reply->deleteLater(); // 释放内存
});
2. 写入多个寄存器(功能码0x10)
QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 0, {100, 200, 300}); // 地址0写入3个值
auto *writeReply = client->sendWriteRequest(writeUnit, 1);
connect(writeReply, &QModbusReply::finished, [=]() {if (writeReply->error() == QModbusDevice::NoError) qDebug() << "写入成功!";writeReply->deleteLater();
});

第五章 异常处理与调试技巧

1. 常见错误排查
问题解决方案
端口被占用netstat -ano
客户端无法连接关闭防火墙或开放502端口
寄存器地址越界检查数据映射范围(如setMap()设置)
大小端转换错误使用qFromBigEndian()转换字节序
2. 启用调试模式
client->setProperty("debug", true);  // 输出详细通信日志

第六章 应用实例

1. 功能设计
- 定时读取数据
- 写数字到保持寄存器
2. 完整代码

mainwindow.h


#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QModbusTcpClient>
#include <QModbusDevice>
#include <QTimer>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow{Q_OBJECT
public:MainWindow(QWidget *parent = nullptr);~MainWindow();void InitMoubusTcp();void connectModbus();//写入寄存器 slaveId 写入的从机ID  startAddress写入的起始地址 values写入的值void writeUnit(int slaveId, int fcode,int startAddress, quint16 value);//读取寄存器 slaveId 读取的从机ID  startAddress读取的起始地址 readNum读取的数量void readUnit(int slaveId, int fcode,int startAddress, int readNum);
public slots:void onReadModbus();//读取modbus寄存器值void disConnectModbus();
private:Ui::MainWindow *ui;QModbusClient *m_modbusTcpDevice = nullptr;QString m_ip;int m_slaveAddr;int m_port;QTimer m_timer;
};#endif // MAINWINDOW_H

mainwindow.cpp


#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);connect(ui->openButton,&QPushButton::clicked,this,[=](){InitMoubusTcp();connectModbus();});connect(ui->sendButton,&QPushButton::clicked,this,[=](){readUnit(ui->slaveAddrEdit->text().toInt(),ui->fcodeComBox->currentText().toInt(),ui->startEdit->text().toInt(),ui->numEdit->text().toInt());});connect(ui->writeButton,&QPushButton::clicked,this,[=](){quint16 value = ui->writeEdit->text().toUInt();writeUnit(ui->slaveAddrEdit->text().toInt(),ui->fcodeComBox->currentText().toInt(),ui->startEdit->text().toInt(),value);});connect(ui->dscBox,&QCheckBox::clicked,this,[=](){if(ui->dscBox->checkState()==Qt::Checked){m_timer.start(ui->tmEdit->text().toUInt());ui->tmEdit->setDisabled(true);}else{m_timer.stop();ui->tmEdit->setEnabled(true);}});connect(&m_timer,&QTimer::timeout,this,[=](){emit ui->sendButton->clicked();});connect(ui->clearButton,&QPushButton::clicked,this,[=](){ui->textEdit->clear();});
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::InitMoubusTcp()
{m_ip = ui->ipEdit->text();m_port = ui->portEdit->text().toInt();m_slaveAddr = ui->slaveAddrEdit->text().toInt();
}void MainWindow::connectModbus()
{disConnectModbus();m_modbusTcpDevice = new QModbusTcpClient(this);connect(m_modbusTcpDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) {ui->textEdit->append(m_modbusTcpDevice->errorString());});connect(m_modbusTcpDevice,&QModbusClient::stateChanged,[this](QModbusDevice::State state){if(state==QModbusDevice::ConnectedState)ui->textEdit->append("connect ModbusTcp Device success!");else if(state==QModbusDevice::ClosingState)ui->textEdit->append("connect ModbusTcp Device Closing.");});m_modbusTcpDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, m_ip);m_modbusTcpDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, m_port);m_modbusTcpDevice->setTimeout(1000);m_modbusTcpDevice->setNumberOfRetries(3);m_modbusTcpDevice->connectDevice();}void MainWindow::writeUnit(int slaveId,int fcode, int startAddress, quint16 value)
{if (!m_modbusTcpDevice)connectModbus();else if (m_modbusTcpDevice->state() != QModbusDevice::ConnectedState)connectModbus();QString sendFrame = QString("%1%2%3").arg(slaveId,2,16,QLatin1Char('0')).arg(fcode,2,16,QLatin1Char('0')).arg(startAddress,4,16,QLatin1Char('0'));QModbusDataUnit::RegisterType rtype;switch (fcode) {case 0x05://写单个线圈rtype = QModbusDataUnit::Coils;break;case 0x06://写单个寄存器rtype = QModbusDataUnit::HoldingRegisters;break;case 0x0F://写多个线圈rtype = QModbusDataUnit::Coils;break;case 0x10://写多个寄存器rtype = QModbusDataUnit::HoldingRegisters;break;default:ui->textEdit->append(QString("function code error."));return;}QModbusDataUnit writeUnit = QModbusDataUnit(rtype,startAddress, 1);sendFrame = QString("%1%2").arg(sendFrame).arg(value,4,16,QLatin1Char('0'));writeUnit.setValue(0, value);ui->textEdit->append(QString("write:%1").arg(QString::fromUtf8(QByteArray::fromHex(sendFrame.toUtf8()).toHex(' ').toUpper())));QModbusReply *reply = m_modbusTcpDevice->sendWriteRequest(writeUnit,slaveId);if (reply) {if (!reply->isFinished()) {connect(reply, &QModbusReply::finished, this, [this, reply]() {if (reply->error() == QModbusDevice::ProtocolError)ui->textEdit->append(QString("Write response error: %1 (Mobus exception: 0x%2)").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16));else if (reply->error() != QModbusDevice::NoError)ui->textEdit->append(QString("Write response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));else if(reply->error() == QModbusDevice::TimeoutError)ui->textEdit->append(QString("Write response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16));else{const QModbusDataUnit unit = reply->result();m_slaveAddr = reply->serverAddress();QVector<quint16> rValue = unit.values();int start = unit.startAddress();int fcode = 0x00;if(unit.registerType() == QModbusDataUnit::HoldingRegisters)fcode = 0x06;else if(unit.registerType() == QModbusDataUnit::Coils)fcode = 0x05;QString reciveFrame;reciveFrame=QString("%1").arg(m_slaveAddr,2,16,QLatin1Char('0'));reciveFrame=QString("%1%2").arg(reciveFrame).arg(fcode,2,16,QLatin1Char('0'));reciveFrame=QString("%1%2").arg(reciveFrame).arg(start,4,16,QLatin1Char('0'));QString msg;foreach (quint16 value, rValue){reciveFrame=QString("%1%2").arg(reciveFrame).arg(value,4,16,QLatin1Char('0'));msg+=QString("地址%1=%2  ").arg(start++).arg(value);}ui->textEdit->append(QString("接收:%1").arg(QString::fromUtf8(QByteArray::fromHex(reciveFrame.toUtf8()).toHex(' ').toUpper())));ui->textEdit->append("写值成功\n"+msg);}reply->deleteLater();});}elsereply->deleteLater();} elseui->textEdit->append(QString("Write error:") + m_modbusTcpDevice->errorString());
}void MainWindow::readUnit(int slaveId, int fcode,int startAddress, int readNum)
{//数据帧结构:地址码|功能码|起始寄存器地址+寄存器数量QString sendFrame = QString("%1%2%3%4").arg(slaveId,2,16,QLatin1Char('0')).arg(fcode,2,16,QLatin1Char('0')).arg(startAddress,4,16,QLatin1Char('0')).arg(readNum,4,16,QLatin1Char('0'));ui->textEdit->append(QString("发送:%1").arg(QString::fromUtf8(QByteArray::fromHex(sendFrame.toUtf8()).toHex(' ').toUpper())));QModbusDataUnit read;switch (fcode) {case 0x01:read = QModbusDataUnit(QModbusDataUnit::Coils, startAddress, readNum);//读线圈break;case 0x02:read = QModbusDataUnit(QModbusDataUnit::DiscreteInputs, startAddress, readNum);//读离散量输入break;case 0x03:read = QModbusDataUnit(QModbusDataUnit::HoldingRegisters, startAddress, readNum);//读保持寄存器break;case 0x04:read = QModbusDataUnit(QModbusDataUnit::InputRegisters, startAddress, readNum);//读输入寄存器break;default:ui->textEdit->append("read frame error.");return;break;}if (auto *reply = m_modbusTcpDevice->sendReadRequest(read, slaveId)) {if (!reply->isFinished()){connect(reply, &QModbusReply::finished, this, &MainWindow::onReadModbus);}else{delete reply;connectModbus();}}else{ui->textEdit->append("Read error: "+m_modbusTcpDevice->errorString());connectModbus();}
}void MainWindow::onReadModbus()
{auto reply = qobject_cast<QModbusReply *>(sender());if (!reply)return;if (reply->error() == QModbusDevice::NoError) {const QModbusDataUnit unit = reply->result();m_slaveAddr = reply->serverAddress();QVector<quint16> rValue = unit.values();int start = unit.startAddress();int fcode = 0x00;if(unit.registerType() == QModbusDataUnit::HoldingRegisters)fcode = 0x03;else if(unit.registerType() == QModbusDataUnit::Coils)fcode = 0x01;else if(unit.registerType() == QModbusDataUnit::DiscreteInputs)fcode = 0x02;else if(unit.registerType() == QModbusDataUnit::InputRegisters)fcode = 0x04;qint16 val;QString reciveFrame;reciveFrame=QString("%1").arg(m_slaveAddr,2,16,QLatin1Char('0'));reciveFrame=QString("%1%2").arg(reciveFrame).arg(fcode,2,16,QLatin1Char('0'));reciveFrame=QString("%1%2").arg(reciveFrame).arg(rValue.size()*2,2,16,QLatin1Char('0'));QString msg;foreach (quint16 value, rValue){val = static_cast<qint16>(value);reciveFrame=QString("%1%2").arg(reciveFrame).arg(val,4,16,QLatin1Char('0'));msg+=QString("地址%1=%2  ").arg(start++).arg(val);}ui->textEdit->append(QString("接收:%1").arg(QString::fromUtf8(QByteArray::fromHex(reciveFrame.toUtf8()).toHex(' ').toUpper())));ui->textEdit->append(msg);}else if (reply->error() == QModbusDevice::ProtocolError) {ui->textEdit->append(QString("Read response error: %1 (Mobus exception: 0x%2)").arg(reply->errorString()).arg(reply->error(), 2, 16,QLatin1Char('0')));}else {ui->textEdit->append(QString("Read response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), 2, 16,QLatin1Char('0')));}reply->deleteLater();
}void MainWindow::disConnectModbus()
{if (!m_modbusTcpDevice)return;m_modbusTcpDevice->disconnectDevice();delete m_modbusTcpDevice;m_modbusTcpDevice = nullptr;ui->textEdit->append("Modbus Tcp 断开连接");
}

📝 七、常见问题与解决方案

问题类型原因分析解决方案
库链接失败路径错误/未编译检查.pro文件路径;重新编译libmodbus
无响应从站地址/功能码错误验证从站配置;抓包分析报文
数据解析异常大小端设置不一致统一主从端字节序(建议大端)
断线频繁网络延迟/未启用重连添加心跳包机制;实现自动重连

八、总结

本文介绍了Qt环境下Modbus TCP通信的完整实现方案,涵盖环境配置、协议解析、连接管理、数据读写和异常处理等内容。首先详细说明Qt Modbus模块的安装与配置方法,包括Qt SerialBus组件集成和第三方libmodbus库的备选方案。其次解析Modbus TCP协议帧结构和功能码,并演示QModbusTcpClient类的核心用法。在实战环节,提供了读取保持寄存器和批量写入寄存器的代码示例,以及连接状态监控、自动重连等关键机制。文章还总结了防火墙配置、端口占用等常见问题的解决方案,并给出完整的GUI应用实现代码框架,包含定时读取、数据写入等典型功能模块。

http://www.lqws.cn/news/487117.html

相关文章:

  • python基于微信小程序的广西文化传承系统
  • Vscode自定义代码快捷方式
  • 2025年小程序地图打车的5大技术革新:实时路况预测与智能调度升级
  • 安卓9.0系统修改定制化____如何编辑和修改安卓手机默认按键配置文件 改变按键功能 操作篇 九
  • 为什么android要使用Binder机制
  • vue3+js实现表格虚拟滚动
  • OpenCV计算机视觉实战(12)——图像金字塔与特征缩放
  • Android 15 变更及适配攻略
  • 代码分析与自动化重构
  • Mysql常见的SQL语句格式
  • 【MySQL基础】MySQL复合查询全面解析:从基础到高级应用
  • Blazor-EditContext
  • 智能化战略实施服务,AI咨询与部署全流程支持
  • [2025CVPR]DeepLA-Net:深度局部聚合网络解析
  • STaR: Self-Taught Reasoner Bootstrapping Reasoning With Reasoning论文笔记
  • ISCSI存储
  • Java性能优化权威指南-操作系统性能监控
  • FreeRTOS 介绍、使用方法及应用场景
  • redis如何使用IO多路复用
  • 从语言到生态:编程语言在各行业的应用格局与未来演进
  • Data Vault 初探(五) —— 定期装载_SQL
  • 从java角度理解io多路复用和redis为什么使用io多路复用
  • docker启动的rabbitmq搭建并集群和高可用
  • AS32系列MCU芯片I2C模块性能解析与调试
  • 秘塔AI搜索:国产无广告智能搜索引擎,重塑高效信息获取体验
  • 1 Studying《Systems.Performance》1-6
  • 跨域视角下强化学习重塑大模型推理:GURU框架与多领域推理新突破
  • 黑马python(十三)
  • 二刷苍穹外卖 day03
  • K8s入门指南:架构解析浓缩版与服务间调用实战演示