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

发布于:2024-12-21 ⋅ 阅读:(11) ⋅ 点赞:(0)

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,具体实现如下。

#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函数)。代码如下(示例):

#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文库