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

保姆级教程:在Qt6中用子线程处理多个QSerialPort,实现多设备同时通信

工业级多线程串口通信框架设计Qt6高效管理多设备通信实战在工业自动化、物联网网关等场景中经常需要同时与多个串口设备如传感器、PLC、模块等进行稳定通信。传统单线程串口处理方式在面对多设备时往往力不从心容易导致界面卡顿、数据丢失等问题。本文将深入探讨如何在Qt6中构建一个可扩展的多线程串口管理框架实现高效、稳定的多设备并行通信。1. 多线程串口通信架构设计1.1 核心架构原理在Qt中实现多串口通信的关键在于线程隔离原则每个QSerialPort实例都应该运行在独立的线程中避免阻塞主线程GUI线程。这种架构有三大优势响应性GUI线程保持流畅不受串口通信延迟影响稳定性单个串口故障不会影响其他设备通信扩展性可动态增减串口设备而不重构核心架构典型的线程模型如下主线程(GUI) ├── 串口管理线程1 │ └── QSerialPort实例1 ├── 串口管理线程2 │ └── QSerialPort实例2 └── ...1.2 关键技术组件实现该架构需要以下Qt核心类协同工作类名职责关键特性QSerialPort串口通信提供设备级API非线程安全QThread线程容器管理线程生命周期不包含业务逻辑QObject工作对象包含实际业务逻辑通过信号槽与外界通信QThreadPool线程池可选用于优化线程资源管理注意QSerialPort实例必须与工作线程有相同的生命周期且不能跨线程直接调用其方法。2. 实现多线程串口管理器2.1 定义串口工作类首先创建处理实际串口操作的Worker类// serialworker.h #pragma once #include QObject #include QSerialPort #include QSerialPortInfo class SerialWorker : public QObject { Q_OBJECT public: explicit SerialWorker(const QString portName, QSerialPort::BaudRate baudRate, QObject *parent nullptr); ~SerialWorker(); signals: void dataReceived(const QByteArray data, const QString portName); void errorOccurred(const QString errorString, const QString portName); public slots: void sendData(const QByteArray data); void closePort(); private slots: void handleReadyRead(); private: QSerialPort *m_serial; QString m_portName; };实现类核心功能// serialworker.cpp #include serialworker.h #include QDebug SerialWorker::SerialWorker(const QString portName, QSerialPort::BaudRate baudRate, QObject *parent) : QObject(parent), m_portName(portName) { m_serial new QSerialPort(this); m_serial-setPortName(portName); m_serial-setBaudRate(baudRate); if(!m_serial-open(QIODevice::ReadWrite)) { emit errorOccurred(m_serial-errorString(), m_portName); return; } connect(m_serial, QSerialPort::readyRead, this, SerialWorker::handleReadyRead); } void SerialWorker::handleReadyRead() { QByteArray data m_serial-readAll(); while(m_serial-waitForReadyRead(50)) data m_serial-readAll(); emit dataReceived(data, m_portName); } void SerialWorker::sendData(const QByteArray data) { if(m_serial-isOpen()) { qint64 bytesWritten m_serial-write(data); if(bytesWritten -1) { emit errorOccurred(m_serial-errorString(), m_portName); } } }2.2 实现线程管理创建管理多个串口线程的控制器类// serialcontroller.h #pragma once #include QObject #include QMap #include QThread class SerialWorker; class SerialController : public QObject { Q_OBJECT public: explicit SerialController(QObject *parent nullptr); ~SerialController(); void addPort(const QString portName, QSerialPort::BaudRate baudRate QSerialPort::Baud9600); void removePort(const QString portName); void sendData(const QString portName, const QByteArray data); signals: void dataReceived(const QByteArray data, const QString portName); void portError(const QString error, const QString portName); private: QMapQString, QThread* m_threads; QMapQString, SerialWorker* m_workers; };实现线程管理逻辑// serialcontroller.cpp #include serialcontroller.h #include serialworker.h #include QDebug SerialController::SerialController(QObject *parent) : QObject(parent) {} void SerialController::addPort(const QString portName, QSerialPort::BaudRate baudRate) { if(m_threads.contains(portName)) return; QThread *thread new QThread(this); SerialWorker *worker new SerialWorker(portName, baudRate); worker-moveToThread(thread); connect(thread, QThread::started, worker, [worker](){ qDebug() 串口线程启动: worker-metaObject()-className(); }); connect(thread, QThread::finished, worker, QObject::deleteLater); connect(thread, QThread::finished, thread, QObject::deleteLater); connect(worker, SerialWorker::dataReceived, this, SerialController::dataReceived); connect(worker, SerialWorker::errorOccurred, this, SerialController::portError); m_threads.insert(portName, thread); m_workers.insert(portName, worker); thread-start(); } void SerialController::removePort(const QString portName) { if(!m_threads.contains(portName)) return; QThread *thread m_threads.value(portName); thread-quit(); thread-wait(); m_threads.remove(portName); m_workers.remove(portName); }3. 高级功能实现3.1 数据帧处理工业设备通信通常需要处理特定协议帧。下面是一个Modbus RTU帧处理示例// 在SerialWorker类中添加 QByteArray SerialWorker::processModbusFrame(const QByteArray rawData) { // 简单的Modbus RTU帧校验 if(rawData.size() 5) return QByteArray(); quint8 expectedCrc calculateModbusCRC(rawData.left(rawData.size()-2)); quint8 actualCrc static_castquint8(rawData.at(rawData.size()-2)) | (static_castquint8(rawData.at(rawData.size()-1)) 8); if(expectedCrc ! actualCrc) { emit errorOccurred(CRC校验失败, m_portName); return QByteArray(); } return rawData.mid(0, rawData.size()-2); } quint16 SerialWorker::calculateModbusCRC(const QByteArray data) { quint16 crc 0xFFFF; for(int i 0; i data.size(); i) { crc ^ static_castquint8(data.at(i)); for(int j 0; j 8; j) { if(crc 0x0001) { crc 1; crc ^ 0xA001; } else { crc 1; } } } return crc; }3.2 线程安全队列对于高频数据采集场景建议使用线程安全队列缓冲数据templatetypename T class ThreadSafeQueue { public: void enqueue(const T item) { QMutexLocker locker(m_mutex); m_queue.enqueue(item); m_condition.wakeOne(); } T dequeue() { QMutexLocker locker(m_mutex); while(m_queue.isEmpty()) m_condition.wait(m_mutex); return m_queue.dequeue(); } private: QQueueT m_queue; QMutex m_mutex; QWaitCondition m_condition; };4. 性能优化与错误处理4.1 关键性能指标多线程串口通信的性能瓶颈通常出现在以下几个方面线程切换开销每个串口一个线程在设备数量多时10会导致显著性能下降数据序列化频繁的信号槽连接传递大数据块会增加内存拷贝硬件限制USB转串口芯片的并发处理能力优化方案对比优化手段适用场景实现复杂度效果提升线程池设备数量多(5)中30%-50%批量传输高频小数据包高20%-40%零拷贝大数据量传输高50%-70%4.2 常见错误处理完善错误处理机制是工业级应用的关键void SerialWorker::handleSerialError(QSerialPort::SerialPortError error) { if(error QSerialPort::NoError) return; QString errorStr; switch(error) { case QSerialPort::DeviceNotFoundError: errorStr 设备不存在; break; case QSerialPort::PermissionError: errorStr 权限不足; break; case QSerialPort::ResourceError: errorStr 资源被占用; break; default: errorStr m_serial-errorString(); } emit errorOccurred(errorStr, m_portName); // 自动重连逻辑 if(error QSerialPort::ResourceError || error QSerialPort::DeviceNotFoundError) { QTimer::singleShot(5000, this, [this](){ if(!m_serial-open(QIODevice::ReadWrite)) { emit errorOccurred(重连失败: m_serial-errorString(), m_portName); } }); } }5. 实际应用案例5.1 工业传感器网络监控假设我们需要监控8个温度传感器通过RS485总线连接实现方案如下硬件连接使用USB转RS485转换器每个传感器配置唯一Modbus地址软件配置// 初始化8个虚拟串口实际使用中应为真实设备 QStringList ports {COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8}; SerialController controller; for(const QString port : ports) { controller.addPort(port, QSerialPort::Baud19200); // 发送Modbus查询命令 QByteArray query; query.append(port.right(1).toInt()); // 设备地址 query.append(0x03); // 功能码 query.append(0x00); // 起始地址高字节 query.append(0x01); // 起始地址低字节 query.append(0x00); // 寄存器数量高字节 query.append(0x01); // 寄存器数量低字节 quint16 crc calculateModbusCRC(query); query.append(crc 0xFF); query.append(crc 8); controller.sendData(port, query); }数据处理// 连接数据接收信号 connect(controller, SerialController::dataReceived, [](const QByteArray data, const QString port) { if(data.size() 7) { // 有效的Modbus响应帧 float temperature data.at(3) 8 | data.at(4); temperature / 10.0f; qDebug() port 温度: temperature °C; } });5.2 多设备通信优化技巧定时轮询策略// 在SerialController中添加定时查询功能 void SerialController::startPolling(int intervalMs) { QTimer *pollTimer new QTimer(this); connect(pollTimer, QTimer::timeout, this, [this](){ for(auto it m_workers.begin(); it ! m_workers.end(); it) { // 发送查询命令 QByteArray query buildQueryCommand(it.key()); it.value()-sendData(query); } }); pollTimer-start(intervalMs); }数据聚合显示// 使用QHash聚合多个设备数据 QHashQString, QVectorfloat deviceData; connect(controller, SerialController::dataReceived, [deviceData](const QByteArray data, const QString port) { float value parseData(data); if(!deviceData.contains(port)) { deviceData[port] QVectorfloat(); } deviceData[port].append(value); // 限制历史数据量 if(deviceData[port].size() 100) { deviceData[port].removeFirst(); } });

