Qt的Modbus协议-RTU从站实现_qt modbus rtu
Qt的Modbus协议-RTU从站实现
- 1、Modbus协议
- 3、文件
-
- 3,1 头文件
- 3.2 cpp文件
- 4、总结
1、Modbus协议
1.1 modbusRTU 协议格式
功能码:对数据帧的功能编号。
寄存器:存放某类数据的内存区域。一个设备可能有多种寄存器,不同的寄存器存放不同类别的数据。
寄存器地址:某个数据在寄存器里的编号。不同的设备定义不同。
1.2 读寄存器功能码0x03
2、Qt实现Modbus RTU协议
2.1 添加相关的库
QT += core gui serialport serialbus
2.2 添加相关的头文件
#include #include #include #include
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); // 波特率115200 modbusDevice->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 一秒读取一次从机数据
//初始化定时器 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 #include #include #include #include #include #include #include #include #include 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> ®isters); 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(); //显示信息框等待用户交互 //如果用户选择了Ok if(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)) { // 设备地址4 if (!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> ®isters){ // 根据协议文档,每个寄存器内部进行字节交换 quint16 low = swapBytes(registers[0]); quint16 high = swapBytes(registers[1]); //组合为32位整数 // qint32 combined = (static_cast(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\" );}
仓库地址