这里介绍的是moveToThread的方式,继承QThread的方式可以参考我的另一篇文章【嵌入式——QT】QThread创建多线程
编程实现
首先创建一个类,但是这个类一定要继承QObject
SerialWorker.h
#ifndef SERIALWORKER_H
#define SERIALWORKER_H
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread>
#include <QQueue>
#include <QTimer>
class SerialWorker : public QObject
{
Q_OBJECT
public:
SerialWorker(QObject *parent = nullptr);
~SerialWorker();
void setProperties(qint32 baudRate, QSerialPort::DataBits dataBits,
QSerialPort::Parity parity, QSerialPort::StopBits stopBits,
QSerialPort::FlowControl flowControl);
void setPortName(QString name);
void setStop(bool isStop = true);
public slots:
void openSerialPort();
void closeSerialPort();
void writeData(const QByteArray &data);
void readData();
void handleSerialError(QSerialPort::SerialPortError error);
QByteArray readDataDelay();
void process();
void handleReadTimeout();
signals:
void dataReceived(const QByteArray &data, QString curCmd);
void errorOccurred(const QString &error);
public:
QQueue<QString> cmdQueue;
QTimer *qtimer = NULL;
QTimer *readTimeout = NULL;
private:
QSerialPort *serialPort = NULL;
QString portName;
QByteArray sumData;
QString curCmd;
bool isStop = false;
};
#endif // SERIALWORKER_H
SerialWorker.cpp
#include "SerialWorker.h"
#include <QDebug>
//指令头
#define PROTOCOL_STX 0xFF
//指令尾
#define PROTOCOL_ETX 0xFE
SerialWorker::SerialWorker(QObject *parent)
: QObject(parent)
{
serialPort = new QSerialPort(this);
connect(serialPort, &QSerialPort::readyRead, this, &SerialWorker::readData);
connect(serialPort, &QSerialPort::errorOccurred, this, &SerialWorker::handleSerialError);
qtimer = new QTimer(this);
connect(qtimer, &QTimer::timeout, this, &SerialWorker::process);
qtimer->start(100);
readTimeout = new QTimer(this);
readTimeout->setSingleShot(true); // 只触发一次超时
connect(readTimeout, &QTimer::timeout, this, &SerialWorker::handleReadTimeout);
}
SerialWorker::~SerialWorker()
{
if (serialPort->isOpen()) {
serialPort->close();
}
delete serialPort;
delete qtimer;
delete readTimeout;
}
void SerialWorker::setProperties(qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::Parity parity,
QSerialPort::StopBits stopBits, QSerialPort::FlowControl flowControl)
{
serialPort->setBaudRate(baudRate);
serialPort->setDataBits(dataBits);
serialPort->setParity(parity);
serialPort->setStopBits(stopBits);
serialPort->setFlowControl(flowControl);
}
void SerialWorker::setPortName(QString name)
{
this->portName = name;
}
void SerialWorker::process()
{
if (!serialPort->isOpen()) {
qDebug() << "Serial port is not open!";
return;
}
// qDebug() << "process";
// while(!isStop) {
// QThread::msleep(500);
if(!cmdQueue.isEmpty()) {
QString cmd = cmdQueue.dequeue();
curCmd = cmd;
writeData(cmd.toUtf8());
} else {
cmdQueue.enqueue("1 sts\n");
// QThread::msleep(100);
cmdQueue.enqueue("2 sts\n");
}
// }
}
void SerialWorker::handleReadTimeout()
{
emit errorOccurred("Read data error");
cmdQueue.clear();
}
void SerialWorker::setStop(bool isStop)
{
this->isStop = isStop;
}
void SerialWorker::openSerialPort()
{
serialPort->setPortName(portName);
if (!serialPort->open(QIODevice::ReadWrite)) {
qDebug() << "Serial port faile";
} else {
qDebug() << "Serial port opened";
}
}
void SerialWorker::closeSerialPort()
{
if (serialPort->isOpen()) {
serialPort->close();
qDebug() << "Serial port closed";
}
}
void SerialWorker::writeData(const QByteArray &data)
{
// qDebug() << "writeData" << QThread::currentThreadId();
if (serialPort->isOpen()) {
if(data.contains("1 sts\n") || data.contains("2 sts\n")) {
} else {
qDebug() << "writeData" << data;
}
if(serialPort->write(data) > 0) {
readTimeout->start(2000);
}
}
}
void SerialWorker::readData()
{
// qDebug() << "readData" << QThread::currentThreadId();
QByteArray recvData = serialPort->readAll();
// qDebug() << "recvData" << recvData;
if (!recvData.isEmpty()) {
sumData.append(recvData);
unsigned char firstByte = static_cast<unsigned char>(sumData[0]);
unsigned char lastByte = static_cast<unsigned char>(sumData[sumData.size() - 1]);
//判断指令头指令尾是否正确
if (lastByte == PROTOCOL_ETX && firstByte == PROTOCOL_STX) {
if (readTimeout->isActive()) {
readTimeout->stop(); // 停止超时定时器
}
// qDebug() << "sumData" << sumData.toHex().toUpper();
emit dataReceived(sumData, curCmd);
sumData.clear();
}
}
}
/*
QSerialPort::ResourceError: 串口设备意外断开。
QSerialPort::DeviceNotFoundError: 找不到设备(通常在尝试打开不存在的串口时)。
QSerialPort::ReadError: 读取数据时发生错误。
QSerialPort::WriteError: 写入数据时发生错误。
*/
void SerialWorker::handleSerialError(QSerialPort::SerialPortError error)
{
qDebug() << "error" << error;
if (error == QSerialPort::ResourceError) {
// qDebug() << "Serial port disconnected!";
emit errorOccurred("Serial port disconnected");
closeSerialPort(); // 关闭串口,清理资源
} else if(error == QSerialPort::DeviceNotFoundError) {
emit errorOccurred("Device not found");
} else if(error == QSerialPort::ReadError) {
emit errorOccurred("Read data error");
} else if(error == QSerialPort::WriteError) {
emit errorOccurred("Write data error");
}
}
QByteArray SerialWorker::readDataDelay()
{
int retryCount = 0;
QByteArray data;
while(retryCount <= 10) {
if(serialPort->waitForReadyRead(500)) {
QByteArray recvData = serialPort->readAll();
if (!recvData.isEmpty()) {
data.append(recvData);
unsigned char firstByte = static_cast<unsigned char>(data[0]);
unsigned char lastByte = static_cast<unsigned char>(data[data.size() - 1]);
//判断指令头指令尾是否正确
if (lastByte == PROTOCOL_ETX && firstByte == PROTOCOL_STX) {
qDebug() << "data" << data.toHex().toUpper();
return data;
}
}
} else {
retryCount++;
}
}
return QByteArray();
}
在主线程中创建线程实例
QThread *workerThread = new QThread(this);
将我们上面创建的类移动到线程中去执行,这样之后在这个类中执行的所有方法,都将是在子线程中执行的,主线程和子线程之间可以通过信号槽的方式来进行通信。
serialWorker->moveToThread(workerThread);
当然将这个工作类移动到子线程之后,还是需要将这个线程启动的,需要调用QThread的start函数来进行启动,表示这个线程启动了,你之后调用的方法都是在这个子线程中执行的。
workerThread->start();
也可以在子线程中编写死循环一直使其去执行,然后绑定个信号,在子线程启动的时候触发这个槽函数
QThread *workerThread = new QThread(this);
connect(workerThread, &QThread::started, serialWorker, &SerialWorker::process);
//子线程槽函数
void SerialWorker::process()
{
while(!isStop) {
QThread::msleep(500);
//逻辑处理
}
}
其实这种写法在子线程中编写死循环是存在问题的,会导致其他的函数在主线程中无法调用,所以这种方式最好不要使用死循环的方式来实现,如果想要编写死循环的话,可以在按照同样的方式重新创建一个线程,或者使用继承QThread的方式来实现。