当前位置: 首页 > news >正文

Qt串口示波器开发实战:从数据解析到动态波形展示

1. Qt串口示波器开发概述

在嵌入式开发中,实时监控传感器数据是常见需求。传统示波器价格昂贵且不便携,而基于Qt开发的串口示波器不仅能实现数据可视化,还能保存历史数据供后续分析。我去年在开发智能硬件项目时,就遇到过需要实时显示多个ADC通道数据的需求。当时尝试了几款现成工具都不理想,最终决定用Qt + QCustomPlot自己开发。

Qt的跨平台特性让这个工具可以在Windows、Linux甚至嵌入式界面上运行。整个开发过程最关键的三个技术点是:串口通信实现、数据解析算法和动态波形绘制。其中数据解析部分我踩过不少坑,特别是处理分段接收的串口数据时,需要设计合理的协议格式。下面我就结合实战代码,详细讲解每个环节的实现方法。

2. 开发环境搭建

2.1 基础组件安装

首先需要安装Qt开发环境,推荐使用Qt 5.15 LTS版本。安装时务必勾选以下模块:

  • Qt Charts(可选,但QCustomPlot更强大)
  • Qt SerialPort
  • MSVC或MinGW编译器(Windows平台)

安装完成后,创建一个新的Qt Widgets Application项目。在.pro文件中添加串口支持:

QT += serialport widgets

2.2 QCustomPlot集成

QCustomPlot是Qt生态中最强大的2D绘图库之一,性能远超Qt自带的QChart。集成方法很简单:

  1. 从官网下载qcustomplot.h和qcustomplot.cpp
  2. 将这两个文件添加到项目目录
  3. 在需要使用的类中包含头文件:
#include "qcustomplot.h"

在Qt Designer中拖入一个Widget,右键提升为QCustomPlot类。提升时需要注意:

  • 头文件填写"qcustomplot.h"
  • 类名填写"QCustomPlot"
  • 全局包含勾选

3. 串口通信实现

3.1 串口配置与打开

Qt提供了QSerialPort类处理串口通信。基本配置参数包括:

  • 波特率(常用115200)
  • 数据位(通常8位)
  • 停止位(1位)
  • 校验位(无校验)
QSerialPort serial; serial.setPortName("COM3"); serial.setBaudRate(QSerialPort::Baud115200); serial.setDataBits(QSerialPort::Data8); serial.setParity(QSerialPort::NoParity); serial.setStopBits(QSerialPort::OneStop); if(serial.open(QIODevice::ReadWrite)) { qDebug() << "串口打开成功"; } else { qDebug() << "错误:" << serial.errorString(); }

3.2 数据接收处理

串口数据接收最大的坑是数据分段问题。当数据量较大时,Qt会分多次发送readyRead信号。我的解决方案是:

  1. 使用缓冲区累积数据
  2. 设计简单协议格式(如JSON)
  3. 实现数据完整性校验
// 在类定义中添加成员变量 QByteArray buffer; // 数据接收槽函数 void MainWindow::handleReadyRead() { buffer += serial.readAll(); // 查找完整数据帧 int start = buffer.indexOf('{'); int end = buffer.indexOf('}'); while(start != -1 && end != -1 && end > start) { QByteArray frame = buffer.mid(start, end-start+1); processData(frame); // 处理完整数据 buffer = buffer.mid(end+1); start = buffer.indexOf('{'); end = buffer.indexOf('}'); } }

4. 动态波形绘制

4.1 QCustomPlot基础配置

动态波形需要实时更新数据并重绘。先进行基础配置:

// 设置背景色和交互功能 ui->customPlot->setBackground(QBrush(QColor(48, 47, 47))); ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); // 添加两条曲线 ui->customPlot->addGraph(); ui->customPlot->graph(0)->setPen(QPen(Qt::green)); ui->customPlot->addGraph(); ui->customPlot->graph(1)->setPen(QPen(Qt::red)); // 设置坐标轴范围 ui->customPlot->xAxis->setRange(0, 500); ui->customPlot->yAxis->setRange(0, 3.3);

4.2 实时更新机制

动态绘制的核心是定时更新数据范围。我采用双缓冲机制避免界面卡顿:

