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

基于Qt的多功能串口通信工具分享:实时数据收发与波形绘制

需要工程源码请私信

基于 Qt 框架开发的多功能串口通信工具,旨在为用户提供稳定、流畅的串口数据收发体验。该工具不仅支持基本的串口通信功能,还集成了定时发送、多线程数据处理、粘包问题解决、实时波形绘制等多种高级功能。通过使用 QSerialPort 进行串口操作,并结合 QSettings 进行配置文件管理,用户可以灵活地配置通信参数,实现对外部设备的数据交互和监控。此外,软件通过使用多线程技术确保串口通信的平稳性,避免因大量数据传输导致界面卡顿。其粘包拆解机制和波形绘制功能,帮助用户更直观地观察通信数据的变化,为硬件调试和通信测试提供了强有力的支持。
在这里插入图片描述

1. 多线程串口通信模块

多线程的串口通信可以避免主线程卡顿,这里我们在 SerialWorker::run() 函数中实现串口的接收和发送操作。

void SerialWorker::run() {serialPort = new QSerialPort();// 设置串口参数serialPort->setPortName(portName);serialPort->setBaudRate(baudRate);serialPort->setDataBits(dataBits);serialPort->setStopBits(stopBits);serialPort->setParity(parity);if (!serialPort->open(QIODevice::ReadWrite)) {emit errors(0, serialPort->errorString());emit connected(0, false);delete serialPort;serialPort = nullptr;return;}emit connected(0, true);  // 连接成功,发送信号emit informations(0, tc("串口已打开"));// 事件循环,保证串口的读写操作不会阻塞主线程QEventLoop eventLoop;QTimer timer;timer.setInterval(10);  // 设置定时器的间隔为10msconnect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);timer.start();while (running) {QByteArray data;{// 从队列中取出数据并发送QMutexLocker locker(&mutex);if (!sendQueue.isEmpty()) {data = sendQueue.dequeue();}}if (!data.isEmpty()) {serialPort->write(data);  // 向串口写入数据if (!serialPort->waitForBytesWritten()) {emit errors(0, serialPort->errorString());} else {emit informations(0, tc("数据已发送: %1").arg(QString::fromUtf8(data)));}}// 读取串口数据if (serialPort->waitForReadyRead(10)) {QByteArray receivedData = serialPort->readAll();emit dataReceived(receivedData);  // 发射信号,传递接收到的数据// 粘包数据处理if (receivedData.contains("#") && receivedData.contains("$")) {m_buf += receivedData;  // 将数据加入缓存for (auto val : parseData(m_buf)) {emit dataLine(val);  // 发送提取出来的数值型数据}}}// 处理事件循环,避免阻塞信号槽机制eventLoop.exec();}serialPort->close();emit informations(0, tc("串口已关闭"));emit connected(0, false);delete serialPort;serialPort = nullptr;
}
关键点:
  • 事件循环:通过 QEventLoop 实现了一个持续运行的事件循环,让串口的读写操作可以实时进行。
  • 发送队列:通过 QMutexLocker 锁定互斥体,保证在多线程环境下操作安全。
  • 信号槽:使用信号 emit 向外部通知连接状态、接收到的数据和错误信息。

2. 粘包拆解模块

粘包问题在串口通信中很常见。我们通过正则表达式匹配接收到的数据,将粘包数据拆解出来。

QList<float> SerialWorker::parseData(QByteArray &data) {QList<float> buf;  // 存储提取出的浮点数数据QRegularExpression regex("#(-?\\d*\\.?\\d+?)\\$");  // 正则表达式,匹配 #number$ 格式QRegularExpressionMatchIterator it = regex.globalMatch(data);int lastMatchEnd = 0;while (it.hasNext()) {QRegularExpressionMatch match = it.next();QString numberStr = match.captured(1);  // 提取匹配的数值字符串bool ok;float number = numberStr.toFloat(&ok);  // 将字符串转换为浮点数if (ok) {buf.append(number);  // 将解析出的数字添加到列表中}lastMatchEnd = match.capturedEnd(0);  // 记录最后一个匹配的结束位置}// 移除已处理的部分,保留未处理的部分以便后续处理if (lastMatchEnd > 0) {data.remove(0, lastMatchEnd);}return buf;  // 返回提取出的浮点数列表
}

在这里插入图片描述

关键点:
  • 正则表达式:通过正则表达式 #(-?\\d*\\.?\\d+?)\\$ 匹配粘包中的数值数据,#$ 是包裹数值的标志,支持匹配正负浮点数。
  • 移除已处理数据:每次处理后,将已解析的数据从缓存中移除,未处理的数据保留在缓存中等待下次处理。

3. 波形绘制模块

接收到的数值数据会显示在一个波形图中,以下是 WaveformWidget 的实现。

