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

news2024/9/28 22:41:42

需要工程源码请私信

基于 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);  // 设置定时器的间隔为10ms
    connect(&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-8

    int 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 管理配置项,支持自动生成和读取配置文件 。

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

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

相关文章

录屏小白福音!三款神器助你轻松上手

生活工作中&#xff0c;需要借助录屏功能越来越家常便饭了&#xff0c;选择录屏软件时&#xff0c;主要考虑的是软件的易用性、功能以及用户评价等因素。以下是如何进行录屏的步骤&#xff0c;以及推荐的四个录屏软件的使用说明&#xff1a;关于如何录屏的步骤操作&#xff0c;…

使用 PowerShell 命令更改 RDP 远程桌面端口(无需修改防火墙设置)

节选自原文&#xff1a;Windows远程桌面一站式指南 | BOBO Blog 原文目录 什么是RDP&#xfffc;开启远程桌面 检查系统版本启用远程桌面连接Windows 在Windows电脑上在MAC电脑上在Android或iOS移动设备上主机名连接 自定义电脑名通过主机名远程桌面使用Hosts文件自定义远程主…

(undone) MIT6.824 Lecture1 笔记

参考1MIT课程视频&#xff1a;https://www.bilibili.com/video/BV16f4y1z7kn/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 参考2某大佬笔记&#xff1a;https://ashiamd.github.io/docsify-notes/#/study/%E5%88%86%E5%B8%83%…

TDSQL-C电商可视化,重塑电商决策新纪元

前言&#xff1a; 在数字化浪潮席卷全球的今天&#xff0c;电子商务行业以其独特的魅力和无限潜力&#xff0c;成为了推动全球经济增长的重要引擎。然而&#xff0c;随着业务规模的急剧扩张&#xff0c;海量数据的涌现给电商企业带来了前所未有的挑战与机遇。如何高效地处理、…

如何从飞机、电报中提取数据

电报&#xff0c;通常简称TG&#xff0c;是一个跨平台的即时通讯软件。客户端是开源的&#xff0c;而服务器是专有的。用户可以交换加密的、自毁的信息&#xff08;类似于“阅读后烧伤”&#xff09;&#xff0c;并共享各种文件&#xff0c;包括照片和视频。它的安全性很高&…

软件设计之SSM(1)

软件设计之SSM(1) 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 尚硅谷新版SSM框架全套视频教程&#xff0c;Spring6SpringBoot3最新SSM企业级开发 资料可以去尚硅谷官网免费领取 学习内容&#xff1a; Spring框架结构SpringIoC容器SpringIoC实践…

SD2.0 Specification之功能切换

文章目录 简述命令参数含义状态数据结构及含义功能切换流程Mode0(查询功能)步骤Mode1(切换功能)步骤示例 本文章主要讲解关于SD2.0功能切换(CMD6)的内容&#xff0c;基础概念和其它内容请参考以下文章。 SD2.0 Specification简述 简述 SD卡将一些功能进行分组&#xff0c;归属…

Python爬虫之requests(二)

Python爬虫之requests&#xff08;二&#xff09; 前面演示了requests模块的四种请求方式。接下来再来演示下综合的练习。 一、requests模块综合练习 需求&#xff1a;爬取搜狗知乎某一个词条对应的某个范围页码表示的页面数据。 点开搜狗首页&#xff0c;有一个知乎的版块…

基于小波变换与稀疏表示优化的RIE数据深度学习预测模型

加入深度实战社区:www.zzgcz.com&#xff0c;免费学习所有深度学习实战项目。 1. 项目简介 本项目旨在通过深度学习模型进行RSOP&#xff08;Remote Sensing Observation Prediction&#xff09;的数据预测。RSOP数据是基于远程传感技术采集的多维信息&#xff0c;广泛应用于…

volatile关键字最全原理剖析

介绍 volatile是轻量级的同步机制&#xff0c;volatile可以用来解决可见性和有序性问题&#xff0c;但不保证原子性。 volatile的作用&#xff1a; 保证了不同线程对共享变量进行操作时的可见性&#xff0c;即一个线程修改了某个变量的值&#xff0c;这新值对其他线程来说是…

Android开发中的ViewModel

在Android应用开发中&#xff0c;ViewModel作为架构组件之一&#xff0c;扮演着管理UI数据和生命周期的关键角色。本文将深入探讨ViewModel如何感知View的生命周期&#xff0c;并分析其内核原理&#xff0c;帮助开发者更好地利用ViewModel优化应用架构。 一、ViewModel简介 在…

isilon存储node节点更换你必须知道的知识

最近一直想要写一篇文章是关于EMC Isilon 存储控制器方面的&#xff0c;是什么力量促使我要写这个文章呢&#xff1f;作为一个卖存储备件的资深搬运工&#xff0c;最近遇到了一些关于控制器方面的备件询价、备件更换方面的问题&#xff0c;每次都要花大量的时间给客户解释。解释…

分库分表常见算法,每个高级开发必知必会?

目录标题 分库分表常见算法哈希取模算法容量&#xff08;时间&#xff09;范围算法范围 取模算法 总结 分库分表是一种数据库设计技术&#xff0c;其目的是为了提高数据库的性能和扩展性。它通过将数据库的表拆分到多个数据库中来实现这一目的。 分库分表常见算法 分库分表分…

鸿蒙媒体开发系列12——音频输入设备管理(AudioRoutingManager)

如果你也对鸿蒙开发感兴趣&#xff0c;加入“Harmony自习室”吧&#xff01;扫描下方名片&#xff0c;关注公众号&#xff0c;公众号更新更快&#xff0c;同时也有更多学习资料和技术讨论群。 有时设备同时连接多个音频输入设备&#xff0c;需要指定音频输入设备进行音频录制&a…

HarmonyOs 学会查看官方文档实现菜单框

1. 学会查看官方文档 HarmonyOS跟上网上的视频学习一段时间后&#xff0c;基本也就入门了&#xff0c;但是有一些操作网上没有找到合适教学的视频&#xff0c;这时&#xff0c;大家就需要养成参考官方文档的习惯了&#xff0c;因为官方的开发文档是我们学习深度任何一门语言或…

OpenCV透视变换:原理、应用与实现

在图像处理与计算机视觉领域&#xff0c;透视变换&#xff08;Perspective Transformation&#xff09;是一种强大的工具&#xff0c;它模拟了人眼或相机镜头观看三维空间物体时的透视效果&#xff0c;从而改变图像的视角和形状。本文将详细介绍透视变换的原理、应用场景以及如…

Java_集合_单列集合Collection

第一章.Collection接口 Collection<E> 集合名 new 实现类对象<E>() 常用方法: boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功) boolean addAll(Collection<? extends E> c) :将另一个集合元素添…

