简单串口编写

1.ui 界面的创建

打开Qt新建一个工程

工程命名为 **QtSerial** ,选择路径时注意路径**不可出现中文名**。 这里选择默认,点击**下一步** 这里基类选择 QWidget,修改类名为**MainWidget**,点击**下一步** 这里选择默认,点击**下一步** 编译套件随便选择,后面可以随时进行更改,点击**下一步** 这里选择默认,点击**完成**

进入工程,双击MainWidget.ui编辑ui文件(这里我们通过Qt的UI界面编辑器来设计ui界面,不以纯代码的形式来进行界面的设计。)

拖拽合适的控件完善布局。

  • 拖拽Tab Widget,并修改第 1 页为“串口助手”,第 2 页为“关于
    image-20210923213127836
  • 操作方法如下(动图展示)
    1

完善其他控件的拖拽,完整 UI 文件可以到此处下载提取码:h7po

最终界面展示图

image-20211024114809591

界面效果展示图(Windows 平台)

image-20211024114925843

2.QSerialPort模块介绍

QT的QtSerialPort模块

Qt中提供了两个C++类,分别是QSerialPortQSerialPortInfo

它们功能如下:

QSerialPort :提供了操作串口的各种接口。

QSerialPortInfo :可以提供计算机中可用串口的各种信息。

QtSerialPort模块使用方法

首先,需要在pro文件中增加如下内容:

QT += serialport    

然后执行qmake,如果未执行 后面添加头文件时会报错。

image-20211005013634387

给项目添加新的**C++**类,

image-20211027145302605

选择C++ Class

取名Serial,点击下一步即可生成对应的文件image-20211005014205307

在生成的serial.h中进行如下操作

#ifndef SERIAL_H
#define SERIAL_H

#include <QObject>
#include <QSerialPort>       //添加串口类的头文件
#include <QSerialPortInfo>	 //添加串口信息的头文件

class Serial : public QObject
{
    Q_OBJECT
public:
    explicit Serial(QObject *parent = nullptr);
    ~Serial(void);			//添加析构函数
    void SerialOpen();		//添加打开串口函数
    void SerialClose();		//添加关闭串口函数
private:
    QSerialPort* MySerial;  //添加串口类成员

signals:
    void SetInfo(QString info);
    void isnoSerialOpen();
};

#endif // SERIAL_H

分别将光标置于函数后面 按下快捷键 alt + enter

~Serial(void);			//添加析构函数
void SerialOpen();		//添加打开串口函数
void SerialClose();		//添加关闭串口函数

出现以下图片时 回车 即可在 cpp 文件中定义函数。
image-20211024112606270

动图展示。

~Serial(void); 用来delete 之后程序中 new出来的变量

void SerialOpen();和 void SerialClose();则是用来进行打开串口的操作。

首先在 cpp 文件中 对MySerial进行实例化。

image-20211027151715679

然后,鼠标放在MySerial上按下快捷键F1,打开QSerialPort的帮助文档。找到Public Functions

image-20211027152623327

打开Detailed Description
image-20211027153753566

从帮助文档中可以看出来,我们需要对串口进行的一些配置。

配置串口参数

操作步骤如下:

1.首先需要设置要打开的串口名,这里可以通过**setPortName()或者setPort()**进行配置

2.然后通过使用open()函数以 read-only (r/o), write-only (w/o), or read-write (r/w) 模式之一打开串口

3.然后,检测串口是否被打开 (且没有其他的进程或者线程打开串口,如果有就关闭串口在重新打开)

4.最后,配置串口参数如配置串口名,波特率,数据位,校验位,停止位和流控位

配置函数如下:

void setPortName(const QString &name) 
bool setBaudRate(qint32 baudRate, QSerialPort::Directions directions = AllDirections)
bool setDataBits(QSerialPort::DataBits dataBits)
bool setParity(QSerialPort::Parity parity)
bool setStopBits(QSerialPort::StopBits stopBits)
bool setFlowControl(QSerialPort::FlowControl flowControl)

首先,我们需要QStringqint32这两个类型的name参数和baudRate参数,这是通过ui界面的Qcombobox选项得到的。因为使用多线程的原因,不能直接调用,所以,这里通过构建结构体,通过传递结构体来传递参数。

