QT串口调试助手V2.0(源码全开源)--上位机+多通道波形显示+数据保存(优化波形显示控件)

首先关于Qt的安装和基本配置这里就不做重复说明了,注:本文在Qt5.14基础上完成 完整的项目开源仓库链接在文章末尾

图形控件——qcustomplot

QCustomPlot是一个基于Qt框架的开源绘图库,用于创建高质量的二维图表和数据可视化。

QCustomPlot的主要功能:
绘制多种图表类型:包括折线图、散点图、柱状图、面积图
交互性:支持图表的缩放、平移、数据点选择等交互操作
多轴支持:可以在图表中添加多个X轴和Y轴,以便绘制复杂的多轴图表
定制化:提供丰富的样式和属性设置,用户可以自定义图表的外观,包括颜色、线条样式、标记
高性能:针对大数据量绘图进行了优化,能够处理大量数据点而不影响性能

主要组件:
QCustomPlot:主绘图控件,所有的绘图操作都在这个控件上进行。
QCPGraph:用于绘制常见的折线图和散点图。
QCPAxis:表示图表的轴,可以自定义轴的范围、标签、刻度等。
QCPItem:图表中的各种辅助元素,如直线、文本标签等。
QCPPlottable:可绘制对象的基类,所有具体的绘图类型都继承自这个类。

该控件使用方法
将qcustomplot的源码一个cpp一个h文件添加到项目工程中,并添加头文件和编译链接就可以便捷的在源码中使用了。注意在放置显示控件时需要先放置一个qt内置的QWidget控件,然后通过右键控件,升格,选中头文件和类名称,设置为QCustomPlot,然后源代码中就可以快乐使用了。具体设置的效果可以参考文末完整的项目链接。
在这里插入图片描述

绘制曲线数据

主要使用到了曲线控件customPlot->addGraph();

这个控件的使用方法整体上和Qt自带的控件差别不大,主要这个控件的视觉效果和长时间大数据绘制效果更好一些

在使用控件之前要先初始化相关的控件内容:

