Qt串口示波器开发实战:从数据解析到动态波形展示
1. Qt串口示波器开发概述在嵌入式开发中实时监控传感器数据是常见需求。传统示波器价格昂贵且不便携而基于Qt开发的串口示波器不仅能实现数据可视化还能保存历史数据供后续分析。我去年在开发智能硬件项目时就遇到过需要实时显示多个ADC通道数据的需求。当时尝试了几款现成工具都不理想最终决定用Qt QCustomPlot自己开发。Qt的跨平台特性让这个工具可以在Windows、Linux甚至嵌入式界面上运行。整个开发过程最关键的三个技术点是串口通信实现、数据解析算法和动态波形绘制。其中数据解析部分我踩过不少坑特别是处理分段接收的串口数据时需要设计合理的协议格式。下面我就结合实战代码详细讲解每个环节的实现方法。2. 开发环境搭建2.1 基础组件安装首先需要安装Qt开发环境推荐使用Qt 5.15 LTS版本。安装时务必勾选以下模块Qt Charts可选但QCustomPlot更强大Qt SerialPortMSVC或MinGW编译器Windows平台安装完成后创建一个新的Qt Widgets Application项目。在.pro文件中添加串口支持QT serialport widgets2.2 QCustomPlot集成QCustomPlot是Qt生态中最强大的2D绘图库之一性能远超Qt自带的QChart。集成方法很简单从官网下载qcustomplot.h和qcustomplot.cpp将这两个文件添加到项目目录在需要使用的类中包含头文件#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信号。我的解决方案是使用缓冲区累积数据设计简单协议格式如JSON实现数据完整性校验// 在类定义中添加成员变量 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-start1); processData(frame); // 处理完整数据 buffer buffer.mid(end1); 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 绘图性能提升当数据量很大时可以采取以下优化限制显示点数如只显示最近1000点使用setData而不是addData关闭抗锯齿// 性能优化设置 ui-customPlot-setNotAntialiasedElements(QCP::aeAll); ui-customPlot-graph(0)-setAdaptiveSampling(true);6.2 内存管理长时间运行可能导致内存增长需要定期清理旧数据// 每1000点清理一次 if(ui-customPlot-graph(0)-dataCount() 1000) { QVectordouble 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; QVectordouble 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 数据丢失问题如果发现数据不连续可以尝试降低波特率测试增加串口缓冲区大小检查硬件连接稳定性// 设置缓冲区大小 serial.setReadBufferSize(1024 * 1024); // 1MB8.2 界面卡顿处理界面卡顿通常是因为刷新频率过高数据量太大未使用队列重绘建议方案将刷新率控制在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 i0; ixData.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.coloradmin.cn/o/2441780.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!