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

打印 上一主题 下一主题

主题 532|帖子 532|积分 1596

首先关于Qt的安装和基本配置这里就不做重复阐明了,注:本文在Qt5.14基础上完成 完备的项目开源仓库链接在文章末端
图形控件——qcustomplot

QCustomPlot是一个基于Qt框架的开源绘图库,用于创建高质量的二维图表和数据可视化。
QCustomPlot的重要功能:
绘制多种图表类型:包括折线图、散点图、柱状图、面积图
交互性:支持图表的缩放、平移、数据点选择等交互操纵
多轴支持:可以在图表中添加多个X轴和Y轴,以便绘制复杂的多轴图表
定制化:提供丰富的样式和属性设置,用户可以自界说图表的外貌,包括颜色、线条样式、标记
高性能:针对大数据量绘图举行了优化,可以或许处理处罚大量数据点而不影响性能
重要组件:
QCustomPlot:主绘图控件,所有的绘图操纵都在这个控件上举行。
QCPGraph:用于绘制常见的折线图和散点图。
QCPAxis:表现图表的轴,可以自界说轴的范围、标签、刻度等。
QCPItem:图表中的各种辅助元素,如直线、文本标签等。
QCPPlottable:可绘制对象的基类,所有具体的绘图类型都继承自这个类。
该控件使用方法
将qcustomplot的源码一个cpp一个h文件添加到项目工程中,并添加头文件和编译链接就可以便捷的在源码中使用了。注意在放置表现控件时须要先放置一个qt内置的QWidget控件,然后通过右键控件,升格,选中头文件和类名称,设置为QCustomPlot,然后源代码中就可以快乐使用了。具体设置的效果可以参考文末完备的项目链接。

绘制曲线数据

重要使用到了曲线控件customPlot->addGraph();
这个控件的使用方法团体上和Qt自带的控件差别不大,重要这个控件的视觉效果和长时间大数据绘制效果更好一些
在使用控件之前要先初始化相干的控件内容:
  1. // 绘图图表初始化
  2. void MainWindow::QPlot_init(QCustomPlot *customPlot)
  3. {
  4.     // 创建定时器,用于定时生成曲线坐标点数据
  5.     QTimer *timer = new QTimer(this);
  6.     timer->start(10);
  7.     connect(timer, SIGNAL(timeout()), this, SLOT(Plot_TimeData_Update()));
  8.     // 图表添加两条曲线
  9.     pGraph1_1 = customPlot->addGraph();
  10.     pGraph1_2 = customPlot->addGraph();
  11.     // 设置曲线颜色
  12.     pGraph1_1->setPen(QPen(Qt::red));
  13.     pGraph1_2->setPen(QPen(Qt::black));
  14.     // 设置坐标轴名称
  15.     customPlot->xAxis->setLabel("X-Times");
  16.     customPlot->yAxis->setLabel("Amplitude of channel");
  17.     // 设置y坐标轴显示范围
  18.     customPlot->yAxis->setRange(-2, 2);
  19.     // 显示图表的图例
  20.     customPlot->legend->setVisible(true);
  21.     // 添加曲线名称
  22.     pGraph1_1->setName("Channel1");
  23.     pGraph1_2->setName("Channel2");
  24.     // 设置波形曲线的复选框字体颜色
  25.     ui->checkBox_1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}"); // 设定前景颜色,就是字体颜色
  26.     // 允许用户用鼠标拖动轴范围,用鼠标滚轮缩放,点击选择图形:
  27.     customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
  28. }
