以前串口编程使用第三方的CnComm.h
编程,CnComm作者博客链接,使用起来还蛮好的,不过既然用qt了就想着用qt自带的QSerialPort,移植性更好一些,结果折腾了好几天,主要遇到的问题就是多线程使用串口的问题,我使用串口有以下要求:
基于以上需求和之前串口编程的经验,我做了如下设计:
程序运行起来之后也可以运行,通过虚拟串口与串口调试助手通信也都正常,但是每次发送数据,qt都会提示类似如下错误:
QObject: Cannot create children for a parent that is in a different thread,(Parent is QSerialPort(0x4ab1ab0), parent's thread is QThread(0xbe3860), current thread is QThread(0xc00df8)
虽然好像用着也没啥问题,但是心理总犯嘀咕,然后就想着把这个问题解决,然后就折腾了两天。
因为提示不在同一个线程,于是我将QSerialPort在线程中创建和打开,让QSerialPort完全属于一个线程,但是这样带来的问题是readReady就无法被触发了,测试connect方式自动、直接、队列都无法被触发;在QSerialPort创建时候还是在串口打开之后再connect都无法成功,原因未明。
查阅资料,有些博主解释了这个报错的原因和解决方式,在主线程中创建QSerialPort,然后新建一个Object,将主线程的QSerialPort指针传入这个Object,然后再结合MoveToThread(),参见博主博客,可能是我理解不到位,我按照这个方式实现后,还是提示对象不在一个线程的问题;
在知乎一个博主回答中,对多线程QSerialPort编程有进一步了解,摘抄原博主内容如下:
于是我做了如下修改,代码如下:
QSerialWorker.h:
#ifndef SERIALWORKER_H
#define SERIALWORKER_H
#include
#include
#include
#include "QtCommon.h"
#include "dataqueue.h"
#include
class SerialWorker : public QObject
{
Q_OBJECT
public:
explicit SerialWorker(DataQueue *pSerialQue,QObject *parent = nullptr);
~SerialWorker();
public:
Qt::HANDLE getThreadID(){
return QThread::currentThreadId();
}
void setSerialTimeout(int sec){
if(sec > 0){
s32SerialTimeout = sec;
}
}
void setSerialParams(QString name,int baudrate,int parity,int data,int stop){
serial_name=name;
serial_baudrate = baudrate;
serial_parity = parity;
serial_data = data;
serial_stop = stop;
}
signals:
void serialOperaRet(bool openOpera = true,bool ret = true);
void serialBytesCount(int tx,int rx);
public slots:
void serialInit();
void sendSerialData(const char *buf,int len);
void onRecvSerialData();
private:
QSerialPort *mSerialPort = nullptr;
QString serial_name="COM10";
int serial_baudrate = 9600;
int serial_parity = 0;
int serial_data = 8;
int serial_stop = 1;
QMutex serialWriteMutex;
DataQueue *m_SerialDataQue;//主线程创建的接收队列
int s32SerialTimeout = 3000;
};
#endif // SERIALWORKER_H
QSerialWorker.cpp:
#include "SerialWorker.h"
SerialWorker::SerialWorker(DataQueue *pSerialQue,QObject *parent)
: QObject(parent),
m_SerialDataQue(pSerialQue)
{
}
SerialWorker::~SerialWorker()
{
if(mSerialPort){
if(mSerialPort->isOpen()){
mSerialPort->clear();
mSerialPort->close();
LOG(INFO)<<"close serial";
emit serialOperaRet(false,true);
mSerialPort->deleteLater();
}
}
}
void SerialWorker::serialInit()
{
mSerialPort = new QSerialPort();
mSerialPort->setPortName(serial_name);
mSerialPort->setBaudRate(serial_baudrate);
mSerialPort->setDataBits(static_cast<QSerialPort::DataBits>(serial_data));
mSerialPort->setStopBits(static_cast<QSerialPort::StopBits>(serial_stop));
mSerialPort->setParity(static_cast<QSerialPort::Parity>(serial_parity));
if(!mSerialPort->open(QIODevice::ReadWrite)){
emit serialOperaRet(true,false);
return ;
}
emit serialOperaRet();
connect(mSerialPort,&QSerialPort::readyRead,this,&SerialWorker::onRecvSerialData);
}
void SerialWorker::sendSerialData(const char *data,int len)
{
if(data == nullptr || len <= 0)return;
QMutexLocker locker(&serialWriteMutex);
if(mSerialPort && mSerialPort->isOpen()){
mSerialPort->clear();
mSerialPort->write(data,len);
mSerialPort->flush();
emit serialBytesCount(len,0);
mSerialPort->waitForBytesWritten(s32SerialTimeout);
}
}
void SerialWorker::onRecvSerialData()
{
if(mSerialPort && mSerialPort->isOpen() && m_SerialDataQue){
QByteArray readData;
readData.append(mSerialPort->readAll());
if(readData.isEmpty()){
LOG(ERROR)<<"readdata error";
return;
}
int len = readData.length();
emit serialBytesCount(0,len);
m_SerialDataQue->PushData(readData.data(),len);
}
}
主进程头文件里申明了如下三个成员:
SerialWorker *mSerialWorker;
QThread *mSerialWorkThread;
主线程实现中在需要打开的时候添加以下程序:
mSerialWorker = new SerialWorker(&serialDataQue);
mSerialWorkThread = new QThread();
mSerialWorker->moveToThread(mSerialWorkThread);
connect(mSerialWorker, &SerialWorker::serialBytesCount, this, &MainWindow::onRecvSerialBytesCount);
connect(mSerialWorker, &SerialWorker::serialOperaRet, this,&MainWindow::onRecvSerialOperaRet);
connect(mSerialWorkThread, &QThread::finished,mSerialWorker, &QObject::deleteLater);
connect(mSerialWorkThread, &QThread::started,mSerialWorker, &SerialWorker::serialInit);
connect(this, &MainWindow::writeToSerial,mSerialWorker, &SerialWorker::sendSerialData);
QString name = “com10”;
int baudrate = 9600;
int data = 8;
int stop = 1;
int parity = 0;
mSerialWorker->setSerialParams(name,baudrate,parity,data,stop);
mSerialWorkThread->start();
主线程里需要关闭时候执行以下程序:
if(mSerialWorkThread){
mSerialWorkThread->quit();
mSerialWorkThread->wait();
mSerialWorkThread->deleteLater();
return;
}
新建继承于QThread的发送线程,添加信号:
signals:
void sendQueryData(const char *data,int len);
在主线程里添加connect:
connect(pSendThread,&sendQueryThread::sendQueryData,this,&MainWindow::writeToSerial);
sendQueryThread::Instance()->start();
经测试符合需求,正常接收,发送数据不再提示不同线程警告,继续验证这样持续打开关闭串口会不会出内存泄漏问题;
非常感谢各位在网络上无私分享的同仁,在此将自己这几天的调试过程记录下来,以供大家参考,水平有限,欢迎指正。
最终因为个人能力有限还是无法很好的驾驭qt下串口编程,可能是我个人问题,我发现虽然我串口多线程了,但是串口读取的QThreadID还是和主线程ID一致,这与我所设想不合,我设想每个串口收发在应用层面完全独立,互不干扰,所以最终还是回归到windows下使用CnComm,linux下使用termios+select串口编程。