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

  • 结构
  • UI部分
  • 代码部分
    • step1:实现串口数据接受
      • 串口接受数据格式
      • 在`.pro`文件中添加`serialport`关键字
      • 在`mainwindow.h`文件中添加头文件和代码
      • 在`mainwindow.cpp`文件中添加代码
      • 配合测试用的Arduino串口发送程序
    • step2:实现绘图
      • 在`.pro`文件中添加`printsupport`关键字![在这里插入图片描述](https://img-blog.csdnimg.cn/20210326084134132.png)
      • 添加`QCustomPlot`
      • 在`mainwindow.h`文件中添加头文件和代码
      • 将UI中的widget提升为Plot控件
      • 在`mainwindow.cpp`文件中添加代码
    • step3:实现保存数据到TXT文件
  • 源代码
  • 参考文献

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

结构

  • 整体的项目完成后的文件结构如下图所示分为项目文件(*.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 slotvoid serialReadyRead();

private部分

//privateQSerialPort *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_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic: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");//设置窗体titlemSerial = new QSerialPort(this);//新建串口类updateSerialPorts();//更新串口mSerialScanTimer = new QTimer(this);//新建一个定时器类mSerialScanTimer->setInterval(5000);//定时间隔为5smSerialScanTimer->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);//接受的数据放入strui->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)实例

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

  1. 以太坊构建DApps系列教程(二):构建TNS代币

    在本系列关于使用以太坊构建DApps教程的第1部分中,我们引导大家做了两个版本的本地区块链进行开发:一个Ganache版本和一个完整的私有PoA版本. 在这一部分中,我们将深入研究并构建我们的TNS代 ...

  2. php调用kettle,kettle系列教程二

    kettle系列教程二 1.Hello World 示例 通过这个例子,介绍kettle的一些基础知识及应用: n 使用Spoon工具 n 转换的创建 n 步骤及连线 n 预定义变量 n 在Spoon ...

  3. 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级) 本章介绍的是企业库加密应用程序模块 ...

  4. C#微信公众号开发系列教程二(新手接入指南)

    此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可直接跳过,也欢迎大神吐槽. 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教 ...

  5. Cocoapods系列教程(二)——开源主义接班人

    原文地址:Cocoapods系列教程(二)--开源主义接班人 引言 在写该博客的时候,博主刚看到一个问题:"那些头衔只是看起来很厉害,实际不难获得?".然后有个神回复写到:&quo ...

  6. 汇川技术小型PLC梯形图编程系列教程(二):AutoShop软件使用技巧介绍

    原文链接:汇川技术小型PLC梯形图编程系列教程(二):AutoShop软件使用技巧介绍 俗话说的好,磨刀不误砍柴工.这里的刀指的是准备工作:手册+熟练使用软件(掌握一些技巧).所以本篇为大家简单介绍一 ...

  7. 2021年淘宝客系列教程(二)事前准备

    2021年淘宝客系列教程(二)事前准备 本系列立志于将淘宝客如何在微信公众号/微信机器人这类基础上运作,到最后开发一个完整的淘宝客系统而编写,如有雷同,纯属借鉴~ 2021年淘宝客系列教程(一)淘宝客 ...

  8. Altium designer18系列教程二 原理图库制作

    Altium designer18系列教程二 原理图库制作 制作原理图库 一.新建原理图库文件(教程一中有提到) 二.界面介绍 三.画原理图库元器件 在前面的教程中主要讲了讲AD18的优点和AD18的 ...

  9. PVE系列教程(二)、安装网心云x86专业版

    为了更好的浏览体验,欢迎光顾勤奋的凯尔森同学个人博客 PVE系列教程(二).安装网心云x86专业版 一.上传x86镜像 把x86镜像上传到local下的ISO镜像中. 二.配置虚拟机 点击创建虚拟机 ...

最新文章

  1. 新的工作电脑 神州优雅A550-i7
  2. V2X 是自动驾驶重要基石,巨头纷纷抢滩布局
  3. 【深度学习的数学】2×3×1层带sigmoid激活函数的神经网络感知机对三角形平面的分类训练预测(绘制出模型结果三维图展示效果)(梯度下降法+最小二乘法+激活函数sigmoid+误差反向传播法)
  4. 简述力法计算弹性固定无铰拱的原理_《隧道结构体系设计》PPT课件.ppt
  5. 对springMVC的简单理解
  6. 数学--数论--费马小定理+求逆元
  7. Socket网络编程——C++实现
  8. 好全的Android面试题
  9. 整套的操作体系:三点看股法
  10. html怎么去li间隙,求助大神 如让图片展示在li标签里面,不要间隙 谢谢
  11. 【问题3】生产环境中的 redis 是怎么部署的?
  12. 高等代数——大学高等代数课程创新教材(丘维声)——3.7笔记+习题
  13. vmsysjack-tupian
  14. Mongodb数据库(上)
  15. 如何保证投票公平_怎样设置投票活动规则才能保持公平性呢?
  16. 速解元宇宙 / Metaverse 迷雾下的行动指南
  17. 学Python需要安装什么软件?Python软件工具大全
  18. SPA 的 SEO 方案对比、最终实践
  19. UNABLE TO READ CONSUMER IDENTITY
  20. 服务器带宽占用检测,怎么看服务器的带宽

热门文章

  1. PAT 1026 Table Tennis (30分)
  2. 阿里云添加云盘并挂载使用步骤
  3. Malloc for kernel output failed, Memory isnt enough
  4. 【第8天】SQL进阶-更新记录(SQL 小虚竹)
  5. Lottie: 让动画实现更简单
  6. Hoppscotch - 免费开源的轻量级 API 接口开发/测试/调试工具,代替 Postman
  7. 数字经济之新零售行动派:鸡毛换糖走向数智化
  8. 奋进新时代 和数BaaS开启下一个波澜壮阔科技新世界
  9. 数据库系统概论第五版:考试预测_HUST_CSE
  10. 游戏感:虚拟感觉的游戏设计师指南——第十三章 超级马里奥兄弟