【QT问题解决】QT Modbus rtu 拖动主界面时modbus的槽函数无法响应的解决方案

发布于:2024-06-21 ⋅ 阅读:(76) ⋅ 点赞:(0)

问题 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 <QMainWindow>
#include <QSerialPort>
#include <QtSerialBus>
#include <QModbusDataUnit>
#include <QModbusClient>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QDebug>
#include <QSettings>
#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();

}

}