基于 Qt 的多线程串口调试工具

采用 Qt 推荐的 QObject::moveToThread() 方案,将耗时的串口读写操作剥离到后台工作线程,主线程只负责 UI 刷新和用户交互。


一、 整体架构设计

我们将项目分为三个核心部分:

  1. UI 层 (MainWindow):负责界面展示、按钮响应、数据可视化。
  2. 业务层 (SerialWorker) :纯业务逻辑类,包含 QSerialPort,负责数据的打包、解析、CRC校验等。
  3. 线程层 (QThread):后台运行的工作线程,不处理具体业务,只为 Worker 提供事件循环。

数据流方向:

  • 发数据 :UI -> 信号触发 -> Worker 槽函数 -> QSerialPort::write()
  • 收数据QSerialPort 触发 readyRead -> Worker 读取 -> 发出信号 -> UI 更新

二、 核心代码实现

1. 串口工作类 (serialworker.h & .cpp)

这个类将在后台线程中运行,千万不要在此类中操作任何 UI 控件

cpp 复制代码
// serialworker.h
#ifndef SERIALWORKER_H
#define SERIALWORKER_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>

class SerialWorker : public QObject
{
    Q_OBJECT
public:
    explicit SerialWorker(QObject *parent = nullptr);
    ~SerialWorker();

public slots:
    // 供 UI 线程调用的槽函数
    void openSerial(const QString &portName, int baudRate);
    void closeSerial();
    void sendData(const QByteArray &data);

private slots:
    // 内部私有槽,用于处理串口数据接收
    void onReadyRead();

signals:
    // 供 UI 线程接收的信号
    void dataReceived(const QByteArray &data);
    void statusChanged(const QString &status);

private:
    QSerialPort *m_serialPort;
};

#endif // SERIALWORKER_H
cpp 复制代码
// serialworker.cpp
#include "serialworker.h"

SerialWorker::SerialWorker(QObject *parent) : QObject(parent)
{
    m_serialPort = new QSerialPort(this);
    connect(m_serialPort, &QSerialPort::readyRead, this, &SerialWorker::onReadyRead);
}

SerialWorker::~SerialWorker()
{
    if (m_serialPort->isOpen()) {
        m_serialPort->close();
    }
}

void SerialWorker::openSerial(const QString &portName, int baudRate)
{
    m_serialPort->setPortName(portName);
    m_serialPort->setBaudRate(baudRate);
    m_serialPort->setDataBits(QSerialPort::Data8);
    m_serialPort->setParity(QSerialPort::NoParity);
    m_serialPort->setStopBits(QSerialPort::OneStop);
    m_serialPort->setFlowControl(QSerialPort::NoFlowControl);

    if (m_serialPort->open(QIODevice::ReadWrite)) {
        emit statusChanged("串口已打开: " + portName);
    } else {
        emit statusChanged("打开失败: " + m_serialPort->errorString());
    }
}

void SerialWorker::closeSerial()
{
    if (m_serialPort->isOpen()) {
        m_serialPort->close();
        emit statusChanged("串口已关闭");
    }
}

void SerialWorker::sendData(const QByteArray &data)
{
    if (m_serialPort->isOpen() && !data.isEmpty()) {
        m_serialPort->write(data);
    }
}

void SerialWorker::onReadyRead()
{
    // 这里的 sleep 是为了应对数据粘包,根据实际情况调整或移除
    QThread::msleep(10); 
    QByteArray buffer = m_serialPort->readAll();
    
    // 如果数据没读完,继续读(防止高频率数据丢失)
    while (m_serialPort->waitForReadyRead(10)) {
        buffer.append(m_serialPort->readAll());
    }

    if (!buffer.isEmpty()) {
        emit dataReceived(buffer); // 发射信号,将数据抛给 UI 层
    }
}
2. 主界面与多线程绑定 (mainwindow.cpp)

在这里完成线程的创建、Worker 的转移以及信号槽的连接。

cpp 复制代码
// mainwindow.cpp 关键片段
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 1. 创建线程和 Worker
    QThread* serialThread = new QThread(this);
    SerialWorker* serialWorker = new SerialWorker();

    // 2. 将 Worker 移动到新线程
    serialWorker->moveToThread(serialThread);

    // 3. 连接信号槽(跨线程,Qt::QueuedConnection 是默认的自动连接方式)
    // UI -> Worker (触发打开/关闭/发送)
    connect(ui->btnOpen, &QPushButton::clicked, {
        serialWorker->openSerial(ui->comboPort->currentText(), 115200);
    });
    connect(ui->btnSend, &QPushButton::clicked, {
        QByteArray data = ui->textEditSend->toPlainText().toUtf8();
        serialWorker->sendData(data);
    });

    // Worker -> UI (更新接收区/状态栏)
    connect(serialWorker, &SerialWorker::dataReceived, this, const QByteArray& data{
        ui->textEditRecv->append(QString::fromUtf8(data));
    });
    connect(serialWorker, &SerialWorker::statusChanged, this, const QString& msg{
        ui->statusbar->showMessage(msg);
    });

    // 4. 启动线程
    serialThread->start();
}