// 定时器更新波形 QTimer *dataTimer = new QTimer(this); connect(dataTimer, &QTimer::timeout, [=](){ static double lastPointKey = 0; // 添加新数据 ui->customPlot->graph(0)->addData(lastPointKey, newValue1); ui->customPlot->graph(1)->addData(lastPointKey, newValue2); // 自动滚动 if(lastPointKey > 500) { ui->customPlot->xAxis->setRange(lastPointKey-500, lastPointKey); } ui->customPlot->replot(QCustomPlot::rpQueuedReplot); lastPointKey += 0.1; }); dataTimer->start(50); // 20Hz刷新率

5. 数据解析优化

5.1 JSON协议设计

我选择JSON格式因为:

  • 可读性好
  • 支持嵌套数据结构
  • 有成熟的解析库

数据格式示例:

{ "timestamp": 123456789, "channels": { "ADC1": 1.234, "ADC2": 2.345 } }

5.2 解析代码实现

Qt内置的QJsonDocument处理JSON非常方便:

void MainWindow::processData(const QByteArray &jsonData) { QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); if(error.error != QJsonParseError::NoError) { qDebug() << "JSON解析错误:" << error.errorString(); return; } QJsonObject root = doc.object(); QJsonObject channels = root["channels"].toObject(); double adc1 = channels["ADC1"].toDouble(); double adc2 = channels["ADC2"].toDouble(); // 更新波形数据... }

6. 性能优化技巧

6.1 绘图性能提升

当数据量很大时,可以采取以下优化:

  1. 限制显示点数(如只显示最近1000点)
  2. 使用setData而不是addData
  3. 关闭抗锯齿
// 性能优化设置 ui->customPlot->setNotAntialiasedElements(QCP::aeAll); ui->customPlot->graph(0)->setAdaptiveSampling(true);

6.2 内存管理

长时间运行可能导致内存增长,需要定期清理旧数据:

// 每1000点清理一次 if(ui->customPlot->graph(0)->dataCount() > 1000) { QVector<double> keys = ui->customPlot->graph(0)->data()->keys(); ui->customPlot->graph(0)->data()->removeBefore(keys.at(900)); }

7. 完整实现示例

下面给出主窗口类的关键代码框架:

class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void handleReadyRead(); void updatePlot(); private: Ui::MainWindow *ui; QSerialPort serial; QTimer *plotTimer; QVector<double> xData, yData1, yData2; double currentValue1 = 0; double currentValue2 = 0; void setupSerial(); void setupPlot(); }; // 初始化函数 void MainWindow::setupSerial() { serial.setPortName("COM3"); // ...其他串口配置 connect(&serial, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead); } void MainWindow::setupPlot() { // 初始化曲线和坐标轴 // ...同前文配置 plotTimer = new QTimer(this); connect(plotTimer, &QTimer::timeout, this, &MainWindow::updatePlot); plotTimer->start(50); }

8. 常见问题解决

8.1 数据丢失问题

如果发现数据不连续,可以尝试:

  1. 降低波特率测试
  2. 增加串口缓冲区大小
  3. 检查硬件连接稳定性
// 设置缓冲区大小 serial.setReadBufferSize(1024 * 1024); // 1MB

8.2 界面卡顿处理

界面卡顿通常是因为:

  1. 刷新频率过高
  2. 数据量太大
  3. 未使用队列重绘

建议方案:

  • 将刷新率控制在20-50Hz
  • 使用QCustomPlot的rpQueuedReplot模式
  • 在独立线程中处理数据解析

9. 扩展功能实现

9.1 数据保存功能

添加CSV格式保存功能:

void MainWindow::saveToCsv() { QString fileName = QFileDialog::getSaveFileName( this, "保存数据", "", "CSV文件 (*.csv)"); QFile file(fileName); if(file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << "时间,通道1,通道2\n"; for(int i=0; i<xData.size(); ++i) { stream << xData[i] << "," << yData1[i] << "," << yData2[i] << "\n"; } } }

9.2 多语言支持

使用Qt的翻译系统实现多语言:

