Qt 串口通信与QThread多线程实现

串口类实现

  1. SerialServer类简介

我们创建了一个SerialServer类,它继承自QObject,用于处理串口的相关操作。该类提供了打开、关闭串口,发送和接收串口数据的功能,并通过信号槽机制与主界面线程进行通信。
2. SerialServer类声明

#ifndef SERIALSERVER_H
#define SERIALSERVER_H

#include <QObject>
#include <QSerialPort>
#include <QWaitCondition>
#include <QMutex>
#include <QCoreApplication>



class SerialServer : public QObject {
    Q_OBJECT

public:
    SerialServer();
    ~SerialServer();

    // 打开串口
    bool openSerialPort(const QString &portName, unsigned int baudRate, unsigned char dataBits = 8, unsigned char parity = 0, unsigned char stopBits = 1);
    void closeSerialPort();         // 关闭串口
    bool isSerialPortOpen() const;  // 串口是否打开

public slots:
    void ReceviceSerialData();                          // 接收串口数据
    void SendSerialData(const QByteArray &data);        // 发送串口数据

signals:
    void serialDataReceivedReady(const QByteArray &data);
    void serialDataSendReady(const QByteArray &data);


private:
    QSerialPort *m_serial;
    QByteArray m_DataBuf;

};

#endif // SERIALSERVER_H

  1. SerialServer类定义

在SerialServer类的定义中,主要实现了串口的打开与关闭、数据的接收与发送功能,并使用信号槽机制通知主线程数据的接收。

#include "SerialServer.h"
#include <QDebug>
#include <QThread>

SerialServer::SerialServer() : m_serial(new QSerialPort(this)) {
}

SerialServer::~SerialServer() {
    closeSerialPort();
}

bool SerialServer::openSerialPort(const QString &portName, unsigned int baudRate, unsigned char dataBits, unsigned char parity, unsigned char stopBits) {
    if (m_serial->isOpen()) {
        return true;
    }
    
    m_serial->setPortName(portName);
    m_serial->setBaudRate(baudRate);
    m_serial->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
    m_serial->setParity(static_cast<QSerialPort::Parity>(parity));
    m_serial->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
    m_serial->setFlowControl(QSerialPort::NoFlowControl);
    
    if (m_serial->open(QIODevice::ReadWrite)) {
        connect(m_serial, &QSerialPort::readyRead, this, &SerialServer::ReceviceSerialData);
        return true;
    } else {
        return false;
    }
}

void SerialServer::closeSerialPort() {
    if (m_serial->isOpen()) {
        m_serial->close();
    }
}

bool SerialServer::isSerialPortOpen() const {
    return m_serial->isOpen();
}


void SerialServer::ReceviceSerialData()
{
    // 获取串口接收到的数据
    QByteArray data = m_serial->readAll();
    
    if (!data.isEmpty()) {
        
        m_DataBuf.append(data);         // 将接收到的数据追加到缓冲区
        int nBufLen = m_DataBuf.size(); // 获取当前缓冲区中的数据长度
        
        // 如果数据长度不足10,则返回,继续等待接收
        if (nBufLen < 10) {
            return;
        }
        
        // 如果长度大于等于10,则处理所有的数据(不固定字节数)
        QString hexData;
        for (int i = 0; i < m_DataBuf.size(); ++i) {
            hexData.append(QString::asprintf("0x%02X ", static_cast<uint8_t>(m_DataBuf[i]))); // 将数据转换为16进制格式
        }
        qDebug() << "接收数据(长度大于等于10):" << hexData << QThread::currentThread();
        
        // 发送接收到的数据到GUI窗口
        emit serialDataReceivedReady(m_DataBuf);
        
        // 清空缓冲区,准备接收新的数据
        m_DataBuf.clear();
    }
}


void SerialServer::SendSerialData(const QByteArray &data)
{
    if (m_serial->isOpen()) {
        m_serial->write(data);  // 接收GUI数据并发送
#if 1 \
    // 发送数据打印
        QString hexData;
        for (int i = 0; i < data.size(); ++i) {
            hexData.append(QString::asprintf("0x%02X ", static_cast<uint8_t>(data[i])));
        }
        qDebug() << "SEND(HEX):" << hexData << QThread::currentThread();
#endif
        qDebug() << "SEND(HEX): " << data.toHex().toUpper() << QThread::currentThread();
    }
}

