Linux QT/C++练习:串口助手基础版-QSerialPort类,QFile类和QFileDialog类练习

本文介绍了如何使用Qt框架在Linux环境下开发一个串口助手,重点涉及QSerialPort类、QFile类和QFileDialog类的运用,包括串口搜索、参数设置、数据收发和存储等功能。通过线程、多线程同步及信号槽机制实现串口通信与界面交互。
摘要由CSDN通过智能技术生成

写在开头

本练习主要涉及QSerialPort类,QFile类QFileDialog类QReadWriteLock类的使用。
其中还包括线程/多线程同步,类之间通过信号传递参数,窗口退出事件/重写事件过滤器实现qlable的点击以及鼠标进入退出操作等。

代码构思

参考常见的串口助手,分析总结一个串口助手工具中应包含以下功能:

  1. 串口搜索功能:程序一起来就应该自动开始搜索串口,并且实时检测串口状态;
  2. 串口参数设置:设置串口的波特率、校验位、停止位和数据位等;
  3. 打开/关闭串口
  4. 收发串口数据
  5. 存储串口数据
  6. 其他。

本练习代码思路

串口的搜索、串口参数的设置、串口打开和关闭以及数据的收发都围绕QSerialPort类进行,用户通过控制UI界面中的控件由信号与槽实现对QSerialPort类的相应操作。而数据存储由QFileDialog类来获取存储路径,通过QFile类来实现打开、读写和关闭文件的操作。
以下是本练习代码的具体思路:

  1. 串口搜索功能:程序运行后开启searththread线程,线程开启定时器以实现实时搜索串口的功能,并根据搜索的结果来更新相对应的全局变量;
  2. 串口参数设置:主界面点击设置按钮打开参数设置serialPortsettingform界面,打开后会初始化界面中对应的QCombox控件的Item项供用户选择,点击确定按钮后根据QComboxCurrent结果更新全局变量。
  3. 打开/关闭串口:主界面控制串口的打开/关闭,打开/关闭状态变化后,界面对应变化为不同样式并且更新状态标志位isSerialPortOpen
  4. 收发串口数据:通过连接QSerialPort的readyRead()信号来显示串口数据,根据不同的标志位决定数据的显示位置(显示在哪个界面哪个控件)和状态(是否需要自动下拉/是否需要存储)等。
  5. 存储串口数据:QFileDialog类获取存储路径,通过QFile类实现打开、读写和关闭操作
  6. 其他:做了一个mark功能,可以直接在文件内插入标记方便快速定位位置。

代码详细设计

在这里插入图片描述

UI界面

1.mainwindow.ui

在这里插入图片描述

2.serialportsettingform.ui

在这里插入图片描述

3.readdataplusform.ui

请添加图片描述

代码部分

1.全局变量

单独把一些全局变量和共用的头文件放在appdata.h/.cpp 里面,这样在写的时候结构比较清晰;严谨一点的还可以创建一个类,把变量设置为私有的,再通过get/set去取值和修改。注意全局变量要加extern关键字并且提前需要初始化,否则每个类都会单独创建一个变量导致无法实现全局变量的效果;具体可以自己研究一下extern,static等关键字的作用并且做练习。

appdata.h

#ifndef APPDATA_H
#define APPDATA_H

//头文件
#include <QDesktopWidget>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QList>
#include <QDebug>
#include <QThread>
#include <QTime>
#include <QMutex>
#include <QReadWriteLock>
#include <QWaitCondition>
#include <QSerialPort>
#include <QEvent>
#include <QFont>
#include <QFileDialog>

//存储串口设置项的结构体
struct SerialportInfo
{
   
    QString portName;                                       //设备名
    QSerialPort::BaudRate portBaud;          //波特率
    QSerialPort::StopBits portStopbits;      //停止位
    QSerialPort::DataBits portDatabits;      //数据位
    QSerialPort::Parity portParity;                //校验位
    QSerialPort::FlowControl portFlowcontrol;//流控制模式

    //设置默认值:初始化的时候就不用额外写
    SerialportInfo()
    {
   
        portName = "";
        portBaud = QSerialPort::Baud921600;
        portStopbits = QSerialPort::OneStop;
        portDatabits = QSerialPort::Data8;
        portParity = QSerialPort::NoParity;
        portFlowcontrol = QSerialPort::NoFlowControl;
    };
};

//枚举,用于信号中去传递不同情况的参数
enum markType
{
   
    markBeginTime,
    markEndTime,
    markTime,
    markEndFlag,
};

extern SerialportInfo checkedSerialportInfo;        //存储当前被选定的串口:用于控制点击打开串口和设置串口的默认参数