鼠标右键点击工程,添加一个新的cpp头文件

image-20211027193012335

设置头文件名称为 SerialInfo.h ,点击下一步,完成。

image-20211027193154402

添加 SerialInfo.h的内容如下。

#ifndef SERIALINFO_H
#define SERIALINFO_H
#include <QVector>
#include <QMetaType>

typedef struct SerialInfos         //串口配置信息
{
     QString comName;    //串口名称
     qint32 baudRate;     //波特率
     qint32 dataBits;     //数据位
     qint32 parity;       //校验位
     qint32 stopBits;     //停止位
     qint32 flowControl;  //流控位
     qint32 Encode;       //编码格式

}Sinfo;


//通过Q_DECLARE_METATYPE声明后,就可以让自定义的类型设置到QVariant。
Q_DECLARE_METATYPE(Sinfo);

#endif // SERIALINFO_H

serial.h中添加头文件

#include "SerialInfo.h"

image-20211027194025777

并添加私有成员

private:
    QSerialPort* MySerial;
    Sinfo *info=nullptr;    //串口配置
	QString InfoSet;        //存储串口配置

image-20211027194229817

修改后的serial.h内容如下

#ifndef SERIAL_H
#define SERIAL_H

#include <QObject>
#include <QSerialPort>       //添加串口类的头文件
#include <QSerialPortInfo>	 //添加串口信息的头文件
#include "SerialInfo.h"

class Serial : public QObject
{
    Q_OBJECT
public:
    explicit Serial(QObject *parent = nullptr);
    ~Serial(void);			//添加析构函数
    void SerialOpen();		//添加打开串口函数
    void SerialClose();		//添加关闭串口函数
private:
    QSerialPort* MySerial;  //添加串口类成员
    Sinfo *info=nullptr;    //串口配置
    QString InfoSet;        //存储串口配置
signals:
    void SetInfo(QString info); //发送串口配置信号
    void isnoSerialOpen();	//发送串口打开失败信号
};

#endif // SERIAL_H

接下来在SerialOpen中操作。

首先判断串口是否打开,如果已经打开就关闭。这里调用SerialClose()(具体内容见下面SerialClose部分)

this->SerialClose();

然后设置串口名

MySerial->setPortName(QString(info->comName));

然后设置串口打开模式,R/W模式,如果设置失败发送错误信息,然后返回。

