Qt子线程、多线程使用串口 QSerialPort QThread QObject

文章示例工程下载地址:
https://download.csdn.net/download/u014779536/13790253

1.创建工程

1.1 建立空白工程

工程名:Qt_MultiThread_SerialPort

image-20201224164458527

1.2 添加控件

添加下图所示的所有控件,并重新命名:

image-20201224172508226

各控件命名如下:

image-20201224172416002

2. 在主线程实现串口发送

实现串口发送我们需要进行以下几个步骤:

  • 查找可用串口
  • 打开串口
  • 通信发送
  • 通信接收
  • 关闭串口
  • 字符串转16字节(发送用)
  • 16字节转字符串(显示用)

2.1 在.pro文件和.h文件中引入QSerialPort

QT       += serialport

image-20201225093918443

#include <QSerialPort>
#include <QSerialPortInfo>

void InitSerialPortName();

image-20201225093950337

2.2 查找可用串口

我们在程序启动时自动搜索电脑设备,并将可用的串口名称添加到串口选择框。

image-20201225095545313

void MainWindow::InitSerialPortName()
{
    // 清空下拉框
    ui->box_portName->clear();

    //通过QSerialPortInfo查找可用串口
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        QString showName = info.portName()+":"+info.description();
        qDebug() << showName.length();
        ui->box_portName->addItem(showName);
    }
}

2.3 打开&关闭串口

在manwindow.h中声明串口

image-20201225102904993

设置打开按钮关闭槽函数:

void MainWindow::on_btn_openPort_clicked()
{
    if(ui->btn_openPort->text()==QString("打开串口"))
    {
        //设置串口名
        QString portName = (ui->box_portName->currentText()).split(":").at(0);
        qDebug() << portName;
        serial_1.setPortName(portName);

        //设置波特率
        serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());

        //设置停止位
        if(ui->box_stopBit->currentText() == "1")
            serial_1.setStopBits(QSerialPort::OneStop);
        else if(ui->box_stopBit->currentText() == "1.5")
            serial_1.setStopBits(QSerialPort::OneAndHalfStop);
        else if(ui->box_stopBit->currentText() == "2")
            serial_1.setStopBits(QSerialPort::TwoStop);

        //设置数据位数
        if(ui->box_dataBits->currentText() == "8")
            serial_1.setDataBits(QSerialPort::Data8);
        else if(ui->box_dataBits->currentText() == "7")
            serial_1.setDataBits(QSerialPort::Data7);
        else if(ui->box_dataBits->currentText() == "6")
            serial_1.setDataBits(QSerialPort::Data6);
        else if(ui->box_dataBits->currentText() == "5")
            serial_1.setDataBits(QSerialPort::Data5);

        //设置奇偶校验
        if(ui->box_parityBit->currentText() == "None")
            serial_1.setParity(QSerialPort::NoParity);
        else if(ui->box_parityBit->currentText() == "Even")
            serial_1.setParity(QSerialPort::EvenParity);
        else if(ui->box_parityBit->currentText() == "Odd")
            serial_1.setParity(QSerialPort::OddParity);

        //设置流控制
        serial_1.setFlowControl(QSerialPort::NoFlowControl);

        //打开串口
        if(!serial_1.open(QIODevice::ReadWrite))
        {
            QMessageBox::about(NULL, "提示", "无法打开串口!");
            return;
        }

        //下拉菜单控件失能
        ui->box_portName->setEnabled(false);
        ui->box_baudrate->setEnabled(false);
        ui->box_dataBits->setEnabled(false);
        ui->box_parityBit->setEnabled(false);
        ui->box_stopBit->setEnabled(false);

        ui->btn_openPort->setText(QString("关闭串口"));
        //发送按键使能
        ui->btn_send->setEnabled(true);
    }
    else
    {
        //关闭串口
        serial_1.close();

        //下拉菜单控件使能
        ui->box_portName->setEnabled(true);
        ui->box_baudrate->setEnabled(true);
        ui->box_dataBits->setEnabled(true);
        ui->box_parityBit->setEnabled(true);
        ui->box_stopBit->setEnabled(true);

        ui->btn_openPort->setText(QString("打开串口"));
        //发送按键失能
        ui->btn_send->setEnabled(false);
    }
}

2.4 将字节序列转换为对应的16进制字符串

/*
 * @breif 将字节序列转换为对应的16进制字符串
 */
QString MainWindow::ByteArrayToHexString(QByteArray data)
{
    QString ret(data.toHex().toUpper());
    int len = ret.length()/2;
    qDebug()<<len;
    for(int i=1;i<len;i++)
    {
        //qDebug()<<i;
        ret.insert(2*i+i-1," ");
    }

    return ret;
}

2.5 将16进制字符串转换为对应的字节序列

/*
 * @breif 将16进制字符串转换为对应的字节序列
 */
