QT5串口多线程--派生类加moveToThread

QT5串口多线程--派生类加moveToThread

  • Chapter1 QT5串口多线程--派生类加moveToThread
    • 前言
    • 新建工程
    • 源码
      • serialobject.h
      • serialobject.cpp
      • manager.h
      • manager.cpp
      • widget.h
      • widget.cpp
    • 测试
  • Chapter2 QT在PC开发中多串口通信有哪些方法
    • 方法
    • 实现
      • 方案一:
      • 需要注意的是:
      • 方案二:
      • 方案三:
      • 方案四:
  • Chapter3 Qt串口助手开发:基于多线程moveToThread方法串口通信工具
    • 1. 项目背景与设计思路
    • 2. 串口助手的主要功能
    • 3. SerialWorker类:串口操作的后台处理
    • 4.MainWindow 类:用户界面的交互逻辑
  • Chapter4 QT学习笔记QserialPort类学习(二):QSerialPort的成员函数


Chapter1 QT5串口多线程–派生类加moveToThread

原文链接:https://blog.csdn.net/weixin_42968757/article/details/109530944

前言

之前讲过继承QThread,在虚函数run()中实现线程里的工作。这是qt4.6之前的方法,目前官方推荐的方法,是继承QObject类,再用方法moveToThread来将QObject派生类移动到新线程中去,这样QObject派生类定义的信号和槽的事件响应行为,都会发生在新的线程中。
这个方法非常简单。下面还是以串口通讯作为例子,这次不用窗口,用控制台的方式。

新建工程

打开QTcreator,新建一个Qt Console Application工程,起好工程名,选用qmake作为编译工具,选择Desktop MinGW 64-bit作为编译器。

ps:选择编译器时,会看到各种各样的编译器,如MSVC,MinGW等,这取决于在安装Qt时你的选择。在选择编译器时还会看到,Desktop和UWP两种平台的选择,Desktop很好解释,就是桌面程序,UWP全称是Universal Windows Platform,即Windows通用应用平台,只支持Window10以上系统,如Windows应用商店上的应用都是UWP平台的。

源码

使用QSerialPort首先得在pro文件中,增加serialport模块

QT +=serialport

在serialobject头文件中,我们定义了serialobject类,继承QObject类,这个serialobject类后面我们要通过moveToThread方法放进线程里,在QObject类new的对象和定义的信号和槽都将运行在新的线程中。

在使用moveToThread方法后,所有与信号关联的槽的运行都在新的线程中,但没有通过信号与槽方式的函数,都属于调用他的线程,比如构造函数。

我们的QserialPort对象是我们用来实际操作和设置串口的对象,设置为private类型,这样将所有的串口操作都放在了serialobject类里,多个串口就new多个serialobject类,可以方便创建多个线程操作。

serialobject.h

#ifndef SERIALOBJECT_H
#define SERIALOBJECT_H

#include 
#include 
#include 
#include 
#include 


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

public:
    void senddata(QByteArray &msg);

private:
    QSerialPort* myQSerialPort = nullptr;
    QTimer* timer = nullptr; //定时器

public slots:
    void slot_receivedata();
    void slot_cyclesend();
    void slot_init();

signals:
    void sig_datareceived(QByteArray msg);
};

#endif // SERIALOBJECT_H

为了避免跨线程创建对象的错误,我们在槽函数init中实例化了QSerialPort类,后面会将init与Qthread的started信号关联起来。

如果在构造函数实例化QSerialPort类,会导致在调用write函数时,报
QObject: Cannot create children for a parent that is in a different thread的错误。
因为write会创建对象,而QserialPort是在构造函数上创建的,属于主线程,不属于子线程。

在init中设置了波特率等信息后,打开串口,串口打开成功的话,将数据readyRead信号和自定义的槽函数receivedata函数关联起来,这样只要串口有数据发来,槽函数就会将其打印到控制窗口上来。

serialobject.cpp

#include "serialobject.h"

serialobject::serialobject(QObject *parent) : QObject(parent)
{
    qDebug()<<"serialobject created"<<QThread::currentThread(); //直接打印当前线程名称和ID
}

serialobject::~serialobject()
{

}