串口服务与GUI主界面

  1. 主窗口类简介

主窗口类aMainWindow通过QThread将SerialServer对象移动到子线程中,以实现串口通信的异步处理。这种设计确保了UI界面在处理串口数据时不会被阻塞,从而提升了用户体验。
2. 主窗口类的声明与定义

主窗口类aMainWindow的主要功能包括:初始化串口配置、连接和断开串口、发送和接收数据,以及更新UI。

#ifndef AMAINWINDOW_H
#define AMAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include <qDebug>
#include <QSerialPortInfo>
#include <QThread>
#include <QTime>
#include "serialserver.h"

static const unsigned int  use_BAUD[8]={1200,2400,4800,9600,19200,38400,57600,115200};
static const QSerialPort::StopBits use_STOPBITS[4]={QSerialPort::OneStop,QSerialPort::OneAndHalfStop,QSerialPort::TwoStop,QSerialPort::UnknownStopBits};
static const QSerialPort::Parity use_PARITY[5]={QSerialPort::NoParity,QSerialPort::EvenParity,QSerialPort::OddParity,QSerialPort::SpaceParity,QSerialPort::MarkParity};
static const QSerialPort::DataBits use_DATABITS[4]={QSerialPort::Data5,QSerialPort::Data6,QSerialPort::Data7,QSerialPort::Data8};

QT_BEGIN_NAMESPACE
namespace Ui {
class aMainWindow;
}
QT_END_NAMESPACE

class aMainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    aMainWindow(QWidget *parent = nullptr);
    ~aMainWindow();
    
public:
    void initControlProcess();  // 控件初始化
    
    void FillSerialControl();   // 填充串口控件数据
    
    void updateControlState(bool flag); // 控件状态更新
    
    void displaySendData(const QByteArray &data);   // 显示发送数据
    
    void appendColoredText(const QString &prefix, const QString &data, const QColor &color);    // 颜色区分数据
    
    QString formatDataBasedOnHexCheck(const QByteArray &data);
    
private slots:
    void on_pushButton_Connect_clicked();       // 连接
    
    void on_pushButton_disConnect_clicked();    // 断开
    
    void on_pushButton_update_clicked();        // 刷新可用串口
    
    void on_pushButton_cleanrecv_clicked();     // 接收区清空
    
    void on_pushButton_send_clicked();          // 发送
    
    void on_pushButton_cleansend_clicked();     // 发送区清空
    
    void onSerialDataReceived(const QByteArray &data);  // 接收的串口数据
    
    void updateButtonState_cleanrecv();         // 接收区/发送区状态判断
    void updateButtonState_cleansend();
    
    void on_checkBox_Hex_stateChanged(int arg1);// 更新勾选状态
    
private:
    Ui::aMainWindow *ui;
    SerialServer *serialServer;
    QThread *serialThread;
};
#endif // AMAINWINDOW_H

  1. 主窗口类的实现

主窗口类的实现中,重点在于如何启动和管理串口服务线程,并通过信号槽机制实现串口数据的收发和UI的更新。

#include "amainwindow.h"
#include "ui_amainwindow.h"
#include <QTextFormat>

aMainWindow::aMainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::aMainWindow)
{
    ui->setupUi(this);
    
    qDebug() << "GUI:" << QThread::currentThread();
    
    initControlProcess();
    
    serialServer = new SerialServer();
    serialThread = new QThread(this);
    serialServer->moveToThread(serialThread);   // 将 SerialServer 对象移动到新线程
    connect(serialServer, &SerialServer::serialDataReceivedReady, this, &aMainWindow::onSerialDataReceived);
    serialThread->start();  // 启动线程
    
    connect(serialThread, &QThread::finished, serialServer, &QObject::deleteLater);
    
    connect(ui->plainTextEdit_recv, &QPlainTextEdit::textChanged, this, &aMainWindow::updateButtonState_cleanrecv);
    connect(ui->pushButton_cleanrecv, &QPushButton::clicked, this, &aMainWindow::on_pushButton_cleanrecv_clicked);
    connect(ui->plainTextEdit_send, &QPlainTextEdit::textChanged, this, &aMainWindow::updateButtonState_cleansend);
    connect(ui->pushButton_cleansend, &QPushButton::clicked, this, &aMainWindow::on_pushButton_cleansend_clicked);
}

