0


【Qt Modbus通信】QModbus实现modbus的主机功能 源码分享

前言

modbus在上下位机数据交互时被广泛使用,因此写了这篇笔记和大家一起学习。
【Qt Modbus通信】libmodbus实现modbus的主机功能/从机功能 源码分享
之前使用libmodbus实现了modbus的主从功能,但发现主机查询从机的从机ID不能大于200+,因此参考QT5的modbusDEMO重新写了一份基于QModbus实现的modbus主机功能。

参考文献

在这里插入图片描述

程序执行效果

QT官方DEMO
在这里插入图片描述modbus主机 运行效果在这里插入图片描述

源码下载

https://gitee.com/jiang_bin_yu/QSerialBus-modbus-master/tree/master/

程序源码

下面我将官方DEMO中的关键代码移植出来,实现了modbus主机程序
一、项目配置

QT += core gui serialport serialbus

二、寻找可用串口

voidMainWindow::freshSerialPortCombox(){foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){
        QSerialPort tempSer;
        tempSer.setPort(info);if(tempSer.open(QIODevice::ReadWrite)){
            ui->comboBox_serialName->addItem(tempSer.portName());
            tempSer.close();}}}

三、初始modbus

void MainWindow::MainWindow::InitModbus(){//获取modbus raw 数据帧connect(SaveLog::Instance(),&SaveLog::sigModbusData,this,&MainWindow::onModbusRawData);if(modbusDevice){
        modbusDevice->disconnectDevice();delete modbusDevice;
        modbusDevice =nullptr;}

    modbusDevice =newQModbusRtuSerialMaster(this);connect(modbusDevice,&QModbusClient::errorOccurred,[this](QModbusDevice::Error){qDebug()<<"modbus Error:"<< modbusDevice->errorString();});}

四、串口连接

voidMainWindow::connectModbus(){disconnnectModbus();if(!modbusDevice)return;
    modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ui->comboBox_serialName->currentText());
    modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,parity);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,baud);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,dataBit);
    modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,stopBit);

    modbusDevice->setTimeout(1000);
    modbusDevice->setNumberOfRetries(0);//连接失败if(modbusDevice->connectDevice()){
        ui->pushButton_connect->setText("断开连接");qDebug()<<"连接成功";}else{
        ui->pushButton_connect->setText("建立连接");qDebug()<<"连接失败";QMessageBox::information(NULL,"Title","串口打开失败");}}

五、写入寄存器
a.writeUnit:写入寄存器 slaveId 写入的从机ID startAddress写入的起始地址 values写入的值

voidMainWindow::writeUnit(int slaveId,int startAddress, QList<quint16> values){if(!modbusDevice){QMessageBox::information(NULL,"Title","请先连接设备");return;}connectModbus();
    QModbusDataUnit writeUnit =QModbusDataUnit(QModbusDataUnit::HoldingRegisters,startAddress, values.size());for(int i=0; i<values.size(); i++){
        writeUnit.setValue(i, values.at(i));}//serverEdit 发生给slave的IDif(auto*reply = modbusDevice->sendWriteRequest(writeUnit,slaveId)){if(!reply->isFinished()){connect(reply,&QModbusReply::finished,this,[this, reply](){if(reply->error()== QModbusDevice::ProtocolError){qDebug()<<QString("Write response error: %1 (Mobus exception: 0x%2)").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(),-1,16);}elseif(reply->error()!= QModbusDevice::NoError){qDebug()<<QString("Write response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(),-1,16);}
                reply->deleteLater();});}else{
            reply->deleteLater();}}else{qDebug()<<QString(("Write error: ")+ modbusDevice->errorString());}}

b.writeUnit的调用方法