void serialobject::senddata(QByteArray &msg)
{
    myQSerialPort->write(msg);  //把数据写入到缓存区
    myQSerialPort->flush();  //把缓存区的数据手动刷新出来
    qDebug()<<QString::fromLocal8Bit("senddata")<<msg<<QThread::currentThread(); //直接打印当前线程名称和ID
}

void serialobject::slot_receivedata()
{
    QByteArray array = myQSerialPort->readAll();
    if(!array.isEmpty())
    {
        qDebug()<<QString::fromLocal8Bit("slot_receivedata:")<<array<<QThread::currentThread(); //直接打印当前线程名称和ID
        emit sig_datareceived(array);
    }
}

void serialobject::slot_cyclesend()
{
    QByteArray msg = "serial send: abcdefg";
    senddata(msg);
    qDebug()<<QString::fromLocal8Bit("串口发送数据:")<<msg<<QThread::currentThread(); //直接打印当前线程名称和ID
}

/*******
 * 为了避免跨线程创建对象的错误,我们在槽函数init中实例化了QSerialPort类,后面会将init与Qthread的started信号关联起来。
 * 如果在构造函数实例化QSerialPort类,会导致在调用write函数时,报
QObject: Cannot create children for a parent that is in a different thread的错误。
因为write会创建对象,而QserialPort是在构造函数上创建的,属于主线程,不属于子线程。

在init中设置了波特率等信息后,打开串口,串口打开成功的话,将数据readyRead信号和自定义的槽函数receivedata函数关联起来,
这样只要串口有数据发来,槽函数就会将其打印到控制窗口上来。
 * ****/
void serialobject::slot_init()
{
    qDebug()<<"serialobject slot_init"<<QThread::currentThread(); //直接打印当前线程名称和ID
    myQSerialPort = new QSerialPort();
    myQSerialPort->setParity(QSerialPort::NoParity); 设置奇偶校验位为0
    myQSerialPort->setDataBits(QSerialPort::Data8);//设置数据位为8bit
    myQSerialPort->setFlowControl(QSerialPort::NoFlowControl);//设置流控制为OFF
    myQSerialPort->setStopBits(QSerialPort::OneStop);//设置停止位为1
    myQSerialPort->setBaudRate(115200);
    myQSerialPort->setPortName("COM1");

    if(myQSerialPort->isOpen()) //如果串口已经打开了 先给他关闭了
    {
        myQSerialPort->clear();
        myQSerialPort->close();
    }
    if(!myQSerialPort->open(QIODevice::ReadWrite)) //用ReadWrite 的模式尝试打开串口
    {
        qDebug()<<myQSerialPort->portName()<<QString::fromLocal8Bit("串口打开失败");
        return;
    }
    else {
        timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, &serialobject::slot_cyclesend);
        timer->start(1000);
        connect(myQSerialPort, &QSerialPort::readyRead, this, &serialobject::slot_receivedata);
        qDebug()<<myQSerialPort->portName()<<QString::fromLocal8Bit("串口打开成功,波特率:")<<myQSerialPort->baudRate()<<QString::fromLocal8Bit("发送周期ms:")<<1000;
    }

}

为了方便管理多线程,我们新建了一个manager类,来控制多线程。也来控制线程间通讯的信号和槽的关系。MoveToThread方法也在这里调用。
这里的manager,我们将QThread的started的信号与serialobject的init关联起来,来实现在子线程中实例化QSerialPort类。

manager.h

#ifndef MANAGER_H
#define MANAGER_H

#include 
#include "serialobject.h"


/*
 *
为了方便管理多线程,我们新建了一个manager类,来控制多线程。也来控制线程间通讯的信号和槽的关系。MoveToThread方法也在这里调用。
这里的manager,我们将QThread的started的信号与serialobject的init关联起来,来实现在子线程中实例化QSerialPort类。
 *
 */
class manager : public QObject
{
    Q_OBJECT
public:
    explicit manager(QObject *parent = nullptr);
    ~manager();

    void thread_init(void);

    serialobject* SObject = nullptr;
    QThread* SerialThread = nullptr;

signals:
    void sig_ReceiveData(QByteArray msg);

};

#endif // MANAGER_H

manager.cpp

#include "manager.h"

manager::manager(QObject *parent) : QObject(parent)
{

}