复制代码
当向曲线添加新数据的时候可以同时控制范围窗口内的坐标轴尺寸,以及打印一些绘图相干的属性数据
  1. void MainWindow::Plot_Show_Update(QCustomPlot *customPlot, double n1, double n2)
  2. {
  3.     cnt++;
  4.     // 给曲线添加数据
  5.     pGraph1_1->addData(cnt, n1);
  6.     pGraph1_2->addData(cnt, n2);
  7.     // 设置x坐标轴显示范围,使其自适应缩放x轴
  8.     customPlot->xAxis->setRange( 0, (pGraph1_1->dataCount() > 1000) ? (pGraph1_1->dataCount()) : 1000);
  9.     // 更新绘图,这种方式在高填充下太浪费资源。rpQueuedReplot,可避免重复绘图。
  10.     customPlot->replot(QCustomPlot::rpQueuedReplot);
  11.     static QTime time(QTime::currentTime());
  12.     double key = time.elapsed() / 1000.0; // 开始到现在的时间,单位秒
  13.     //计算帧数
  14.     static double lastFpsKey;
  15.     static int frameCount;
  16.     frameCount++;
  17.     if (key - lastFpsKey > 1) // 每1秒求一次平均值
  18.     {
  19.         // 帧数和数据总数
  20.         ui->statusbar->showMessage(
  21.             QString("Refresh rate: %1 FPS, Total data volume: %2")
  22.                 .arg(frameCount / (key - lastFpsKey), 0, 'f', 0)
  23.                 .arg(customPlot->graph(0)->data()->size() + customPlot->graph(1)->data()->size()),
  24.             0);
  25.         lastFpsKey = key;
  26.         frameCount = 0;
  27.     }
  28. }
复制代码
曲线的绘制效果:

要实现上面的多通道效果还须要自行适配串口解析部门,然后匹配对应的数据后将数值传入到目的曲线对象。
完备的项目源码如下,包含数据解析、串口控制部门、同时实现数据保存到csv文件(代码中只保存了数组前两位的数值,须要保存多少数据可以自行修改代码实现)
项目Cpp文件:
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. MainWindow::MainWindow(QWidget *parent)
  4.     : QMainWindow(parent), ui(new Ui::MainWindow)
  5. {
  6.     ui->setupUi(this);
  7.     // 给widget绘图控件,设置个别名,方便书写
  8.     pPlot1 = ui->widget_1;
  9.     // 状态栏指针
  10.     sBar = statusBar();
  11.     // 初始化图表1
  12.     QPlot_init(pPlot1);
  13.     cnt = 0;
  14.     setWindowTitle("数据采集系统");
  15.     serialport = new QSerialPort;
  16.     find_port();                    //查找可用串口
  17.     timerserial = new QTimer();
  18.     QObject::connect(serialport,&QSerialPort::readyRead, this, &MainWindow::serial_timerstart);
  19.     QObject::connect(timerserial,SIGNAL(timeout()), this, SLOT(Read_Date()));
  20.     ui->close_port->setEnabled(false);//设置控件不可用
  21. }
  22. // 析构函数
  23. MainWindow::~MainWindow()
  24. {
  25.     delete ui;
  26. }
  27. //查找串口
  28. void MainWindow::find_port()
  29. {
  30.     //查找可用的串口
  31.     bool fondcom = false;
  32.     ui->com->clear();
  33.     foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
  34.     {
  35.         QSerialPort serial;
  36.         serial.setPort(info);   //设置串口
  37.         if(serial.open(QIODevice::ReadWrite))
  38.         {
  39.             //
  40.             ui->com->addItem(serial.portName());        //显示串口name
  41.             fondcom = true;
  42.             QString std = serial.portName();
  43.             QByteArray comname = std.toLatin1();
  44.             //QMessageBox::information(this,tr("SerialFond"),tr((const char *)comname.data()),QMessageBox::Ok);
  45.             serial.close();
  46.             ui->open_port->setEnabled(true);
  47.         }
  48.     }
  49.     if(fondcom==false)
  50.     {
  51.         QMessageBox::information(this,tr("Error"),tr("Serial Not Fond!Plase cheak Hardware port!"),QMessageBox::Ok);
  52.     }
  53. }
  54. /* 打开并设置串口参数 */
  55. void MainWindow::on_open_port_clicked()
  56. {
  57.     update();
  58.     //find_port();     //重新查找com
  59.      //初始化串口
  60.          serialport->setPortName(ui->com->currentText());        //设置串口名
  61.          if(serialport->open(QIODevice::ReadWrite))              //打开串口成功
  62.          {
  63.              serialport->setBaudRate(ui->baud->currentText().toInt());       //设置波特率
  64.              switch(ui->bit->currentIndex())                   //设置数据位数
  65.              {
  66.                  case 8:serialport->setDataBits(QSerialPort::Data8);break;
  67.                  default: break;
  68.              }
  69.              switch(ui->jiaoyan->currentIndex())                   //设置奇偶校验
  70.              {
  71.                  case 0: serialport->setParity(QSerialPort::NoParity);break;
  72.                  default: break;
  73.              }
  74.              switch(ui->stopbit->currentIndex())                     //设置停止位
  75.              {
  76.                  case 1: serialport->setStopBits(QSerialPort::OneStop);break;
  77.                  case 2: serialport->setStopBits(QSerialPort::TwoStop);break;
  78.                  default: break;
  79.              }
  80.              serialport->setFlowControl(QSerialPort::NoFlowControl);     //设置流控制
  81.              // 设置控件可否使用
  82.             ui->close_port->setEnabled(true);
  83.             ui->open_port->setEnabled(false);
  84.             ui->refresh_port->setEnabled(false);
  85.          }
  86.          else    //打开失败提示
  87.          {
  88.             // Sleep(100);
  89.              QMessageBox::information(this,tr("Erro"),tr("Open the failure"),QMessageBox::Ok);
  90.          }
  91. }
  92. /* 关闭串口并禁用关联功能 */
  93. void MainWindow::on_close_port_clicked()
  94. {
  95.     serialport->clear();        //清空缓存区
  96.     serialport->close();        //关闭串口
  97.     ui->open_port->setEnabled(true);
  98.     ui->close_port->setEnabled(false);
  99.     ui->refresh_port->setEnabled(true);
  100. }
  101. /* 开始接收数据
  102. * */
  103. void MainWindow::on_recive_data_clicked()
  104. {
  105.     QString str = "START_SEND_DATA\r\n";
  106.     QByteArray str_utf8 = str.toUtf8();
  107.     if(serialport->isOpen())serialport->write(str_utf8);
  108.     else QMessageBox::information(this,tr("ERROE"),tr("串口未连接,请先检查串口连接"),QMessageBox::Ok);
  109. }
  110. void MainWindow::serial_timerstart()
  111. {
  112.     timerserial->start(1);
  113.     serial_bufferClash.append(serialport->readAll());
  114. }
  115. //串口接收数据帧格式为:帧头'*' 帧尾'#' 数字间间隔符号',' 符号全为英文格式
  116. void MainWindow::Read_Date()
  117. {
  118.     QString string;
  119.     QStringList serialBuferList;
  120.     int list_length = 0;//帧长
  121.     QString str = ui->Receive_text_window->toPlainText();
  122.     timerserial->stop();//停止定时器
  123. //    qDebug()<< "[Serial LOG]serial read data:" <<serial_bufferClash;
  124.     QByteArray bufferbegin = "*";   //帧头
  125.     int index=0;
  126.     QByteArray bufferend = "#";     //帧尾
  127.     int indexend = 1;
  128.     QByteArray buffercashe;
  129.     index = serial_bufferClash.indexOf(bufferbegin,index);
  130.     indexend = serial_bufferClash.indexOf(bufferend,indexend);
  131. //    qDebug()<< index<< indexend;
  132.     int bufferlens=0;
  133.     if((index<serial_bufferClash.size())&&(indexend<serial_bufferClash.size()))
  134.     {
  135.         bufferlens = indexend - index-1;
  136.         buffercashe = serial_bufferClash.mid(index+1,bufferlens);
  137.         qDebug()<< "[Serial LOG]serial chack data:" <<buffercashe;
  138.         string.prepend(buffercashe);
  139.         serialBuferList = string.split(" ");      //数据分割
  140.         list_length=serialBuferList.count();    //帧长
  141.         if (list_length>1)
  142.         {
  143.             clash.data1 = serialBuferList[0].toDouble();
  144.             clash.data2 = serialBuferList[1].toDouble();
  145.             plot_buffer.push_back(clash);
  146.             clash.data1 = serialBuferList[2].toDouble();
  147.             clash.data2 = serialBuferList[3].toDouble();
  148.             plot_buffer.push_back(clash);
  149.             clash.data1 = serialBuferList[4].toDouble();
  150.             clash.data2 = serialBuferList[5].toDouble();
  151.             plot_buffer.push_back(clash);
  152.         }
  153.     }
  154.     else
  155.     {
  156.         qDebug()<< "[Serial LOG][ERROR]recive data:" <<serial_bufferClash;
  157.     }
  158.     str+="succeed:"+buffercashe;
  159.     str += "  ";
  160.     ui->Receive_text_window->clear();
  161.     ui->Receive_text_window->append(str);
  162.     serial_bufferClash.clear();
  163. }
  164. /*  刷新串口按键的按钮槽函数
  165. * */
  166. void MainWindow::on_refresh_port_clicked()
  167. {
  168.     find_port();
  169. }
  170. // 绘图图表初始化
  171. void MainWindow::QPlot_init(QCustomPlot *customPlot)
  172. {
  173.     // 创建定时器,用于定时生成曲线坐标点数据
  174.     QTimer *timer = new QTimer(this);
  175.     timer->start(10);
  176.     connect(timer, SIGNAL(timeout()), this, SLOT(Plot_TimeData_Update()));
  177.     // 图表添加两条曲线
  178.     pGraph1_1 = customPlot->addGraph();
  179.     pGraph1_2 = customPlot->addGraph();
  180.     // 设置曲线颜色
  181.     pGraph1_1->setPen(QPen(Qt::red));
  182.     pGraph1_2->setPen(QPen(Qt::black));
  183.     // 设置坐标轴名称
  184.     customPlot->xAxis->setLabel("X-Times");
  185.     customPlot->yAxis->setLabel("Channel Data");
  186.     // 设置y坐标轴显示范围
  187.     customPlot->yAxis->setRange(-2, 2);
  188.     // 显示图表的图例
  189.     customPlot->legend->setVisible(true);
  190.     // 添加曲线名称
  191.     pGraph1_1->setName("Channel1");
  192.     pGraph1_2->setName("Channel2");
  193.     // 设置波形曲线的复选框字体颜色
  194.     ui->checkBox_1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}"); // 设定前景颜色,就是字体颜色
  195.     // 允许用户用鼠标拖动轴范围,用鼠标滚轮缩放,点击选择图形:
  196.     customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
  197. }
  198. int data_lens = 0;
  199. // 定时器溢出处理槽函数。用来生成曲线的坐标数据。
  200. void MainWindow::Plot_TimeData_Update()
  201. {
  202.     int lens = plot_buffer.size();
  203.     if (lens > data_lens)
  204.     {
  205.         for(int i=data_lens;i<lens;i++)
  206.         {
  207.             Plot_Show_Update(pPlot1, plot_buffer[i].data1, plot_buffer[i].data2);
  208.             data_lens++;
  209.             qDebug()<<"[Plot LOG]data_lens:"<<data_lens<< "size:"<<lens;
  210.         }
  211.     }
  212. }
  213. // 曲线更新绘图
  214. void MainWindow::Plot_Show_Update(QCustomPlot *customPlot, double n1, double n2)
  215. {
  216.     cnt++;
  217.     // 给曲线添加数据
  218.     pGraph1_1->addData(cnt, n1);
  219.     pGraph1_2->addData(cnt, n2);
  220.     // 设置x坐标轴显示范围,使其自适应缩放x轴
  221.     customPlot->xAxis->setRange( 0, (pGraph1_1->dataCount() > 100) ? (pGraph1_1->dataCount()) : 100);
  222.     // 更新绘图,这种方式在高填充下太浪费资源。rpQueuedReplot,可避免重复绘图。
  223.     customPlot->replot(QCustomPlot::rpQueuedReplot);
  224.     static QTime time(QTime::currentTime());
  225.     double key = time.elapsed() / 1000.0; // 开始到现在的时间,单位秒
  226.     //计算帧数
  227.     static double lastFpsKey;
  228.     static int frameCount;
  229.     frameCount++;
  230.     if (key - lastFpsKey > 1) // 每1秒求一次平均值
  231.     {
  232.         // 帧数和数据总数
  233.         ui->statusbar->showMessage(
  234.             QString("Refresh rate: %1 FPS, Total data volume: %2")
  235.                 .arg(frameCount / (key - lastFpsKey), 0, 'f', 0)
  236.                 .arg(customPlot->graph(0)->data()->size() + customPlot->graph(1)->data()->size()),
  237.             0);
  238.         lastFpsKey = key;
  239.         frameCount = 0;
  240.     }
  241. }
  242. /* 清空缓存数据
  243. * */
  244. void MainWindow::on_clean_data_clicked()
  245. {
  246.     qDebug()<< "[Clean Data]";
  247.     plot_buffer.clear();
  248.     data_lens = 0;
  249.     cnt = 0;
  250.     pGraph1_1->data().data()->clear();
  251.     pGraph1_2->data().data()->clear();
  252.     pPlot1->graph(0)->data().clear();
  253.     pPlot1->graph(1)->data().clear();
  254. }
  255. // setVisible设置可见性属性,隐藏曲线,不会对图例有任何影响。推荐使用。
  256. void MainWindow::on_checkBox_1_stateChanged(int arg1)
  257. {
  258.     if (arg1)
  259.     {
  260.         pGraph1_1->setVisible(true);
  261.     }
  262.     else
  263.     {
  264.         pGraph1_1->setVisible(false); // void QCPLayerable::setVisible(bool on)
  265.     }
  266.     pPlot1->replot();
  267. }
  268. void MainWindow::on_checkBox_2_stateChanged(int arg1)
  269. {
  270.     if (arg1)
  271.     {
  272.         pGraph1_2->setVisible(true);
  273.     }
  274.     else
  275.     {
  276.         pGraph1_2->setVisible(false); // void QCPLayerable::setVisible(bool on)
  277.     }
  278.     pPlot1->replot();
  279. }
  280. // 保存缓冲区数据为csv文件
  281. void MainWindow::on_savedata_csv_clicked()
  282. {
  283.      if(plot_buffer.size()<1)
  284.      {
  285.          QMessageBox::information(this, "提示","当前数据为空");
  286.          return;
  287.      }
  288.      serialport->clear();        //清空缓存区
  289.      timerserial->stop();
  290.      serialport->close();        //关闭串口
  291.      ui->open_port->setEnabled(true);
  292.      ui->close_port->setEnabled(false);
  293.      QString csvFile = QFileDialog::getExistingDirectory(this);
  294.      QDateTime current_date_time =QDateTime::currentDateTime();
  295.      QString current_date =current_date_time.toString("yyyy_MM_dd_hh_mm");
  296.      csvFile += tr("/sensor_Save_%1.csv").arg(current_date);
  297.      if(csvFile.isEmpty())
  298.      {
  299.         QMessageBox::information(this,tr("警告"),tr("文件路径错误,无法打开文件,请重试"),QMessageBox::Ok);
  300.      }
  301.      else
  302.      {
  303.          qDebug()<< csvFile;
  304.          QFile file(csvFile);
  305.          if ( file.exists())
  306.          {
  307.                  //如果文件存在执行的操作,此处为空,因为文件不可能存在
  308.          }
  309.          file.open( QIODevice::ReadWrite | QIODevice::Text );
  310.          QTextStream out(&file);
  311.          out<<tr("data1,")<<tr("data2,\n");     //写入表头
  312.          // 创建 CSV 文件
  313.          for (const auto &data : plot_buffer) {
  314.              out << QString("%1,%2").arg(data.data1).arg(data.data2) << "\n";
  315.          }
  316.          file.close();
  317.          QMessageBox::information(this, "提示","数据保存成功");
  318.      }
  319.      serialport->open(QIODevice::ReadWrite);        //打开串口
  320.      ui->open_port->setEnabled(false);
  321.      ui->close_port->setEnabled(true);
  322. }
复制代码
完备项目链接->完备的项目工程Github链接

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表