aMainWindow::~aMainWindow()
{
    serialThread->quit();
    serialThread->wait();
    delete serialServer;  // 删除 serialServer 对象
    delete ui;
}

void aMainWindow::initControlProcess()
{
    FillSerialControl();
    
    ui->label_ConnectState->setText("未连接");
    ui->label_RS->setText("UTF-8");
    
    ui->pushButton_cleanrecv->setEnabled(false);
    ui->pushButton_cleansend->setEnabled(false);
    
    updateControlState(false);
}

void aMainWindow::FillSerialControl()
{
    // 填充波特率
    for (int i = 0; i < 8; ++i) {
        ui->comboBox_Baud->addItem(QString::number(use_BAUD[i]));
    }
    
    // 填充数据位
    QStringList dataBitsLabels = {"5", "6", "7", "8"};
    for (int i = 0; i < 4; ++i) {
        ui->comboBox_Databits->addItem(dataBitsLabels[i], use_DATABITS[i]);
    }
    
    // 填充校验位
    QStringList parityLabels = {"No Parity", "Even Parity", "Odd Parity", "Space Parity", "Mark Parity"};
    for (int i = 0; i < 5; ++i) {
        ui->comboBox_Parity->addItem(parityLabels[i], use_PARITY[i]);
    }
    
    // 填充停止位
    QStringList stopBitsLabels = {"1", "1.5", "2", "Unknown"};
    for (int i = 0; i < 4; ++i) {
        ui->comboBox_Stopbits->addItem(stopBitsLabels[i], use_STOPBITS[i]);
    }
    
    // 自动获取可用串口号
    ui->comboBox_Com->clear();
    const auto ports = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &portInfo : ports) {
        ui->comboBox_Com->addItem(portInfo.portName());
    }
    
    // 如果没有找到可用的串口
    if (ui->comboBox_Com->count() == 0) {
        ui->comboBox_Com->addItem(QStringLiteral("无可用串口"));
    }
}

void aMainWindow::updateControlState(bool flag)
{
    QList<QPushButton*> buttons = {ui->pushButton_disConnect,ui->pushButton_send};
    
    for (QPushButton* button : buttons) {
        button->setEnabled(flag);
    }
}

void aMainWindow::displaySendData(const QByteArray &data)
{
    QString strSend = formatDataBasedOnHexCheck(data);
    appendColoredText("<SEND>: ", strSend, Qt::blue);
}


void aMainWindow::appendColoredText(const QString &prefix, const QString &data, const QColor &color)
{
    QDateTime dateTime = QDateTime::currentDateTime();
    QString s_time = dateTime.toString("hh:mm:ss.zzz"); // 获取当前时间
    
    QMetaObject::invokeMethod(this, [=] {
        QTextCursor cursor(ui->plainTextEdit_recv->textCursor());
        cursor.movePosition(QTextCursor::End);
        ui->plainTextEdit_recv->setTextCursor(cursor);
        
        QTextCharFormat format;
        format.setForeground(color); // 设置传入的颜色
        
        cursor.insertText(s_time + prefix, format); // 插入时间戳和前缀
        format.setForeground(Qt::black); // 恢复为黑色字体(数据部分)
        cursor.insertText(data + "\n", format);
    }, Qt::QueuedConnection);
}


QString aMainWindow::formatDataBasedOnHexCheck(const QByteArray &data)
{
    QString formattedData;
    
    if (ui->checkBox_Hex->isChecked()) {
        formattedData = data.toHex(' ').toUpper(); // hex 格式
        ui->label_RS->setText("HEX");
    } else {
        formattedData = QString::fromUtf8(data); // 原始数据格式
        ui->label_RS->setText("UTF-8");
    }
    return formattedData;
}


void aMainWindow::on_pushButton_Connect_clicked()
{
    QString portName = ui->comboBox_Com->currentText();
    unsigned int baudRate = ui->comboBox_Baud->currentText().toUInt();
    QSerialPort::DataBits dataBits = static_cast<QSerialPort::DataBits>(ui->comboBox_Databits->currentData().toInt());
    QSerialPort::Parity parity = static_cast<QSerialPort::Parity>(ui->comboBox_Parity->currentData().toInt());
    QSerialPort::StopBits stopBits = static_cast<QSerialPort::StopBits>(ui->comboBox_Stopbits->currentData().toInt());
    
    QMetaObject::invokeMethod(serialServer, [=]{
        if (serialServer->openSerialPort(portName, baudRate, dataBits, parity, stopBits)) {
            qDebug() << "串口打开成功:" << portName << baudRate << dataBits << stopBits << parity;
            
            // 更新UI在主线程中完成
            QMetaObject::invokeMethod(this, [=] {
                ui->label_ConnectState->setText("已连接");
                ui->pushButton_Connect->setEnabled(false);
                updateControlState(true);
            }, Qt::QueuedConnection);
        } else {
            qDebug() << "串口打开失败,请检查串口是否被占用或配置是否正确:" << portName << baudRate << dataBits << stopBits << parity;
        }
    }, Qt::QueuedConnection);
}