SPI驱动学习七(SPI_Slave_Mode驱动程序框架)

目录 一、SPI_Slave_Mode驱动程序框架1. Master和Slave模式差别1.1 主设备 (Master)1.2 从设备 (Slave)1.3 示例 2. SPI传输概述2.1 数据组织方式2.2 SPI控制器数据结构 3. SPI Slave Mode数据传输过程4. 如何编写程序4.1 设备树4.2 内核相关4.3 简单的示例代码4.3.1 master和s…

测试用例的进阶二

1. 按开发阶段划分 1.1 测试金字塔 从上到下&#xff0c;对于测试人员代码就是要求越来越低&#xff1b; 从下到上&#xff0c;越来越靠近用户&#xff1b; 从下到上&#xff0c;定位问题的成本越来越高&#xff1b; 1.2 单元测试(Unit Testing) 单元测试是对软件组成单元进…

如何使用ssm实现北关村基本办公管理系统的设计与实现

TOC ssm721北关村基本办公管理系统的设计与实现jsp 第一章 绪论 1.1 选题背景 目前整个社会发展的速度&#xff0c;严重依赖于互联网&#xff0c;如果没有了互联网的存在&#xff0c;市场可能会一蹶不振&#xff0c;严重影响经济的发展水平&#xff0c;影响人们的生活质量。…