MainWindow::~MainWindow()
{
    // 优雅退出线程
    serialThread->quit();
    serialThread->wait();
    delete serialWorker; // 注意:由于使用了 moveToThread,必须在线程结束后删除
    delete ui;
}

三、 进阶功能实现建议(针对嵌入式调试)

作为一个硬核的嵌入式开发者,你的调试工具还需要以下几个"杀手锏"功能:

1. 十六进制(Hex)收发

SerialWorker::sendDataonReadyRead 中加入转换逻辑:

cpp 复制代码
// 发送前:将 "AA BB CC" 转换为 QByteArray
QByteArray hexStrToByteArray(const QString &str) {
    QByteArray ret;
    QStringList sl = str.trimmed().split(" ");
    foreach (QString s, sl) {
        ret.append((char)s.toUInt(nullptr, 16));
    }
    return ret;
}

// 接收后:将 QByteArray 转换为 "AA BB CC"
QString byteArrayToHexStr(const QByteArray &ba) {
    QString ret;
    for (int i = 0; i < ba.size(); ++i) {
        ret.append(QString("%1 ").arg((quint8)ba.at(i), 2, 16, QChar('0')).toUpper());
    }
    return ret;
}
2. 高并发数据实时绘图(使用 QCustomPlot)

如果在 UI 层直接 new QPoint 并刷新图表,CPU 占用率会飙升。

  • 优化方案 :在 SerialWorker 中解析出数据后,通过信号把 double 数值发给 UI,UI 层使用 QTimer(例如每 50ms 刷新一次图表)批量从队列中取数据绘制,而不是来一个数据画一个点。
3. 串口自动重连与设备热插拔

利用 Windows 的本地 API 或轮询 QSerialPortInfo::availablePorts() 来检测设备拔出或插入,并通过信号通知 UI 弹出提示。

参考代码 基于QT的多线程串口调试工具 www.youwenfan.com/contentcsu/70247.html

四、 避坑

  1. 不要跨线程直接调用对象方法
    永远使用 signal/slot 机制。如果你在 UI 线程直接调用 serialWorker->sendData(),它就不再具备线程安全性,会导致不可预知的崩溃。
  2. 内存释放顺序
    由于使用了 moveToThreadSerialWorker 的析构函数会在其所在的线程中执行。绝对不能 在 UI 线程的析构函数中直接 delete serialWorker;,必须使用 deleteLater() 或者等待线程完全结束后再释放。
  3. 事件循环(Event Loop)
    moveToThread 强依赖目标线程的事件循环。如果你在子线程中写了死循环或者阻塞了线程,SerialWorker 中的槽函数(包括 onReadyRead)将永远不会被执行。
相关推荐
郝学胜-神的一滴16 小时前
Qt 高级开发 024:QSplitter分裂器布局精讲
开发语言·c++·qt·程序人生·用户界面
-凌凌漆-17 小时前
【Qt】std::shared_ptr<>与std::make_shared<>
开发语言·qt
郝学胜_神的一滴18 小时前
Qt 高级开发 025:打造优雅界面的艺术与高效重构之道
c++·qt
十五年专注C++开发19 小时前
Qt之QScopedPointer、QScopeGuard、QScopedValueRollback使用及源码解读
开发语言·c++·qt·qscopedpointer·qscopeguard
yugi98783821 小时前
基于Qt的实用二维码生成解决方案
开发语言·qt
不爱吃糖的程序媛1 天前
环境搭建教程:HarmonyOS PC 录屏工具(Qt5 + OHOS Native Media)
qt·华为·harmonyos
-凌凌漆-1 天前
【Qt】C++中protected与private的区别
开发语言·c++·qt
-凌凌漆-1 天前
Qt QML应用层框架
开发语言·qt
走好每一步2 天前
1、VTK+QT + cmake编程 三维圆柱体
qt·vtk
小小码农Come on2 天前
Qt::WA_StyledBackground属性的作用
开发语言·qt