问题 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();
}
}