QByteArray MainWindow::HexStringToByteArray(QString HexString)
{
    bool ok;
    QByteArray ret;
    HexString = HexString.trimmed();
    HexString = HexString.simplified();
    QStringList sl = HexString.split(" ");

    foreach (QString s, sl) {
        if(!s.isEmpty())
        {
            char c = s.toInt(&ok,16)&0xFF;
            if(ok){
                ret.append(c);
            }else{
                qDebug()<<"非法的16进制字符:"<<s;
                QMessageBox::warning(0,tr("错误:"),QString("非法的16进制字符: \"%1\"").arg(s));
            }
        }
    }
    //qDebug()<<ret;
    return ret;
}

2.6 使用串口发送

/*
 * @breif 发送数据
 */
void MainWindow::on_btn_send_clicked()
{
    //获取界面上的数据并转换成utf8格式的字节流
    QByteArray data;

    if(ui->check_hexSend->isChecked() == true)
        data = HexStringToByteArray(ui->edit_dataSend->toPlainText());
    else
        data = ui->edit_dataSend->toPlainText().toUtf8();

    serial_1.write(data);
}

/*
 * @breif 清空发送框
 */
void MainWindow::on_btn_clearSend_clicked()
{
    ui->edit_dataSend->clear();
}

image-20201225103752645

效果演示:

串口发送

2.7 使用串口接收

串口接收有两种方式:

  1. 主动查询
  2. 等待消息接收中断

我们这里介绍第二种。

image-20201225104325404

[signal] void QIODevice::readyRead()

每当有新数据可用于从设备的当前读取通道读取时,都会发出此信号。 仅当有新数据可用时(例如,当网络套接字上有新的网络数据有效负载到达时,或将新的数据块附加到设备上时),它才会再次发出。

readyRead()不会递归地发出; 如果您重新进入事件循环或在连接到readyRead()信号的插槽内调用waitForReadyRead(),则不会重新发出该信号(尽管waitForReadyRead()可能仍返回true)。

对于实现从QIODevice派生的类的开发人员请注意:当新数据到达时,您应该始终发出readyRead()(不要仅仅因为缓冲区中仍有待读取的数据而发出它)。 在其他情况下不要发出readyRead()。

编写接收函数:

void MainWindow::serialPort_readyRead()
{
    // 获取当前时间字符串
    QDateTime current_date_time =QDateTime::currentDateTime();
    QString dateStr =current_date_time.toString("[yyyy-MM-dd hh:mm:ss.zzz]");

    //从接收缓冲区中读取数据
    QByteArray buffer = serial_1.readAll();
    QString bufferStr = ByteArrayToHexString(buffer);

    QString displayStr = dateStr+"\n"+bufferStr+"\n";

    //从界面中读取以前收到的数据
    QString oldString = ui->browser_dataReceive->toPlainText();
    oldString = oldString + QString(displayStr);
    //清空以前的显示
    ui->browser_dataReceive->clear();
    //重新显示
    ui->browser_dataReceive->append(oldString);
}

在构造函数关联信号与槽:

//连接信号和槽
    connect(&serial_1, &QSerialPort::readyRead, this, &MainWindow::serialPort_readyRead);

效果演示:

image-20201225105322175

串口发送接收

3. 在子线程实现串口发送与接收

假如我们要对一个串口进行长时间的轮询监控,我们不可能把它放在GUI线程处理,这样会造成界面卡顿,此时,我们就需要在子线程使用串口,具体怎么使用呢?我们将串口指针传入子线程进行处理。

3.1 建立串口事务处理子线程类:SerialWorker

点击 “Add New":

image-20201225110114011

选择”C++ class“:

image-20201225110206485

设置类属性:

image-20201225110314782

image-20201225110519133

3.2 修改默认工程

1. 修改 serialworker.h

image-20201225110902665

#ifndef SERIALWORKER_H
#define SERIALWORKER_H

#include <QObject>
#include <QSerialPort>

class SerialWorker : public QObject
{
    Q_OBJECT
public:
    explicit SerialWorker(QSerialPort *ser,QObject *parent = nullptr);

signals:

private:
    QSerialPort *serial;
};

#endif // SERIALWORKER_H

2. 修改 serialworker.cpp

image-20201225155250256

3.3 增加子线程串口发送、接收槽函数、结果通知信号

.h文件:

image-20201225161457347

#ifndef SERIALWORKER_H
#define SERIALWORKER_H

#include <QObject>
#include <QSerialPort>

class SerialWorker : public QObject
{
    Q_OBJECT
public:
    explicit SerialWorker(QSerialPort *ser,QObject *parent = nullptr);

signals:
    void sendResultToGui(QString result);

public slots:
    void doDataSendWork(const QByteArray data);
    void doDataReciveWork();

private:
    QSerialPort *serial;
};

#endif // SERIALWORKER_H

cpp文件:

image-20201225162529041

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