extern QList<QSerialPortInfo> searthDeviceNameList; //搜到的设备的列表:这个只有设备名字,这个列表每一项都要存入SerialportList并且有默认值
extern QList<SerialportInfo> searthDeviceInfoList;  //与当前存在的设备保持一致,与SerialportList不一定一致
extern QList<SerialportInfo> SerialportList;        //存储所有本地获取过的串口的设置项:用于插入-拔掉-再插入的时候可以调出之前存储的参数

//extern QMutex mutex;
extern QReadWriteLock readWriteLock;                //读写锁,用来防止checkedSerialportInfo读取和写入的时候冲突
//extern QWaitCondition isupdateList;

extern bool movetoEndl;                                 //标志位:是否需要自动下拉
extern bool btn_revTextPlusOpen;             //标志位:是否此时拓展界面是被打开的

extern QString dataSavePath;                     //串口数据的存储路径
extern bool saveStateFlag;                          //标志位:是否需要存储到文件里

class appdata
{
   
public:
    appdata();
};
#endif // APPDATA_H

appdata.cpp 全局变量需要初始化

#include "appdata.h"

SerialportInfo checkedSerialportInfo;//存储当前被选定的串
QList<SerialportInfo> SerialportList;//存储所有已存在串口的设置项:存之前先判断一下之前是否已经存在数据
QList<QSerialPortInfo> searthDeviceNameList;
QList<SerialportInfo> searthDeviceInfoList;

QMutex mutex;
QReadWriteLock readWriteLock;
QWaitCondition isupdateList;//等待事件

bool movetoEndl = true;
bool btn_revTextPlusOpen = false;

QString dataSavePath = NULL;//存储文件的路径
bool saveStateFlag = false;

appdata::appdata()
{
   
}

2.searthportthread线程

所有的类里面,最先去写搜索线程searthportthread。因为搜索要做的事情比较单一且目的明确,与其他类的关联主要是全局变量的关联。

功能:

  1. 开启一个1s的定时器,重写timerEvent()事件。实际上线程开启定时器后就结束了,主要是靠定时器来跑,这一点需要理解。
  2. 搜索到的串口列表先传给searthDeviceNameList变量(该列表只有串口名),根据其去更新变量searthDeviceInfoList(搜索到的设备信息列表,该变量是携带串口参数信息的列表)。再去更新变量与SerialportList(本地储存的设备列表,该变量包含所有搜索过的串口信息,目的是为了实现当某个串口插入-拔出-再插入能记忆之前修改的参数的功能)
  3. 每次搜索完要检查上一次的checkedSerialportInfo串口是否还存在(该变量为:当前被选中的串口及其信息,用于主界面点击设置和打开按钮时候确定是打开哪一个串口而存在)
  4. 额外加一个检测用户是否更新设置参数,保持全局变量的参数一致。

searthportthread.h

#ifndef SEARTHPORTTHREAD_H
#define SEARTHPORTTHREAD_H

#include <QObject>
#include "appdata.h"

class searthportthread : public QThread
{
   
    Q_OBJECT
public:
    searthportthread();
    void run() override;
    void timerEvent(QTimerEvent *event);
    void updateSerialportList();
    void updatecheckedSerialportInfo();
    void recoveryBySerialportList();
private:
    int serathTimeIntervaltime;           //控制搜索的间隔
signals:
    void update_cmb_deviceList();
};

#endif // SEARTHPORTTHREAD_H

searthportthread.cpp

#include "searthportthread.h"

searthportthread::searthportthread()
{
   
    serathTimeIntervaltime = startTimer(1000);//开启定时器
}

void searthportthread::run()
{
   
}

void searthportthread::timerEvent(QTimerEvent *event)
{
   
    if(event->timerId() == serathTimeIntervaltime)
    {
   
        searthDeviceNameList =  QSerialPortInfo::availablePorts();
        updateSerialportList();
        recoveryBySerialportList();
        updatecheckedSerialportInfo();
    }
}