if(!MySerial->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口
    {
        emit isnoSerialOpen();  //发送打开失败的标志
        return;
    }

其中isnoSerialOpen()为设置的发送打开失败信号。

然后设置波特率,波特率通过info->baudRate设置

MySerial->setBaudRate(qint32(info->baudRate));

设置数据位,这里通过switch函数设置,其中setDataBits();中的参数通过使用F1查看,具体操作步骤如下。

使用同样的方法设置检验位、停止位和流控位。

在**SerialOpen()**中实现上述操作步骤,具体代码如下:

void Serial::SerialOpen()
{
    this->SerialClose();
    MySerial->setPortName(QString(info->comName));
    InfoSet=QString::fromLocal8Bit("串口:"); //InfoSet存储串口设置信息,发送给mainWidget
    InfoSet.append(QString(info->comName));
    if(!MySerial->open(QIODevice::ReadWrite))//用ReadWrite 的模式尝试打开串口
    {
        emit isnoSerialOpen();  //发送打开失败的标志
        return;
    }
    //设置波特率
    bool Bflag = MySerial->setBaudRate(qint32(info->baudRate));
        if(Bflag){
            InfoSet.append(QString::fromLocal8Bit(" 波特率:"));
            //第一个参数为int变量,第二个参数10表示转换为10进制数
            QString baudRateinfo = QString::number(int(info->baudRate),10);
            InfoSet.append(baudRateinfo);
        }
        else{
            InfoSet.QString::fromLocal8Bit("波特率:Unknown");
        };
        //设置数据位
        switch (info->dataBits) {
                case 0:
                       MySerial->setDataBits(QSerialPort::Data5);
                       InfoSet.append(QString::fromLocal8Bit(" 数据位:5"));
                       break;
                case 1:
                       MySerial->setDataBits(QSerialPort::Data6);
                       InfoSet.append(QString::fromLocal8Bit(" 数据位:6"));
                       break;
                case 2:
                       MySerial->setDataBits(QSerialPort::Data7);
                       InfoSet.append(QString::fromLocal8Bit(" 数据位:7"));
                       break;
                case 3:
                       MySerial->setDataBits(QSerialPort::Data8);
                       InfoSet.append(QString::fromLocal8Bit(" 数据位:8"));
                       break;
                default:
                        MySerial->setDataBits(QSerialPort::UnknownDataBits);
                        InfoSet.append(QString::fromLocal8Bit(" 数据位:Unknown"));
                        break;
                }
        //设置校验位
        switch (info->parity) {
                case 0:
                    MySerial->setParity(QSerialPort::EvenParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:Even"));
                    break;
                case 1:
                    MySerial->setParity(QSerialPort::MarkParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:Mark"));
                    break;
                case 2:
                    MySerial->setParity(QSerialPort::NoParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:None"));
                    break;
                case 3:
                    MySerial->setParity(QSerialPort::OddParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:Odd"));
                    break;
                case 4:
                    MySerial->setParity(QSerialPort::SpaceParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:Space"));
                    break;
                default:
                    MySerial->setParity(QSerialPort::UnknownParity);
                    InfoSet.append(QString::fromLocal8Bit(" 校验位:Unknown"));
                    break;
                }
        //设置停止位
        switch (info->stopBits) {
                case 0:
                    MySerial->setStopBits(QSerialPort::OneStop);
                    InfoSet.append(QString::fromLocal8Bit(" 停止位:1"));
                    break;
                case 1:
                    MySerial->setStopBits(QSerialPort::OneAndHalfStop);
                    InfoSet.append(QString::fromLocal8Bit(" 停止位:1.5"));
                    break;
                case 2:
                    MySerial->setStopBits(QSerialPort::TwoStop);
                    InfoSet.append(QString::fromLocal8Bit(" 停止位:2"));
                    break;
                default:
                    MySerial->setStopBits(QSerialPort::UnknownStopBits);
                    InfoSet.append(QString::fromLocal8Bit(" 停止位:Unknown"));
                    break;
                }
        //设置流控位
        switch (info->flowControl) {
                case 0:
                    MySerial->setFlowControl(QSerialPort::NoFlowControl);
                    InfoSet.append(QString::fromLocal8Bit(" 流控位:None"));
                    break;
                case 1:
                    MySerial->setFlowControl(QSerialPort::HardwareControl);
                    InfoSet.append(QString::fromLocal8Bit(" 流控位:Hardware"));
                    break;
                case 2:
                    MySerial->setFlowControl(QSerialPort::SoftwareControl);
                    InfoSet.append(QString::fromLocal8Bit(" 流控位:Software"));
                    break;
                default:
                    MySerial->setFlowControl(QSerialPort::UnknownFlowControl);
                    InfoSet.append(QString::fromLocal8Bit(" 流控位:Unknown"));
                    break;
                }
        emit SetInfo(InfoSet);//发送串口配置信号
}

添加**SerialClose()**函数。具体内容如下:

void Serial::SerialClose()
{
    if(MySerial->isOpen())//如果串口已经打开了 先给他关闭了
    {
        MySerial->clear();
        MySerial->close();
    }
}

添加接收串口配置参数函数。这个函数主要用从接收从ui界面处选择的串口参数,并保存在info中,前面提到了是通过结构体来传递参数的,因此构造函数时,要添加结构体的形参。

serial.h中添加公共函数*void RecvSerialConfig(Sinfo data);

public:
	void RecvSerialConfig(Sinfo *data);  //接收串口配置参数函数

按下alt+enter,在serial.cpp中添加定义。

void Serial::RecvSerialConfig(Sinfo *data)
{
    if(info!=nullptr)   //删除原先内存空间
    {
        delete info;
    }
    this->info = new Sinfo;//防止内存泄漏,关闭时 delete info;
    //接收参数设置
    this->info->Encode=data->Encode;
    this->info->baudRate=data->baudRate;
    this->info->comName=data->comName;
    this->info->dataBits=data->dataBits;
    this->info->flowControl=data->flowControl;
    this->info->parity=data->parity;
    this->info->stopBits=data->stopBits;
}

这里要注意,申请内存空间时,结束后必须释放,不然容易导致内存泄漏。因此,需要在**~Serial(void);中添加delete info;**

**~Serial(void);**函数如下:

Serial::~Serial()
{
    delete info;
}

配置发送和接收函数

配置完打开和关闭函数后,这里要配置发送和接收函数

serial.h中添加公共函数void SendData(QByteArray data, bool hexflag);void RecvData();。添加信号void isRecvData(QByteArray);

public:
    void SendData(QByteArray data, bool hexflag); //发送数据
    void RecvData(); //接收数据
signals:
    void isRecvData(QByteArray); //接收数据信号

按下alt+enter,在serial.cpp中添加定义。

其中void SendData(QByteArray data, bool hexflag); 函数中,dataui界面传递的数据,hexflag则为是否通过hex模式发送。

isRecvData(QByteArray);则是向ui传递串口接收的数据。

具体函数内容为:

void Serial::SendData(QByteArray data, bool hexflag)
{
    if(data.isEmpty())
    {
        return;//没有读取到数据就退出
    }
    if(hexflag==true)
    {   //hex模式直接发送
        MySerial->write(data);
    }
    else{ //判断编码格式在发送
         data=SetCodeType(data,info->Encode); //先根据编码转换数据编码格式
         MySerial->write(data);
    }
}

void Serial::RecvData()
{
    QByteArray info = MySerial->readAll();
    if(info.isEmpty())
     {
       return ;//没有读取到数据就退出
     }
    emit isRecvData(info);
}

这里**SetCodeType();**函数为自己定义的设置数据编码格式函数。具体实现方式见 配置编码格式函数

配置编码格式函数

给项目添加新的C++类,右击工程,选择**ADD NEW …**。

选择C++ Class

按照如下图选择,点击下一步完成

修改codetype.h内容如下:

#ifndef CODETYPE_H
#define CODETYPE_H

#include <QString>
#include <QTextCodec>

//编码格式列表
typedef enum
{
    ASCII = 0,
    Utf8,     //Utf8编码格式
    Utf16,    //Utf16编码格式
    GBK,  //GBK编码格式、兼容GBK18030、GB2312
    Big5,     //Big5
    ShiftJIS
}CodeType;
//设置编码格式
QByteArray SetCodeType(QByteArray const &data,qint32 control);
//解析编码格式
QByteArray GetCodeType(QByteArray const &data, qint32 control);
#endif // CODETYPE_H

修改codetype.cpp内容如下:

#include "codetype.h"
//编码
QByteArray SetCodeType(const QByteArray &data, qint32 control)
{
    QByteArray tmpData;
    switch (control) {
        case ASCII: tmpData=QTextCodec::codecForName("latin1")->fromUnicode(data);break;
        case Utf8: tmpData= QTextCodec::codecForName("UTF-8")->fromUnicode(data);break;
        case Utf16: tmpData= QTextCodec::codecForName("UTF-16")->fromUnicode(data);break;
        case GBK: tmpData= QTextCodec::codecForName("GBK")->fromUnicode(data);break;
        case Big5: tmpData= QTextCodec::codecForName("Big5")->fromUnicode(data);break;
        case ShiftJIS: tmpData= QTextCodec::codecForName("Shift-JIS")->fromUnicode(data);break;
        default:;break;
    }
    return tmpData;
}

//解码
QByteArray GetCodeType(const QByteArray &data, qint32 control)
{
    QString tmpData;
    switch (control) {
        case ASCII: tmpData= QTextCodec::codecForName("latin1")->toUnicode(data);break;
        case Utf8: tmpData= QTextCodec::codecForName("UTF-8")->toUnicode(data);break;
        case Utf16: tmpData= QTextCodec::codecForName("UTF-16")->toUnicode(data);break;
        case GBK: tmpData= QTextCodec::codecForName("GBK")->toUnicode(data);break;
        case Big5: tmpData= QTextCodec::codecForName("Big5")->toUnicode(data);break;
        case ShiftJIS: tmpData= QTextCodec::codecForName("Shift-JIS")->toUnicode(data);break;
        default:;break;
    }
    return tmpData.toUtf8(); //设置成Unicode格式
}

这里主要用到了QTextCodec这个类,具体内容可以看Qt的帮助文档,这里只简要概括。

1.需要包含**#include **这个头文件。

2.QTextCodec 类主要是将数据用来在非 Unicode 格式和 Unicode 之间进行转换。

至此,QtSerialPort配置完成。

3.线程类对象的添加和UI界面的参数设置

添加任务类对象和线程类对象

打开工程,选择mainwidget.h,添加我们创建的类Serial和线程类QThread

内容如下:

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include "serial.h"  //添加自定义类serial头文件
#include <QThread>   //添加线程类QThread头文件

QT_BEGIN_NAMESPACE
namespace Ui { class MainWidget; }
QT_END_NAMESPACE

class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = nullptr);
    ~MainWidget();
    QThread *t1;    //1.创建子线程对象
    Serial *m_work; //2.创建任务类对象
    
private:
    Ui::MainWidget *ui;

};
#endif // MAINWIDGET_H

mainwidget.cpp中将创建的对象实例化,并将任务函数移入子线程。

内容如下:

#include "mainwidget.h"
#include "ui_mainwidget.h"

MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MainWidget)
{
    ui->setupUi(this);
    
    //1.创建子线程对象,动态分配空间,指定父对象
    t1 = new QThread(this);
    //2.创建任务类对象,动态分配空间,不能指定父对象
    m_work = new Serial;//防止内存泄漏,关闭窗口时 delete m_work;
    //3.将任务对象移动到某个子线程中
    m_work->moveToThread(t1);
    
}

MainWidget::~MainWidget()
{
    delete ui;
    delete m_work;  //关闭时释放内存空间
}

注:这里线程操作参考之前的文章Qt多线程的使用记录中的方式二。

初始化ui界面参数

在开始工作前需要初始化ui界面的参数,如设置QCombobox的下拉框内容。因此需要创建一个void initUI(void);函数。打开mainwidget.h,添加我们需要创建的函数,并使用快捷键alt+enter,在mainwidget.cpp中定义void initUI(void);函数。

设置内容如下:

public:
    MainWidget(QWidget *parent = nullptr);
    ~MainWidget();
    QThread *t1;    //1.创建子线程对象
    Serial *m_work; //2.创建任务类对象
    void initUI();  //初始化UI界面参数,如设置QCombobox的item和设置QSS样式。

其中 void initUI(); 函数内容如下。

void MainWidget::initUI()
{
     QStringList baudrateList; //设置波特率列表
     baudrateList<<"1200"<<"2400"<<"4800"<<"9600"<<"19200"<<"38400"
                <<"57600"<<"115200";
     ui->comBaudRate->addItems(baudrateList); //将设置的Item加入到列表中
     ui->comBaudRate->setEditable(true); //设置可以手动输入波特率
     //限定波特率手动输入时只能输入数字且范围为(0, 1000000)即最高1M
     isBaudRateRange= new QIntValidator; //防止内存泄漏,关闭窗口时 delete aIntValidator;
     isBaudRateRange->setRange(0, 1000000);
     ui->comBaudRate->setValidator(isBaudRateRange);
     ui->comBaudRate->setCurrentIndex(3);  //设置默认显示第4个即9600(从0开始)

     QStringList databitsList; //设置数据位列表,根据串口中配置顺序设置。
     databitsList<<"5"<<"6"<<"7"<<"8";
     ui->comDataBits->addItems(databitsList); //将设置的Item加入到列表中
     ui->comDataBits->setCurrentIndex(3);  //设置默认显示第4个即8bit(从0开始)

     QStringList parityList; //设置校验位列表,根据串口中配置顺序设置。
     parityList<<"Even"<<"Mark"<<"None"<<"Odd"<<"Space";
     ui->comParity->addItems(parityList); //将设置的Item加入到列表中
     ui->comParity->setCurrentIndex(2);  //设置默认显示第3个即None(从0开始)

     QStringList stopbitsList; //设置停止位列表,根据串口中配置顺序设置。
     stopbitsList<<"1"<<"1.5"<<"2";
     ui->comStopBits->addItems(stopbitsList); //将设置的Item加入到列表中
     ui->comStopBits->setCurrentIndex(0);  //设置默认显示第1个即1bit(从0开始)

     QStringList encodeList; //设置编码格式列表,根据编码格式中配置顺序设置。
     encodeList<<"ASCII"<<"UTF8"<<"UTF16"<<"GBK"<<"Big5"<<"ShiftJIS";
     ui->comEncode->addItems(encodeList); //将设置的Item加入到列表中
     ui->comEncode->setCurrentIndex(0);  //设置默认显示第1个即ASCII(从0开始)

}

注:

  1. 其中需要在mainwidget.h中添加 #include < QStringList > ;
  2. 配置的item参数需要同之前switch设置的参数顺序相同。
  3. 要在mainwidget.h中添加 #include < QIntValidator >;并创建一个私有成员,QIntValidator isBaudRateRange;*

设置完成后在MainWidget中运行 initUI(); 函数

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

    //0.初始化ui界面;
    initUI();
    //1.创建子线程对象,动态分配空间,指定父对象
    t1 = new QThread(this);
    //2.创建任务类对象,动态分配空间,不能指定父对象
    m_work = new Serial;//防止内存泄漏,关闭窗口时 delete m_work;
    //3.将任务对象移动到某个子线程中
    m_work->moveToThread(t1);

}

至此,QCombobox 参数初始化完成。(后期仍会在 initUI() 中进行其他配置如设置QSS样式等)。

利用QSerialPortInfo得到串口号

这里我们会发现并没有设置串口的Item参数。主要原因是,电脑再识别设备的串口号时,会任意分配可用的端口号,因此这里我们通过使用QSerialPortInfo类来得到可用端口号,并设置到串口的Item中。

首先,打开mainwidget.h,添加**#include ** , 然后创建 void getportInfo(); 函数,并使用快捷键alt+enter,在mainwidget.cpp 中定义 void getportInfo(); 函数。

实现步骤:

首先检测可用的设备端口号,当存在可用的端口号时,通过遍历的方式,将可用的端口号保存在portStringList中。然后设置到串口的Item中。无可用端口号时,弹出信号框。

具体内容如下:

void MainWidget::getportInfo()
{
    qint32 comCnt=0; //保存当前可用的串口数量
    QStringList portStringList; //保存当前可用的串口列表

    if(portStringList.length()!=0) 
    {
      portStringList.clear();//检测portStringList内容当不为空时清除再次调用时使用
    } 
    if(ui->comPortName->count()!=0)
    {
        ui->comPortName->clear();//检测comPortName的列表数量当不为空时清除再次调用时使用
    }
    //获取串口设备数量
    comCnt = QSerialPortInfo::availablePorts().length();
    if(comCnt!=0)
    {
       //获取串口信息
       foreach (const QSerialPortInfo &qspinfo, QSerialPortInfo::availablePorts())
       {
            portStringList+=qspinfo.portName();
       }
       ui->comPortName->addItems(portStringList);
    }
    else{   
        message("未检测到串口!");
        }
}

注:

  1. 其中message();为自己重新封装的信号框函数,具体内容见下。

打开mainwidget.h,添加 #include < QMessageBox > , 然后重新封装函数 void message(const char str); ,并使用快捷键alt+enter,在*mainwidget.cpp 中定义 void message(const char *str);函数。

具体内容如下:

void MainWidget::message(const char *str)
{
    QMessageBox msgBox;
    msgBox.setText(QString::fromLocal8Bit(str));
    msgBox.exec();
}

然后在MainWidget中运行 getportInfo(); 函数

这里要注意,在拔插串口设备时,可用的端口号会产生变化,但 getportInfo(); 函数仅在打开程序时运行一次,因此,这里需要增加更新端口号的方法。

实现方法:

  1. 添加检测拔插设备的函数,当设备变化时重新运行getportInfo();
  2. 使用定时器,设定每100ms检测一次,当设备变化时重新运行getportInfo();
  3. 增加按钮,当点击按钮时重新运行getportInfo();

这里选择增加按钮的方法,主要原因是简单且不占用资源。

image-20211028233127314

增加按钮的信号与槽,当点击按钮时,重新运行getportInfo();

具体内容如下:

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

    //0.初始化ui界面;配置qss样式
    initUI();
    //1.创建子线程对象,动态分配空间,指定父对象
    t1 = new QThread(this);
    //2.创建任务类对象,动态分配空间,不能指定父对象
    m_work = new Serial;//防止内存泄漏,关闭窗口时 delete m_work;
    //3.将任务对象移动到某个子线程中
    m_work->moveToThread(t1);
    //4.获取串口设备信息
    getportInfo();
        //添加更新按钮信号与槽,用于更新串口设备信息
        connect(ui->upBtn,&QPushButton::clicked,this,&MainWidget::getportInfo);

}

至此,利用QSerialPortInfo得到串口号完成。

4.配置UI界面的串口打开和关闭

设置串口参数配置数组

从之前的Serial配置可知,我们是通过配置ui界面的QCombobox来进行串口参数的配置的,因此想要配置的参数应用到Serial,需要设置结构体,通过信号与槽来传递结构体的值,来传递配置参数。

这里需要在mainwidget.h中添加**#include “SerialInfo.h”头文件,并添加一个私有成员info**。

private:
    Ui::MainWidget *ui;
    QIntValidator* isBaudRateRange;//设置波特率输入范围
    Sinfo *info=nullptr; //设置串口配置的参数

注:

  1. Sinfo *info可以见之前配置的内容。

mainwidget.h中创建**void getComboBoxInfo();**用以获取配置参数。

具体内容如下:

void MainWidget::getComboBoxInfo()
{
    if(info!=nullptr)   //删除原先内存空间
    {
        delete info;
    }
    this->info = new Sinfo;//防止内存泄漏,关闭窗口时 delete info;
    this->info->comName=ui->comPortName->currentText();  //设置串口号
    this->info->baudRate=ui->comBaudRate->currentText().toInt();  //设置波特率
    this->info->dataBits=ui->comDataBits->currentIndex();  //设置数据位
    this->info->parity=ui->comParity->currentIndex();      //设置检验位
    this->info->stopBits=ui->comStopBits->currentIndex();  //设置停止位
    this->info->flowControl=0 ;                           //设置流控位,默认值为0无流控
    this->info->Encode=ui->comEncode->currentIndex();      //设置编码格式
    
}

注:

  1. 其中,info在退出时要delete

设置完参数时,记得放进MainWidget中执行。但MainWidget中之在打开程序时执行一次,因此,需要添加信号与槽来更新info的参数。由于使用的是QCombobox,查阅帮助文档可知,能用到的信号函数为**currentIndexChanged();**。

具体内容:

从截图中我们可以看出,当QComboboxindex变化时,可以返回两种参数:一种是当前所选变化的index值,另一种是当前所选变化的text的值。

因此可以在mainwidget.h中创建**void updataComboBox();**用以更新配置参数。

具体内容如下:

void MainWidget::updataComboBox()
{
    connect(ui->comPortName, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
        this,[=](const QString &text){
        /* ... */
        this->info->comName=text;  //设置串口号
     });
    connect(ui->comBaudRate, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
        this,[=](const QString &text){
        /* ... */
        this->info->baudRate=text.toInt();  //设置串口波特率
     });
    connect(ui->comDataBits, QOverload<int>::of(&QComboBox::currentIndexChanged),
        this,[=](int index){
        /* ... */
        this->info->dataBits=index;  //设置串口数据位
     });
    connect(ui->comParity, QOverload<int>::of(&QComboBox::currentIndexChanged),
        this,[=](int index){
        /* ... */
        this->info->parity=index;  //设置串口校验位
    });
    connect(ui->comStopBits, QOverload<int>::of(&QComboBox::currentIndexChanged),
        this,[=](int index){
        /* ... */
        this->info->stopBits=index;  //设置串口停止位
    });
    connect(ui->comEncode, QOverload<int>::of(&QComboBox::currentIndexChanged),
        this,[=](int index){
        /* ... */
        this->info->Encode=index;  //设置串口编码格式
    });
/**预留流控控制**/
//    connect(ui->comFlowControl, QOverload<int>::of(&QComboBox::currentIndexChanged),
//        this,[=](int index){
//        /* ... */
//        this->info->flowControl=index;  //设置串口流控位
//    });
}

至此,设置串口参数配置数组完成。

设置打开串口和关闭串口按钮

通过设置的ui界面可知,我们仅设置了一个按钮,因此想通过一个按钮去触发打开和关闭串口时就需要添加别的条件,这里可以使用两种解决方法。

  1. 设置按钮的文字,通过判断文字来判断当前所处的状态。(**setText()**函数)
  2. 设置按钮的状态,通过判断按钮状态来判断当前所处的状态。(**setChecked()**函数)

这里我们使用**setText()**的方法。

mainwidget.h中创建**void SerialOpen();void SerialClose();**用以打开和关闭串口。同时创建发送配置,打开和关闭串口信号。

内容如下:

public:
    void SerialOpen();  //打开串口函数
    void SerialClose(); //关闭串口函数
signals:
    void SendSerialConfig(Sinfo *info);  //发送串口配置信号
    void isCloseSerial();             //发送关闭串口信号
    void isOpenSerial();              //发送打开串口信号

根据上述步骤,修改**SerialOpen();SerialClose()**内容如下:

//打开串口设备
void MainWidget::SerialOpen(void)
{
    //设置串口打开按钮文本样式
     if(ui->OpenComBtn->text() == QString::fromLocal8Bit("打开串口")){
         ui->OpenComBtn->setText(QString::fromLocal8Bit("关闭串口"));
         emit SendSerialConfig(info); //发送串口配置信号
         emit isOpenSerial();
     }
     else{
         ui->OpenComBtn->setText(QString::fromLocal8Bit("打开串口"));
         this->SerialClose();   //执行关闭串口设备
     }
}
//关闭串口设备
void MainWidget::SerialClose(void)
{
    emit isCloseSerial();
}

设置完SerialOpen();SerialClose()后,记得放进MainWidget中执行。

具体内容如下:

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

    //0.初始化ui界面;配置qss样式
    initUI();
    //1.创建子线程对象,动态分配空间,指定父对象
    t1 = new QThread(this);
    //2.创建任务类对象,动态分配空间,不能指定父对象
    m_work = new Serial;//防止内存泄漏,关闭窗口时 delete m_work;
    //3.将任务对象移动到某个子线程中
    m_work->moveToThread(t1);
    //4.获取串口设备信息
    getportInfo();
        //添加更新按钮信号与槽,用于更新串口设备信息
        connect(ui->upBtn,&QPushButton::clicked,this,&MainWidget::getportInfo);
    //5.获取串口参数配置数组
    getComboBoxInfo();
       //添加串口参数数组的信号与槽,用于更新串口参数配置数组
       updataComboBox();
    //6.打开线程并运行m_work的SerialOpen()和SerialClose()
    connect(ui->OpenComBtn,&QPushButton::clicked,this,[=]{
            this->SerialOpen();
            t1->start();
        });
        qRegisterMetaType<Sinfo>("Sinfo");
        //添加传递串口参数配置的信号与槽,用于传递串口参数配置
        connect(this,&MainWidget::SendSerialConfig,m_work,&Serial::RecvSerialConfig);
        //添加打开串口的信号与槽,用于打开串口
        connect(this,&MainWidget::isOpenSerial,m_work,&Serial::SerialOpen);
        //添加串口打开失败标志
        connect(m_work,&Serial::isnoSerialOpen,this,[=](){
            message("串口不存在,或被占用!");
            ui->OpenComBtn->setText(QString::fromLocal8Bit("打开串口"));
        });
        //添加关闭串口的信号与槽,用于释放串口
        connect(this,&MainWidget::isCloseSerial,m_work,&Serial::SerialClose);

}

注:

  1. 传递串口参数结构体时,需要先声明。

设置打开串口后,更新串口信息在ui界面上。

添加相应的信号与槽,具体内容如下

//7.从m_work处传递回来的Serialinfo显示在ui界面上
connect(m_work,&Serial::SetInfo,this,[=](QString info){
    ui->Info->setText(info);
});

设置成功后,添加当串口关闭时,清除INFO的内容。因此需要在**SerialClose()**中添加以下内容。

//关闭串口设备
void MainWidget::SerialClose(void)
{
    ui->Info->clear();  //清空显示的配置参数
    emit isCloseSerial(); //发送关闭串口信号
}

至此,设置打开串口和关闭串口按钮完成。