// 绘图图表初始化
void MainWindow::QPlot_init(QCustomPlot *customPlot)
{// 创建定时器,用于定时生成曲线坐标点数据QTimer *timer = new QTimer(this);timer->start(10);connect(timer, SIGNAL(timeout()), this, SLOT(Plot_TimeData_Update()));// 图表添加两条曲线pGraph1_1 = customPlot->addGraph();pGraph1_2 = customPlot->addGraph();// 设置曲线颜色pGraph1_1->setPen(QPen(Qt::red));pGraph1_2->setPen(QPen(Qt::black));// 设置坐标轴名称customPlot->xAxis->setLabel("X-Times");customPlot->yAxis->setLabel("Amplitude of channel");// 设置y坐标轴显示范围customPlot->yAxis->setRange(-2, 2);// 显示图表的图例customPlot->legend->setVisible(true);// 添加曲线名称pGraph1_1->setName("Channel1");pGraph1_2->setName("Channel2");// 设置波形曲线的复选框字体颜色ui->checkBox_1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}"); // 设定前景颜色,就是字体颜色// 允许用户用鼠标拖动轴范围,用鼠标滚轮缩放,点击选择图形:customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}

当向曲线添加新数据的时候可以同时控制范围窗口内的坐标轴尺寸,以及打印一些绘图相关的属性数据

void MainWindow::Plot_Show_Update(QCustomPlot *customPlot, double n1, double n2)
{cnt++;// 给曲线添加数据pGraph1_1->addData(cnt, n1);pGraph1_2->addData(cnt, n2);// 设置x坐标轴显示范围,使其自适应缩放x轴customPlot->xAxis->setRange( 0, (pGraph1_1->dataCount() > 1000) ? (pGraph1_1->dataCount()) : 1000);// 更新绘图,这种方式在高填充下太浪费资源。rpQueuedReplot,可避免重复绘图。customPlot->replot(QCustomPlot::rpQueuedReplot);static QTime time(QTime::currentTime());double key = time.elapsed() / 1000.0; // 开始到现在的时间,单位秒//计算帧数static double lastFpsKey;static int frameCount;frameCount++;if (key - lastFpsKey > 1) // 每1秒求一次平均值{// 帧数和数据总数ui->statusbar->showMessage(QString("Refresh rate: %1 FPS, Total data volume: %2").arg(frameCount / (key - lastFpsKey), 0, 'f', 0).arg(customPlot->graph(0)->data()->size() + customPlot->graph(1)->data()->size()),0);lastFpsKey = key;frameCount = 0;}
}

曲线的绘制效果:
在这里插入图片描述
要实现上面的多通道效果还需要自行适配串口解析部分,然后匹配对应的数据后将数值传入到目标曲线对象。

完整的项目源码如下,包含数据解析、串口控制部分、同时实现数据保存到csv文件(代码中只保存了数组前两位的数值,需要保存多少数据可以自行修改代码实现)

项目Cpp文件:

#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);// 给widget绘图控件,设置个别名,方便书写pPlot1 = ui->widget_1;// 状态栏指针sBar = statusBar();// 初始化图表1QPlot_init(pPlot1);cnt = 0;setWindowTitle("数据采集系统");serialport = new QSerialPort;find_port();                    //查找可用串口timerserial = new QTimer();QObject::connect(serialport,&QSerialPort::readyRead, this, &MainWindow::serial_timerstart);QObject::connect(timerserial,SIGNAL(timeout()), this, SLOT(Read_Date()));ui->close_port->setEnabled(false);//设置控件不可用
}// 析构函数
MainWindow::~MainWindow()
{delete ui;
}//查找串口
void MainWindow::find_port()
{//查找可用的串口bool fondcom = false;ui->com->clear();foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){QSerialPort serial;serial.setPort(info);   //设置串口if(serial.open(QIODevice::ReadWrite)){//ui->com->addItem(serial.portName());        //显示串口namefondcom = true;QString std = serial.portName();QByteArray comname = std.toLatin1();//QMessageBox::information(this,tr("SerialFond"),tr((const char *)comname.data()),QMessageBox::Ok);serial.close();ui->open_port->setEnabled(true);}}if(fondcom==false){QMessageBox::information(this,tr("Error"),tr("Serial Not Fond!Plase cheak Hardware port!"),QMessageBox::Ok);}
}/* 打开并设置串口参数 */
void MainWindow::on_open_port_clicked()
{update();//find_port();     //重新查找com//初始化串口serialport->setPortName(ui->com->currentText());        //设置串口名if(serialport->open(QIODevice::ReadWrite))              //打开串口成功{serialport->setBaudRate(ui->baud->currentText().toInt());       //设置波特率switch(ui->bit->currentIndex())                   //设置数据位数{case 8:serialport->setDataBits(QSerialPort::Data8);break;default: break;}switch(ui->jiaoyan->currentIndex())                   //设置奇偶校验{case 0: serialport->setParity(QSerialPort::NoParity);break;default: break;}switch(ui->stopbit->currentIndex())                     //设置停止位{case 1: serialport->setStopBits(QSerialPort::OneStop);break;case 2: serialport->setStopBits(QSerialPort::TwoStop);break;default: break;}serialport->setFlowControl(QSerialPort::NoFlowControl);     //设置流控制// 设置控件可否使用ui->close_port->setEnabled(true);ui->open_port->setEnabled(false);ui->refresh_port->setEnabled(false);}else    //打开失败提示{// Sleep(100);QMessageBox::information(this,tr("Erro"),tr("Open the failure"),QMessageBox::Ok);}
}/* 关闭串口并禁用关联功能 */
void MainWindow::on_close_port_clicked()
{serialport->clear();        //清空缓存区serialport->close();        //关闭串口ui->open_port->setEnabled(true);ui->close_port->setEnabled(false);ui->refresh_port->setEnabled(true);
}/* 开始接收数据* */
void MainWindow::on_recive_data_clicked()
{QString str = "START_SEND_DATA\r\n";QByteArray str_utf8 = str.toUtf8();if(serialport->isOpen())serialport->write(str_utf8);else QMessageBox::information(this,tr("ERROE"),tr("串口未连接,请先检查串口连接"),QMessageBox::Ok);
}void MainWindow::serial_timerstart()
{timerserial->start(1);serial_bufferClash.append(serialport->readAll());
}//串口接收数据帧格式为:帧头'*' 帧尾'#' 数字间间隔符号',' 符号全为英文格式
void MainWindow::Read_Date()
{QString string;QStringList serialBuferList;int list_length = 0;//帧长QString str = ui->Receive_text_window->toPlainText();timerserial->stop();//停止定时器
//    qDebug()<< "[Serial LOG]serial read data:" <<serial_bufferClash;QByteArray bufferbegin = "*";   //帧头int index=0;QByteArray bufferend = "#";     //帧尾int indexend = 1;QByteArray buffercashe;index = serial_bufferClash.indexOf(bufferbegin,index);indexend = serial_bufferClash.indexOf(bufferend,indexend);
//    qDebug()<< index<< indexend;int bufferlens=0;if((index<serial_bufferClash.size())&&(indexend<serial_bufferClash.size())){bufferlens = indexend - index-1;buffercashe = serial_bufferClash.mid(index+1,bufferlens);qDebug()<< "[Serial LOG]serial chack data:" <<buffercashe;string.prepend(buffercashe);serialBuferList = string.split(" ");      //数据分割list_length=serialBuferList.count();    //帧长if (list_length>1){clash.data1 = serialBuferList[0].toDouble();clash.data2 = serialBuferList[1].toDouble();plot_buffer.push_back(clash);clash.data1 = serialBuferList[2].toDouble();clash.data2 = serialBuferList[3].toDouble();plot_buffer.push_back(clash);clash.data1 = serialBuferList[4].toDouble();clash.data2 = serialBuferList[5].toDouble();plot_buffer.push_back(clash);}}else{qDebug()<< "[Serial LOG][ERROR]recive data:" <<serial_bufferClash;}str+="succeed:"+buffercashe;str += "  ";ui->Receive_text_window->clear();ui->Receive_text_window->append(str);serial_bufferClash.clear();
}/*  刷新串口按键的按钮槽函数* */
void MainWindow::on_refresh_port_clicked()
{find_port();
}// 绘图图表初始化
void MainWindow::QPlot_init(QCustomPlot *customPlot)
{// 创建定时器,用于定时生成曲线坐标点数据QTimer *timer = new QTimer(this);timer->start(10);connect(timer, SIGNAL(timeout()), this, SLOT(Plot_TimeData_Update()));// 图表添加两条曲线pGraph1_1 = customPlot->addGraph();pGraph1_2 = customPlot->addGraph();// 设置曲线颜色pGraph1_1->setPen(QPen(Qt::red));pGraph1_2->setPen(QPen(Qt::black));// 设置坐标轴名称customPlot->xAxis->setLabel("X-Times");customPlot->yAxis->setLabel("Channel Data");// 设置y坐标轴显示范围customPlot->yAxis->setRange(-2, 2);// 显示图表的图例customPlot->legend->setVisible(true);// 添加曲线名称pGraph1_1->setName("Channel1");pGraph1_2->setName("Channel2");// 设置波形曲线的复选框字体颜色ui->checkBox_1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}"); // 设定前景颜色,就是字体颜色// 允许用户用鼠标拖动轴范围,用鼠标滚轮缩放,点击选择图形:customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}int data_lens = 0;
// 定时器溢出处理槽函数。用来生成曲线的坐标数据。
void MainWindow::Plot_TimeData_Update()
{int lens = plot_buffer.size();if (lens > data_lens){for(int i=data_lens;i<lens;i++){Plot_Show_Update(pPlot1, plot_buffer[i].data1, plot_buffer[i].data2);data_lens++;qDebug()<<"[Plot LOG]data_lens:"<<data_lens<< "size:"<<lens;}}
}// 曲线更新绘图
void MainWindow::Plot_Show_Update(QCustomPlot *customPlot, double n1, double n2)
{cnt++;// 给曲线添加数据pGraph1_1->addData(cnt, n1);pGraph1_2->addData(cnt, n2);// 设置x坐标轴显示范围,使其自适应缩放x轴customPlot->xAxis->setRange( 0, (pGraph1_1->dataCount() > 100) ? (pGraph1_1->dataCount()) : 100);// 更新绘图,这种方式在高填充下太浪费资源。rpQueuedReplot,可避免重复绘图。customPlot->replot(QCustomPlot::rpQueuedReplot);static QTime time(QTime::currentTime());double key = time.elapsed() / 1000.0; // 开始到现在的时间,单位秒//计算帧数static double lastFpsKey;static int frameCount;frameCount++;if (key - lastFpsKey > 1) // 每1秒求一次平均值{// 帧数和数据总数ui->statusbar->showMessage(QString("Refresh rate: %1 FPS, Total data volume: %2").arg(frameCount / (key - lastFpsKey), 0, 'f', 0).arg(customPlot->graph(0)->data()->size() + customPlot->graph(1)->data()->size()),0);lastFpsKey = key;frameCount = 0;}
}/* 清空缓存数据* */
void MainWindow::on_clean_data_clicked()
{qDebug()<< "[Clean Data]";plot_buffer.clear();data_lens = 0;cnt = 0;pGraph1_1->data().data()->clear();pGraph1_2->data().data()->clear();pPlot1->graph(0)->data().clear();pPlot1->graph(1)->data().clear();
}// setVisible设置可见性属性,隐藏曲线,不会对图例有任何影响。推荐使用。
void MainWindow::on_checkBox_1_stateChanged(int arg1)
{if (arg1){pGraph1_1->setVisible(true);}else{pGraph1_1->setVisible(false); // void QCPLayerable::setVisible(bool on)}pPlot1->replot();
}void MainWindow::on_checkBox_2_stateChanged(int arg1)
{if (arg1){pGraph1_2->setVisible(true);}else{pGraph1_2->setVisible(false); // void QCPLayerable::setVisible(bool on)}pPlot1->replot();
}// 保存缓冲区数据为csv文件
void MainWindow::on_savedata_csv_clicked()
{if(plot_buffer.size()<1){QMessageBox::information(this, "提示","当前数据为空");return;}serialport->clear();        //清空缓存区timerserial->stop();serialport->close();        //关闭串口ui->open_port->setEnabled(true);ui->close_port->setEnabled(false);QString csvFile = QFileDialog::getExistingDirectory(this);QDateTime current_date_time =QDateTime::currentDateTime();QString current_date =current_date_time.toString("yyyy_MM_dd_hh_mm");csvFile += tr("/sensor_Save_%1.csv").arg(current_date);if(csvFile.isEmpty()){QMessageBox::information(this,tr("警告"),tr("文件路径错误,无法打开文件,请重试"),QMessageBox::Ok);}else{qDebug()<< csvFile;QFile file(csvFile);if ( file.exists()){//如果文件存在执行的操作,此处为空,因为文件不可能存在}file.open( QIODevice::ReadWrite | QIODevice::Text );QTextStream out(&file);out<<tr("data1,")<<tr("data2,\n");     //写入表头// 创建 CSV 文件for (const auto &data : plot_buffer) {out << QString("%1,%2").arg(data.data1).arg(data.data2) << "\n";}file.close();QMessageBox::information(this, "提示","数据保存成功");}serialport->open(QIODevice::ReadWrite);        //打开串口ui->open_port->setEnabled(false);ui->close_port->setEnabled(true);
}

完整项目链接->完整的项目工程Github链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/30217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【王树森】深度强化学习(DRL)学习笔记

目录 第一部分&#xff1a;基础知识1.机器学习基础2.蒙特卡洛估计3.强化学习基础知识3.1 马尔科夫决策过程马尔可夫决策过程&#xff08;Markov decision process&#xff0c;MDP&#xff09;智能体环境状态状态空间动作动作空间奖励状态转移状态转移概率 3.2 策略策略定义 3.3…

基于Go开发的开源远程桌面分享工具ScreeGo编译使用

1. 克隆源码 : git clone --recursive https://github.com/screego/server.git 2. 使用GoLand打开工程 3.进入Server目录运行go mod downalod 下载依赖 4.创建配置文件screego.config.development.local内容如下&#xff1a; SCREEGO_EXTERNAL_IP你的公网IP 5. 编译并运行服务…

工业互联网的独特UI风格

工业互联网的独特UI风格

工业 web4.0,UI 风格令人赞叹

工业 web4.0&#xff0c;UI 风格令人赞叹

自学网络安全 or Web安全,一般人我还是劝你算了吧

由于我之前写了不少网络安全技术相关的文章&#xff0c;不少读者朋友知道我是从事网络安全相关的工作&#xff0c;于是经常有人私信问我&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f; 要学哪些东西&#xff1f; 有哪些方向&#xff1f; 怎么选&#xff1f;…

《OKR工作法》读书笔记

花了两个晚上的时间看完了《OKR工作法》这本书&#xff0c;谈不上有什么感想&#xff0c;因为工作后&#xff0c;其实就一直在用这种方法&#xff0c;所谓当局者迷嘛&#xff0c;习以为常也就谈不上多少新的启发。所以&#xff0c;这篇文章纯粹是一篇读书笔记&#xff0c;把我认…

【STM32】矩阵计算器

【STM32】矩阵计算器 资料链接请在文章末尾获取~ 1.说明 使用元器件&#xff1a;stm32f103c8t6最小系统板x1&#xff0c;0.96寸OLED显示屏四角x1&#xff0c;4x4矩阵按键x1; 参考&#xff1a;正点原子有关4脚OLED驱动float型数据的驱动文件&#xff0c;CSDN有关矩阵横向扫描…

数据模型——饮食记录

数据模型——饮食记录 本次实验完成饮食记录的数据模型&#xff0c;如下图所示 该饮食记录模型与上次的记录项数据模式定义处理方式相同&#xff0c;我们首先分析其数据结构&#xff0c;我们发现首先有早餐、午餐、晚餐等记录类型数据模型&#xff0c;其包括了id、类型名称、类…

AI人工智能产品经理,就该这么学!

前言 想入行AI人工智能产品经理&#xff0c;该如何学习呢&#xff1f; 随时AI的兴起&#xff0c;AI产品经理开始爆火&#xff0c;很多功能性产品经理想转行做AI产品经理。转行的原因&#xff0c;这个仁者见仁智者见智。唯一的共同点就是&#xff0c;大家都看好AI行业的发展前景…

Java 诊断神器 Arthas使用笔记

Arthas 是一款开源在线 Java 诊断工具&#xff0c;采用命令行交互模式&#xff0c;支持 web 端在线诊断&#xff0c;同时提供丰富的 Tab 自动补全功能&#xff0c;进一步方便进行问题的定位和诊断。得益于 Arthas 强大且丰富的功能。 1.JDK原生定位工具 平时开发中会用到JDK中…

edge如何找包已经安装的插件。

我的目录&#xff1a;C:\Users\Administrator\AppData\Local\Microsoft\Edge\User Data\Default\Extensions 如图&#xff1a; 如何我要打包如下图 注意iD的名字 多次操作选择到ID的目录&#xff0c;再向下。 如上图之后。打包。 显示成功 并提供地址。 找到相应地址&#x…

监控员工上网软件有哪些|4款好用的员工上网行为管理软件推荐

在当今数字化办公环境中&#xff0c;确保网络安全、提升工作效率、以及规范员工上网行为成为企业管理的重要组成部分。 为此&#xff0c;一套高效的员工上网行为管理软件显得尤为关键。 本文将为您推荐五款市场上广受好评的员工上网行为管理软件&#xff0c;帮助您有效监控与管…

linux离线安装chrony服务校准时间

基础环境 Linux forlinx 5.10.35 #53 SMP PREEMPT Thu Mar 30 01:04:19 CST 2023 aarch64 aarch64 aarch64 GNU/Linux chrony源码包 下载地址&#xff1a;https://download.tuxfamily.org/chrony/ 以chrony-4.5.tar.gz举例说明 详细步骤 1.解压chrony tar zxvf chrony-4.…

【Spine学习14】之 裁剪

1、新建裁剪 2、在页面中随便点几下 圈出对应位置 3、点编辑裁剪 或者按空格键 退出编辑模式&#xff0c; 页面就只剩下对应区域&#xff0c;这个区域可以任意拖动 放大缩小显示。 tips&#xff1a; 如果手动选择区域描绘不准确&#xff0c;可以启用对应图片的网格 然后复制…

无监督学习:从理论到实践的全面指南

本文深入讲解了无监督学习中的K-means、层次聚类、密度聚类、PCA、t-SNE和自编码器算法&#xff0c;涵盖其原理、数学基础、实现步骤及应用实例&#xff0c;并提供了详细的代码示例。 关注作者&#xff0c;复旦AI博士&#xff0c;分享AI领域全维度知识与研究。拥有10年AI领域研…

外链应该怎么做才有效?

做有效的外链&#xff0c;关键在于策略和执行&#xff0c;高质量的独立站外链就是一个不错的选择&#xff0c;确保是dofollow&#xff0c;每一条都被谷歌收录&#xff0c;并保证长期留存&#xff0c;至少一年以上&#xff0c;这种外链就是能发挥最大效果的外链&#xff0c;名为…

全新防关联技术出炉:亚马逊测评环境优化,下单成功率大提升

在竞争激烈的测评行业中&#xff0c;构建一个稳定且高效的环境系统成为了制胜的关键。然而&#xff0c;市场上现有的环境方案如虚拟机、模拟机、GCS、云手机、VPS等不仅成本高昂&#xff0c;而且面临着在风控严格的平台上如亚马逊难以逃脱检测的挑战&#xff0c;进而影响了测评…

Memory use report提示信息

Memory use report: Heap dump has been created at C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2021.3\tmp\hprof-temp\heapDump-idea-1718785956302.hprof. It will be analyzed next time you start IntelliJ IDEA.Memory use report提示信息 Memory use…

【两数之和】

两数之和 一、题目二、暴力解法三、哈希表四、map字典1.基本方法.set()添加键值对.get()通过键获取值.has()判断map是否有这个键 2.map和set的联系和区别共同点共同点MapSet 一、题目 二、暴力解法 三、哈希表 解题思路&#xff1a;将nums的元素依次以键值对的方式存储在map字典…

MacOS之解决:开盖启动问题(七十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…