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

Qt的Modbus协议-RTU从站实现

Qt的Modbus协议-RTU从站实现

  • 1、Modbus协议
    • 1.1 modbusRTU 协议格式
    • 1.2 读寄存器功能码0x03
    • 2、Qt实现Modbus RTU协议
    • 2.1 添加相关的库
    • 2.2 添加相关的头文件
    • 2.3 设置串口参数
    • 2.4 一秒读取一次从机数据
    • 2.5 处理读取数据
    • 2.6 大小端排序
  • 3、文件
    • 3,1 头文件
    • 3.2 cpp文件
  • 4、总结

1、Modbus协议

Modbus协议是一种工业自动化领域广泛应用的串行通信协议,由Modicon公司(现施耐德电气)于1979年推出,现已成为工业设备通信的业界标准13。

1.1 modbusRTU 协议格式

设备地址:设备的通讯地址、站号。

功能码:对数据帧的功能编号。

寄存器:存放某类数据的内存区域。一个设备可能有多种寄存器,不同的寄存器存放不同类别的数据。

寄存器地址:某个数据在寄存器里的编号。不同的设备定义不同。

1.2 读寄存器功能码0x03

在这里插入图片描述

这个意思是设备地址为04,功能码是03,,寄存器起始地址为 0x80,查询数据分别为 0x1234和 0x5678;

2、Qt实现Modbus RTU协议

2.1 添加相关的库

QT       += core gui serialport serialbus

在这里插入图片描述

2.2 添加相关的头文件

#include <QSerialPort>
#include <QtSerialBus>
#include <QModbusDataUnit>
#include <QModbusClient>

在这里插入图片描述

2.3 设置串口参数

// 函数功能:初始化Modbus RTU串行通信连接
void mainInterface::setupModbusConnectxion()
{// 创建Modbus RTU主设备实例modbusDevice = new QModbusRtuSerialMaster(this);// 配置串口通信参数(标准Modbus RTU设置)modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1");  // 使用COM1端口modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud115200);  // 波特率115200modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);  // 8位数据位modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);  // 1位停止位modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);  // 无校验位// 连接错误信号槽(Lambda表达式处理错误)connect(modbusDevice, &QModbusDevice::errorOccurred, [](QModbusDevice::Error error) {qDebug() << "Modbus Error:" << error;  // 打印错误类型(如超时、校验错误等)});// 尝试建立物理连接if (!modbusDevice->connectDevice()) {qDebug() << "无法连接";  // 连接失败(可能端口被占用或参数错误)} else {qDebug() << "成功连接";  // 连接成功,可开始发送Modbus请求}
}

2.4 一秒读取一次从机数据

利用定时器来达到1秒钟读取一次
    //初始化定时器dataTimer = new QTimer(this);connect(dataTimer, &QTimer::timeout, this, &mainInterface::readFlowData);dataTimer->start(1000); // 每秒读取一次
读取数据
    // 创建Modbus数据读取单元// 参数说明:// 1. HoldingRegisters - 读取保持寄存器类型,对应功能码0x03// 2. 4104 - 起始寄存器地址(对应设备文档中的4104或0x1008)// 3. 2 - 读取2个寄存器(32位数据通常需要2个16位寄存器)QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 4104, 2);// 发送读取请求到从设备(设备地址为4)if (auto *reply = modbusDevice->sendReadRequest(readUnit, 4)) {// 检查请求是否已完成(立即完成可能表示错误)if (!reply->isFinished()) {// 连接完成信号到处理槽函数connect(reply, &QModbusReply::finished, this, &mainInterface::handleReadResult);} else {// 立即完成的异常情况,释放reply对象delete reply;}} else {// 发送请求失败,输出错误信息qDebug() << "读取错误: " << modbusDevice->errorString();}
}

2.5 处理读取数据

