Qt6重写基于Qcustomplot的GUI串口收发绘图软件

Qt6串口绘图软件重构


有人反馈之前Qt5版本的串口控件教程链接: QT5系列教程二—基于qcustomplot的QT5 GUI串口收发绘图软件实现现在因为版本升级的原因,无法成功复刻了。加之自己电脑重做系统顺手安装了最新版Qt6。查阅了Qcustomplot官方论坛,很多友人反馈虽然最后更新时间为2022年,但是2.1.1版本在Qt6.9上使用很丝滑,想着测试下。特此记录。

效果展示

请添加图片描述

软件准备

版本升级参考这个贴子吧 大差不差,基本思路
使用Qt自带的Maintenance Tool将Qt6.9升级为QT6.10

运行调试旧代码

运行

选择“Qt 6.10.1 llvm-mingw 64-bit”编译器,之前那个版本写的比较早,所以用最新下载的QcustomPlot2.1.1中新文件替换了旧的文件。对Qt5老程序进行编译(项目迁移最快的方法就是先运行下,看报啥错,报啥解决啥),很幸运,只有几个警告,编译成功。

调试

下位机发送串口信息的Arduino代码

这次手头没有找到wemos D1mini,只有一块Uno。无所谓,Arduino代码最帅气的地方就是通用性。只要更换下载的板子类型就能丝滑运行。

// 下位机Arduino代码
int i=1;  //初始化全局变量用来计数
String str;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);//串口波特率9600
  randomSeed(analogRead(5)); // randomize using noise from analog pin 5
}
void loop() {
  // put your main code here, to run repeatedly: 
  randNumber = random(300);  //300以内随机数
  str="sample: "+String(i)+" "+String(randNumber);//字头+序号+随机数
  Serial.println(str);//串口输出
  i++;
  delay(100);
}

上位机运行状态

  • 电脑接入下位机后,可以顺利找到COM口,点击连接后,文本框里可以正常显示数据,
    在这里插入图片描述
  • 绘图部分不工作,等很久,会跳动一小下。
  • 原本程序中用作调试的,在成功解析到“sample”字头后就在调试窗口输出的语句一直没有执行。qDebug() << "Got a sample " << parts.at(1).toDouble() << parts.at(2).toDouble();//在调试栏输出收到的数值
    //对接收的数据进行处理并进行绘制
    if (str.startsWith("sample:",Qt::CaseInsensitive)) {
       QStringList parts = str.split(" ");//wemosD1MINI上传的数据格式是三个部分,每个部分用空格分开。eg:sample:+空格+ID+空格+采集值
       if (parts.size() == 3) {//判断采集数值正确
         qDebug() << "Got a sample " << parts.at(1).toDouble() << parts.at(2).toDouble();//在调试栏输出收到的数值
         double num = parts.at(1).toDouble();
         double mag = parts.at(2).toDouble();
         mData->add(QCPGraphData(num, mag));//将收到的数据放入mData中
         ui->plot->rescaleAxes();//重设数周比例
         ui->plot->replot();//重绘
       }
    }

调试思路

能够在文本框里显示数据说明串口的接受功能没有问题。而原本用于调试的语句却没有反应,问题应该出现在if (str.startsWith("sample:",Qt::CaseInsensitive))没有执行。所以增加qDebug() <<str这句调试语句到if判断之外。此处的str为上几句代码中sting数据转换之后的串口原始数据流。通过这种方式来看看此时输出的数据是什么。为什么明明数据有接收到,却判断不出来。
运行后串口显示支离破碎的信息。没有完整的“sample:+序号+数值”的形式。大概长这样:
在这里插入图片描述
这下就很明显了,数据收到了,但是不是预想的一条条的完整状态。所以if语句根本就没有执行过。所以自然也不会画图了。
考虑是串口数据接受判断的问题。找到了Qt自带的qtserialport-everywhere-src-6.10.0下载地址:https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/qt/6.10/6.10.0/submodules/)1中的例程。运行查看了下,发现这个串口接受用的是阻塞的方式,早就忘光了串口数据阻塞和非阻塞的细节。所以狗屁通了一下。

