qt 串口多线程 的一个例子

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 <QObject>
#include <QThread>
#include <QEventLoop>
#include <QSerialPort>
#include <QTimer>
#include <mutex>
#include <QSharedPointer>

#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<QTimer>		m_recevieTimer = NULL;
private slots:
	void			on_receive_allData();
private:	
	//将qstring转16进制
	QByteArray		QStringHexToByteArrayHex(QString src);

};

TSerialPort.cpp

#include "TSerialPort.h"
#include <QMetaEnum>
#include<QDebug>


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<QSerialPort::Parity>();
	_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 <mutex>
#include <memory>
#include <QMap>
#include <QObject>
#include <QSharedPointer>
#include <QMap>
#include <QThread>
#include <QVector>
#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<SerialportCtrl> 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<TSeriaPortInfo>	m_vecSerialInfo;
	QMap<QString, TSerialPort*>	m_mapSerialPort;
	bool	init_open_serial(TSeriaPortInfo info);

};

#include "SerialportCtrl.h"
#include <QDebug>

std::once_flag  SerialportCtrl::m_initFlag;
std::unique_ptr<SerialportCtrl> 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<SerialportCtrl>(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<QString, TSerialPort*>::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)

  • 17
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Qt是一种广泛使用的C++跨平台应用程序开发框架,主要用于开发GUI程序。Qt提供了串口通信的相关和函数,同时也支持多线程开发。Qt串口通信中,最重要的是QSerialPort,它提供了一系列与串口通信相关的函数,包括打开串口、设置串口参数、读取数据、写入数据等。而对于多线程开发,在Qt中通常使用QThread来创建线程。在串口通信多线程开发中,可以使用QThread创建一个新的线程来处理串口通信,从而提高程序的并发性和稳定性。可以使用Qt的信号和槽机制来实现不同线程之间的通讯和数据传递。同时,在多线程开发中需要注意线程的同步和互斥,以避免多线程访问同一资源造成的冲突和数据损坏。 Qt串口多线程开发的源码实现需要先创建一个串口通信,然后继承QThread创建一个新的线程,在该线程中调用串口通信的函数进行通讯。在实现过程中需要使用信号和槽机制来实现不同线程之间的通讯和数据传递,同时也需要考虑线程的同步和互斥问题,以避免多线程访问同一资源造成的冲突和数据损坏。 总之,Qt提供了非常完善的串口通信和多线程开发的支持,并且在实现过程中也比较简单,只需要熟悉Qt的相关和函数即可。同时,在开发过程中也需要遵循一些基本的原则,如线程安全、代码可读性等,以确保程序的质量和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值