// 函数功能:处理Modbus读取请求的返回结果
void mainInterface::handleReadResult()
{// 获取发送信号的QModbusReply对象(即触发此槽的reply对象)auto reply = qobject_cast<QModbusReply *>(sender());if (!reply){return;  // 转换失败则直接返回}// 检查Modbus通信是否无错误if (reply->error() == QModbusDevice::NoError) {// 获取返回的寄存器数据单元const QModbusDataUnit unit = reply->result();// 准备存储转换结果的变量QString dataStr;float flowValuee = 0;// 将两个寄存器的值转换为浮点数// 参数说明:// unit.value(0) - 第一个寄存器的值(高16位)// unit.value(1) - 第二个寄存器的值(低16位)// 0 - 可能的转换选项(如字节序)flowValuee = convertToFloat(unit.value(0), unit.value(1), 0);// 在UI上显示带一位小数的流量值ui->flowlabel->setText(QString::number(flowValuee, 'f', 1));}else {// 输出错误信息到调试窗口qDebug() << "读取数据错误" << reply->errorString();}// 安全释放reply对象(Qt的内存管理机制)reply->deleteLater();
}

2.6 大小端排序

/*** @brief 将两个16位寄存器值转换为32位浮点数* @param reg1 第一个寄存器值(高/低16位,取决于字节序)* @param reg2 第二个寄存器值(高/低16位,取决于字节序)* @param isBigEndian 字节序标志:true表示大端模式,false表示小端模式* 大端模式:高字节在低地址,低字节在高地址* 小端模式:低字节在低地址,高字节在高地址* @return 转换后的32位浮点数*/
float mainInterface::convertToFloat(quint16 reg1, quint16 reg2, bool isBigEndian)
{// 使用联合体实现二进制数据到浮点数的类型转换union {quint32 i;  // 32位整型用于存储组合后的寄存器值float f;    // 32位浮点数用于最终输出} converter;// 根据字节序组合两个16位寄存器if (isBigEndian) {// 大端模式:reg1为高16位,reg2为低16位converter.i = (reg1 << 16) | reg2;} else {// 小端模式:reg2为高16位,reg1为低16位converter.i = (reg2 << 16) | reg1;}// 通过联合体直接返回浮点数表示return converter.f;
}

3、文件

3,1 头文件

#ifndef MAININTERFACE_H
#define MAININTERFACE_H#include <QWidget>
#include <QDateTime>
#include <QTimer>
#include <QSerialPort>
#include <QtSerialBus>
#include <QModbusDataUnit>
#include <QModbusClient>
#include <QProcess>
#include <windows.h>
#include <QMessageBox>namespace Ui {
class mainInterface;
}class mainInterface : public QWidget
{Q_OBJECTpublic:explicit mainInterface(QWidget *parent = nullptr);~mainInterface();typedef union /*申明一个联合体*/
{
float dataf;
uint32_t data32;
uint16_t data16[2];
uint8_t data8[4];
} data_t;private slots:void on_exitPushButton_clicked();void readFlowData();void updateDateTime();void handleReadResult();void on_keyboardPushButton_clicked();void on_historyPushButton_clicked();void on_setPushButton_clicked();void on_helpPushButton_clicked();private:Ui::mainInterface *ui;QTimer *dataTimer;QTimer *clockTimer;QModbusClient *modbusDevice;void setupModbusConnection();float parseFloatFromRegisters(const QVector<quint16> &registers);quint16 swapBytes(quint16 value);QProcess *keyboardProcess;bool keyboardVisible;float convertToFloat(quint16 reg1, quint16 reg2, bool isBigEndian);};#endif // MAININTERFACE_H

3.2 cpp文件