void WaveformWidget::addDataPoint(float value) {if (dataPoints.size() >= maxDataPoints) {dataPoints.pop_front();  // 如果数据点过多,则移除最旧的数据点}dataPoints.push_back(value);  // 添加新的数据点totalPointsReceived++;  // 统计接收到的总点数calculateStatistics();  // 计算最大值、最小值和平均值update();  // 触发界面重绘
}void WaveformWidget::paintEvent(QPaintEvent *) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);  // 开启抗锯齿,使波形更平滑drawAxes(painter);  // 绘制坐标轴drawGrid(painter);  // 绘制背景网格drawData(painter);  // 绘制波形数据drawStatistics(painter);  // 显示最大、最小和平均值
}
关键点:
  • 实时绘制:当新的数据点被添加时,调用 update() 触发重绘,显示最新的波形。
  • 平滑显示:通过 QPainter::setRenderHint(QPainter::Antialiasing) 开启抗锯齿,使波形显示更加平滑。

4. 配置文件管理模块

软件通过 config.ini 文件存储配置项,QSettings 用于管理配置的读写。

void MainWindow::createConfigFile(const QString &fileName, const QStringList &values) {QFile file(fileName);if (file.exists()) return;  // 如果文件已经存在,则跳过QSettings settings(fileName, QSettings::IniFormat);settings.setIniCodec(QTextCodec::codecForName("UTF-8"));  // 设置编码为UTF-8int groupIndex = 1;for (const QString &value : values) {QString groupName = QString("%1").arg(groupIndex);settings.beginGroup(groupName);  // 创建新的组settings.setValue(QString("%1").arg(groupIndex), value);  // 写入键值对settings.endGroup();groupIndex++;}
}QStringList MainWindow::readConfigFile(const QString &fileName) {QStringList iniinfors;QSettings settings(fileName, QSettings::IniFormat);settings.setIniCodec(QTextCodec::codecForName("UTF-8"));QStringList groups = settings.childGroups();  // 读取所有组groups.sort();for (const QString &group : groups) {settings.beginGroup(group);QStringList keys = settings.childKeys();  // 读取所有键for (const QString &key : keys) {iniinfors << settings.value(key).toString();  // 获取键值}settings.endGroup();}return iniinfors;
}
关键点:
  • 配置文件自动生成:如果配置文件不存在,会自动生成一个 config.ini 文件,并写入初始值。
  • 配置文件读取:软件启动时,通过 readConfigFile() 函数读取配置项,并将其加载到界面上。

总结

通过上述代码与注释,软件实现了以下核心功能:

  • 多线程串口通信:避免主线程阻塞,通过事件循环确保数据收发的实时性。
  • 粘包数据拆解:使用正则表达式解析粘包数据,确保接收的数据是正确的。
  • 实时波形绘制:接收到的数据点会动态绘制在波形图中,提供可视化的反馈。
  • 配置文件管理:通过 QSettings 管理配置项,支持自动生成和读取配置文件 。

相关文章:

  • 网络协议一般分为几类?如何划分
  • 从基础到进阶:Docker 实践与应用的全方位解析
  • 从零开始搭建UVM平台(二)-加入factory机制
  • Junit 5 - 理解Mockito,提高UT 覆盖率
  • element plus 重写drawer\dialog样式不生效
  • 【Python快速学习笔记01】下载解释器/环境变量配置/PyCharm下载/第一个代码
  • 笔记整理—linux进程部分(1)进程终止函数注册、进程环境、进程虚拟地址
  • 如何在iPad上用Chrome实现无痕浏览
  • 亚信安慧AntDB基于操作符的隐式转换
  • 【k8s】:DevOps 模式详解
  • ASCII Unicode UTF-8 字符集 字符编码
  • 使用腾讯地图,在地图上圈选标记半径范围
  • 预防工作场所的违规政策
  • Vue 技术入门 day1 模版语法、数据绑定、事件处理、计算属性与监视、class和style绑定、条件渲染v-if/v-show、列表渲染v-for
  • 回归预测合集|基于灰狼优化21个机器学习和深度学习的数据回归预测Matlab程序 多特征输入单输出
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • Angular Elements 及其运作原理
  • create-react-app做的留言板
  • gitlab-ci配置详解(一)
  • HTML中设置input等文本框为不可操作
  • JS数组方法汇总
  • k8s如何管理Pod
  • linux安装openssl、swoole等扩展的具体步骤
  • mysql 5.6 原生Online DDL解析
  • opencv python Meanshift 和 Camshift
  • pdf文件如何在线转换为jpg图片
  • spring boot下thymeleaf全局静态变量配置
  • Vue2 SSR 的优化之旅
  • Vue学习第二天
  • Yeoman_Bower_Grunt
  • 前端工程化(Gulp、Webpack)-webpack
  • 思否第一天
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • 阿里云服务器如何修改远程端口?
  • 湖北分布式智能数据采集方法有哪些?
  • #AngularJS#$sce.trustAsResourceUrl
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • (1)(1.13) SiK无线电高级配置(五)
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (Windows环境)FFMPEG编译,包含编译x264以及x265
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (接上一篇)前端弄一个变量实现点击次数在前端页面实时更新
  • (每日一问)基础知识:堆与栈的区别
  • (十八)三元表达式和列表解析
  • (贪心 + 双指针) LeetCode 455. 分发饼干
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)es进行聚合操作时提示Fielddata is disabled on text fields by default
  • .NET Core 版本不支持的问题
  • .net 提取注释生成API文档 帮助文档