QT工程创建及串口调试工具实现


前言

欢迎来到本博客!在这篇文章中,我们将一起探索如何使用Qt框架创建一个串口调试工具。串口通信在嵌入式系统、物联网设备以及许多其他应用中起着重要作用。通过这个工具,我们可以方便地与外部设备进行串口通信,并进行调试和测试。

Qt是一款强大而受欢迎的跨平台应用程序开发框架,它提供了丰富的工具和库,使得开发GUI应用程序变得更加简单和高效。通过结合Qt的功能和串口通信的知识,我们将创建一个易于使用和功能齐全的串口调试工具。

在本文中,我们将从头开始建立这个工具。我们将涉及到打开和关闭串口、发送和接收数据、设置串口参数等重要功能。我们将使用Qt提供的串口类和相关API来实现这些功能,并结合用户界面来展示数据的交互过程。

在开始之前,确保你已经安装了Qt开发环境,并熟悉Qt的基本知识。如果你还不熟悉Qt,不用担心,我们将尽量解释清楚每个步骤和概念。

接下来,让我们一起开始这个有趣而实用的项目吧!


一、新建QT程序

1.创建qt程序

  • Widgets Application:支持桌面平台的有图形用户界面的应用程序。GUI的设计完全基于C++语言,采用Qt提供的C++类库。
  • Console Application:控制台应用程序,无GUI界面。 Quick
  • Application:GUI开发框架,其界面设计采用QML语言(类似于WPF的xaml),一般用于移动设备和嵌入式设备上无边框的应用程序设计。
  • Qt Quick Controls 2 Application,创建基于 Qt Quick Controls 2 组件的可部署的 Qt Quick 2 应用程序。

选择Qt Widgets Application

在这里插入图片描述

2.项目命名和存储

在这里插入图片描述

3.选择构建系统

在这里插入图片描述

构建系统选择cmake将生成如下项目:

在这里插入图片描述

选择qmake

在这里插入图片描述
在这里插入图片描述

4.选择需要创建界面的基类

指定要为其生成骨架源代码文件的类的基本信息。
在这里插入图片描述

5.翻译语言选择

在这里插入图片描述

6.选择构建套件

在这里插入图片描述

7.项目管理确认

在这里插入图片描述

8.项目目录

在这里插入图片描述


二、串口程序编辑

1.ui设计

在这里插入图片描述

2.查找可用串口

.pro添加串口配置

QT       += serialport

mainwindow.h 头文件添加串口配置

#include <QSerialPort>
#include <QSerialPortInfo>

mainwindow.h 头文件添加串口配置

private:
    void InitSerialPortName();

mainwindow.cpp 添加实现函数

void MainWindow::InitSerialPortName()
{
    // 清空下拉框
    ui->PortNumber->clear();

    //通过QSerialPortInfo查找可用串口
    foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
    {
        QString showName = info.portName()+":"+info.description();
        qDebug() << showName.length();
        ui->PortNumber->addItem(showName);
    }
}

添加调用

在这里插入图片描述
在这里插入图片描述

3.串口打开关闭

mainwindow.cpp 头文件添加QMessageBox

#include <QMessageBox>

在构造函数中设置初始样式

    //设置串口状态标签为红色 表示未连接状态
    ui->SerialStatus->setStyleSheet("color:red");
    //statusBar 状态栏显示端口状态 未连接
    QString sm = "%1 CLOSED";
    QString status = sm.arg(ui->PortNumber->currentText());
    ui->statusbar->showMessage(status); //底部状态栏
    ui->statusbar->setStyleSheet("color:red");

串口打开槽函数 on_SerialOpen_clicked,设置相关参数,相关标签显示状态,使能转化等。代码如下:

void MainWindow::on_SerialOpen_clicked()
{
    if(ui->SerialOpen->text()==QString("打开串口"))
    {
        //设置串口名
        QString portName = (ui->PortNumber->currentText()).split(":").at(0);
        qDebug() << portName;
        serial_1.setPortName(portName);

        //设置波特率
        serial_1.setBaudRate(ui->BaudRate->currentText().toInt());

        //设置停止位
        if(ui->StopBit->currentText() == "1")
            serial_1.setStopBits(QSerialPort::OneStop);
        else if(ui->StopBit->currentText() == "1.5")
            serial_1.setStopBits(QSerialPort::OneAndHalfStop);
        else if(ui->StopBit->currentText() == "2")
            serial_1.setStopBits(QSerialPort::TwoStop);

        //设置数据位数
        if(ui->DataBit->currentText() == "8")
            serial_1.setDataBits(QSerialPort::Data8);
        else if(ui->DataBit->currentText() == "7")
            serial_1.setDataBits(QSerialPort::Data7);
        else if(ui->DataBit->currentText() == "6")
            serial_1.setDataBits(QSerialPort::Data6);
        else if(ui->DataBit->currentText() == "5")
            serial_1.setDataBits(QSerialPort::Data5);

        //设置奇偶校验
        if(ui->CheckDigit->currentText() == "无")
            serial_1.setParity(QSerialPort::NoParity);
        else if(ui->CheckDigit->currentText() == "偶校验")
            serial_1.setParity(QSerialPort::EvenParity);
        else if(ui->CheckDigit->currentText() == "奇校验")
            serial_1.setParity(QSerialPort::OddParity);

        //设置流控制
        serial_1.setFlowControl(QSerialPort::NoFlowControl);

        //打开串口
        if(serial_1.open(QIODevice::ReadWrite)){
            //设置串口状态标签为绿色 表示已连接状态
            ui->SerialStatus->setText("OPENED");
            ui->SerialStatus->setStyleSheet("color:green");
            //statusBar 状态栏显示端口状态
            QString sm = "%1 OPENED, %2, %3, %4, %5";
            QString status = sm.arg(serial_1.portName()).arg(serial_1.baudRate())
                                 .arg(serial_1.dataBits()).arg(serial_1.parity()).arg(serial_1.stopBits());
            ui->statusbar->showMessage(status);
            ui->statusbar->setStyleSheet("color:green");
        }
        else{
            QMessageBox::about(NULL, "提示", "无法打开串口!");
            return;
        }

        //下拉菜单控件失能
        ui->PortNumber->setEnabled(false);
        ui->BaudRate->setEnabled(false);
        ui->DataBit->setEnabled(false);
        ui->CheckDigit->setEnabled(false);
        ui->StopBit->setEnabled(false);

        ui->SerialOpen->setText(QString("关闭串口"));
        //发送按键使能
        ui->SerialSend->setEnabled(true);
    }
    else
    {
        //关闭串口
        serial_1.close();

        //下拉菜单控件使能
        ui->PortNumber->setEnabled(true);
        ui->BaudRate->setEnabled(true);
        ui->DataBit->setEnabled(true);
        ui->CheckDigit->setEnabled(true);
        ui->StopBit->setEnabled(true);

        ui->SerialOpen->setText(QString("打开串口"));
        //设置串口状态标签为绿色 表示已连接状态
        ui->SerialStatus->setText("CLOSED");
        ui->SerialStatus->setStyleSheet("color:red");
        //statusBar 状态栏显示端口状态
        QString sm = "%1 CLOSED";
        QString status = sm.arg(serial_1.portName());
        ui->statusbar->showMessage(status);
        ui->statusbar->setStyleSheet("color:red");
        //发送按键失能
        ui->SerialSend->setEnabled(false);
    }
}

演示如下:

在这里插入图片描述
底部状态

在这里插入图片描述

4.数据转化

字节序列转16进制代码如下:

QString MainWindow::ByteArrayToHexString(QByteArray data)
{
    QString ret(data.toHex().toUpper());
    int len = ret.length()/2;
    qDebug()<<len;
    for(int i=1;i<len;i++)
    {
        //qDebug()<<i;
        ret.insert(2*i+i-1," ");
    }

    return ret;
}

16进制转字节序列代码如下:

/*
 * @breif 将16进制字符串转换为对应的字节序列
 */
QByteArray MainWindow::HexStringToByteArray(QString HexString)
{
    bool ok;
    QByteArray ret;
    HexString = HexString.trimmed();
    HexString = HexString.simplified();
    QStringList sl = HexString.split(" ");

    foreach (QString s, sl) {
        if(!s.isEmpty())
        {
            char c = s.toInt(&ok,16)&0xFF;
            if(ok){
                ret.append(c);
            }else{
                qDebug()<<"非法的16进制字符:"<<s;
                QMessageBox::warning(0,tr("错误:"),QString("非法的16进制字符: \"%1\"").arg(s));
            }
        }
    }
    //qDebug()<<ret;
    return ret;
}