#include "maininterface.h"
#include "ui_maininterface.h"mainInterface::mainInterface(QWidget *parent) :QWidget(parent),ui(new Ui::mainInterface)
{ui->setupUi(this);keyboardVisible = false;this->setWindowFlag(Qt::FramelessWindowHint);           //去边框//    this->setAttribute(Qt::WA_TranslucentBackground);       //半透明背景//初始化定时器dataTimer = new QTimer(this);connect(dataTimer, &QTimer::timeout, this, &mainInterface::readFlowData);dataTimer->start(1000); // 每秒读取一次//更新时间clockTimer = new QTimer(this);connect(clockTimer, &QTimer::timeout, this, &mainInterface::updateDateTime);clockTimer->start(1000); // 每秒更新一次时间// 初始化键盘进程keyboardProcess = new QProcess(this);keyboardVisible = false;//初始化Modbus连接setupModbusConnection();//初始化时间显示updateDateTime();}mainInterface::~mainInterface()
{delete ui;
}//退出
void mainInterface::on_exitPushButton_clicked()
{QMessageBox quitMes;                        //创建退出弹窗对象quitMes.setWindowTitle("关闭界面");          //弹窗标题quitMes.setWindowIcon(QIcon(":/widdgetMainInterface/exit.png"));        //设置窗口图标quitMes.setIcon(QMessageBox::Warning);      //弹窗图片quitMes.setText("请确认是否退出");              //弹窗文本quitMes.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);      //设置Ok和Cancle两个按钮quitMes.setButtonText(QMessageBox::Ok, "确认");           //Ok改为确认quitMes.setButtonText(QMessageBox::Cancel,"取消");        //Cancle改为取消int result = quitMes.exec();       //显示信息框等待用户交互//如果用户选择了Okif(result == QMessageBox::Ok){this->close();          //关闭界面}else    //用户取消什么都不做{}this->close();
}//设置串口参数
void mainInterface::setupModbusConnection()
{modbusDevice = new QModbusRtuSerialMaster(this);// 设置Modbus RTU参数modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1");modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,QSerialPort::Baud115200);modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);// 连接错误信号connect(modbusDevice, &QModbusDevice::errorOccurred, [](QModbusDevice::Error error) {qDebug() << "Modbus Error:" << error;});// 尝试连接设备if (!modbusDevice->connectDevice()) {qDebug() << "无法连接";} else {qDebug() << "成功连接";}
}//读取数据
void mainInterface::readFlowData()
{if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState){return;}// 读取流量寄存器 (地址4104-4105, 对应0x1008-0x1009)QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 4104, 2);//    QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 2);if (auto *reply = modbusDevice->sendReadRequest(readUnit, 4)) { // 设备地址4if (!reply->isFinished()) {connect(reply, &QModbusReply::finished, this, &mainInterface::handleReadResult);} else {delete reply;}} else {qDebug() << "读取错误: " << modbusDevice->errorString();}
}//更新时间
void mainInterface::updateDateTime()
{QDateTime currentTime = QDateTime::currentDateTime();QString ymdTimeStr = currentTime.toString("yyyy年MM月dd日");QString hmsTimeStr = currentTime.toString("hh:mm:ss");ui->currentTimeymdlabel->setText(ymdTimeStr);ui->currentTimehmslabel->setText(hmsTimeStr);}//大小端排序
//大端模式:高字节在低地址,低字节在高地址
//小端模式:低字节在低地址,高字节在高地址
float mainInterface::convertToFloat(quint16 reg1, quint16 reg2, bool isBigEndian)
{union {quint32 i;float f;} converter;if (isBigEndian) {converter.i = (reg1 << 16) | reg2;} else {converter.i = (reg2 << 16) | reg1;}return converter.f;
}//处理读取数据
void mainInterface::handleReadResult()
{auto reply = qobject_cast<QModbusReply *>(sender());if (!reply){return;}if (reply->error() == QModbusDevice::NoError) {const QModbusDataUnit unit = reply->result();// 将数据值转换为字符串QString dataStr;float flowValuee = 0;flowValuee = convertToFloat(unit.value(0),unit.value(1),0);// 显示带一位小数的结果ui->flowlabel->setText(QString::number(flowValuee, 'f', 1));}else {qDebug() << "读取数据错误" << reply->errorString();}reply->deleteLater();}//高低字节交换
quint16 mainInterface::swapBytes(quint16 value)
{return ((value << 8) & 0xFF00) | ((value >> 8) & 0x00FF);}//转换数据
float mainInterface::parseFloatFromRegisters(const QVector<quint16> &registers)
{// 根据协议文档,每个寄存器内部进行字节交换quint16 low = swapBytes(registers[0]);quint16 high = swapBytes(registers[1]);//组合为32位整数//    qint32 combined = (static_cast<quint32>(high)<<16) | low;quint32 combined = (qFromBigEndian(high) << 16) | qFromBigEndian(low);// 重新解释为浮点数float result;memcpy(&result, &combined, sizeof(result));return result;}//软键盘
void mainInterface::on_keyboardPushButton_clicked()
{if (!keyboardVisible) {bool started = false;// 方法1: 使用QProcess尝试启动keyboardProcess->start("osk.exe");if (keyboardProcess->waitForStarted(1500)) {keyboardVisible = true;started = true;qDebug() << "软键盘已启动 (QProcess)";}// 方法2: 如果QProcess失败,尝试完整路径if (!started) {QString oskPath = "C:\\Windows\\System32\\osk.exe";keyboardProcess->start(oskPath);if (keyboardProcess->waitForStarted(1500)) {keyboardVisible = true;started = true;qDebug() << "软键盘已启动 (完整路径)";}}// 方法3: 如果前两种方法失败,使用Windows API绕过重定向if (!started) {qDebug() << "尝试使用Windows API启动软键盘";// 禁用文件系统重定向PVOID oldValue;Wow64DisableWow64FsRedirection(&oldValue);// 使用ShellExecute启动HINSTANCE result = ShellExecuteW(0,L"open",L"osk.exe",0,0,SW_SHOWNORMAL);// 恢复文件系统重定向Wow64RevertWow64FsRedirection(oldValue);if (reinterpret_cast<int>(result) > 32) {keyboardVisible = true;started = true;qDebug() << "软键盘已启动 (Windows API)";} else {qDebug() << "Windows API启动失败, 错误码:" << reinterpret_cast<int>(result);}}if (!started) {QMessageBox::warning(this, "错误", "无法启动软键盘。请确保系统支持屏幕键盘(osk.exe)。");}else {// 关闭软键盘if (keyboardProcess->state() == QProcess::Running) {keyboardProcess->terminate();if (keyboardProcess->waitForFinished(1000)) {keyboardVisible = false;qDebug() << "软键盘已关闭";} else {qDebug() << "无法关闭软键盘:" << keyboardProcess->errorString();}} else {keyboardVisible = false;}}}}//历史记录
void mainInterface::on_historyPushButton_clicked()
{QMessageBox::information(this,"历史记录","查看历史记录");
}//设置
void mainInterface::on_setPushButton_clicked()
{QMessageBox::information(this, "系统设置", "系统设置功能");
}//帮助
void mainInterface::on_helpPushButton_clicked()
{QMessageBox::information(this, "帮助", "流量监控系统帮助文档\n\n""1. 系统每秒自动更新流量数据\n""2. 点击键盘图标调用系统软键盘\n");
}

仓库地址

4、总结

以上就是Qt的Modbus协议-RTU从站实现的整个过程了,浏览过程中,如若发现错误,欢迎大家指正,有问题的欢迎评论区留言或者私信。最后,如果大家觉得有所帮助,可以点一下赞,谢谢大家!祝大家天天开心,顺遂无虞!

相关文章:

  • 泰国零售巨头 CJ Express 借助 SAP 内存数据库实现高效数据管理
  • Qt背景平铺
  • AQS独占模式——资源获取和释放源码分析
  • 泰国数码电商系统定制|3C产品详情泰语化+售后管理,适配泰国数码零售
  • 串口输出版UART接收中断程序 (8259端口400H/402H)
  • 韦东奕论文解读
  • 开发者视角:一键拉起功能解析
  • 1.14 express小项目 和 用到的 jwt详解
  • Java并发进阶系列:深度讨论高并发跳表数据结构ConcurrentSkipListMap的源代码实现(上)
  • 磁盘配额管理
  • Git分页器和Node.js常见问题解决方式
  • 为何京东与蚂蚁集团竞相申请稳定币牌照?
  • 1.13使用 Node.js 操作 SQLite
  • 英飞凌亮相SEMICON China 2025:以SiC、GaN技术引领低碳化与数字化未来
  • 【Google Chrome】谷歌浏览器历史版本下载
  • 容器的本质是进程
  • React第六十二节 Router中 createStaticRouter 的使用详解
  • 仪表刻度动态显示控件--小三角指针
  • YOLOV11改进之多尺度扩张残差模块(MS-DRM)
  • 【论文写作参考文献地址】
  • 医院网站建设与管理ppt/百度一下浏览器下载安装
  • 取名算命网站的源代码asp+access/百度app打开
  • 一个网站可以做几个关键词/搜索引擎优化seo专员
  • 昆明做网站建设方案/关键词优化怎么写
  • 做监控的有哪些网站/如何制作一个自己的网站
  • 手机端网站模板下载/seo在线优化排名