manager::~manager()
{
    SerialThread->quit();  // 退出工作线程
    SerialThread->wait();  // 等待线程完全退出
    delete SObject;   // 删除串口工作类对象
}

void manager::thread_init()
{
    //serial receive&send thread
    SObject = new serialobject();
    SerialThread = new QThread();
    SObject->moveToThread(SerialThread);
    connect(SerialThread, &QThread::started, SObject, &serialobject::slot_init);
    connect(SObject, &serialobject::sig_datareceived, this, &manager::sig_ReceiveData);
    SerialThread->start();
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include 
#include 
#include "manager.h"

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pBtn_Start_clicked();
    void slot_serialDataReceived(QByteArray msg);

private:
    Ui::Widget *ui;
    manager* my_manager = nullptr;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    qDebug()<<"Widget:"<<QThread::currentThread();
    my_manager = new manager();
//    connect(my_manager->SObject, &serialobject::sig_datareceived, this, &Widget::slot_serialDataReceived);
    connect(my_manager, &manager::sig_ReceiveData, this, &Widget::slot_serialDataReceived);
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_pBtn_Start_clicked()
{
    my_manager->thread_init();
}

void Widget::slot_serialDataReceived(QByteArray msg)
{
    QString date = QDateTime::currentDateTime().toString();
    date += ": " + msg;
    ui->textEdit->append(date);
}

测试

我们已经在main中,serialobject构建函数和receivedata槽函数,放了打印当前线程的方法QThread::currentThread()。
在这里插入图片描述
可以看到,serialobject的构造函数和主线程属于同一个线程,串口初始化和串口接受都在同一个子线程进行。这种方法要比继承QThread的方法要简单和清晰很多,也不容易出现跨线程创建对象的问题。

Chapter2 QT在PC开发中多串口通信有哪些方法

原文链接:https://blog.csdn.net/ren365880/article/details/140384988

方法

方案一: 在单独的线程中创建并管理QSerialPort对象,但不要让该对象成为任何其他对象的子对象。在该线程中启动一个事件循环,并使用信号和槽来跨线程通信。如果您的应用程序涉及到复杂的串口通信,并且需要处理大量的数据或保持UI的响应性,您可能需要考虑将串口通信放在单独的线程中。

方案二: 使用Qt的并发框架(如QtConcurrent)来处理与串口通信相关的数据处理任务,但让QSerialPort对象保持在主线程或专用的IO线程中。

方案三(不推荐,但可行): 为每个串口创建一个独立的线程,在每个线程中处理相应串口的通信任务。这种方法可以避免多个串口同时操作时可能出现的阻塞问题,确保每个串口的通信能够及时响应。示例代码可参考:利用 Qt 多线程机制实现双路串口数据流的接收和发送(附工程源代码) ,如果你确实需要将QSerialPort对象移动到另一个线程,请确保你完全理解Qt的线程模型,并且你能够正确地管理对象的生命周期和线程间的同步。这通常是不必要的,也是复杂的,并且容易出错。

方案四:(类似方案一,比较简单,推荐) 在主线程中持有多个QSerialPort的连接对象,QT 中的 QSerialPort 类提供了对串口操作的支持。可以创建多个 QSerialPort 对象来分别处理不同的串口。在使用时,需设置串口的参数(如波特率、数据位、停止位等),然后通过信号与槽机制来实现数据的接收和发送。例如,连接 readyRead 信号来接收数据,调用 write 函数发送数据。在Qt中,信号和槽的连接是自动的,只要对象没有被销毁,并且信号在对象生命周期内被正确发出,槽函数就会被调用。但是,如果槽函数是跨线程的,那么您需要确保信号和槽的参数是线程安全的,或者使用Qt的元对象系统(meta-object system)来进行线程间的通信。方案四的串口对象在同一个线程中,因此不需要担心这个问题。

实现

方案一:

首先,你需要一个类来封装QSerialPort,这个类将负责串口的配置(如波特率、数据位、停止位等)、打开串口、关闭串口、发送数据和接收数据。

#include   
#include   
  
class SerialPortManager : public QObject {  
    Q_OBJECT  
  
public:  
    SerialPortManager(QObject *parent = nullptr) : QObject(parent), serial(new QSerialPort(this)) {  
        connect(serial, &QSerialPort::readyRead, this, &SerialPortManager::readData);  
    }  
  
    void openSerialPort(const QString &portName, int baudRate) {  
        serial->setPortName(portName);  
        serial->setBaudRate(baudRate);  
        // 设置其他参数,如数据位、停止位等  
        if (serial->open(QIODevice::ReadWrite)) {  
            qDebug() << "Serial port opened successfully";  
        } else {  
            qDebug() << "Failed to open serial port";  
        }  
    }  
  
    void closeSerialPort() {  
        if (serial->isOpen()) {  
            serial->close();  
            qDebug() << "Serial port closed";  
        }  
    }  
  
    void writeData(const QByteArray &data) {  
        if (serial->isOpen()) {  
            serial->write(data);  
        }  
    }  
  
private slots:  
    void readData() {  
        QByteArray data = serial->readAll();  
        // 处理接收到的数据  
        qDebug() << "Received data:" << data;  
    }  
  
private:  
    QSerialPort *serial;  
};

然后,你需要一个或多个线程来运行串口管理类。这可以通过继承QThread来实现,但更好的做法是使用QThread的run方法来启动一个事件循环,并在其中运行你的串口管理类。

#include   
  
class SerialPortThread : public QThread {  
    Q_OBJECT  
  
public:  
    SerialPortThread(QObject *parent = nullptr) : QThread(parent), manager(new SerialPortManager(this)) {}  
  
    void run() override {  
        exec(); // 启动事件循环  
    }  
  
    void openPort(const QString &portName, int baudRate) {  
        manager->openSerialPort(portName, baudRate);  
    }  
  
    // 其他必要的方法...  
  
private:  
    SerialPortManager *manager;  
};

最后,在你的主程序或主窗口中,你可以创建多个SerialPortThread实例,并为每个实例配置不同的串口。

#include   
#include "SerialPortThread.h"  
  
int main(int argc, char *argv[]) {  
    QCoreApplication a(argc, argv);  
  
    SerialPortThread *thread1 = new SerialPortThread();  
    thread1->openPort("COM1", 9600);  
    thread1->start(); // 实际上在SerialPortThread中已经隐式启动事件循环  
  
    // 对于额外的串口,创建更多的SerialPortThread实例  
  
    return a.exec();  
}

需要注意的是:

确保在适当的时机关闭串口和线程。
考虑使用Qt的信号和槽机制来处理线程间的通信,避免直接调用线程内部的对象方法。
线程安全:确保在多个线程中访问共享资源时采取适当的同步措施。
错误处理:在实际应用中,应该添加更多的错误处理和异常捕获机制。

方案二:

没有实际的使用过

方案三:

可以查看博客中的代码,为什么这个方案不推荐:
在Qt中,将QSerialPort对象作为某个类的子对象(即使用new QSerialPort()),并且随后尝试通过moveToThread()将其移动到另一个线程中,这通常是不推荐的,也是不安全的。原因如下:
对象生命周期和所有权: 当你使用new QSerialPort()时,你正在创建一个QSerialPort对象,并将其父对象设置为当前对象。在Qt中,子对象的生命周期是由其父对象管理的。如果父对象被销毁,那么它的所有子对象也会被销毁。然而,当你尝试将子对象移动到另一个线程时,这可能会破坏Qt的对象层次结构和生命周期管理机制。
线程亲和性: QSerialPort(以及大多数Qt的IO类)被设计为在其被创建的线程中执行IO操作。尽管你可以通过moveToThread()改变对象的线程亲和性(即它的事件处理在哪个线程中发生),但这并不意味着你可以安全地在另一个线程中执行IO操作。对于QSerialPort来说,这可能会导致未定义的行为,包括程序崩溃。
Qt的线程模型: Qt的线程模型是基于事件循环的。当你将一个对象移动到另一个线程时,你实际上是在告诉Qt将该对象的事件(如信号和槽的调用)发送到该线程的事件循环中。但是,这并不意味着对象本身可以在该线程中执行非事件驱动的IO操作。

方案四:

首先新建一个串口连接类:

#include "serialcontroller.h"

SerialController::SerialController(QObject *parent) : QObject(parent){

}

/**
 * 析构函数
 * @brief SerialController::~SerialController
 */
SerialController::~SerialController(){
    if (m_serialPort->isOpen()) {
        m_serialPort->close();
        delete m_serialPort;
    }
}

/**
 * 初始化串口连接
 * @brief SerialController::initSerialPort
 * @param id 对象ID
 * @param portName 串口名
 */
void SerialController::initSerialPort(int id,QString portName){
    m_portName = portName;
    m_id = id;
    m_serialPort = new QSerialPort(this);
    m_serialPort->setPortName(m_portName);
    m_serialPort->setBaudRate(QSerialPort::Baud9600,QSerialPort::AllDirections);
    m_serialPort->setDataBits(QSerialPort::Data8);//数据位为8位
    m_serialPort->setFlowControl(QSerialPort::NoFlowControl);//无流控制
    m_serialPort->setParity(QSerialPort::NoParity);	//无校验位
    m_serialPort->setStopBits(QSerialPort::OneStop); //一位停止位

    if(!m_serialPort->open(QIODevice::ReadWrite)){ //用ReadWrite 的模式尝试打开串口
        emit sendUpdateUI(20,0,m_portName+"打开失败");//发送更新UI的信号
        return;
    }
    // 连接信号和槽
    connect(m_serialPort,&QSerialPort::readyRead,this,&SerialController::readData);

}

/**
 * 读取串口信息的槽并发送信号
 * @brief MainWindow::readData
 */
void SerialController::readData(){
    emit sendUpdateUI(20,2,"读取串口数据");
    QByteArray buf;
    buf = m_serialPort->readAll();
    QString hexStr;
    if(!buf.isEmpty()){
        // 将接收到的字节转换为16进制字符串
        for (char byte : buf) {
            hexStr += QString("%1").arg(byte&0xff, 2, 16, QChar('0'));
        }
    }
    buf.clear();
    emit sendDataToMain(hexStr);
}


/**
 * 发送数据
 * @brief SerialController::sendData
 * @param data 要发送的字节数组
 */
void SerialController::sendData(const QByteArray &data){
    m_serialPort->write(data);
}

在MainWindow中新建方法和槽

/**
 * 初始化串口
 * @brief MainWindow::initSerialPort
 */
void MainWindow::initSerialPort(){
    QStringList ports = port.split(" "); //连接指定的1到多个串口
    for (int i=0;i<ports.length();i++) {
        SerialController *con = new SerialController(this);
        connect(con, &SerialController::sendDataToMain, this, &MainWindow::receiveData);
        con->initSerialPort(i,ports[i]);
        serialControllers.append(con);//把对象放到数组中
    }
}



/**
 * 接收串口发来信息的槽
 * @brief MainWindow::receiveData
 * @param data 串口信息
 */
void MainWindow::receiveData(const QString &data){
    //接收到串口发来的信息后进行业务上的处理
}

在对串口发送信息时,可从数组中循环出来,可以群发,也可以根据自定的ID判断对指定的接口发

foreach(SerialController* con,serialControllers){
    con->sendData(QByteArray::fromHex(data));
}

Chapter3 Qt串口助手开发:基于多线程moveToThread方法串口通信工具

原文链接:https://blog.csdn.net/chenai886/article/details/142335803

介绍了一个基于Qt框架开发的简易串口助手,满足粉丝的需求。该项目展示了如何利用Qt的moveToThread方法实现多线程串口通信,确保数据接收和发送功能的流畅性。项目中的核心类包括SerialWorker类和MainWindow类,分别负责串口操作和用户界面交互。

1. 项目背景与设计思路

Qt 是一个跨平台的 C++ 开发框架,具有强大的 GUI 开发能力和对硬件接口(如串口)的支持。串口通信通常涉及长时间的读写操作,因此为了避免阻塞用户界面线程,需要将串口操作放入后台线程处理。本项目通过 moveToThread 方法实现了这一需求,即将串口操作移至一个独立的工作线程中,保证界面在数据处理过程中仍然保持响应。

2. 串口助手的主要功能

该串口助手工具具备以下主要功能:

  • 串口的自动检测与配置。
  • 数据的十六进制格式与文本格式发送和接收。
  • 数据的发送、接收、及错误处理。
  • 多线程处理,确保串口操作不会阻塞主界面。

3. SerialWorker类:串口操作的后台处理

SerialWorker类是串口助手的核心,专门用于处理串口的开启、关闭、数据收发等操作。该类继承自QObject,其设计遵循Qt的信号与槽机制,以实现异步通信。

  • 构造与析构:串口对象serial在构造时初始化为nullptr,并在析构时安全关闭串口,释放资源。

  • startSerialPort方法:该方法通过Q_INVOKABLE修饰,可以在其他线程中被调用。它用于设置串口的端口名和波特率,并打开串口,准备进行读写操作。

  • handleReadyRead槽函数:这是处理串口数据接收的关键函数,当串口接收到数据时,它会被触发,读取数据并发射dataReceived信号。

  • handleWriteData槽函数:该函数用于向串口发送数据,在串口打开时调用serial->write()方法发送数据,确保数据通过串口传输出去。

4.MainWindow 类:用户界面的交互逻辑

MainWindow类是串口助手的主界面,负责用户操作与后台串口通信的交互。

  • UI初始化与线程处理:在构造函数中,将SerialWorker对象移到独立线程workerThread中,确保串口操作在后台线程执行,不阻塞界面。通过QMetaObject::invokeMethod实现主线程对工作线程的安全调用。

  • 串口检测:populateSerialPorts方法自动检测可用串口,并将其填充到下拉菜单供用户选择。

  • 串口启动与关闭:点击启动按钮后,获取串口名和波特率,通过invokeMethod调用SerialWorker的startSerialPort方法开启串口;点击停止按钮则关闭串口。

  • 数据发送:应用支持文本和十六进制两种格式的发送,用户选择格式后,数据会被处理并通过handleWriteData方法发送至串口。

  • 数据接收与显示:接收到串口数据后,通过信号将数据传给MainWindow,并实时显示,支持文本与十六进制格式的切换。

  • 错误处理:当出现错误时,SerialWorker通过信号将错误信息传递给MainWindow,主窗口会通过弹窗通知用户。

#ifndef SERIALWORKER_H
#define SERIALWORKER_H
 
#include 
#include 
#include 
 
#define tc(a) QString::fromLocal8Bit(a)
 
class SerialWorker : public QObject
{
    Q_OBJECT
public:
    explicit SerialWorker(QObject *parent = nullptr);  // 构造函数
    ~SerialWorker();  // 析构函数
 
    Q_INVOKABLE void startSerialPort(const QString &portName, int baudRate);  // 启动串口,Q_INVOKABLE使其可被invokeMethod调用
    Q_INVOKABLE void stopSerialPort();  // 关闭串口,Q_INVOKABLE使其可被invokeMethod调用
 
signals:
    void dataReceived(const QByteArray &data);  // 数据接收信号
    void errorOccurred(const QString &error);   // 错误信号
    void writeData(const QByteArray &data);     // 写数据信号
 
public slots:
    void handleWriteData(const QByteArray &data);  // 写数据槽函数
 
private slots:
    void handleReadyRead();  // 处理串口接收数据槽函数
 
private:
    QSerialPort *serial;  // QSerialPort 对象指针
};
 
#endif // SERIALWORKER_H
#include "serialworker.h"
#include 
 
SerialWorker::SerialWorker(QObject *parent) : QObject(parent)
{
    serial = nullptr;  // 初始化时serial为空,稍后在startSerialPort中创建
}
 
SerialWorker::~SerialWorker()
{
    if (serial) {
        if (serial->isOpen()) {
            serial->close();  // 如果串口打开,关闭串口
        }
        delete serial;  // 删除serial对象
    }
}
 
// 启动串口,Q_INVOKABLE 使其可被跨线程调用
void SerialWorker::startSerialPort(const QString &portName, int baudRate)
{
    if (!serial) {
        serial = new QSerialPort();  // 创建串口对象
    }
 
    serial->setPortName(portName);   // 设置串口名称
    serial->setBaudRate(baudRate);   // 设置波特率
 
    // 连接串口的 readyRead 信号到 handleReadyRead 槽函数
    connect(serial, &QSerialPort::readyRead, this, &SerialWorker::handleReadyRead);
 
    // 打开串口,读写模式
    if (serial->open(QIODevice::ReadWrite)) {
        qDebug() << tc("串口成功打开:") << portName;
    } else {
        emit errorOccurred(serial->errorString());  // 发送错误信号
    }
}
 
// 停止串口操作
void SerialWorker::stopSerialPort()
{
    if (serial && serial->isOpen()) {
        serial->close();  // 关闭串口
        qDebug() << tc("串口已关闭");
    }
}
 
// 写入数据到串口
void SerialWorker::handleWriteData(const QByteArray &data)
{
    if (serial && serial->isOpen()) {
        serial->write(data);  // 写数据到串口
    } else {
        emit errorOccurred(tc("串口未打开"));  // 如果串口未打开,发送错误信号
    }
}
 
// 处理串口接收到的数据
void SerialWorker::handleReadyRead()
{
    QByteArray data = serial->readAll();  // 读取所有数据
    emit dataReceived(data);  // 发出数据接收信号
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include 
#include 
#include "serialworker.h"
 
#define tc(a) QString::fromLocal8Bit(a)
 
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
 
 
protected:
    void closeEvent(QCloseEvent *e)override;
    void initStyle();
 
private slots:
    void on_startButton_clicked();  // 点击启动按钮槽函数
    void on_sendButton_clicked();   // 点击发送按钮槽函数
    void on_stopButton_clicked();   // 点击停止按钮槽函数
    void handleDataReceived(const QByteArray &data);  // 处理接收到的数据槽函数
    void handleError(const QString &error);  // 处理错误槽函数
 
    void populateSerialPorts();  // 自动检索可用串口并填充到下拉列表
 
private:
    Ui::MainWindow *ui;
    SerialWorker *serialWorker;  // 串口工作类对象
    QThread *workerThread;       // 工作线程对象
};
 
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 
#include 
#include 
#include 
#include 
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , serialWorker(new SerialWorker)
    , workerThread(new QThread(this))
{
    ui->setupUi(this);
 
    // 将 SerialWorker 移动到 workerThread
    serialWorker->moveToThread(workerThread);
 
    // 启动工作线程
    workerThread->start();
 
    // 连接信号和槽
    connect(serialWorker, &SerialWorker::dataReceived, this, &MainWindow::handleDataReceived);
    connect(serialWorker, &SerialWorker::errorOccurred, this, &MainWindow::handleError);
 
    // 预填充常用波特率
    ui->baudRateComboBox->addItems({"9600", "115200", "38400", "19200", "57600"});
 
    // 自动检索串口并填充到下拉列表
    populateSerialPorts();
    initStyle();
}
 
MainWindow::~MainWindow()
{
    workerThread->quit();  // 退出工作线程
    workerThread->wait();  // 等待线程完全退出
    delete serialWorker;   // 删除串口工作类对象
    delete ui;
}
void MainWindow::initStyle()
{
    //加载样式表
    QString qss;
    QFile file(":/qss/psblack.css");
    if (file.open(QFile::ReadOnly)) {
#if 1
        //用QTextStream读取样式文件不用区分文件编码 带bom也行
        QStringList list;
        QTextStream in(&file);
        //in.setCodec("utf-8");
        while (!in.atEnd()) {
            QString line;
            in >> line;
            list << line;
        }
 
        qss = list.join("\n");
#else
        //用readAll读取默认支持的是ANSI格式,如果不小心用creator打开编辑过了很可能打不开
        qss = QLatin1String(file.readAll());
#endif
        QString paletteColor = qss.mid(20, 7);
        qApp->setPalette(QPalette(paletteColor));
        qApp->setStyleSheet(qss);
        file.close();
    }
 
 
 
 
}
void MainWindow::closeEvent(QCloseEvent *e)
{
    on_stopButton_clicked();
 
    QMainWindow::closeEvent(e);
}
 
// 自动检索系统可用的串口并填充到ComboBox中
void MainWindow::populateSerialPorts()
{
    ui->portNameComboBox->clear();  // 清空现有的串口列表
 
    // 获取可用串口列表并添加到ComboBox中
    const QList<QSerialPortInfo> serialPorts = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : serialPorts) {
        ui->portNameComboBox->addItem(info.portName());
    }
 
    // 如果没有可用串口,提示警告
    if (ui->portNameComboBox->count() == 0) {
        QMessageBox::warning(this, tc("警告"), tc("未检测到可用的串口"));
    }
}
 