在这里插入图片描述
看来问题就在这里了。所以把现在的现象总结归纳了下,生成提示词给到狗屁通寻求帮助。具体提示词如下。主要结构为三个部分:介绍下位机数据格式,介绍目前接收到的数据现状,给出串口收发源代码。
在这里插入图片描述
狗屁通很靠谱,直接给出诊断结果。
在这里插入图片描述
在这里插入图片描述
狗屁通甚至人性化的为我提供了代码整合服务,于是我将mainwindow.h和mainwindow.cpp两个文件上传。
在这里插入图片描述
结果反馈:
在这里插入图片描述
在这里插入图片描述
通过以上修改。程序完美运行。感谢狗屁通。

源代码

链接: https://gitcode.com/happyjoey217/Qt6SerailDraw

mainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include "qcustomplot.h"

#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_connect_pushButton_clicked();
    void on_send_Button_clicked();
    void on_saveGraphButton_clicked();
    void on_clear_Button_clicked();

    void serialReadyRead();

private:
    Ui::MainWindow *ui;

    QByteArray mRecvBuffer;    // 串口接收缓存

    QSerialPort *mSerial;
    QList<QSerialPortInfo> mSerialPorts;
    QTimer *mSerialScanTimer;
    QSharedPointer<QCPGraphDataContainer> mData;
    void updateSerialPorts();
};
#endif // MAINWINDOW_H

mainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->setWindowTitle("Serial");

    mSerial = new QSerialPort(this);
    updateSerialPorts();

    mSerialScanTimer = new QTimer(this);
    mSerialScanTimer->setInterval(5000);
    mSerialScanTimer->start();

    connect(mSerialScanTimer, &QTimer::timeout,
            this, &MainWindow::updateSerialPorts);

    connect(ui->lineEdit, &QLineEdit::returnPressed,
            this, &MainWindow::on_send_Button_clicked);

    connect(mSerial, &QSerialPort::readyRead,
            this, &MainWindow::serialReadyRead);
    //绘图部分

    mData = QSharedPointer<QCPGraphDataContainer>(new QCPGraphDataContainer);

    /* 绘图初始化 */
    ui->plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
    ui->plot->legend->setVisible(true);
    QFont legendFont = font();
    legendFont.setPointSize(10);
    ui->plot->legend->setFont(legendFont);
    ui->plot->legend->setSelectedFont(legendFont);
    ui->plot->legend->setSelectableParts(QCPLegend::spItems);
    ui->plot->yAxis->setLabel("Magnitude");
    ui->plot->xAxis->setLabel("Sample");
    ui->plot->clearGraphs();
    ui->plot->addGraph();

    ui->plot->graph()->setPen(QPen(Qt::black));
    ui->plot->graph()->setData(mData);
    ui->plot->graph()->setName("STM32 ADC");
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::updateSerialPorts()
{
    mSerialPorts = QSerialPortInfo::availablePorts();

    ui->serialComboBox->clear();
    for (QSerialPortInfo port : mSerialPorts) {
        ui->serialComboBox->addItem(port.portName(), port.systemLocation());
    }
}


