Qt之串口设计-线程实现(十二)

Qt开发 系列文章 - Serial-port(十二)


目录

前言

一、SerialPort

二、实现方式

1.创建类

2.相关功能函数

3.用户使用

4.效果演示

5.拓展应用-实时刷新

总结


前言

Qt作为一个跨平台的应用程序开发框架,在串口编程方面提供了方便易用的API,使得开发者能够轻松实现基于串口通信的各种应用。

Qt框架中提供的一个串口通讯类QtSerialPort,它属于Qt自带的模块类,专门用于进行串行通信,使用时只需要在工程文件pro内添加QT += serialport即可,这种实现方式封装程度高、使用简单,与Qt框架集成紧密,利用Qt的信号与槽机制进行事件处理采用,但给我的感觉在一些高速数据处理时,不是很好用。因此本文推荐采用Windows API调用相关串口功能函数,用于从文件或设备中读取数据。


**一、**SerialPort

在Qt平台中,调用window相关API,例如CreatFile、readFile、writeFile等函数,以实现串口通讯,并采用C语言实现。

二、实现方式

本文将采用线程实现对串口数据的收发,创建一个线程类,将该类作为主窗口的私有变量使用。

在该线程类中,通过Windows,串口被抽象为文件,对串口的读、写,实际上就是对文件的读写。

1.创建类

在Qt项目上创建一个串口类,属于线程public QThread,具体实现如下。

c 复制代码
#include <QThread>
#include <stdio.h>
#include <windows.h>
//缓冲区大小
#define BUF_SIZE    1000
class comt4hread : public QThread
{  
    Q_OBJECT
public:
    comt4hread();
    ~comt4hread(){}
    bool start_flag;
    bool save_flag;
    QByteArray filebuf;
    QByteArray sendbuf;
    const char *ComName;
    int BaudValue;
    int BitValue;
    int ParitySelt;
    int StopSelt;
protected:
    HANDLE OpenSerial(const char *com, //串口名称,如COM1,COM2
        int baud,       //波特率:常用取值:CBR_9600、CBR_19200、CBR_38400、CBR_115200、CBR_230400、CBR_460800
        int byteSize,   //数位大小:可取值7、8;
        int parity,     //校验方式:可取值NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY
        int stopBits);
    void run(void);
    void ProData(QByteArray);
};

在上面代码中,需要添加头文件#include <Windows.h>,该函数的具体用法可以参考微软MS的官方文档Win32 应用 |Microsoft 学习,里面比较详细。

2.相关功能函数

该线程类的实现:首先在构造函数初始化相关变量;然后通过OpenSerial函数完成对串口的一系列设置,包括波特率、校验位等;最后启动线程run后,在while循环中实现对串口的读(ReadFile函数)、写(WriteFile函数)。代码如下(示例):

c 复制代码
#include "com4thread.h"
comt4hread::comt4hread(){
    start_flag = false;
    ComName = "COM4";
}
HANDLE comt4hread::OpenSerial(const char *com, //串口名称,如COM1,COM2
    int baud,       //波特率:常用取值:CBR_9600、CBR_19200、CBR_38400、CBR_115200、CBR_230400、CBR_460800
    int byteSize,   //数位大小:可取值7、8;
    int parity,     //校验方式:可取值NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY
    int stopBits)   //停止位:ONESTOPBIT、ONE5STOPBITS、TWOSTOPBITS;
{
    DCB dcb;
    BOOL b = FALSE;
    COMMTIMEOUTS CommTimeouts;
    HANDLE comHandle = INVALID_HANDLE_VALUE;
    //打开串口
    WCHAR wszClassName[10];
    memset(wszClassName, 0, sizeof(wszClassName));
    MultiByteToWideChar(CP_ACP, 0, com, int(strlen(com)+1), wszClassName,
        sizeof(wszClassName) / sizeof(wszClassName[0]));
    comHandle = CreateFile(wszClassName,            //串口名称
        GENERIC_READ | GENERIC_WRITE,      //可读、可写
        0,            // No Sharing
        NULL,         // No Security
        OPEN_EXISTING,// Open existing port only
//        FILE_ATTRIBUTE_NORMAL,            // Non Overlapped I/O
        0, //同步方式
        NULL);        // Null for Comm Devices
    if (INVALID_HANDLE_VALUE == comHandle) {
        qDebug() << "CreateFile fail.";
        return comHandle;
    }
    // 设置读写缓存大小
    b = SetupComm(comHandle, BUF_SIZE, BUF_SIZE);
    if (!b)
        qDebug() << "SetupComm fail.";
    //设定读写超时
    CommTimeouts.ReadIntervalTimeout = MAXDWORD;//读间隔超时
    CommTimeouts.ReadTotalTimeoutMultiplier = 0;//读时间系数
    CommTimeouts.ReadTotalTimeoutConstant = 0;//读时间常量
    CommTimeouts.WriteTotalTimeoutMultiplier = 1;//写时间系数
    CommTimeouts.WriteTotalTimeoutConstant = 1;//写时间常量
    b = SetCommTimeouts(comHandle, &CommTimeouts); //设置超时
    if (!b)
        qDebug() << "SetCommTimeouts fail.";
    //设置串口状态属性
    GetCommState(comHandle, &dcb); // 获取当前
    dcb.BaudRate = ulong(baud);           // 波特率
    dcb.ByteSize = uchar(byteSize);       // 每个字节有位数
    dcb.Parity   = uchar(parity);         // 无奇偶校验位
    dcb.StopBits = uchar(stopBits);       // 一个停止位
    b = SetCommState(comHandle, &dcb);//设置
    if (!b)
        qDebug() << "SetCommState fail.";
    return comHandle;
}
void comt4hread::run(void)
{
    BOOL err = FALSE;
    DWORD wRLen = 0;
    DWORD wWLen = 0;
    char buf[BUF_SIZE] = {0};
    HANDLE comHandle = INVALID_HANDLE_VALUE;//串口句柄
    QByteArray tempbuf;
    //打开串口
    comHandle = OpenSerial(ComName, CBR_115200, 8, NOPARITY, ONESTOPBIT);
    //comHandle = OpenSerial(ComName, BaudValue, BitValue, ParitySelt, StopSelt);
    qDebug() << comHandle;
    if (INVALID_HANDLE_VALUE == comHandle) {
        qDebug() << "OpenSerial COM fail!";
        return;
    }
    qDebug() << "Open COM Successfully!";
    //循环接收消息,收到消息后将消息内容
    while(start_flag){
        wRLen = 0;
        //读串口消息
        err = ReadFile(comHandle, buf, sizeof(buf)-1, &wRLen, NULL);
        if (err && wRLen > 0) //读成功并且数据大小大于0
        {
            tempbuf.append(buf, int(wRLen));
            if(save_flag)
                filebuf.append(tempbuf);
            //处理数据
            ProData(tempbuf);
            tempbuf.clear();
        }
        //写串口消息
        if(!sendbuf.isEmpty()){
            err = WriteFile(comHandle, sendbuf, size_t(sendbuf.size()), &wWLen, NULL);
            if (!err)
                qDebug() << "SendData_WriteFile fail.";
            sendbuf.clear();
        }
    }
}