// 启动串口操作
void MainWindow::on_startButton_clicked()
{
    QString portName = ui->portNameComboBox->currentText();  // 从 ComboBox 中获取串口名
    int baudRate = ui->baudRateComboBox->currentText().toInt();  // 从 ComboBox 中获取波特率
 
    // 使用 QMetaObject::invokeMethod 来确保在工作线程中启动串口
    QMetaObject::invokeMethod(serialWorker, "startSerialPort", Qt::QueuedConnection,
                              Q_ARG(QString, portName), Q_ARG(int, baudRate));
}
 
// 发送数据到串口
void MainWindow::on_sendButton_clicked()
{
    QByteArray data;
 
 
    if(ui->radioSendHEX->isChecked())
    {
        QString input = ui->sendDataEdit->text().remove(QRegExp("\\s")); // Remove all spaces
        data = QByteArray::fromHex(input.toLocal8Bit()); // Convert the cleaned string to QByteArray
    }
    else
    {
        data=(ui->sendDataEdit->text().toLocal8Bit());
    }
 
    // 使用 QMetaObject::invokeMethod 来确保在工作线程中发送数据
    QMetaObject::invokeMethod(serialWorker, "handleWriteData", Qt::QueuedConnection,
                              Q_ARG(QByteArray, data));
 
 
 
 
    QString msg;
    msg.append(QDateTime::currentDateTime().toString("hh:mm:ss.(zzz)  "));
    msg.append(tc("发送  "));
    msg.append(ui->radioSendHEX->isChecked()? data.toHex(' ').toUpper():QString::fromLocal8Bit(data));
    ui->receiveDataEdit->append(msg);
 
 
}
 