void MainWindow::on_connect_pushButton_clicked()
{

    ui->connect_pushButton->setEnabled(false);
    QString serialLoc = ui->serialComboBox->currentData().toString();
      if (mSerial->isOpen()) {
        qDebug("Serial already connected, disconnecting!");// << "Serial already connected, disconnecting!";
        mSerial->close();
    }

    mSerial->setPortName(serialLoc);
    mSerial->setBaudRate(QSerialPort::Baud9600);
    mSerial->setDataBits(QSerialPort::Data8);
    mSerial->setParity(QSerialPort::NoParity);
    mSerial->setStopBits(QSerialPort::OneStop);
    mSerial->setFlowControl(QSerialPort::NoFlowControl);

    if(mSerial->open(QIODevice::ReadWrite)) {
        qDebug("SERIAL: OK!") ;
    } else {
        qDebug("SERIAL: ERROR!") ;
    }
    ui->connect_pushButton->setEnabled(true);
}
void MainWindow::on_send_Button_clicked()
{
    if (mSerial->isOpen()) {

        QString str= ui->lineEdit->text();
        ui->lineEdit->clear();
        str.append("\r\n");
        mSerial->write(str.toLocal8Bit());
    } else {
        qDebug( "Serial port not connected!");
    }

}
void MainWindow::serialReadyRead()
{
    // 1. 先把所有收到的数据追加到缓存
    mRecvBuffer.append(mSerial->readAll());

    // 2. 按照 "\r\n" 切分消息(ESP32 默认以 \r\n 结尾)
    int index;
    while ((index = mRecvBuffer.indexOf("\r\n")) != -1)
    {
        // 取出一条完整消息
        QByteArray line = mRecvBuffer.left(index);
        mRecvBuffer.remove(0, index + 2); // 删除包括 \r\n

        // 转 QString
        QString str = QString::fromUtf8(line);

        // 显示到文本框
        ui->outputTextBrowser->insertPlainText(str + "\n");
        QScrollBar *sb = ui->outputTextBrowser->verticalScrollBar();
        sb->setValue(sb->maximum());

        // 解析 sample: x y 结构
        if (str.startsWith("sample:", Qt::CaseInsensitive))
        {
            QStringList parts = str.split(" ");
            if (parts.size() == 3)
            {
                double num = parts.at(1).toDouble();
                double mag = parts.at(2).toDouble();

                qDebug() << "Got sample" << num << mag;

                // 加入绘图
                mData->add(QCPGraphData(num, mag));
                ui->plot->rescaleAxes();
                ui->plot->replot();
            }
        }
    }
}


void MainWindow::on_saveGraphButton_clicked()
{
    //step1:生成txt文件
   /* QString filename = QFileDialog::getSaveFileName(this,
                                                    tr("Save pdf"), "",
                                                    tr("Pdf files (*.txt)"));

    if (!filename.isEmpty()) {
        //ui->plot->savePdf(filename);

        QFile file(filename);//文件命名
        if (!file.open(QFile::WriteOnly | QFile::Text))     //检测文件是否打开
        {
            QMessageBox::information(this, "Error Message", "Please Select a Text File!");
            return;
        }
        QTextStream out(&file);                 //分行写入文件
        out << ui->outputTextBrowser->toPlainText();
    }*/

    //step2:将描述和文件名存入数据库
    QSqlDatabase ldb = QSqlDatabase::addDatabase("QODBC");
    ldb.setDatabaseName("DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};FIL={MS Access};DBQ=E:/serialPortTest/my.accdb");

    QSqlQuery mquery;
    bool ok = ldb.open();
    qDebug( "!");
    if(ok)
    {
        qDebug( "DB connected!");
        //存数据到数据库
        mquery.next();
        bool isok=mquery.exec("Insert INTO [test] ([content], [filename]) VALUES ('tomato', '202103241043.txt')");

        //检测存入正确
        isok=mquery.exec("select * from test;");
        if (!isok)
        {
            qDebug( "DB not connected!");
            ldb.close();
        }
        mquery.next();
        mquery.seek(1);
        QString sHostName=mquery.value(1).toString();
        mquery.clear();
        ldb.close();
        qDebug()<<sHostName;
     }
    else{
         // 打开本地数据库失败,
        qDebug()<<ldb.lastError();
     }
 }

void MainWindow::on_clear_Button_clicked()
{

    mData->clear();
    ui->plot->rescaleAxes();
    ui->plot->replot();

}

qt中Desktop Qt 6.10.1 llvm-mingw 64-bit和Desktop Qt 6.10.1 MinGw 64-bit区别

图片来自狗屁通截图。
在这里插入图片描述
在这里插入图片描述

更换编译器

左侧菜单->项目->选择编译器
在这里插入图片描述

参考文献


  1. 博客中给出了清华大学开源软件镜像网站中的串口控件的使用方法。这个开源镜像中软件好多。nice。链接: https://blog.youkuaiyun.com/qq_36393978/article/details/127861862 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值