QT5系列教程二---基于qcustomplot的QT5 GUI串口收发绘图软件实现

2 篇文章 0 订阅
1 篇文章 0 订阅


这个项目根据参考文献【1】完成。实现了将WeMos D1Mini采集的数据上传到电脑,并实时绘图。采集完成后点击保存按钮时,可将原始数据存入到TXT文件中。
新录制了视频~ 顺便吐槽了下。。。

https://www.bilibili.com/video/BV1S84y1Q72z/?vd_source=59162da97f9dd227370a5d91fe690f9b
在这里插入图片描述

结构

  • 整体的项目完成后的文件结构如下图所示分为项目文件(*.pro)、头文件(*.h)、源代码(*.cpp)和窗体(*.ui)四个部分。
  • 设计思路是先完成整体界面的设计,布置好界面上各个控件的布局位置等。然后根据实际项目需求逐一完善各个功能。
    在这里插入图片描述

UI部分

  • 在Qt Creator界面左侧快捷菜单中选择编辑页面,双击mainwidow.ui文件跳转到UI的开发界面,其对应的是左侧快捷菜单的设计页面。
    在这里插入图片描述

  • 在设计界面按照下图界面拖拽相应的控件到相应的位置。(第八个控件plot可以先不放)
    在这里插入图片描述

  • 对上图2、4、5、7对应的按钮点击右键,选择转到槽
    在这里插入图片描述

  • 选择 clicked()事件,点击ok
    在这里插入图片描述

  • 这时在mainwindow.cpp文件中就会生成相应的响应函数。点击后想要实现的功能添加在这里就可以了
    在这里插入图片描述

  • 相应的mainwindow.h的下面所示的位置也会自动添加代码
    在这里插入图片描述

  • 控件属性名称对照表
    在这里插入图片描述
    整体界面的设置比较简单,不涉及负责的布局和层级关系,也没有考虑窗体的自适应性。只需要按照博主上一篇教程中介绍的方式在左侧找到相应的控件拖拽到窗体上,然后更改其名称,方便后续程序调用时可以快速定位和识别即可。可能有点陌生的是绘图区域Qplot控件在界面的实现。这一步可以先不做。稍后会详细叙述。

代码部分

step1:实现串口数据接受

本部分主要实现的功能是利用QTimer定时对串口设备进行检测,点击连接按钮后利用QSerialPort控件接收数据并显示在QTextBrowser里面。

串口接受数据格式

  • 数据格式sample: 2 4978
数据头空格ID空格数据
ample:空格1空格4987
  • 串口波特率:9600 无停止位 无校验位

.pro文件中添加serialport关键字

在这里插入图片描述

mainwindow.h文件中添加头文件和代码

  • 添加serialport和timer的相关的头文件
    在这里插入图片描述
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
  • 添加功能代码
    在这里插入图片描述

private slot部分

//private slot
    void serialReadyRead();

private部分

//private
    QSerialPort *mSerial;
    QList<QSerialPortInfo> mSerialPorts;
    QTimer *mSerialScanTimer;
    void updateSerialPorts();

mainwindow.cpp文件中添加代码

  • 在cpp的主程序 添加代码
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);
}

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

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

  • on_connect_pushButton_clicked()函数中添加代码
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);
}
  • 添加serialReadyRead()函数