// 停止串口操作
void MainWindow::on_stopButton_clicked()
{
    // 使用 QMetaObject::invokeMethod 来确保在工作线程中关闭串口
    QMetaObject::invokeMethod(serialWorker, "stopSerialPort", Qt::QueuedConnection);
}
 
// 处理串口接收到的数据
void MainWindow::handleDataReceived(const QByteArray &data)
{
    QString msg;
    msg.append(QDateTime::currentDateTime().toString("hh:mm:ss.(zzz)  "));
    msg.append(tc("接收  "));
    msg.append(ui->radioRecvHex->isChecked()? data.toHex(' ').toUpper():QString::fromLocal8Bit(data));
    ui->receiveDataEdit->append(msg);
 
 
}
 
// 处理串口错误
void MainWindow::handleError(const QString &error)
{
    QMessageBox::critical(this, tc("错误"), error);  // 显示错误信息
}

Chapter4 QT学习笔记QserialPort类学习(二):QSerialPort的成员函数

原文链接

本文主要参考的是官方手册,力争写一个可信的,详尽可查的QserialPort类学习手册。

bool QSerialPort::flush();

这个函数的功能是将内部写缓存中的数据写入到尽可能多地写入到串口中去,而且是无阻塞的。如果写入了任何数据,那么函数返回true,否则就返回false。

