Qt练习:多线程串口通信

最近在学习Qt编程,为了练手,想做一个串口通信的小软件为之后的项目做准备,经过几天的学习与练习初步搭建起了多线程串口通信的框架及较少的其他功能

功能简介

  1. 串口接收
  2. 主线程显示ui,子线程接收串口数据,数据通过信号和槽发送到主线程
  3. 打开串口启动子线程,关闭串口后关闭子线程
  4. 采用定时器延时方法读取缓存区的数据
  5. 自行选择串口号与波特率,默认数据位为8、停止位为1、无奇偶校验
  6. 16进制显示项目中暂未用到,程序附在最后留存

界面设计

由于以后要进行其他数据的解析,用于测试的界面很简单,只设计了很少的控件,UI界面设计如下:
Qt练习:多线程串口通信_第1张图片
Qt练习:多线程串口通信_第2张图片

子线程

最开始练习时设计过单线程的串口助手,但是当数据量太大时软件会卡死,因此这次采用了多线程设计。
首先创建子线程类serialThread,并创建QSerialPort对象及相关初始化方法,主要代码如下:

serialthread.h

#ifndef SERIALTHREAD_H
#define SERIALTHREAD_H

#include
#include
#include
#include
#include
#include


class serialThread : public QThread
{
    Q_OBJECT
public:
    serialThread();

public:
    QSerialPort *Serial;
    void InitPortName(const QString &portName);                 //初始化串口名
    void InitPortBaudRate(qint32 baudRate);                     //...波特率
    void InitPortDataBits(QSerialPort::DataBits dataBits);      //...数据位
    void InitPortParity(QSerialPort::Parity parity);            //...奇偶校验
    void InitPortStopBits(QSerialPort::StopBits stopbits);      //...停止位
    void InitFlowControl(QSerialPort::FlowControl flowcontrol); //...控制流
    bool OpenPort();                                            //打开串口
    void ClosePort();                                           //关闭串口
public:
    QByteArray buffer;

signals:
    void dataSend(QByteArray);                                  //发送从串口读取到的数据的信号
    void timeout();

private slots:
    void timeUpdate();
    void data_receive();
    void run();

private:
    QTimer *time;
};



#endif // SERIALTHREAD_H

然后声明子线程相关方法,连接信号和槽,主要代码如下:
serialthread.cpp

#include "serialthread.h"

serialThread::serialThread()
{
    Serial=new QSerialPort();
    time=new QTimer;
}

/**
 * @brief 数据接收处理步骤
 * 1.打开串口,开启子线程
 * 2.串口收到数据后启动定时器,延时10ms,防止数据未发送完
 * 3.延时结束后读缓存区的数据
 * 4.将读取到的数据通过信号发送至主线程
 */

void serialThread::run()
{
    QObject::connect(Serial,&QSerialPort::readyRead,this,&serialThread::data_receive);  //收到数据触发槽函数启动定时器
    QObject::connect(time,SIGNAL(timeout()),this,SLOT(timeUpdate()));                   //延时结束开始读数据
}

void serialThread::data_receive()
{
    time->start(10);
}

void serialThread::timeUpdate()
{
    time->stop();
    buffer = Serial->readAll();              //读数据
    emit dataSend(buffer);                   //数据发送至主函数

#if 0  
    //以下代码为测试其他功能用的
    for (int i=0;i<(buffer.size()-10);i++)
    {

        if(buffer.at(i)==0x55)
        {
            switch (buffer.at(i+1))
            {
            case 0x51:{}break;
            case 0x52:{}break;
            case 0x53:{}break;
            case 0x54:{}break;
            case 0x56:{}break;
            case 0x57:{}break;
            case 0x58:{}break;
            case 0x59:{}break;
            case 0x5A:{}break;
            default:{}break;
            }
        }
    }
#endif
    buffer.clear();
}

void serialThread::InitPortName(const QString &portName)
{
    Serial->setPortName(portName);                          //设置串口名的方法,下同
}

void serialThread::InitPortBaudRate(qint32 baudRate)
{
     Serial->setBaudRate(baudRate);
}

void serialThread::InitPortDataBits(QSerialPort::DataBits dataBits)
{
    Serial->setDataBits(dataBits);
}

void serialThread::InitPortStopBits(QSerialPort::StopBits stopbits)
{
    Serial->setStopBits(stopbits);
}

void serialThread::InitPortParity(QSerialPort::Parity parity)
{
    Serial->setParity(parity);
}

void serialThread::InitFlowControl(QSerialPort::FlowControl flowcontrol)
{
    Serial->setFlowControl(flowcontrol);
}

bool serialThread::OpenPort()
{
    if(Serial->open(QIODevice::ReadWrite))
        return true;
    else
        return false;
}

void serialThread::ClosePort()
{
    Serial->close();
}

主线程

主线程主要用于UI界面的显示以及启动子线程,其中按钮的槽方法时通过UI编辑器生成的,代码如下:
mainwindow.h

#include "serialthread.h"

serialThread::serialThread()
{
    Serial=new QSerialPort();
    time=new QTimer;
}

/**
 * @brief 数据接收处理步骤
 * 1.打开串口,开启子线程
 * 2.串口收到数据后启动定时器,延时10ms,防止数据未发送完
 * 3.延时结束后读缓存区的数据
 * 4.将读取到的数据通过信号发送至主线程
 */