相关文章:

保姆级教程:在Qt6中用子线程处理多个QSerialPort,实现多设备同时通信

工业级多线程串口通信框架设计:Qt6高效管理多设备通信实战 在工业自动化、物联网网关等场景中,经常需要同时与多个串口设备(如传感器、PLC、模块等)进行稳定通信。传统单线程串口处理方式在面对多设备时往往力不从心,容…...

工业现场设备的监控系统(有完整资料)

资料查找方式:特纳斯电子(电子校园网):搜索下面编号即可编号:T1532310M设计简介:本设计是工业现场设备的监控系统,主要实现以下功能:通过温湿度传感器检测温湿度,湿度过高…...

QMCDecode:三步解锁QQ音乐加密格式,实现跨平台音乐自由

QMCDecode:三步解锁QQ音乐加密格式,实现跨平台音乐自由 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac,qmc0,qmc3转mp3, mflac,mflac0等转flac),仅支持macOS,可自动识别到QQ音乐下载目录&…...

5步掌握Whisper.cpp离线语音识别:从零到精通的实践手册

5步掌握Whisper.cpp离线语音识别:从零到精通的实践手册 【免费下载链接】whisper.cpp Port of OpenAIs Whisper model in C/C 项目地址: https://gitcode.com/GitHub_Trending/wh/whisper.cpp 在当今数据隐私日益重要的时代,云端语音识别服务面临…...