void aMainWindow::on_pushButton_disConnect_clicked()
{
    QMetaObject::invokeMethod(serialServer, [=] {
        serialServer->closeSerialPort();
    }, Qt::QueuedConnection);
    
    qDebug() << "断开串口连接";
    ui->label_ConnectState->setText("未连接");
    ui->pushButton_Connect->setEnabled(true);
    updateControlState(false);
}

void aMainWindow::on_pushButton_update_clicked()
{
    // 自动获取可用串口号
    ui->comboBox_Com->clear();
    const auto ports = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &portInfo : ports) {
        ui->comboBox_Com->addItem(portInfo.portName());
    }
    
    // 如果没有找到可用的串口
    if (ui->comboBox_Com->count() == 0) {
        ui->comboBox_Com->addItem(QStringLiteral("无可用串口"));
    }
    qDebug() << "串口已刷新,请注意保存";
}

void aMainWindow::on_pushButton_cleanrecv_clicked()
{
    if (ui->plainTextEdit_recv->toPlainText().isEmpty()) {
        ui->pushButton_cleanrecv->setEnabled(false);
    } else {
        ui->plainTextEdit_recv->clear();
    }
}

void aMainWindow::on_pushButton_send_clicked()
{
    QString text = ui->plainTextEdit_send->toPlainText();
    QByteArray data = text.toUtf8();
    
    if (!text.isEmpty()) {
        
        // 发送数据的部分放入serialServer的线程
        QMetaObject::invokeMethod(serialServer, [=] {
            serialServer->SendSerialData(data);
        }, Qt::QueuedConnection);
        
        // UI更新部分在主线程中执行
        QDateTime dateTime = QDateTime::currentDateTime();
        QString s_time = dateTime.toString("hh:mm:ss.zzz");
        QString strSend = QString(data);
        
        appendColoredText("<SEND>: ", strSend, Qt::blue);
        
    } else {
        qDebug() << "发送数据不可为空";
        ui->statusbar->showMessage("发送数据不可为空.", 1500);
    }
}


void aMainWindow::on_pushButton_cleansend_clicked()
{
    ui->plainTextEdit_send->clear();
    
    if (ui->plainTextEdit_send->toPlainText().isEmpty()) {
        ui->pushButton_cleansend->setEnabled(false);
    } else {
        ui->plainTextEdit_send->clear();
    }
}

void aMainWindow::onSerialDataReceived(const QByteArray &data)
{
    QString strRecv = formatDataBasedOnHexCheck(data);
    appendColoredText("<RECV>: ", strRecv, Qt::green);
}



void aMainWindow::updateButtonState_cleanrecv()
{
    ui->pushButton_cleanrecv->setEnabled(!ui->plainTextEdit_recv->toPlainText().isEmpty());
}

void aMainWindow::updateButtonState_cleansend()
{
    ui->pushButton_cleansend->setEnabled(!ui->plainTextEdit_send->toPlainText().isEmpty());
}

void aMainWindow::on_checkBox_Hex_stateChanged(int arg1)
{
    if (ui->checkBox_Hex->isChecked()) {
        ui->label_RS->setText("HEX");
    } else {
        ui->label_RS->setText("UTF8");
    }
}

在这里插入图片描述
在这里插入图片描述

通过本文,我们实现了一个基于QThread的串口通信类,SerialServer。该类能够在单独的线程中处理串口的打开、关闭以及数据的收发操作,并通过信号槽机制与主线程通信。这种方式可以有提高应用程序的响应性,避免界面卡顿。
在实际使用中,可以根据项目的具体需求对SerialServer类进行扩展和改进,比如增加对异常情况的处理、支持更多的串口配置选项等。
希望这篇文章对你在Qt中的串口通信开发有所帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值