QT5系列教程二---基于qcustomplot的QT5 GUI串口收发绘图软件实现
这个项目根据参考文献【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
关键字![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6de022d4e8995b4e418bce8f307c3b3f.png)
添加QCustomPlot
- QCostomPlot下载地址.
- 解压后将
qcustomplot.h
和qcustomplot.cpp
两个文件复制到工程文件目录下。
- 将
qcustomplot.h
和qcustomplot.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)实例