void MainWindow::serialReadyRead()
{
    QByteArray data = mSerial->readAll();
    QString str = QString(data);
    ui->outputTextBrowser->insertPlainText(str);
    QScrollBar *sb = ui->outputTextBrowser->verticalScrollBar();
    sb->setValue(sb->maximum());
}
  • 添加`updateSerialPorts()函数
void MainWindow::updateSerialPorts()
{
    mSerialPorts = QSerialPortInfo::availablePorts();

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

Tips:
如果报找不到sb对象的错误。可以在ui界面将QTextBrowser的vertiScrollBar的属性设置成Alwayson。
在这里插入图片描述

配合测试用的Arduino串口发送程序

long randNumber;
int i=1;
String str;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  randomSeed(analogRead(5)); // randomize using noise from analog pin 5
}
void loop() {
  // put your main code here, to run repeatedly: 
  randNumber = random(300);  
  str="sample: "+String(i)+" "+String(randNumber);
  Serial.println(str);
  i++;
  delay(100);
}

step2:实现绘图

.pro文件中添加printsupport关键字在这里插入图片描述

添加QCustomPlot

  • QCostomPlot下载地址.
    在这里插入图片描述
  • 解压后将qcustomplot.hqcustomplot.cpp两个文件复制到工程文件目录下。
    在这里插入图片描述
    在这里插入图片描述
  • qcustomplot.hqcustomplot.cpp两个文件添加到工程中
    在这里插入图片描述
    在这里插入图片描述
  • 完成上一步的操作后,新添加的两个文件就出现在项目文件目录中了。
    在这里插入图片描述

mainwindow.h文件中添加头文件和代码

  • 添加头文件
    在这里插入图片描述
  • private中添加一个变量声明mData
    在这里插入图片描述

将UI中的widget提升为Plot控件

  • 放置一个widget,在界面上拖拽到合适大小。更名为plot。如果已经做过这个操作,可以省略直接跳到下一步做提升。
    在这里插入图片描述
  • widget控件上点击右键,选择提升为。。。
    在这里插入图片描述
  • 在提升的类名称中输入QCustomPlot,点击添加完成提升。
    在这里插入图片描述

mainwindow.cpp文件中添加代码

在这里插入图片描述

    //绘图部分

    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("数据测试");

step3:实现保存数据到TXT文件

void MainWindow::on_saveGraphButton_clicked()
{
   //step1:生成txt文件
   QString filename = QFileDialog::getSaveFileName(this,
                                                    tr("Save txt"), "",
                                                    tr("Txt 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();
    }
}

源代码

源代码百度云下载地址
提取码:7dhu
csdn下载https://download.csdn.net/download/happyjoey217/16126955

  • 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;

    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);//ui为窗体指针
    this->setWindowTitle("Serial");//设置窗体title

    mSerial = new QSerialPort(this);//新建串口类
    updateSerialPorts();//更新串口

    mSerialScanTimer = new QTimer(this);//新建一个定时器类
    mSerialScanTimer->setInterval(5000);//定时间隔为5s
    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");//设置y标题
    ui->plot->xAxis->setLabel("Sample");//设置x标题
    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();//清空界面中comboxButton控件中的显示
    for (QSerialPortInfo port : mSerialPorts) {//将刚刚更新的串口名逐一写入到combox控件中,方便使用者后续选择
        ui->serialComboBox->addItem(port.portName(), port.systemLocation());
    }
}


void MainWindow::on_connect_pushButton_clicked()//连接串口按钮按下时的响应函数
{
    ui->connect_pushButton->setEnabled(false);//使连接按钮处于无法点击的状态
    QString serialLoc = ui->serialComboBox->currentData().toString();//选中combox控件中显示的串口号
      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(); 将lineEdit控件中的字符串放入str中
        ui->lineEdit->clear();//情况lineEdit中的显示
        str.append("\r\n");//在发送的数值末尾增加回车换行
        mSerial->write(str.toLocal8Bit());//发送数据
    } else {
        qDebug( "Serial port not connected!");//qt调试窗口显示方便测试
    }

}
void MainWindow::serialReadyRead()//串口数据收集处理函数
{
    QByteArray data = mSerial->readAll();//读取串口接受的数据
    QString str = QString(data);//接受的数据放入str
    ui->outputTextBrowser->insertPlainText(str);//数据显示在界面的Textbrowser控件中
    QScrollBar *sb = ui->outputTextBrowser->verticalScrollBar();//textbrowser的滚动条
    sb->setValue(sb->maximum());//保持textBROWSER控件内光标永远在最后一行。

    //对接收的数据进行处理并进行绘制
    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();//重绘
       }
    }
}

void MainWindow::on_saveGraphButton_clicked()
{
    //step1:生成txt文件
    QString filename = QFileDialog::getSaveFileName(this,
                                                    tr("Save txt"), "",
                                                    tr("Txt files (*.txt)"));//打开系统的打开文件对话框

    if (!filename.isEmpty()) {
        //ui->plot->savePdf(filename);//plot控件自带存pdf的方法
        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();//将textBrowser控件中的采集数据存入txt文件中。
    }
 }
void MainWindow::on_clear_Button_clicked()//清除绘图按钮点击后的响应函数
{
    mData->clear(); //清楚mData的值
    ui->plot->rescaleAxes();//ui窗体上的plot控件重新规划数周
    ui->plot->replot();//plot重绘
}

参考文献

【1】项目参考的网站https://svenssonjoel.github.io/pages/qt_serial_datacollection/index.html
【2】youtube https://www.youtube.com/watch?v=dCllDmB-ftk
在这里插入图片描述
【3】QSerialPort类官方文档

【4】widget如何提升为QcustomPlot https://www.cnblogs.com/yingjiehit/p/3988701.html
https://www.qcustomplot.com/index.php/tutorials/settingup
【5】QTextBrowser如何打开并显示txt文件内容
【6】win10下使用Qt64位编译,读写access数据库进行海量数据计算,附SIGSEGV及32位64位驱动问题
【7】QT与数据库连接实例
在这里插入图片描述
连接数据库的时候这个mingw要选择64位的
【8】【原创】QT数据库学习和以连接Access为例
【9】Qt之操作数据库(SQLite)实例

  • 29
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值