Qt串口助手开发:基于多线程moveToThread方法串口通信工具

      介绍了一个基于Qt框架开发的简易串口助手,满足粉丝的需求。该项目展示了如何利用Qt的moveToThread方法实现多线程串口通信,确保数据接收和发送功能的流畅性。项目中的核心类包括SerialWorker类和MainWindow类,分别负责串口操作和用户界面交互。

1. 项目背景与设计思路

Qt 是一个跨平台的 C++ 开发框架,具有强大的 GUI 开发能力和对硬件接口(如串口)的支持。串口通信通常涉及长时间的读写操作,因此为了避免阻塞用户界面线程,需要将串口操作放入后台线程处理。本项目通过 moveToThread 方法实现了这一需求,即将串口操作移至一个独立的工作线程中,保证界面在数据处理过程中仍然保持响应。

2. 串口助手的主要功能

该串口助手工具具备以下主要功能:

  • 串口的自动检测与配置。
  • 数据的十六进制格式与文本格式发送和接收。
  • 数据的发送、接收、及错误处理。
  • 多线程处理,确保串口操作不会阻塞主界面。

3. SerialWorker类:串口操作的后台处理

SerialWorker类是串口助手的核心,专门用于处理串口的开启、关闭、数据收发等操作。该类继承自QObject,其设计遵循Qt的信号与槽机制,以实现异步通信。

  • 构造与析构:串口对象serial在构造时初始化为nullptr,并在析构时安全关闭串口,释放资源。

  • startSerialPort方法:该方法通过Q_INVOKABLE修饰,可以在其他线程中被调用。它用于设置串口的端口名和波特率,并打开串口,准备进行读写操作。

  • handleReadyRead槽函数:这是处理串口数据接收的关键函数,当串口接收到数据时,它会被触发,读取数据并发射dataReceived信号。

  • handleWriteData槽函数:该函数用于向串口发送数据,在串口打开时调用serial->write()方法发送数据,确保数据通过串口传输出去。

    4.MainWindow 类:用户界面的交互逻辑

    MainWindow类是串口助手的主界面,负责用户操作与后台串口通信的交互。

  • UI初始化与线程处理:在构造函数中,将SerialWorker对象移到独立线程workerThread中,确保串口操作在后台线程执行,不阻塞界面。通过QMetaObject::invokeMethod实现主线程对工作线程的安全调用。

  • 串口检测populateSerialPorts方法自动检测可用串口,并将其填充到下拉菜单供用户选择。

  • 串口启动与关闭:点击启动按钮后,获取串口名和波特率,通过invokeMethod调用SerialWorkerstartSerialPort方法开启串口;点击停止按钮则关闭串口。

  • 数据发送:应用支持文本和十六进制两种格式的发送,用户选择格式后,数据会被处理并通过handleWriteData方法发送至串口。

  • 数据接收与显示:接收到串口数据后,通过信号将数据传给MainWindow,并实时显示,支持文本与十六进制格式的切换。

  • 错误处理:当出现错误时,SerialWorker通过信号将错误信息传递给MainWindow,主窗口会通过弹窗通知用户。

  • #ifndef SERIALWORKER_H
    #define SERIALWORKER_H#include <QObject>
    #include <QSerialPort>
    #include <QThread>#define tc(a) QString::fromLocal8Bit(a)class SerialWorker : public QObject
    {Q_OBJECT
    public:explicit SerialWorker(QObject *parent = nullptr);  // 构造函数~SerialWorker();  // 析构函数Q_INVOKABLE void startSerialPort(const QString &portName, int baudRate);  // 启动串口,Q_INVOKABLE使其可被invokeMethod调用Q_INVOKABLE void stopSerialPort();  // 关闭串口,Q_INVOKABLE使其可被invokeMethod调用signals:void dataReceived(const QByteArray &data);  // 数据接收信号void errorOccurred(const QString &error);   // 错误信号void writeData(const QByteArray &data);     // 写数据信号public slots:void handleWriteData(const QByteArray &data);  // 写数据槽函数private slots:void handleReadyRead();  // 处理串口接收数据槽函数private:QSerialPort *serial;  // QSerialPort 对象指针
    };#endif // SERIALWORKER_H
    
    #include "serialworker.h"
    #include <QDebug>SerialWorker::SerialWorker(QObject *parent) : QObject(parent)
    {serial = nullptr;  // 初始化时serial为空,稍后在startSerialPort中创建
    }SerialWorker::~SerialWorker()
    {if (serial) {if (serial->isOpen()) {serial->close();  // 如果串口打开,关闭串口}delete serial;  // 删除serial对象}
    }// 启动串口,Q_INVOKABLE 使其可被跨线程调用
    void SerialWorker::startSerialPort(const QString &portName, int baudRate)
    {if (!serial) {serial = new QSerialPort();  // 创建串口对象}serial->setPortName(portName);   // 设置串口名称serial->setBaudRate(baudRate);   // 设置波特率// 连接串口的 readyRead 信号到 handleReadyRead 槽函数connect(serial, &QSerialPort::readyRead, this, &SerialWorker::handleReadyRead);// 打开串口,读写模式if (serial->open(QIODevice::ReadWrite)) {qDebug() << tc("串口成功打开:") << portName;} else {emit errorOccurred(serial->errorString());  // 发送错误信号}
    }// 停止串口操作
    void SerialWorker::stopSerialPort()
    {if (serial && serial->isOpen()) {serial->close();  // 关闭串口qDebug() << tc("串口已关闭");}
    }// 写入数据到串口
    void SerialWorker::handleWriteData(const QByteArray &data)
    {if (serial && serial->isOpen()) {serial->write(data);  // 写数据到串口} else {emit errorOccurred(tc("串口未打开"));  // 如果串口未打开,发送错误信号}
    }// 处理串口接收到的数据
    void SerialWorker::handleReadyRead()
    {QByteArray data = serial->readAll();  // 读取所有数据emit dataReceived(data);  // 发出数据接收信号
    }
    
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H#include <QMainWindow>
    #include <QSerialPortInfo>
    #include "serialworker.h"#define tc(a) QString::fromLocal8Bit(a)QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACEclass MainWindow : public QMainWindow
    {Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();protected:void closeEvent(QCloseEvent *e)override;void initStyle();private slots:void on_startButton_clicked();  // 点击启动按钮槽函数void on_sendButton_clicked();   // 点击发送按钮槽函数void on_stopButton_clicked();   // 点击停止按钮槽函数void handleDataReceived(const QByteArray &data);  // 处理接收到的数据槽函数void handleError(const QString &error);  // 处理错误槽函数void populateSerialPorts();  // 自动检索可用串口并填充到下拉列表private:Ui::MainWindow *ui;SerialWorker *serialWorker;  // 串口工作类对象QThread *workerThread;       // 工作线程对象
    };#endif // MAINWINDOW_H
    
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QMessageBox>
    #include <QSerialPortInfo>
    #include <QDebug>
    #include <QFile>
    #include <QDateTime>
    MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow), serialWorker(new SerialWorker), workerThread(new QThread(this))
    {ui->setupUi(this);// 将 SerialWorker 移动到 workerThreadserialWorker->moveToThread(workerThread);// 启动工作线程workerThread->start();// 连接信号和槽connect(serialWorker, &SerialWorker::dataReceived, this, &MainWindow::handleDataReceived);connect(serialWorker, &SerialWorker::errorOccurred, this, &MainWindow::handleError);// 预填充常用波特率ui->baudRateComboBox->addItems({"9600", "115200", "38400", "19200", "57600"});// 自动检索串口并填充到下拉列表populateSerialPorts();initStyle();
    }MainWindow::~MainWindow()
    {workerThread->quit();  // 退出工作线程workerThread->wait();  // 等待线程完全退出delete serialWorker;   // 删除串口工作类对象delete ui;
    }
    void MainWindow::initStyle()
    {//加载样式表QString qss;QFile file(":/qss/psblack.css");if (file.open(QFile::ReadOnly)) {
    #if 1//用QTextStream读取样式文件不用区分文件编码 带bom也行QStringList list;QTextStream in(&file);//in.setCodec("utf-8");while (!in.atEnd()) {QString line;in >> line;list << line;}qss = list.join("\n");
    #else//用readAll读取默认支持的是ANSI格式,如果不小心用creator打开编辑过了很可能打不开qss = QLatin1String(file.readAll());
    #endifQString paletteColor = qss.mid(20, 7);qApp->setPalette(QPalette(paletteColor));qApp->setStyleSheet(qss);file.close();}}
    void MainWindow::closeEvent(QCloseEvent *e)
    {on_stopButton_clicked();QMainWindow::closeEvent(e);
    }// 自动检索系统可用的串口并填充到ComboBox中
    void MainWindow::populateSerialPorts()
    {ui->portNameComboBox->clear();  // 清空现有的串口列表// 获取可用串口列表并添加到ComboBox中const QList<QSerialPortInfo> serialPorts = QSerialPortInfo::availablePorts();for (const QSerialPortInfo &info : serialPorts) {ui->portNameComboBox->addItem(info.portName());}// 如果没有可用串口,提示警告if (ui->portNameComboBox->count() == 0) {QMessageBox::warning(this, tc("警告"), tc("未检测到可用的串口"));}
    }// 启动串口操作
    void MainWindow::on_startButton_clicked()
    {QString portName = ui->portNameComboBox->currentText();  // 从 ComboBox 中获取串口名int baudRate = ui->baudRateComboBox->currentText().toInt();  // 从 ComboBox 中获取波特率// 使用 QMetaObject::invokeMethod 来确保在工作线程中启动串口QMetaObject::invokeMethod(serialWorker, "startSerialPort", Qt::QueuedConnection,Q_ARG(QString, portName), Q_ARG(int, baudRate));
    }// 发送数据到串口
    void MainWindow::on_sendButton_clicked()
    {QByteArray data;if(ui->radioSendHEX->isChecked()){QString input = ui->sendDataEdit->text().remove(QRegExp("\\s")); // Remove all spacesdata = QByteArray::fromHex(input.toLocal8Bit()); // Convert the cleaned string to QByteArray}else{data=(ui->sendDataEdit->text().toLocal8Bit());}// 使用 QMetaObject::invokeMethod 来确保在工作线程中发送数据QMetaObject::invokeMethod(serialWorker, "handleWriteData", Qt::QueuedConnection,Q_ARG(QByteArray, data));QString msg;msg.append(QDateTime::currentDateTime().toString("hh:mm:ss.(zzz)  "));msg.append(tc("发送  "));msg.append(ui->radioSendHEX->isChecked()? data.toHex(' ').toUpper():QString::fromLocal8Bit(data));ui->receiveDataEdit->append(msg);}// 停止串口操作
    void MainWindow::on_stopButton_clicked()
    {// 使用 QMetaObject::invokeMethod 来确保在工作线程中关闭串口QMetaObject::invokeMethod(serialWorker, "stopSerialPort", Qt::QueuedConnection);
    }// 处理串口接收到的数据
    void MainWindow::handleDataReceived(const QByteArray &data)
    {QString msg;msg.append(QDateTime::currentDateTime().toString("hh:mm:ss.(zzz)  "));msg.append(tc("接收  "));msg.append(ui->radioRecvHex->isChecked()? data.toHex(' ').toUpper():QString::fromLocal8Bit(data));ui->receiveDataEdit->append(msg);}// 处理串口错误
    void MainWindow::handleError(const QString &error)
    {QMessageBox::critical(this, tc("错误"), error);  // 显示错误信息
    }
    

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

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

相关文章

【数据可视化】Arcgis api4.x 热力图、时间动态热力图、timeSlider时间滑块控件应用 (超详细、附免费教学数据、收藏!)

1.效果 目录 1.效果 2.安装配置 3.热力图 4.TimeSlider滑块应用 4.1 时间滑块控件 4.2 添加控件 5.时间动态热力图 2.安装配置 这里不教大家如何在前端框架使用arcgis api。不过npm安装、css如何引入、教学数据存放与图层加载的教程&#xff0c;可以浏览我之前发的一篇文…

高效财税自动化软件的特点与优势

随着企业管理信息系统和互联网的不断发展&#xff0c;企业对财务管理提出了更高的要求。为有效助力企业规范财务工作&#xff0c;提高工作效率和准确性&#xff0c;实现信息化管理&#xff0c;越来越多的企业选择引入RPA等高效财税自动化软件。本文金智维将围绕RPA高效财税自动…

LeetCode 2332.坐上公交的最晚时间 (双指针 + 贪心)

给你一个下标从 0 开始长度为 n 的整数数组 buses &#xff0c;其中 buses[i] 表示第 i 辆公交车的出发时间。同时给你一个下标从 0 开始长度为 m 的整数数组 passengers &#xff0c;其中 passengers[j] 表示第 j 位乘客的到达时间。所有公交车出发的时间互不相同&#xff0c;…

基于SpringCloud的能源管理系统-能源管理平台源码-双碳平台源码-能管管理系统源码

一、介绍 基于SpringCloud的能管管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码 二、软件架构 二、功能介绍 三、数字大屏展示 四、数据采集原理 五、软件截图

Mycat搭建读写分离

启动Mycat 进入 /mycat/conf/datasources目录下&#xff0c;修改prototypeDs.datasource.json文件 去mycat/bin目录用启动mycat ./mycat start (关闭mycat ./mycat stop)连接mycat 默认端口8066 用户名root 密码123456 注意&#xff1a;这里ip设为null表示任何ip都可以访问…

【设计模式-组合】

**Composite Pattern&#xff08;组合模式&#xff09;**是一种结构型设计模式&#xff0c;旨在将对象组合成树形结构&#xff0c;以表示“部分-整体”的层次结构。这种模式允许客户端以统一的方式处理单个对象和对象集合&#xff0c;从而简化了树形结构的处理。 核心思想 组…

LLM应用实战: 文档问答系统Kotaemon-1. 简介及部署实践

1.背景 本qiang~这两周关注到一个很火的开源文档问答系统Kotaemon&#xff0c;从8月28日至今短短两周时间&#xff0c;github星标迅猛增长10K&#xff0c;因此计划深挖一下其中的原理及奥秘。 本篇主要是Kotaemon的简介信息&#xff0c;涉及到主要特点&#xff0c;与传统文档…

MindShare PCIE 3.0 笔记-第一二章

MindShare 官网&#xff0c;地址如下: MindShare Chapter 1&#xff1a;PCIE 背景介绍 - PCI 总线模型 1. 以 PCI 总线作为外设总线的 SOC 芯片架构 下图展示了一个以 PCI 总线作为外设总线的 SOC 芯片架构(PCI 总线类似 AXI 下的 AHB&#xff1f;)&#xff1a; 由上图可知…

虚拟机与物理机的文件共享

之前往虚拟机里传文件都是直接拖拽或者借助工具上传&#xff0c;都不太方便&#xff0c;倘若物理机的文件直接能在虚拟机里读取使用&#xff0c;那多好啊~ 1 虚拟机设置 注意文件夹名称不要中文/空格 2 验证Kali下分享文件夹功能是否启用 vmware-hgfsclient 3 创建挂载目录…

数据库基础知识---------------------------(2)

MYSQL的存储过程 就是数据库 SQL 语言层面的代码封装与重用 语法格式 delimiter 自定义结束符号 create procedure 存储名({in,out,inout} 参数名,数据类型...) begin sql 语句 end 自定义结束符 delimiter; 变量定义 局部变量 用户自定义 仅在begin / end 块中有效 当将查询…

apach httpd多后缀解析漏洞

漏洞详情&#xff1a; httpd支持一个文件拥有多个后缀&#xff0c;并为不同后缀执行不同的指令。 那么&#xff0c;在有多个后缀的情况下&#xff0c;只要一个文件含有.php后缀的文件即将被识别成PHP文件&#xff0c;没必要是最后一个后缀。 利用这个特性&#xff0c;可以绕过…

Linux硬连接、软连接和复制的区别

‌硬连接、软连接和复制在Linux系统中的主要区别体现在以下三点&#xff1a; 文件链接的方式文件独立性文件系统的操作上。‌ 一、硬连接 1. 硬连接是通过ln命令创建的&#xff0c;它为文件创建别名&#xff0c;与源文件共享同一inode号码&#xff0c;因此硬连接和源文件实际…

Mint Expedition Season 3 拉开帷幕:登顶高峰的时刻到了

自 7 月 15 日 Mint Expedition 启动以来&#xff0c;Mint&#xff0c;一条专注于 NFT 行业的以太坊 Layer 2&#xff0c;日常交易量和交易额都出现了爆发式增长。这一成功离不开 Mint 社区的合作&#xff0c;包括 Minters、Web3 去中心化应用程序的开发者&#xff0c;以及大量…

02 ETH

以太坊与比特币有什么不同&#xff1f; 以太坊立足比特币创新之上&#xff0c;于 2015 年启动&#xff0c;两者之间有一些显著不同。 比特币就仅仅是比特币&#xff1b;以太坊包括以太币&#xff0c;以太币才是和比特币对等的存在。以太坊是可编程的&#xff0c;所以你可以在…

示例:WPF中Grid显示网格线的几种方式

一、目的&#xff1a;介绍一下WPF中Grid显示网格线的几种方式 二、几种方式 1、重写OnRender绘制网格线&#xff08;推荐&#xff09; 效果如下&#xff1a; 实现方式如下&#xff1a; public class LineGrid : Grid{protected override void OnRender(DrawingContext dc){Pen…

SQL 多表联查

目录 1. 内联接&#xff08;INNER JOIN&#xff09; 2. 左外联接&#xff08;LEFT JOIN&#xff09; 3. 右外联接&#xff08;RIGHT JOIN&#xff09; 4. 全外联接&#xff08;FULL JOIN&#xff09; 5. 交叉联接&#xff08;CROSS JOIN&#xff09; 6. 自联接&#xff0…

MATLAB系列07:稀疏矩阵、单元阵列和结构

MATLAB系列07&#xff1a;稀疏矩阵、单元阵列和结构 7. 稀疏矩阵、单元阵列和结构7.1 稀疏矩阵7.1.1 sparse数据类型7.1.1.1 产生稀疏矩阵7.1.1.2 稀疏矩阵的运算 7.2 单元阵列(cell array)7.2.1 创建单元阵列7.2.1.1 用赋值语句创建单元阵列7.2.1.2 用cell函数创建单元阵列 7.…

Day02Day03

1. 为什么拦截器不会去拦截/admin/login上&#xff0c;是因为在SpringMvc中清除了这种可能。 2.使用自己定义注解&#xff0c;实现AOP&#xff08;insert ,update&#xff09; 3.使用update最好使用动态语句&#xff0c;可以使用多次 4.使用阿里云的OSS存储。用common类 5.在写…

【BoF】《Bag of Freebies for Training Object Detection Neural Networks》

arXiv-2019 https://github.com/dmlc/gluon-cv 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method4.1 Visually Coherent Image Mixup for Object Detection4.2 Classification Head Label Smoothing4.3 Data Preprocessing4.4 Traini…

[Redis][Redis简介]详细讲解

目录 1.认识 Redis2.Redis 特性1.速度快2.基于键值对的数据结构的服务器3.丰富的功能4.简单稳定5.客户端语言多6.高扩展性7.持久化(Persistence)8.主从复制9.⾼可⽤和分布式 3.Redis 使用场景1.数据库2.Cache3.消息队列 4.注意 1.认识 Redis Redis是⼀种基于键值对(Key-Value)…