告别Ghost!用官方镜像给NVMe硬盘装Win11,驱动加载这一步很多人会错

从Ghost到原生安装:NVMe硬盘Win11系统部署全指南 当一块崭新的NVMe固态硬盘插入主板M.2插槽时,多数用户期待的是即插即用的流畅体验。然而在官方镜像安装界面中,那个令人困惑的"找不到驱动器"提示,往往成为技术小白与硬…...

视觉Transformer在姿态估计领域的范式革新:ViTPose技术深度解析

视觉Transformer在姿态估计领域的范式革新:ViTPose技术深度解析 【免费下载链接】ViTPose The official repo for [NeurIPS22] "ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation" and [TPAMI23] "ViTPose: Vision Transfor…...

避坑指南:辰华CHI软件宏命令(Macro Command)编写与调试的5个常见错误

辰华CHI宏命令实战避坑手册:5个高频错误解析与调试技巧 在电化学测试领域,辰华CHI软件的宏命令功能一直是科研人员的得力助手,但就像任何强大的工具一样,它也可能成为效率黑洞——当你在凌晨三点的实验室里,面对满屏红…...

告别复杂推导:用数学归纳法5步搞定Pinsker不等式的证明(思路拆解)

数学归纳法五步拆解Pinsker不等式:从基础引理到降维技巧的完整指南 第一次看到Pinsker不等式时,那个关于概率分布之间KL散度与平方距离的不等式关系让我既着迷又困惑。教科书上常见的证明往往依赖复杂的变分法或积分技巧,直到发现这个基于数学…...