5.创建子线程收发数据

5.1 创建子线程类

右键项目“添加新文件”
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.2 实现子线程工作类

serialworker.cpp 修改代码如下:

SerialWorker::SerialWorker(QSerialPort *ser,QObject *parent)
    : QObject(parent),serial(ser)

增加子线程串口发送、接收槽函数、结果通知信号,数据发送信号

serialworker.h 代码如下:

#ifndef SERIALWORKER_H
#define SERIALWORKER_H

#include <QObject>
#include <QSerialPort>

class SerialWorker : public QObject
{
    Q_OBJECT
public:
    explicit SerialWorker(QSerialPort *ser,QObject *parent = nullptr);

signals:
    void sendDataToGui(QByteArray data);
    void sendResultToGui(QByteArray result);

public slots:
    void doDataSendWork(const QByteArray data);
    void doDataReciveWork();

private:
    QSerialPort *serial;
};

#endif // SERIALWORKER_H

serialworker.cpp 代码如下:

#include "serialworker.h"
#include <QDebug>
#include <QThread>

SerialWorker::SerialWorker(QSerialPort *ser,QObject *parent)
    : QObject(parent),serial(ser)
{

}

void SerialWorker::doDataSendWork(const QByteArray data)
{
    qDebug() <<  "子线程槽函数发送数据:" << data << "线程ID:" << QThread::currentThreadId();

    // 发送数据
        serial->write(data);
    // 信号
    emit sendDataToGui(data);
    //    serialPort_readyDisplay(data);
}

void SerialWorker::doDataReciveWork()
{
    // 1.收到数据
    QByteArray buffer = serial->readAll();

    // 2.进行数据处理
    QString resultStr = buffer;
    qDebug() <<  "子线程发送结果信号:" << resultStr << "线程ID:" << QThread::currentThreadId();

    // 3.将结果发送到主线程
    emit sendResultToGui(buffer);
}

5.3 增加主线程信号和数据处理函数

在mainwindow.h中增加与子线程结果接收槽函数,发送数据处理槽函数和发送信号:

signals:
    void serialDataSend(const QByteArray data);

public slots:
    void handleResults(QByteArray result);
    void handleData(QByteArray data);

添加实现:

void MainWindow::handleResults(QByteArray result)
{
    qDebug() <<  "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();
    serialPort_readyDisplay(result,"RX");
}

void MainWindow::handleData(QByteArray data){
    serialPort_readyDisplay(data,"TX");
}

void MainWindow::serialPort_readyDisplay(QByteArray data, QString flag)
{
    // 获取当前时间字符串
    QDateTime current_date_time =QDateTime::currentDateTime();
    QString dateStr =current_date_time.toString("[yyyy-MM-dd hh:mm:ss.zzz]");

    QString bufferStr = ByteArrayToHexString(data);
    QString displayStr = dateStr+"\n";
    if(!QString::compare(flag,QString("RX"),Qt::CaseSensitive)){
        displayStr += "RX: " + bufferStr + "\n";
    }else{
        displayStr += "TX: " + bufferStr + "\n";
    }
    qDebug()<<QString::compare(flag,QString("RX"),Qt::CaseSensitive);
    //从界面中读取以前收到的数据
    QString oldString = ui->Data_Receive->toPlainText();
    oldString = oldString + QString(displayStr);
    //    qDebug()<<oldString;
    //清空以前的显示
    ui->Data_Receive->clear();
    //重新显示
    ui->Data_Receive->append(oldString);
}

5.4 定义主线程与子线程联系

在GUI线程中添加串口子线程,mainwindow.h 添加子线程定义

    QThread serialThread_1; // 定义子线程