void serialThread::run()
{
    QObject::connect(Serial,&QSerialPort::readyRead,this,&serialThread::data_receive);  //收到数据触发槽函数启动定时器
    QObject::connect(time,SIGNAL(timeout()),this,SLOT(timeUpdate()));                   //延时结束开始读数据
}

void serialThread::data_receive()
{
    time->start(10);
}

void serialThread::timeUpdate()
{
    time->stop();
    buffer = Serial->readAll();              //读数据
    emit dataSend(buffer);                   //数据发送至主函数

#if 0  
    //以下代码为测试其他功能用的
    for (int i=0;i<(buffer.size()-10);i++)
    {

        if(buffer.at(i)==0x55)
        {
            switch (buffer.at(i+1))
            {
            case 0x51:{}break;
            case 0x52:{}break;
            case 0x53:{}break;
            case 0x54:{}break;
            case 0x56:{}break;
            case 0x57:{}break;
            case 0x58:{}break;
            case 0x59:{}break;
            case 0x5A:{}break;
            default:{}break;
            }
        }
    }
#endif
    buffer.clear();
}

void serialThread::InitPortName(const QString &portName)
{
    Serial->setPortName(portName);                          //设置串口名的方法,下同
}

void serialThread::InitPortBaudRate(qint32 baudRate)
{
     Serial->setBaudRate(baudRate);
}

void serialThread::InitPortDataBits(QSerialPort::DataBits dataBits)
{
    Serial->setDataBits(dataBits);
}

void serialThread::InitPortStopBits(QSerialPort::StopBits stopbits)
{
    Serial->setStopBits(stopbits);
}

void serialThread::InitPortParity(QSerialPort::Parity parity)
{
    Serial->setParity(parity);
}

void serialThread::InitFlowControl(QSerialPort::FlowControl flowcontrol)
{
    Serial->setFlowControl(flowcontrol);
}

bool serialThread::OpenPort()
{
    if(Serial->open(QIODevice::ReadWrite))
        return true;
    else
        return false;
}

void serialThread::ClosePort()
{
    Serial->close();
}

mainwindow.cpp

#include "gpsmap.h"
#include "ui_gpsmap.h"

gpsMap::gpsMap(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::gpsMap)
{
    ui->setupUi(this);
    portThread = new serialThread();

    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())     //搜索串口并在界面显示
    {
        ui->portBox->addItem(info.portName());
    }
    ui->baudBox->setCurrentIndex(0);                                           //设置波特率初始值

    QObject::connect(portThread, SIGNAL(dataSend(QByteArray)),this,SLOT(dataShow(QByteArray)));  //连接子线程发送数据与主线程接收数据的信号和槽
}

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

void gpsMap::dataShow(QByteArray buf)    //数据显示
{
    QByteArray buffer = buf;
    QString recv=QString(buffer);

    ui->textEdit->insertPlainText(recv);
}

void gpsMap::on_searchBtn_clicked()    //搜索按钮的槽方法
{
    ui->portBox->clear();
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        ui->portBox->addItem(info.portName());
    }
}

void gpsMap::on_openBtn_clicked()
{
    if(ui->openBtn->text()==QString("打开"))
    {
        //串口相关参数设置
        portThread->InitPortName(ui->portBox->currentText());
        portThread->InitPortBaudRate(ui->baudBox->currentText().toInt());
        portThread->InitPortDataBits(QSerialPort::Data8);
        portThread->InitPortStopBits(QSerialPort::OneStop);
        portThread->InitPortParity(QSerialPort::NoParity);
        portThread->InitFlowControl(QSerialPort::NoFlowControl);
        
        //若打开失败则提示
        if(!portThread->OpenPort())
        {
            QMessageBox::about(NULL,"提示","无法打开串口!");
            return;
        }
        
        //串口打开后使相关控件失能
        ui->portBox->setEnabled(false);
        ui->baudBox->setEnabled(false);
        ui->searchBtn->setEnabled(false);
        ui->openBtn->setText(QString("关闭"));
        
        //启动子线程
        portThread->start();
    }
    else {
        portThread->quit();
        portThread->ClosePort();
        ui->portBox->setEnabled(true);
        ui->baudBox->setEnabled(true);
        ui->searchBtn->setEnabled(true);

        ui->openBtn->setText(QString("打开"));
    }
}

总结

软件虽实现了串口接收功能,但是当数据量很大时会发生丢帧现象,搜索相关资料后采用定时器延时方法,虽有所改善但仍未解决,接收缓存区只能够存放99字节数据,不知道是自己检测的原因还是其他原因,留待以后解决。

由于这是第一次独立编写Qt小软件,走了很多弯路,在此感谢CSDN博主分享的方法及代码,如有错误,望指正。

附:十六进制显示程序(添加到显示程序)

	QString recv=QString(buffer);
    if(ui->hexBox->isChecked())//添加QCheckBox控件,判断其状态
    {
        QString strDis;
        QString hexRecv=recv.toUtf8().toHex().toUpper();
        for (int i =0;i<hexRecv.length();i+=2) {
            QString st=hexRecv.mid(i,2);
            strDis+=st;
            strDis+=" ";
        }
        ui->receiveEdit->append(strDis);
    }

你可能感兴趣的:(Qt练习:多线程串口通信)