OpCore Simplify:黑苹果EFI一键生成的终极指南

OpCore Simplify:黑苹果EFI一键生成的终极指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为复杂的OpenCore配置而烦恼吗&#xf…...

从气象云图到地形渲染:用Python Matplotlib的contourf函数实现数据可视化实战

从气象云图到地形渲染:用Python Matplotlib的contourf函数实现数据可视化实战 当气象学家需要展示台风路径上的温度分布,当地质工程师分析地震波传播的强度变化,或是当环境科学家研究污染物扩散范围时,他们面临的共同挑战是如何将…...

TouchGal 完全指南:5步掌握Galgame文化社区核心功能

TouchGal 完全指南:5步掌握Galgame文化社区核心功能 【免费下载链接】kun-touchgal-next TouchGAL是立足于分享快乐的一站式Galgame文化社区, 为Gal爱好者提供一片净土! 项目地址: https://gitcode.com/gh_mirrors/ku/kun-touchgal-next TouchGal是一个专为G…...

终极鼠标增强方案:Mac Mouse Fix让你的普通鼠标在macOS上超越苹果触控板

终极鼠标增强方案:Mac Mouse Fix让你的普通鼠标在macOS上超越苹果触控板 【免费下载链接】mac-mouse-fix Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad! 项目地址: https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix 还在为macO…...

丹青识画入门必学:中文多模态提示词设计与意境引导技巧

丹青识画入门必学:中文多模态提示词设计与意境引导技巧 1. 理解多模态提示词的核心价值 多模态提示词是连接视觉内容与语言描述的关键桥梁。在丹青识画这样的智能影像雅鉴系统中,提示词的质量直接决定了生成描述的准确性和艺术性。 传统的图像识别系统…...

Display Driver Uninstaller终极指南:5分钟彻底解决显卡驱动冲突问题

Display Driver Uninstaller终极指南:5分钟彻底解决显卡驱动冲突问题 【免费下载链接】display-drivers-uninstaller Display Driver Uninstaller (DDU) a driver removal utility / cleaner utility 项目地址: https://gitcode.com/gh_mirrors/di/display-driver…...

如何利用IP查询定位识别电商刷单?4个关键指标+工具配置方案

“老板,这个爆款的订单量不对劲——同一个IP下了20多单,收货地址天南地北。”大促值班夜,风控系统突然告警。我调出日志,查了这批IP的归属地和网络类型,清一色的数据中心网段。针对“刷单团伙利用数据中心IP批量下单”…...

Lingo3D React集成实战:构建交互式3D游戏界面的完整指南

Lingo3D React集成实战:构建交互式3D游戏界面的完整指南 【免费下载链接】lingo3d Lingo3D is a web-first 3d game development library with React and Vue integration. 项目地址: https://gitcode.com/gh_mirrors/li/lingo3d Lingo3D是一个面向Web的3D游…...

信号处理入门:用Python和SciPy玩转傅里叶变换,5分钟搞定频谱分析

信号处理实战:用Python和SciPy实现傅里叶变换与频谱分析 第一次接触傅里叶变换时,那些复杂的积分符号和数学公式确实让人望而生畏。但当我发现只需要几行Python代码就能将音频信号分解成不同频率成分时,一切都变得直观起来。本文将带你绕过数…...

如何自建IP地址查询定位平台?从数据采集到API发布全流程指南

内部系统日活突破千万后,运维团队发现一个尴尬的问题:每次用户请求都要调用外部IP查询API,不仅每月产生数万元账单,还因为网络抖动导致P99延迟飘到200ms以上。更麻烦的是,安全团队提出“所有IP数据不得出境”&#xff…...

解决Raspberry Pi上的jInput库问题

引言 在使用Java开发跨平台的应用程序时,处理不同操作系统下的库文件加载问题是一个常见的挑战。尤其是在Raspberry Pi(Pi3B+)上运行时,jInput库的加载问题可能会困扰不少开发者。本文将通过一个实例,详细介绍如何解决在Raspbian64系统上jInput库加载失败的问题。 问题描…...

超越Agent:当服务器不让装软件时,用Zabbix SNMP监控的3种高阶玩法与模板优化