调用这个函数就立刻把缓存的数据发到串口。成功写入到串口的数据量取决于操作系统。在大多数情况下,这个函数不需要被调用,因为QSerialPort类会在控件返回到事件循环的时候自动开始发送数据。在非事件循环情况下,可以调用waitForBytesWritten()函数来代替。

注意:使用flush()函数的时候,串口必须打开,否则会报错。

void QSerialPort::setReadBufferSize(qint64 size);

设置QSerialPort的内部读缓存大小为参数“size”的大小。

如果这个缓存的大小被限制在了某个确定值上,QSerialPort就会严格执行,不会缓存超过其大小的数据。有一种特殊情况,缓存大小是0(默认值就是0),这就意味着读缓存大小没有限制,并且所有进来的数据都会被缓存下来。

这个函数在以下情况下是非常有用的:

1、数据只是在某个特定的时间点来读取(比如:在实时流应用上);

2、保护串口,防止接收过多的数据,因为某些情况下这可能会导致应用超出内存。

[virtual] qint64 QSerialPort::bytesAvailable() const;

这个函数是QIODevice::bytesAvailable()的继承和重载。这个函数会返回串口收到的那些等待读取的数据字节数。

上一篇:x58 x79 x99区别(x58和x79主板哪个好)
下一篇:2014年vivo出的手机(vivo520l手机是那一年出的)