QSerialPort只能在实例化的线程调用,如果跨线程调用QSerialPort,则会报错,类似于Socket notifiers cannot be enabled or disabled from another thread。
为什么QSerialPort,QTcpSokcet,QModbusClient等等IO类会出现跨线程报错呢,因为当他们被实例化的时候,它们就处于实例化它们的事件循环里了,如果跨线程,也就是是跨事件循环是不被允许的。
一个QSerialPort,QTcpSokcet,QModbusClient等等IO类被motothread另外一个线程了,相当于处于它处于另外一个事件循环里。
QT有2种方法可以跨线程调用对象,一种是使用信号槽方式,一种使用QMetaObject::invokeMethod方式。
本文的例子使用的是QMetaObject::invokeMethod的方式,当然也可以改成使用信号槽的方式。
下面是写数据的同步异步调用的代码,onWriteData是调用对象的槽函数,异步ASync使用的是Qt::QueuedConnection方法,同步Sync使用的是 Qt::BlockingQueuedConnection。Qt::BlockingQueuedConnection的作用是阻塞发送方的运行直到接收方执行完毕。
bool SerialportCtrl::write_data(QString connName, QString strSend, bool isHex, int msTimeOut, bool bAsync)
{
bool ret = true;
if (bAsync)
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::QueuedConnection,
Q_ARG(QString, connName),
Q_ARG(QString, strSend),
Q_ARG(bool, isHex),
Q_ARG(int, msTimeOut),
Q_ARG(bool, bAsync));
}else
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(QString, connName),
Q_ARG(QString, strSend),
Q_ARG(bool, isHex),
Q_ARG(int, msTimeOut),
Q_ARG(bool, bAsync));
}
return ret;
}
TSerialPort.h
/*************************************************
*File name :NetWorkCtrl
*Author :luo jinqu
*Version :1.0.1
*Date : 2022.06.07
*Description : 继承QSerialPort实现的串口通讯类TSerialPort
*History :
*1. Date :
*Author :
*Modification :
*************************************************/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "./include/Common.h"
#include "./System/SystemConfig.h"
using namespace Common;
class TSerialPort : public QSerialPort
{
Q_OBJECT
public:
TSerialPort(QObject *parent = Q_NULLPTR);
~TSerialPort();
signals :
void sig_finished ();
void sig_recvMsg (QString name, QString strRecv);
private slots:
void onErrorOccurred (QSerialPort::SerialPortError error);
void onReadyRead ();
public :
bool isOpened();
void setSerialInfo (TSeriaPortInfo info);
void setRecvHexData(bool bHexData = false) { m_bRecvHexData = bHexData; };
public slots:
bool onOpenPort (QString connName);
void onClosePort (QString connName);
bool onWriteData (QString connName,QString strSend,bool isHex=false, int msTimeout = 3000, bool bAsync = true);
bool onReadData (QString connName,QString& strRecv,int msTimeout = 3000);
bool onWriteReadData (QString connName,QString strSend, QString& strRecv, bool isHex = false, int msTimeout = 3000);
private:
bool wait_result_sync(int msTimeout = 3000); //等待同步结果的返回
private:
TSeriaPortInfo m_SerialInfo;
QMutex* m_pRecursiveMutex = nullptr;
QThread* m_pThread = nullptr;
private:
bool m_bRecvHexData = false;
QString m_temporayRecvData;
QString m_strRecv;
QSharedPointer m_recevieTimer = NULL;
private slots:
void on_receive_allData();
private:
//将qstring转16进制
QByteArray QStringHexToByteArrayHex(QString src);
};
TSerialPort.cpp
#include "TSerialPort.h"
#include
#include
TSerialPort::TSerialPort(QObject *parent)
:QSerialPort(parent)
{
connect(this, &QSerialPort::readyRead, this, &TSerialPort::onReadyRead);
connect(this, &QSerialPort::errorOccurred, this, &TSerialPort::onErrorOccurred);
m_pRecursiveMutex = new QMutex(QMutex::Recursive);
m_pThread = new QThread;
this->moveToThread(m_pThread);
m_pThread->start();
}
TSerialPort::~TSerialPort()
{
}
bool TSerialPort::isOpened()
{
return this->isOpen();
}
void TSerialPort::setSerialInfo(TSeriaPortInfo info)
{
m_SerialInfo = info;
}
bool TSerialPort::onOpenPort(QString connName)
{
QMutexLocker locker(m_pRecursiveMutex);
if (connName != m_SerialInfo.name) return false;
QSerialPort::BaudRate _baudrate;
QSerialPort::Parity _Parity;
QSerialPort::DataBits _databits;
QSerialPort::StopBits _stopbits;
_baudrate = (QSerialPort::BaudRate)m_SerialInfo.baudrate;
_databits = (QSerialPort::DataBits)m_SerialInfo.databits;
QMetaEnum metaParity = QMetaEnum::fromType();
_Parity = (QSerialPort::Parity) metaParity.keyToValue(m_SerialInfo.parity.toStdString().c_str());
if (m_SerialInfo.stopbits == "1")
{
_stopbits = QSerialPort::StopBits::OneStop;
}
else if (m_SerialInfo.stopbits == "1.5")
{
_stopbits = QSerialPort::StopBits::OneAndHalfStop;
}
else if (m_SerialInfo.stopbits == "2")
{
_stopbits = QSerialPort::StopBits::TwoStop;
}
else
{
_stopbits = QSerialPort::StopBits::UnknownStopBits;
}
this->setPortName(m_SerialInfo.comNO);
if (this->open(QIODevice::ReadWrite))
{
this->setReadBufferSize(1024);
if (!this->setBaudRate(_baudrate)) return false;
if (!this->setDataBits(_databits)) return false;
if (!this->setParity(_Parity)) return false;
if (!this->setStopBits(_stopbits)) return false;
if (!this->setFlowControl(QSerialPort::NoFlowControl)) return false;
this->setFlowControl(QSerialPort::NoFlowControl); //设置流控制
qInfo() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 打开成功")
.arg(m_SerialInfo.name)
.arg(m_SerialInfo.comNO)
.arg(m_SerialInfo.baudrate)
.arg(m_SerialInfo.databits)
.arg(m_SerialInfo.parity)
.arg(m_SerialInfo.stopbits);
return true;
}
return false;
}
void TSerialPort::onClosePort(QString connName)
{
QMutexLocker locker(m_pRecursiveMutex);
if (connName != m_SerialInfo.name) return ;
if (isOpened())
{
this->clear();
this->close();
}
}
bool TSerialPort::onWriteData(QString connName, QString strSend, bool isHex, int msTimeout, bool bAsync)
{
QMutexLocker locker(m_pRecursiveMutex);
if (connName != m_SerialInfo.name) return false;
this->readAll(); //发送之前,清空接收缓冲
if (isHex)
{
QByteArray hexBuf = QStringHexToByteArrayHex(strSend);
this->write(hexBuf);
}
else
{
QByteArray sendByteArray = strSend.toLatin1(); //必须分开写
char* data = new char[sendByteArray.size() + 1];
strcpy_s(data, sendByteArray.size() + 1, sendByteArray.data());
this->write(data);
FREE_ANY(data);
}
bool bWriteSucceed = true;
if (!bAsync)
{
bWriteSucceed = this->waitForBytesWritten(msTimeout);
}
return bWriteSucceed;
}
bool TSerialPort::onReadData(QString connName, QString& strRecv, int msTimeout)
{
QMutexLocker locker(m_pRecursiveMutex);
if (connName != m_SerialInfo.name) return false;
bool rtn = true;
rtn = wait_result_sync(msTimeout);
if (rtn)
{
strRecv = m_strRecv;
m_strRecv.clear();
}
if (strRecv.isEmpty())
{
return false;
}
return true;
}
bool TSerialPort::onWriteReadData(QString connName, QString strSend, QString& strRecv, bool isHex, int msTimeout)
{
//异步写入,同步接收
onWriteData(connName, strSend, isHex, msTimeout, true);
return onReadData(connName, strRecv, msTimeout);
}
void TSerialPort::onReadyRead()
{
m_recevieTimer.reset(new QTimer);
connect(m_recevieTimer.data(), &QTimer::timeout, this, &TSerialPort::on_receive_allData);
m_recevieTimer.data()->start(500);
QByteArray byteArray = this->readAll();
if (m_bRecvHexData)
{
QString hexString = byteArray.toHex();
m_temporayRecvData.append(hexString);
}
else
{
m_temporayRecvData.append(QString(byteArray));
}
}
void TSerialPort::on_receive_allData()
{
m_recevieTimer.data()->stop();
m_strRecv = m_temporayRecvData;
m_temporayRecvData.clear();
emit sig_finished();
emit sig_recvMsg(m_SerialInfo.name, m_strRecv);
}
bool TSerialPort::wait_result_sync(int msTimeout)
{
bool ret = false;
QTimer timer;
QEventLoop eventLoop;
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit, Qt::DirectConnection);
connect(this, &TSerialPort::sig_finished, &eventLoop, &QEventLoop::quit, Qt::DirectConnection);
timer.start(msTimeout);
eventLoop.exec();
if (!timer.isActive())
{
qCritical() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 超时")
.arg(m_SerialInfo.name)
.arg(m_SerialInfo.comNO)
.arg(m_SerialInfo.baudrate)
.arg(m_SerialInfo.databits)
.arg(m_SerialInfo.parity)
.arg(m_SerialInfo.stopbits);
ret = false;
}
else
{
timer.stop();
ret = true;
}
return ret;
}
void TSerialPort::onErrorOccurred(QSerialPort::SerialPortError error)
{
if (error != QSerialPort::NoError)
{
qWarning() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 发生错误:%7")
.arg(m_SerialInfo.name)
.arg(m_SerialInfo.comNO)
.arg(m_SerialInfo.baudrate)
.arg(m_SerialInfo.databits)
.arg(m_SerialInfo.parity)
.arg(m_SerialInfo.stopbits)
.arg(this->errorString());
}
}
QByteArray TSerialPort::QStringHexToByteArrayHex(QString src)
{
return QByteArray::fromHex(src.toLatin1());
}
下面是SerialportCtrl类,用于管理所有的串口类的。
/*************************************************
*File name :NetWorkCtrl
*Author :luo jinqu
*Version :1.0.1
*Date : 2022.06.17
*Description :
SerialportCtrl为一个单例类,SerialportCtrl类使用QMap管理多个TSerialPort类,使用一个QString作为TSerialPort的标识,此QString是TSeriaPortInfo的name.
SerialportCtrl提供了串口读写和连接的同步异步函数,函数中使用QMetaObject::invokeMethod 来进行同步异步线程调用
参数Qt::DirectConnection 同一线程ID下同步
参数Qt::QueuedConnection 线程ID下异步
参数Qt::BlockingQueuedConnection 不同线程ID下的同步.信号和槽函数不能处于同一线程,否则死锁。
槽函数会在槽函数所处线程执行,但信号线程阻塞,等待槽函数执行完毕。
注意:QT所有的IO类都不能在不同的线程中调用,否则会报错Socket notifiers cannot be enabled or disabled from another thread。
所以用QMetaObject::invokeMethod进行跨线程调用TSerialPort
*History :
*1. Date :
*Author :
*Modification :
*************************************************/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "./System/SystemConfig.h"
#include "./include/Common.h"
using namespace Common;
#include"TSerialPort.h"
/*单例模式,返回SerialportCtrl的唯一静态指针*/
#define SERIALPORTCTRL_INSTANCE SerialportCtrl::instance()
class SerialportCtrl : public QObject
{
Q_OBJECT
public:
static SerialportCtrl* instance();
~SerialportCtrl();
private:
static std::once_flag m_initFlag;
static std::unique_ptr m_pInstance;
SerialportCtrl(QObject *parent = nullptr);
signals:
void sig_recvSerialMsg(QString name, QString msg);
public slots:
void initWork();
void exitWork();
public:
bool isOpened (QString connName);
bool open_port (QString connName, bool bAsync=true);
void close_port (QString connName);
bool write_data (QString connName, QString strSend, bool isHex = false, int msTimeOut = 3000, bool bAsync = true);
bool read_data (QString connName, QString& strRecv, int msTimeOut = 3000);
bool write_read_data (QString connName, QString strSend, QString& strRecv, bool isHex = false, int msTimeOut = 3000);
void setRecvHexData (QString connName, bool bHexData = false);
private:
QVector m_vecSerialInfo;
QMap m_mapSerialPort;
bool init_open_serial(TSeriaPortInfo info);
};
#include "SerialportCtrl.h"
#include
std::once_flag SerialportCtrl::m_initFlag;
std::unique_ptr SerialportCtrl::m_pInstance = nullptr;
SerialportCtrl::SerialportCtrl(QObject* parent)
:QObject(parent)
{
}
SerialportCtrl::~SerialportCtrl()
{
}
SerialportCtrl* SerialportCtrl::instance()
{
std::call_once(m_initFlag, [&]() {m_pInstance = std::unique_ptr(new SerialportCtrl()); });
return m_pInstance.get();
}
void SerialportCtrl::initWork()
{
SYSTEM_CONFIG_INSTANCE->getSerialPortInfo(m_vecSerialInfo);
for (int i = 0; i < m_vecSerialInfo.size(); i++)
{
init_open_serial(m_vecSerialInfo[i]);
}
}
bool SerialportCtrl::init_open_serial(TSeriaPortInfo info)
{
TSerialPort* pSerialPort = new TSerialPort;
connect(pSerialPort, &TSerialPort::sig_recvMsg, this, &SerialportCtrl::sig_recvSerialMsg);
pSerialPort->setSerialInfo(info);
m_mapSerialPort.insert(info.name, pSerialPort);
return open_port(info.name);
}
bool SerialportCtrl::isOpened(QString connName)
{
if (m_mapSerialPort.contains(connName))
{
return m_mapSerialPort[connName]->isOpened();
}
else
{
return false;
}
}
bool SerialportCtrl::open_port(QString connName, bool bAsync)
{
SYSTEM_CONFIG_INSTANCE->getSerialPortInfo(m_vecSerialInfo);
TSeriaPortInfo serialInfo;
bool isExist = false;
for each (TSeriaPortInfo info in m_vecSerialInfo)
{
if (connName == info.name)
{
serialInfo = info;
isExist = true;
break;
}
}
if (!isExist)
{
qWarning() << QStringLiteral("串口配置中不存在[%1]对象,无法重连").arg(connName);
return false;
}
bool ret = false;
if (!m_mapSerialPort.contains(connName))
{
ret = init_open_serial(serialInfo);
return false;
}
m_mapSerialPort[connName]->setSerialInfo(serialInfo);
if (bAsync)
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onOpenPort", Qt::QueuedConnection,
Q_ARG(QString, connName));
}
else
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onOpenPort", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret), Q_ARG(QString, connName));
}
return ret;
}
void SerialportCtrl::close_port(QString connName)
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onClosePort", Qt::QueuedConnection, Q_ARG(QString, connName));
}
bool SerialportCtrl::write_data(QString connName, QString strSend, bool isHex, int msTimeOut, bool bAsync)
{
bool ret = true;
if (bAsync)
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::QueuedConnection,
Q_ARG(QString, connName),
Q_ARG(QString, strSend),
Q_ARG(bool, isHex),
Q_ARG(int, msTimeOut),
Q_ARG(bool, bAsync));
}else
{
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(QString, connName),
Q_ARG(QString, strSend),
Q_ARG(bool, isHex),
Q_ARG(int, msTimeOut),
Q_ARG(bool, bAsync));
}
return ret;
}
bool SerialportCtrl::read_data(QString connName, QString& strRecv,int msTimeOut)
{
bool ret = false;
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onReadData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(QString, connName),
Q_ARG(QString&, strRecv),
Q_ARG(int, msTimeOut));
return ret;
}
bool SerialportCtrl::write_read_data(QString connName, QString strSend, QString& strRecv, bool isHex, int msTimeOut)
{
bool ret = false;
QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteReadData", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, ret),
Q_ARG(QString, connName),
Q_ARG(QString, strSend),
Q_ARG(QString&, strRecv),
Q_ARG(bool, isHex),
Q_ARG(int, msTimeOut));
return ret;
}
void SerialportCtrl::setRecvHexData(QString connName, bool bHexData)
{
m_mapSerialPort[connName]->setRecvHexData(bHexData);
}
void SerialportCtrl::exitWork()
{
QMap::const_iterator iter;
for (iter = m_mapSerialPort.constBegin(); iter != m_mapSerialPort.constEnd(); iter++)
{
close_port(iter.key());
}
}
例子程序下载地址,没有设置需要积分下载。如果下载需要有积分,不是我干的。
这是一个串口多线程的一个模块-C++文档类资源-CSDN下载这是一个串口多线程的一个模块更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/weixin_41516902/86272213
下面是一个上位机软件,有兴趣可以看一下
mobius_upper_computer_open: 用Qt写的上位机软件。主要功能是收集PLC信息,处理后发送给MES系统,或者将MES系统的一些消息转给PLC。支持多线程下的流程程序编辑。 (gitee.com)