// 在代码中使用tr() setWindowTitle(tr("串口示波器")); // 创建翻译文件 QTranslator translator; translator.load(":/lang/zh_CN.qm"); qApp->installTranslator(&translator);

10. 嵌入式端实现

在STM32上使用cJSON库生成数据:

#include "cJSON.h" void send_sensor_data() { cJSON *root = cJSON_CreateObject(); cJSON_AddNumberToObject(root, "temp", read_temp()); cJSON_AddNumberToObject(root, "hum", read_humidity()); char *json_str = cJSON_PrintUnformatted(root); HAL_UART_Transmit(&huart1, (uint8_t*)json_str, strlen(json_str), 100); cJSON_Delete(root); free(json_str); }

开发过程中发现,当JSON数据超过50字节时,建议分帧发送。我在STM32端实现了简单的数据分帧算法,确保接收端能正确重组数据包。实际测试表明,这种方案在115200波特率下能稳定传输10Hz的传感器数据。

http://www.jsqmd.com/news/524660/

相关文章:

  • OpenWebUI与Dify无缝集成实战:5分钟搞定ChatFlow应用部署
  • 408考研党必看:计算机组成原理存储系统大题TLB实战解析(附真题答案)
  • Unity微信小游戏CDN部署实战:从打包到加速的完整链路
  • 2026年01优质线缆缠绕机厂家推荐:180度翻转机、90度翻转机、O 型翻转机、V 型翻转机、卧式缠绕机、卷材缠绕机选择指南 - 优质品牌商家
  • 我的世界花园客服咨询AI流量赋能,重塑智能体验新标杆 - 王老吉弄
  • 2026指纹浏览器在网络数据采集场景中的合规应用与技术实践
  • 2268816-76-6,Sulfo-DBCO-TFPester,一种水溶性的异双功能生物正交交联试剂
  • 保姆级教程:如何在Ubuntu 20.04上为RK3588搭建完整的编译环境
  • 2026年自媒体去AI味工具推荐:这3款写出来真不像AI写的 - 还在做实验的师兄
  • 计算机毕业设计:基于Flask与Echarts的动漫数据可视化分析平台 Flask框架 可视化 爬虫 大数据 机器学习 番剧推荐(建议收藏)✅
  • 读书-让我心甘情愿早睡的方法
  • 2026年论文AI率100%怎么降到合格线?3步拆解完整路径 - 还在做实验的师兄
  • ArcGIS新手必看:地块面积统计失败的5个常见原因及解决方法(附Global Mapper对比)
  • 保姆级教程:用YOLOv5s训练一个能区分‘人车一体’的电动车检测模型(附5000+监控数据集)
  • 图像处理入门:别再死记硬背了,用Moore边界跟踪算法理解‘邻域’与‘搜索顺序’的本质
  • 从原理到实践:基于AD603的AGC电路设计与性能调优
  • 解决.NetCore2.2升级3.1时的HTTP 500.37错误:ANCM启动超时全攻略
  • 从命令行到点鼠标:iStore增强插件如何让OpenWrt小白也能玩转Docker和内网穿透
  • MNase-seq实验避坑指南:从样本制备到数据分析的完整流程
  • WPF自定义树形表格控件:从零构建TreeListView
  • FPGA实战:如何用Verilog优雅实现边沿检测(附Modelsim仿真避坑指南)
  • 手把手教你用STM32 HAL库实现超低功耗设计:从寄存器配置到唤醒策略
  • 告别GUI!用Vitis HLS命令行+TCL脚本实现自动化综合的保姆级教程
  • 从医疗成像到工业检测:CMOS图像传感器NIR技术的最新应用案例解析
  • openclaw平替之nanobot源码解析(八):Gateway进阶——定时任务与心跳机制
  • Ubuntu 22.04 下 Fcitx5 输入法配置全攻略:从安装到美化(附常见问题解决)
  • 第13章 Agent Teams —— 组建你的 AI 团队
  • AI头像生成器场景解析:从角色设计到AI绘图的全链路方案
  • Apple服务扣费客服咨询AI流量赋能,重塑智能体验新标杆 - 王老吉弄
  • 20253919 2025-2026-2 《网络攻防实践》第2次作业