3.用户使用

创建完上面的线程类后,用户需要调用/使用它,首先在构造函数初始化串口、定时器相关变量,然后通过定时器实时获取串口,当串口有效时,启动串口,具体含义实现如下。

复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow){
    ui->setupUi(this);
    /****串口初始化*****/
    Initcomthread();   
    /****定时器初始化****/
    InitTimerEvent();
}
MainWindow::~MainWindow()
{
    delete ui;   
}
void MainWindow::Initcomthread(void)
{
    comth = new comt4hread();
    comth->start_flag = false;
    comth->save_flag = false;
    //comth->start(QThread::HighPriority);
    //connect(this, SIGNAL(comconfig(QStringList)), comth, SLOT(comconfig(QStringList)));
}
void MainWindow::InitTimerEvent(void)
{
    timecnt = 0;
    ftimer_flag = true;
    show_1s_flag = true;
    m_pluseTimeid = startTimer(100); // 100毫秒事件处理
}
//发送数据
void MainWindow::on_sendButton_clicked()
{   
    QByteArray temp = ui->textEdit->toPlainText().toLatin1();   
    comth->sendbuf.append(temp);
//    comth->send_flag = true;
    ui->listWidgetRecv->addItem("本地发送数据:" + temp.toHex(' ').toUpper());
    ui->listWidgetRecv->setCurrentRow(ui->listWidgetRecv->count() - 1);
}
void MainWindow::timerEvent(QTimerEvent *t)
{
    if(t->timerId() == m_pluseTimeid) // 100毫秒事件处理
    {
        static quint8 cnt=0;
        cnt++;
        if(cnt == 10){
            //查找可用的串口
            if(ftimer_flag)
            {
                if(!(ui->PortBox->currentText().isEmpty()))
                {
                    ui->lineEdit_6->setText("获取可用串口");
                    ui->lineEdit_6->setStyleSheet("color:red;");
                    ftimer_flag = false;
                    show_1s_flag = true;
                }
                else
                {
                    ui->lineEdit_6->setText(QString::number(timecnt));
                    timecnt++;
                    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
                    {
                        QSerialPort serialtemp;
                        serialtemp.setPort(info);
                        if(serialtemp.open(QIODevice::ReadWrite))
                        {
                            ui->PortBox->addItem(serialtemp.portName());
                            serialtemp.clear();
                            serialtemp.close();
                        }
                    }
                }
            }
            else
            {
                if(ui->PortBox->currentText().isEmpty()){
                    ftimer_flag = true;
                    timecnt = 0;
                }
                show_1s_flag = true;
            }
            cnt = 0;
        }
    }
}
void MainWindow::on_PortBox_activated(const QString &arg1)
{
    if(arg1.isEmpty())
        return;
    comth->ComName = ui->PortBox->currentText().toUtf8();
    comth->BaudValue = ui->BaudBox->currentText().toInt();
    comth->BitValue = ui->BitNumBox->currentText().toInt();
    comth->ParitySelt = ui->ParityBox->currentText().toInt();
    comth->StopSelt = ui->StopBox->currentText().toInt();
    comth->start(QThread::HighPriority);
}
void MainWindow::on_clear_clicked()
{
    ui->listWidgetRecv->clear();
}

4.效果演示

软件搜索到没有串口时,开始计时。

有串口时,选择相应串口,Qt输出栏提示Open COM Successful!

5.拓展应用-实时刷新

一般我们工业上使用串口,为了实现对数据实时采集、并通过曲线显示出来。这时我们一般采取的是,先在线程中将串口收到的数据进行解析处理ProData,然后将解析出来的结果值通过变量传递给主界面上,让其知晓,然后通过主窗口的定时器或者信号槽函数显示在UI界面上。

如果是要绘制曲线,可以参考博文Qt之第三方库QCustomPlot使用(二)-CSDN博客,了解曲线绘制的特性及使用方法。


总结

博文中相应的工程代码Qt-Case.zip 利用Qt开发软件进行编的例程,为博文提供案例-CSDN文库

相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
小bo波7 天前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能15 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构