• QT modbus rtu 拖动主界面时,modbus的槽函数无法响应


    问题 Modbus放在主线程,界面事件会阻塞信号传输

    在使用QT5.14.2时 使用QT自带的QModbusClient类实现对一个力传感器的数据读取。本人为了测试就将modbus读取逻辑等都写在主线程中,但是本人采用modbus异步通讯的方式,给从站发一个读取数据的信号(sendReadRequest),然后使用信号槽等待从站回复数据(readSerialForceData)。这种方式的好处是不会阻塞主线程。

    但是!本人发现一个问题,就是在我拖动主界面的时候,modbus通讯就停止了!当我松开鼠标的时候,modbus正常通讯!目前还不知道为什么会这样,猜测是鼠标事件影响了modbus的信号槽机制。但是我试了一下,其他的槽函数都是能正常触发的,比如定时器之类。在我拖动界面的时候,UI界面也可以正常的刷新。只有modbus被阻塞了。

    void MainWindow::on_readRequest()
    {
        // 读取寄存器
        QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters,0,1);
        if (auto *reply = modbusDevice->sendReadRequest(readUnit, 1))
        {
            if (!reply->isFinished())
                connect(reply, &QModbusReply::finished, this, &MainWindow::readSerialForceData);
            else
                delete reply; // broadcast replies return immediately
        }
        else
        {
            //底部状态栏显示
            ui->statusBar->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
        }
    }
    
    void MainWindow::readSerialForceData()
    {
    
        auto reply = qobject_cast<QModbusReply *>(sender());
        if (!reply)
            return;
    
        if (reply->error() == QModbusDevice::NoError)
        {
            const QModbusDataUnit unit = reply->result();
            //遍历数据单元的值
            for (int i = 0, total = int(unit.valueCount()); i < total; ++i)
            {
                qint16 sValue = static_cast<qint16>(unit.value(i));
                forceData_Temp = sValue/10.0;
                const QString entry = tr("Address: %1, Value: %2").arg(unit.startAddress() + i).arg(QString::number(sValue));
                ui->textBrowser_Log->append(entry);
            }
        }
        else if (reply->error() == QModbusDevice::ProtocolError)
        {
            statusBar()->showMessage(tr("Read response error: %1 (Mobus exception: 0x%2)").
                                     arg(reply->errorString()).
                                     arg(reply->rawResult().exceptionCode(), -1, 16), 5000);
        }
        else
        {
            statusBar()->showMessage(tr("Read response error: %1 (code: 0x%2)").
                                     arg(reply->errorString()).
                                     arg(reply->error(), -1, 16), 5000);
        }
    
        reply->deleteLater();
    }
    

    解决方案 将modbus放在子线程实现

    经过测试,发现将modbus放在子线程中,通过信号槽与主线程进行通讯,将数据保存成静态变量或者全局变量,即可解决问题。

    这里新建一个serialThread 类用
    .h文件

    class serialThread : public QObject
    {
        Q_OBJECT
    public:
        serialThread(QObject *parent = Q_NULLPTR);
        ~serialThread();
    
        QModbusClient *modbusDevice = nullptr;
    
        QDateTime startTime;
        QDateTime endTime;
        double intervalTime = 0;//串口数据间隔时间
        double intervalTime_Last=0;
        //数据采集定时器
        QTimer *pollTimer;//串口数据读取定时器
    
    signals:
        void sig_connectSerialFinished(bool flag);
        void sig_disconnectSerialFinished(bool flag);
    
    public slots:
        void startThread();
        void on_connectSerial();
        void on_disconnectSerial();
        void on_startReadData();
        void on_stopReadData();
        void on_readRequest();
        void on_readRequestFinished();
    };
    
    #endif // SERIALTHREAD_H
    

    .cpp文件

    #include "serialthread.h"
    
    serialThread::serialThread(QObject *parent ) : QObject(parent)
    {
        qDebug()<<"串口线程构造函数:"<<QThread::currentThreadId();
    }
    
    serialThread::~serialThread()
    {
        //退出串口
        if (modbusDevice)
        {
            modbusDevice->disconnectDevice();
        }
    
        delete modbusDevice;
    
    }
    
    void serialThread::startThread()
    {
        qDebug()<<"串口线程成员函数 startThread :"<<QThread::currentThreadId();
        modbusDevice = new QModbusRtuSerialMaster(this);
    }
    
    void serialThread::on_connectSerial()
    {
    
        qDebug()<<"串口线程成员函数 on_connectSerial :"<<QThread::currentThreadId();
        QMutexLocker locker(&ForceSensorGlobal::mutex);  // 锁定互斥锁
    
        //设置串口号
        modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ForceSensorGlobal::SeriComName);
        //设置波特率
        modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,ForceSensorGlobal::BaudRate);
        //设置数据位
        modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,ForceSensorGlobal::DataBits);
        //设置奇偶检验
        switch(ForceSensorGlobal::ParityID)
        {
        case 0: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);break;
        case 1: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::EvenParity);break;
        case 2: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::OddParity);break;
        case 3: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::SpaceParity);break;
        case 4: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::MarkParity);break;
        default: break;
        }
        //设置停止位
        switch(ForceSensorGlobal::StopBitsID)
        {
        case 0: modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);break;
        case 1: modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneAndHalfStop);break;
        case 2: modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::TwoStop);break;
        default: break;
        }
        //设置执行请求时间
        modbusDevice->setTimeout(1000);
        //设置执行请求次数
        modbusDevice->setNumberOfRetries(3);
    
        //连接串口 并发送成功与否标志
        emit sig_connectSerialFinished(modbusDevice->connectDevice());
    
        pollTimer = new QTimer;
        pollTimer->setTimerType(Qt::PreciseTimer);//精确定时
        pollTimer->setInterval(ForceSensorGlobal::IntervalTime);
        connect(pollTimer,&QTimer::timeout, this, &serialThread::on_readRequest);
    
    }
    
    void serialThread::on_disconnectSerial()
    {
        modbusDevice->disconnectDevice();
        emit sig_disconnectSerialFinished(true);
    
    }
    
    void serialThread::on_startReadData()
    {
        QMutexLocker locker(&ForceSensorGlobal::mutex);  // 锁定互斥锁
    
        //清空一下数据 方便再次存储
        ForceSensorGlobal:: recordData.clear();
        QVector<double> vectorTemp;
        for (int i = 0; i < 2; i++)
        {
            ForceSensorGlobal::recordData.append(vectorTemp);
        }
        startTime = QDateTime::currentDateTime();//获取开始时间
        pollTimer->setInterval(ForceSensorGlobal::IntervalTime);
        pollTimer->start();
        qDebug() <<"子线程 on_startReadData ";
    }
    
    void serialThread::on_stopReadData()
    {
        pollTimer->stop();
        qDebug() <<"子线程 on_stopReadData ";
    }
    
    void serialThread::on_readRequest()
    {
        // 读取寄存器
        QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters,0,1);
        if (auto *reply = modbusDevice->sendReadRequest(readUnit, 1))
        {
            if (!reply->isFinished())
                connect(reply, &QModbusReply::finished, this, &serialThread::on_readRequestFinished);
            else
                reply->deleteLater(); // broadcast replies return immediately
        }
        else
        {
            //读取错误
            qDebug() <<"读取错误: " <<modbusDevice->errorString();
        }
    }
    
    void serialThread::on_readRequestFinished()
    {
        auto reply = qobject_cast<QModbusReply *>(sender());
        if (!reply)
            return;
    
    
        QMutexLocker locker(&ForceSensorGlobal::mutex);  // 锁定互斥锁
        if (reply->error() == QModbusDevice::NoError)
        {
            const QModbusDataUnit unit = reply->result();
            //遍历数据单元的值
            for (int i = 0, total = int(unit.valueCount()); i < total; ++i)
            {
                //接受信号的时间
                endTime = QDateTime::currentDateTime();
                intervalTime = startTime.msecsTo(endTime) / 1000.0;
    
                double now = (intervalTime - intervalTime_Last)*1000.0;
                qDebug() <<now;
                intervalTime_Last = intervalTime;
    
                //收到的数据是uint16 需转换为int16
                qint16 sValue = static_cast<qint16>(unit.value(i));
                //传感器默认有一位小数,需除以10
                double forceData_Temp = sValue/10.0;
                //将时间 力数据存在到全局变量中
                ForceSensorGlobal::recordData[0].append(intervalTime);
                ForceSensorGlobal::recordData[1].append(forceData_Temp);
    
            }
        }
        else if (reply->error() == QModbusDevice::ProtocolError)
        {
            //接收到的响应信息是协议错误
            qDebug() <<"接收到的响应信息是协议错误: " <<modbusDevice->errorString();
        }
        else
        {
            //接收到的响应信息是其他错误
            qDebug() <<"接收到的响应信息是其他错误: " <<modbusDevice->errorString();
        }
    
        reply->deleteLater();
    }
    
    

    在主线程中通过moveToThread,将modbus放入子线程

    主界面.h文件

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "forcesensorglobal.h"
    #include "qcustomplot.h"
    #include "CurvePlot.h"
    #include "serialthread.h"
    
    namespace Ui {
    class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
        /*************串口通讯线程***************/
        //串口线程
        QThread * mySerialThread;
        //串口操作类
        serialThread  *mySerialModbus;
    
        /***************定时器**********************/
        QTimer *plotTimer;//
        //UI界面初始化
        void initUI();
        //搜索串口
        void SearchSerialPorts();
    
    signals:
        void sig_connectSerial();
        void sig_disconnectSerial();
        void sig_startReadData();
        void sig_stopReadData();
    
    private slots:
        void on_plotTimerTimeOut();
        void on_pBtn_connectSerial_clicked();
        void on_pBtn_disconnectSerial_clicked();
        //接受子线程 modbus串口连接完成信号
        void on_connectSerialFinished(bool flag);
        //接受子线程 modbus串口断开完成信号
        void on_disconnectSerialFinished(bool flag);
        void on_pBtn_refreshSerial_clicked();
        void on_pBtn_startRead_clicked();
        void on_pBtn_stopRead_clicked();
        void on_pBtn_sendData_clicked();
    private:
        Ui::MainWindow *ui;
    };
    
    #endif // MAINWINDOW_H
    

    主界面.cpp文件

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        initUI();
        /*创建串口线程*/
        mySerialModbus = new serialThread();
        mySerialThread = new QThread;
        mySerialModbus->moveToThread(mySerialThread);
        //线程开始
        mySerialThread->start();
        connect(mySerialThread, &QThread::started, mySerialModbus, &serialThread::startThread);
        connect(mySerialThread, &QThread::finished, mySerialModbus, &QObject::deleteLater);//终止线程时要调用deleteLater槽函数
        connect(mySerialThread, &QThread::finished, mySerialModbus, &QObject::deleteLater);
    
        /*绘图刷新定时器*/
        plotTimer = new QTimer;
        plotTimer->setInterval(100);
        connect(plotTimer,&QTimer::timeout, this, &MainWindow::on_plotTimerTimeOut);
    
        /*****************信号槽*********************************/
        //主线->子线程 串口连接信号
        connect(this, &MainWindow::sig_connectSerial, mySerialModbus, &serialThread::on_connectSerial);
        //主线->子线程 串口断开信号
        connect(this, &MainWindow::sig_disconnectSerial, mySerialModbus, &serialThread::on_disconnectSerial);
        //主线->子线程 开始采集信号
        connect(this, &MainWindow::sig_startReadData, mySerialModbus, &serialThread::on_startReadData);
        //主线->子线程 停止采集信号
        connect(this, &MainWindow::sig_stopReadData, mySerialModbus, &serialThread::on_stopReadData);
        //子线程->主线 串口连接完成信号
        connect(mySerialModbus, &serialThread::sig_connectSerialFinished,this,&MainWindow::on_connectSerialFinished);
        //子线程->主线 串口断开完成信号
        connect(mySerialModbus, &serialThread::sig_disconnectSerialFinished,this,&MainWindow::on_disconnectSerialFinished);
    
    void MainWindow::on_pBtn_connectSerial_clicked()
    {
        //更新数据
        ForceSensorGlobal::SeriComName = ui->cBox_SeriComName->currentText();
        ForceSensorGlobal::BaudRate = ui->cBox_BaudRate->currentText().toInt();
        ForceSensorGlobal::DataBits = ui->cBox_DataBits->currentText().toInt();
        ForceSensorGlobal::ParityID = ui->cBox_Parity->currentIndex();
        ForceSensorGlobal::StopBitsID = ui->cBox_StopBits->currentIndex();
        ForceSensorGlobal::IntervalTime = ui->sBox_Interval->value();
    
        emit sig_connectSerial();
        qDebug()<<"主线程 sig_connectSerial :"<<QThread::currentThreadId();
    
    }
    
    void MainWindow::on_pBtn_disconnectSerial_clicked()
    {
        on_pBtn_stopRead_clicked();
        emit sig_disconnectSerial();
    }
    
    void MainWindow::on_connectSerialFinished(bool flag)
    {
        if (flag)
        {
            // 设置控件可否使用
            ui->pBtn_connectSerial->setEnabled(false);
            ui->pBtn_refreshSerial->setEnabled(false);
            ui->pBtn_disconnectSerial->setEnabled(true);
        }
        else    //打开失败提示
        {
    
            QMessageBox::information(this,tr("错误"),tr("连接从站失败!"),QMessageBox::Ok);
        }
    }
    
    void MainWindow::on_disconnectSerialFinished(bool flag)
    {
        if (flag)
        {
            // 设置控件可否使用
            ui->pBtn_connectSerial->setEnabled(true);
            ui->pBtn_refreshSerial->setEnabled(true);
            ui->pBtn_disconnectSerial->setEnabled(false);
        }
        else
        {
            QMessageBox::information(this,tr("错误"),tr("断开连接失败!"),QMessageBox::Ok);
        }
    
    }
    
    void MainWindow::on_pBtn_refreshSerial_clicked()
    {
        //填充串口号组合框
        SearchSerialPorts();
    }
    
    void MainWindow::on_pBtn_startRead_clicked()
    {
        ForceSensorGlobal::IntervalTime = ui->sBox_Interval->value();
        emit sig_startReadData();
        plotTimer->start();
    
    }
    
    void MainWindow::on_pBtn_stopRead_clicked()
    {
        plotTimer->stop();
        emit sig_stopReadData();
    
    }
    
    }
    
  • 相关阅读:
    CrownCAD 2022 特征操作及编辑
    一文看懂推荐系统:物品冷启02:简单的召回通道
    springboot+vue+elementui毕业生就业信息招聘网站java501
    文盘 Rust -- tokio 绑定 cpu 实践
    【Spring框架】Spring监听器的源码分析
    loganalyzer 展示数据库中的日志
    Win10 系统下VisualStudio2019 配置Open3D-0.15.2(C++)
    信德新材深交所上市:市值109亿 尹洪涛父子为实控人
    【NLP】第 3 章:NLP 和 文本Embeddings
    熟悉Redis命令行
  • 原文地址:https://blog.csdn.net/qq_42011369/article/details/139751746