SerialWorker::SerialWorker(QSerialPort *ser,QObject *parent)
    : QObject(parent),serial(ser)
{

}

void SerialWorker::doDataSendWork(const QByteArray data)
{
    qDebug() <<  "子线程槽函数发送数据:" << data << "线程ID:" << QThread::currentThreadId();

    // 发送数据
    serial->write(data);
}

void SerialWorker::doDataReciveWork()
{
    // 1.收到数据
    QByteArray buffer = serial->readAll();

    // 2.进行数据处理
    QString resultStr = buffer;
    qDebug() <<  "子线程收到数据:" << resultStr << "线程ID:" << QThread::currentThreadId();

    // 3.将结果发送到主线程
    emit sendResultToGui(resultStr);
}

3.4 在GUI线程中增加数据发送信号、结果接收槽

在mainwindow.h中增加与子线程结果接收槽函数:

signals:
    void serialDataSend(const QByteArray data);
    
public slots:
    void handleResults(QString &result);

image-20201225162630041

在mainwindow.cpp 发送数据:

/*
 * @breif 发送数据
 */
void MainWindow::on_btn_send_clicked()
{
    //获取界面上的数据并转换成utf8格式的字节流
    QByteArray data;

    if(ui->check_hexSend->isChecked() == true)
        data = HexStringToByteArray(ui->edit_dataSend->toPlainText());
    else
        data = ui->edit_dataSend->toPlainText().toUtf8();

    // 在主线程发送
    //serial_1.write(data);

    // 在子线程发送
    emit serialDataSend(data);
    qDebug() <<  "主线程发送信号,线程ID:" << QThread::currentThreadId();
}

在mainwindow.cpp 结果结果:

void MainWindow::handleResults(QString &result)
{
    qDebug() <<  "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();
}

3.5 在GUI线程中添加串口子线程

在mainwindow.h定义子线程:

QThread serialThread_1; // 定义子线程

image-20201225171523401

在mainwindow.h使用串口子线程:

image-20201226102123994

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

    InitSerialPortName();

    // 1.新建串口处理子线程
    SerialWorker *serialWorker = new SerialWorker(&serial_1);
	
	// 将串口和子类一同移入子线程
    (&serial_1)->moveToThread(&ioThread);
    serialWorker->moveToThread(&serialThread_1);

    // 2.连接信号和槽
    connect(&serialThread_1, &QThread::finished,
            serialWorker, &QObject::deleteLater);           // 线程结束,自动删除对象
    connect(this, &MainWindow::serialDataSend,
            serialWorker, &SerialWorker::doDataSendWork);   // 主线程串口数据发送的信号
    connect(&serial_1, &QSerialPort::readyRead,
            serialWorker, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号
    connect(serialWorker, &SerialWorker::sendResultToGui,
            this, &MainWindow::handleResults);              // 主线程收到数据结果的信号

    // 3.开始运行子线程
    serialThread_1.start();                   // 线程开始运行

    // 在主线程接收串口数据
    //connect(&serial_1, &QSerialPort::readyRead, this, &MainWindow::serialPort_readyRead);
}

MainWindow::~MainWindow()
{
    // 退出串口1子线程
    serialThread_1.quit();
    serialThread_1.wait();

    delete ui;
}

3.6 效果演示

子线程串口发送接收

image-20201226111216556

文章示例工程下载地址:
https://download.csdn.net/download/u014779536/13790253

Qt中,线程不能直接访问UI控件,因为UI控件只能在主线程中访问。但是可以通过信号和槽机制来实现线程与UI控件的交互。具体步骤如下: 1.在主线程中定义一个槽函数,用于更新UI控件的状态。 2.在线程中定义一个信号,用于触发主线程中的槽函数。 3.在主线程中创建线程对象,并将线程中的信号连接到主线程中的槽函数。 4.在线程中通过信号触发主线程中的槽函数,从而更新UI控件的状态。 下面是两个例: 引用: ``` MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //关联信号 connect(this,&MainWindow::setui,this,&MainWindow::SetUI); mythread = new MyThread(this); mythread->start();//启动线程 } void MainWindow::SetUI() { this->ui->pushButton->setText("开始"); } ``` 在主线程中定义了一个槽函数SetUI(),用于更新UI控件pushButton的文本。在构造函数中,将主线程中的信号setui连接到槽函数SetUI()。在线程中,通过emit关键字触发setui信号,从而更新UI控件的状态。 引用: ``` Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { ui->setupUi(this); sonThread *sonthread = new sonThread; //创建线程对象 sonthread->label=ui->label; //将主界面UI指针赋给线程中的指针对象 sonthread->start(); //启动线程 qDebug()<<"Dialog()"<<QThread::currentThreadId(); } ``` 在主线程中创建了一个线程sonthread,并将主界面UI指针赋给线程中的指针对象label。在线程中,通过访问label指针来更新UI控件label的状态。
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级D洋葱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值