voidMainWindow::on_pushButton_send_clicked(){
    QList<quint16> values;for(quint16 i=0; i<READNUM; i++){
        values.append(i);}writeUnit(0x01,0,values);//修改从机0x01,0~50寄存器的值}

五、读取寄存器
a.readUnit:读取寄存器 slaveId 读取的从机ID startAddress读取的起始地址 readNum读取的数量

voidMainWindow::readUnit(int slaveId,int startAddress,int readNum){if(!modbusDevice){QMessageBox::information(NULL,"Title","请先连接设备");return;}connectModbus();
    QMutexLocker lock(&m_modbusMutex);if(auto*reply = modbusDevice->sendReadRequest(QModbusDataUnit(QModbusDataUnit::HoldingRegisters, startAddress, readNum), slaveId)){if(!reply->isFinished())connect(reply,&QModbusReply::finished,this,&MainWindow::onReadReady);elsedelete reply;}else{qDebug()<<"Read error: "<< modbusDevice->errorString();}}

b.接受modbus返回数据

voidMainWindow::onReadReady(){auto reply =qobject_cast<QModbusReply *>(sender());if(!reply)return;if(reply->error()== QModbusDevice::NoError){const QModbusDataUnit unit = reply->result();if(unit.valueCount()== READNUM)for(uint i =0; i < unit.valueCount(); i++){const QString entry =tr("Address: %1, Value: %2").arg(unit.startAddress()+ i).arg(QString::number(unit.value(i),
                                             unit.registerType()<= QModbusDataUnit::Coils ?10:16));
                ui->textBrowser->append(entry);}}elseif(reply->error()== QModbusDevice::ProtocolError){qDebug()<<QString("Read response error: %1 (Mobus exception: 0x%2)").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(),-1,16);}else{qDebug()<<QString("Read response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(),-1,16);}
    reply->deleteLater();}

六、如果你想获得你发送和接受的modbus数据帧,可以参考下我下面这个愚蠢的办法
a.设置日志过滤器,开启关于qt.modbus*的日志打印

QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = true"));

开启后你的调试栏讲会打印modbus相关的内容在这里插入图片描述
b.如果你想获取这些内容并发送给你的界面,你需要开启日志重定向,即装载新的日志钩子(详细请参考上面码云提供的源码 代码在:QModbusDemo/savelog.cpp)

//日志重定向#if(QT_VERSION <=QT_VERSION_CHECK(5,0,0))voidLog(QtMsgType type,constchar*msg)#elsevoidLog(QtMsgType type,const QMessageLogContext &,const QString &msg)#endif{//加锁,防止多线程中qdebug太频繁导致崩溃static QMutex mutex;
    QMutexLocker locker(&mutex);
    QString content;//这里可以根据不同的类型加上不同的头部用于区分switch(type){case QtDebugMsg:
        content =QString("%1").arg(msg);break;case QtWarningMsg:
        content =QString("QtWarningMsg: %1").arg(msg);break;case QtCriticalMsg:
        content =QString("QtCriticalMsg: %1").arg(msg);break;case QtFatalMsg:
        content =QString("QtFatalMsg: %1").arg(msg);break;default:break;}SaveLog::Instance()->save(content);}//安装日志钩子,输出调试信息到文件,便于调试voidSaveLog::start(){#if(QT_VERSION <=QT_VERSION_CHECK(5,0,0))qInstallMsgHandler(Log);#elseqInstallMessageHandler(Log);#endif}

c.在日志重定向中,拦截(RTU client) Sent Serial ADU:和(RTU client) Received ADU:关键字的日志,并将其内容截取发送给mainwindow

voidSaveLog::save(const QString &content){if(1){
        QString msg =static_cast<QString>(content);
        std::cout << msg.toLocal8Bit().data()<< std::endl;//方法改进:之前每次输出日志都打开文件,改成只有当日期改变时才新建和打开文件if(content.contains("(RTU client) Sent Serial ADU:")){//(RTU client) Sent Serial ADU: 0xfe0300000029901b//std::cout << "SendMsg" << msg.toUtf8().data() << std::endl;
            msg = msg.remove(msg.indexOf("(RTU client) Sent Serial ADU:"),32);

            msg = msg.left(msg.size());
            msg = msg.toUpper();int n = msg.length();while(n-2>0){
                n = n -2;
                msg.insert(n," ");}
            emit sigModbusData(msg,0);//std::cout << "SendMsg:  " << msg.toLocal8Bit().data() << std::endl;}elseif(content.contains("(RTU client) Received ADU:")){//std::cout << "RecvMsg" << msg.toLocal8Bit().data() << std::endl;
            msg = msg.remove(msg.indexOf("(RTU client) Received ADU:"),28);
            msg = msg.left(msg.size()-1);
            msg = msg.toUpper();int n = msg.length();while(n-2>0){
                n = n -2;
                msg.insert(n," ");}
            emit sigModbusData(msg,1);//std::cout << "Received:  " << msg.toLocal8Bit().data() << std::endl;}elseif(!content.contains("(RTU client)")){if(this->fileName.isEmpty()){
                QString fileName =QString("%1/Log/%2_log_%3.txt").arg(path).arg(name).arg(QDATETIME);if(this->fileName != fileName){this->fileName = fileName;if(file->isOpen()){
                        file->close();}

                    file->setFileName(fileName);
                    file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);}}
            QTextStream logStream(file);
            logStream <<QString("%1--Debug     %2").arg(QDATETIME_zzz).arg(msg)<<"\n";}}}

d.绑定信号与槽,将接受到的modbus数据帧显示在textBrowser上

//获取modbus raw 数据帧connect(SaveLog::Instance(),&SaveLog::sigModbusData,this,&MainWindow::onModbusRawData);//获取modbus数据帧 0发送的数据 1接受的数据voidMainWindow::onModbusRawData(QString data,int type){
    QString color;
    QString text;if(type ==0)//发送{
        text ="发送数据帧:";
        color ="#F37257";}elseif(type ==1)//接受{
        text ="接受数据帧:";
        color ="#1777D7";}// 设置文字(样式+内容)
    QString  str =QString("<font color=\"%1\">"+QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss ")+ text + data +"</font>").arg(color);
    ui->textBrowser->append(str);}

程序效果如下图所示

在这里插入图片描述

标签: qt ui 开发语言

本文转载自: https://blog.csdn.net/qq_37373742/article/details/125974053
版权归原作者 jbyyy、 所有, 如有侵权,请联系我们删除。

“【Qt Modbus通信】QModbus实现modbus的主机功能 源码分享”的评论:

还没有评论