在mainwindow.cpp中定义子线程使用

    // 1.新建串口处理子线程
    SerialWorker *serialWorker = new SerialWorker(&serial_1);
    serialWorker->moveToThread(&serialThread_1);

    // 2.连接信号和槽
    connect(&serialThread_1, &QThread::finished,
            serialWorker, &QObject::deleteLater);           // 线程结束,自动删除对象
    connect(this, &MainWindow::serialDataSend,
            serialWorker, &SerialWorker::doDataSendWork);   // 主线程串口数据发送的信号
    connect(serialWorker, &SerialWorker::sendDataToGui,
            this, &MainWindow::handleData);                 // 主线程串口数据发送的信号
    connect(&serial_1, &QSerialPort::readyRead,
            serialWorker, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号
    connect(serialWorker, &SerialWorker::sendResultToGui,
            this, &MainWindow::handleResults);              // 主线程收到数据结果的信号


    // 3.开始运行子线程
    serialThread_1.start();                   // 线程开始运行

效果演示
在这里插入图片描述

6.命令发送

需求:在下方的命令框中按命令格式输入命令,发送到发送框中回显,点击发送向串口发送命令数据。

设置命令提示代码如下:

void MainWindow::cmd_btn_init()
{
    ui->rspi_parameter_1->setPlaceholderText("页号");
    ui->rspi_parameter_2->setPlaceholderText("寄存器偏移");
    ui->rspi_parameter_3->setPlaceholderText("字节数");
    ui->wspi_parameter_1->setPlaceholderText("页号");
    ui->wspi_parameter_2->setPlaceholderText("寄存器偏移");
    ui->wspi_parameter_3->setPlaceholderText("MSB:LSB");
    ui->rpphy_parameter_1->setPlaceholderText("页号");
    ui->rpphy_parameter_2->setPlaceholderText("寄存器偏移");
    ui->rpphy_parameter_3->setPlaceholderText("字节数");
    ui->wpphy_parameter_1->setPlaceholderText("页号");
    ui->wpphy_parameter_2->setPlaceholderText("寄存器偏移");
    ui->wpphy_parameter_3->setPlaceholderText("MSB:LSB");
    ui->rphy_parameter_1->setPlaceholderText("页号");
    ui->rphy_parameter_2->setPlaceholderText("寄存器偏移");
    ui->rphy_parameter_3->setPlaceholderText("字节数");
    ui->wphy_parameter_1->setPlaceholderText("页号");
    ui->wphy_parameter_2->setPlaceholderText("寄存器偏移");
    ui->wphy_parameter_3->setPlaceholderText("MSB:LSB");
}

设置命令发送槽函数。

void MainWindow::on_rspiSend_clicked()
{
    ui->Data_Send->clear();
    // 读取四个 LineEdit 的文本值
    QString value1 = "rspi";
    QString value2 = ui->rspi_parameter_1->text();
    QString value3 = ui->rspi_parameter_2->text();
    QString value4 = ui->rspi_parameter_3->text();

    // 组合成字符串
    QString combinedString = value1 + " " + value2 + " " + value3 + " " + value4;

    // 在控制台输出组合后的字符串(可根据需求进行其他处理)
    qDebug() << "Combined String: " << combinedString;
    ui->Data_Send->append(combinedString);
}

设置命令悬浮框解析命令
在这里插入图片描述
按照需要设置如下

在这里插入图片描述

效果如下:
在这里插入图片描述

命令发送:

在这里插入图片描述

7.文件发送

mainwindow.h 文件设置代码如下:

/*---QString--*/
#include <QString>
/*---QDateTime--*/
#include <QDateTime>
/*---QTimer--*/
#include <QTimer>
/**/
#include <QFile>
#include <QFileDialog>
/**/
#include <QToolBar>
/*---QDebug--*/
#include <QDebug>
#include <QMessagebox>
#include <QTextEdit>
#include <QTextCursor>
#include <QMimeDatabase>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QtCore/QTextCodec>
#else
#include <QtCore5Compat/QTextCodec>
#endif
private:
    /*文件发送相关*/
    bool isSendFile;    //是否是发送文件
    int type;   //文件类型
    int FrameCount;     //已发送帧数
    double ProgressBarValue;   //发送文件进度条
    int FrameLen;      //发送文件帧大小
    int FrameGap;    //发送文件帧时间间隔
    int FrameNumber;    //包数量
    int LastFrameLen;   //最后一帧长度
    double ProgressBarStep;    //进度条每发送一帧的步长
    int filelen;    //文件大小bytes
    char *fbuf; //存取文件的数组
    QString FileText;   //用于发送文本文件
    QByteArray FileTextBytes;   //用于将FileText转为char

mainwindow.cpp 选择文件发送槽函数

//选择发送文件槽函数
void MainWindow::on_SerialChoosefileBtn_clicked()
{
    /*选择并打开文件*/
    QString curPath = QDir::currentPath();  //系统当前目录
    QString dlgTitle = "打开文件";  //对话框标题
    QString filter = "所有文件(*.*);;二进制文件(*.bin *.elf);;文本文件(*.txt)"; //文件过滤器
    QString filepath = QFileDialog::getOpenFileName(this,dlgTitle,curPath,filter);
    QFileInfo fileinfo(filepath);
    filelen = fileinfo.size();  //获取文件大小
    if (filepath.isEmpty())
    {
        QMessageBox::warning(this,"警告","请选择文件");
        return;
    }
    //文件路径显示到发送框
    ui->Data_Send->clear();
    ui->Data_Send->append(filepath);

    QFile file(filepath);
    if (!file.exists())
    {
        QMessageBox::warning(this,"警告","文件不存在");
        return;
    }
    if (!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::warning(this,"警告","文件打开失败");
        return;
    }
    /*判断文件类型*/
    type = 0;
    QMimeDatabase db;
    QMimeType mime = db.mimeTypeForFile(filepath);
    if (mime.name().startsWith("text/"))
    {
        type = 1;	//文本文件
    }
    //else if (mime.name().startsWith("application/"))
    else
    {
        type = 2;	//二进制文件
    }
    ui->Data_Send->append(mime.name());
    /*读取文件*/
    switch(type)
    {
    case 1:
    {
        //QIODevice读取普通文本
        QByteArray data = file.readAll();
        //data.toHex();
        file.close();
        if (data.isEmpty())
        {
            QMessageBox::information(this, "提示", "文件内容空");
            return;
        }
        /* 判断编码 */
        QTextCodec::ConverterState state;
        QTextCodec *codec = QTextCodec::codecForName("UTF-8");
        FileText = codec->toUnicode(data.constData(),data.size(),&state);
        //若有无效字符则是GBK编码
        if (state.invalidChars > 0)
        {
            //转码后返回
            FileText = QTextCodec::codecForName("GBK")->toUnicode(data);
        }
        else
        {
            FileText =  data;  //QByteArray  -> QString  直接强转就行
        }
        FileTextBytes = FileText.toLocal8Bit();  //支持中文,latine不支持中文
        fbuf = new char[filelen];
        fbuf = FileTextBytes.data();
        //int sendNum = global_port.write(&fbuf[0], filelen);
    }
    break;
    case 2:
    {
        //使用QDataStream读取二进制文件
        QDataStream datain(&file);
        fbuf = new char[filelen];
        datain.readRawData(&fbuf[0],filelen);
        //datain.readBytes(&fbuf[0],filelen);
        if(fbuf[0] == 0x40){
            QMessageBox::information(this, "提示", "对的");
        }
        file.close();
    }
    break;
    }
    //显示文件大小信息,即字符长度,空格包括在内
    QString info = QString("%1%2").arg("文件大小为:").arg(filelen);
                       ui->Data_Send->append(info);
    /*
    //显示文件内容
    if (0)
    {
        ui->SerialRecvWin->append(FileText);
    }
    else
    {
        ui->SerialRecvWin->append(FileText);
    }
    //设置显示焦点在最顶部
    ui->SerialRecvWin->moveCursor(QTextCursor::Start,QTextCursor::MoveAnchor);

*/
    /*标记有文件发送*/
    isSendFile = true;   //是否是发送文件
    FrameCount = 0;     //已发送的帧个数
    ProgressBarValue = 0;   //进度条
}

设置了显示路径,文件类型,文件大小和文件内容,可按需要显示。
效果如下:

在这里插入图片描述


总结

从头开始建立一个QT工程。涉及到ui设计,设置串口参数,打开和关闭串口、发送和接收数据、命令发送、文件发送等功能。我们将使用Qt提供的串口类和相关API来实现这些功能,并结合用户界面来展示数据的交互过程。希望可以帮到大家。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值