超越Agent:Zabbix SNMP监控在受限环境下的高阶实践 想象一下这样的场景:凌晨三点,你被告警电话惊醒,一台关键业务服务器出现性能问题。但当你准备登录排查时,却发现这台服务器严格禁止安装任何监控Agent——这是许多运…...

使用Python和YahooQuery增强财务数据分析

在数据分析领域,Python已经成为许多分析师和数据科学家的首选工具。尤其是在金融分析中,利用Python可以快速处理和分析大量财务数据。今天,我们将探讨如何使用yahooquery库结合财务报表数据与历史股价数据,从而为我们的分析提供更丰富的视角。 基本概念介绍 yahooquery是…...

告别手忙脚乱!Windows Terminal、Tmux、Tabby、WindTerm四大终端分屏快捷键保姆级对比

终端分屏效率革命:四大工具快捷键深度解析与实战指南 在开发者与运维人员的日常工作中,终端工具如同武士的刀剑,而分屏操作则是提升效率的必杀技。面对Windows Terminal、Tmux、Tabby和WindTerm这四大主流终端工具,如何快速掌握它…...

别再手动敲AT指令了!用STM32CubeMX HAL库驱动ESP8266连接OneNET的保姆级教程

STM32CubeMX与HAL库驱动ESP8266连接OneNET的工程化实践 在物联网设备开发中,WiFi模块的集成往往是项目成败的关键节点。传统基于AT指令的手动调试方式不仅效率低下,还容易引入人为错误。本文将展示如何利用STM32CubeMX生成的HAL库代码,构建一…...

当PPT演示遇上时间焦虑:这款悬浮计时器如何让你从容掌控全场

当PPT演示遇上时间焦虑:这款悬浮计时器如何让你从容掌控全场 【免费下载链接】ppttimer 一个简易的 PPT 计时器 项目地址: https://gitcode.com/gh_mirrors/pp/ppttimer 想象一下这样的场景:你站在会议室前方,精心准备的PPT正在大屏幕…...

100+打印机型号的Linux驱动解决方案:foo2zjs深度技术解析

100打印机型号的Linux驱动解决方案:foo2zjs深度技术解析 【免费下载链接】foo2zjs A linux printer driver for QPDL protocol - copy of http://foo2zjs.rkkda.com/ 项目地址: https://gitcode.com/gh_mirrors/fo/foo2zjs 在Linux系统中配置打印机驱动一直是…...

终极Align-Anything训练指南:从SFT到PPO的完整多模态对齐流程详解

终极Align-Anything训练指南:从SFT到PPO的完整多模态对齐流程详解 【免费下载链接】align-anything Align Anything: Training All-modality Model with Feedback 项目地址: https://gitcode.com/gh_mirrors/al/align-anything Align-Anything是一个强大的开…...

Rh123-Fe₃O₄ NPs,Rhodamine 123标记四氧化三铁纳米颗粒,化学结构特点

Rh123-Fe₃O₄ NPs,Rhodamine 123标记四氧化三铁纳米颗粒,化学结构特点Rh123-Fe₃O₄ NPs(Rhodamine 123标记四氧化三铁纳米颗粒)是一类由磁性无机核与有机荧光分子通过界面化学构建的复合纳米体系,其化学结构特点主要…...

超越心跳包:5种防止SSH断连的奇技淫巧,从tmux到Mosh全攻略

超越心跳包:5种防止SSH断连的奇技淫巧,从tmux到Mosh全攻略 每次跨国视频会议卡成PPT时,我总想起那些年在哈萨克斯坦油田调试设备的经历——卫星网络延迟高达800ms,SSH连接平均存活时间不超过3分钟。传统的心跳包配置在这种极端环境…...

别再硬扛期刊论文了!Paperxie 这四步,帮你把 “难产稿” 变成投稿通关文

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/期刊论文https://www.paperxie.cn/ai/journalArticleshttps://www.paperxie.cn/ai/journalArticles 你有没有过这种时刻?对着空白文档发呆三小时,期刊论文的标题都定不下来&#xf…...

PoeCharm架构解析:基于数据本地化的流放之路角色构建系统优化

PoeCharm架构解析:基于数据本地化的流放之路角色构建系统优化 【免费下载链接】PoeCharm Path of Building Chinese version 项目地址: https://gitcode.com/gh_mirrors/po/PoeCharm Path of Building(PoB)作为流放之路(Pa…...