//每次搜索完都要检查:
//更新全局变量中的三个关于设备的List
//1.searthDeviceNameList(当前搜到的设备名列表)与searthDeviceInfoList(上一次搜索的设备信息列表)是否有差异,并且更新searthDeviceInfoList
//2.更新后的searthDeviceInfoList(当前搜索到的设备列表)与SerialportList(本地储存的设备列表)是否有差异,并且更新SerialportList
void searthportthread::updateSerialportList()
{
   
    //1.对比searthDeviceNameList(当前搜到的设备名列表)与searthDeviceInfoList(上一次搜索的设备信息列表)是否有差异
    if(searthDeviceInfoList.isEmpty())
    {
   
        foreach(QSerialPortInfo i,searthDeviceNameList)
        {
   
            SerialportInfo temp;
            temp.portName = i.portName();
            searthDeviceInfoList.append(temp);
            qDebug()<<"searth new port = "<<i.portName();
            emit update_cmb_deviceList();//这个信号只有确认了串口变换后才发
        }
    }
    else
    {
   
        //既要判断是否多了也要判断是否少了 需要两次嵌套循环
        //发现不同就更新bool值,并且在当前循环的末尾去更新
        //判断是否减少了串口
        bool isNeedUpdate_reduce = true;
        //这里用for循环是因为foreach只能遍历List不能修改
        for(int index = 0;index<searthDeviceInfoList.size();index++)
        {
   
            SerialportInfo temp = searthDeviceInfoList[index];
            foreach(QSerialPortInfo i,searthDeviceNameList)
            {
   
                if(i.portName() == temp.portName)
                {
   
                    isNeedUpdate_reduce = false;
                    break;
                }
                else
                {
   
                    isNeedUpdate_reduce = true;
                }
            }
            if(isNeedUpdate_reduce)
            {
   
                qDebug()<<searthDeviceInfoList[index].portName<<"Exit!";
                searthDeviceInfoList.removeAt(index);
                emit update_cmb_deviceList();
            }
        }

        //判断是否多了串口
        //发现不同就更新bool值,并且在当前循环的末尾去更新
        bool isNeedUpdate_add = false;
        for(int index = 0;index<searthDeviceNameList.size();index++)
        {
   
            QSerialPortInfo temp = searthDeviceNameList[index];
            foreach(SerialportInfo i,searthDeviceInfoList)
            {
   
                if(i.portName == temp.portName())
                {
   
                    isNeedUpdate_add = false;
                    break;
                }
                else
                {
   
                    isNeedUpdate_add = true;
                }
            }
            if(isNeedUpdate_add)
            {
   
                SerialportInfo a;
                a.portName = temp.portName();
                searthDeviceInfoList.append(a);
                emit update_cmb_deviceList();
                qDebug()<<"searth new port = "<<temp.portName();
            }
        }
    }

    //2.对比更新后的searthDeviceInfoList(当前搜索到的设备列表)与SerialportList(本地储存的设备列表)是否有差异
    //只加不减,因为本地储存要有一定的记忆功能
    if(SerialportList.isEmpty() && !searthDeviceInfoList.isEmpty())
    {
   
        SerialportList = searthDeviceInfoList;
    }
    else
    {
   
        bool isNeedupdate = false;
        //因为本地储存只需要直接append就好,不需要index值,所以使用foreach
        foreach (SerialportInfo i, searthDeviceInfoList)
        {
   
            foreach(SerialportInfo j,SerialportList)
            {
   
                if(i.portName == j.portName)
                {
   
                    isNeedupdate = false;
                    break;
                }
                else
                {
   
                    isNeedupdate = true;
                }
            }
            if(isNeedupdate)
            {
   
                SerialportInfo temp;
                temp.portName = i.portName;
                SerialportList.append(temp);
                isNeedupdate = false;
            }
        }
    }
}

//每次搜索完要检查上一次的checkedSerialportInfo串口是否还存在
//与searthDeviceInfoList对比:选定的值一定要是当前存在的串口
//1.如果当前searthDeviceInfoList为0,就将当前选定项置空--->无法置空---->恢复默认值
//2.如果由串口数量0到1,直接更新checkedSerialportInfo(可以直接归为第3点)
//3.如果有从N到M,先去判断当前确定的项是否还存在;是的话就不变,不是的话就默认变为SerialportList第一个(包括设备名和设备参数)

//原则:先去判断是否有变更,再统一去写新值 频繁写值容易造成读值错误 此处需要加读写锁
void searthportthread::updatecheckedSerialportInfo()
{
   
    if(searthDeviceInfoList.isEmpty())
    {
   
        SerialportInfo temp;
        checkedSerialportInfo = temp;
    }
    else
    {
   
        bool NeedUpdate = true;
        foreach(SerialportInfo i,searthDeviceInfoList)
        {
   
            if(i.portName == checkedSerialportInfo.portName)//代表之前的串口还存在
            {
   
                NeedUpdate = false;
            }
        }
        if(NeedUpdate)//不存在了就取第一个为当前的值
        {
   
            //这个是需要频繁去获取的参数,所以需要加锁
            readWriteLock.lockForWrite();
            checkedSerialportInfo = searthDeviceInfoList.at(0);
            readWriteLock.unlock();
        }
    }
}

//保证与searthDeviceInfoList与SerialportList的参数一致
//用于当用户主动更新参数的情况
void searthportthread::recoveryBySerialportList()
{
   
    for
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值