Qt串口异步通信案例(从机线程)

文章目录

  • 串口线程类
    • 初始化串口类
    • 打开串口并发送数据
    • 析构函数
  • 窗口设置
  • 窗口函数实现

串口线程类

SlaveThread(从机线程)

目的:等待并响应来自主机的请求,然后发送预设的响应数据。
关键行为:线程启动后,通过串口监听请求数据。当检测到有数据可读时,读取请求数据。读取完毕后,向串口写入预设的响应数据。如果写入成功,通过信号 request(QString) 发送接收到的请求信息给外部监听者。如果在读取请求或写入响应时发生超时,会触发 timeout(QString) 信号。
同步机制:使用 QMutex 保证线程安全,检查和更新串口号、等待超时时间以及响应内容,但没有使用 QWaitCondition,因为它是一个被动监听请求的线程,不需要主动唤醒机制。

SlaveThread (从机线程) 示例

假设在一个自动化仓库管理系统中,有多个库存检查站,每个站点装备了一个读取条形码的扫描枪作为从设备(Slave)。这些从设备并不主动发起操作,而是等待仓库管理系统(主设备)的指令来执行任务。
场景描述:

任务:当一个物品进入或离开仓库时,仓库管理系统需要记录该物品的条形码信息。
流程:主设备(如中央服务器)通过网络向各个站点的从机发送请求,要求读取条形码。从机(SlaveThread实例)不断监听串口,一旦接收到请求,立刻激活扫描枪读取条形码,然后通过串口将条形码数据作为响应发回给主设备。
特点:在这个场景中,SlaveThread体现为被动响应设备的职责,等待主设备的指令,执行读取任务并反馈结果。

SlaveThread 更适用于那些需要持续监听并响应外部指令的场景,如传感器数据采集、远程控制设备的响应等。(只采集数据,并不需要主动发送请求,而是始终监听并响应数据的到来)

class SlaveThread : public QThread
{Q_OBJECTpublic:explicit SlaveThread(QObject *parent = nullptr);//~SlaveThread();void startSlave(const QString &portName, int waitTimeout, const QString &response);//初始化串口及启动线程signals:void request(const QString &s);//请求消息void error(const QString &s);//串口错误消息void timeout(const QString &s);//串口连接超时消息private:void run() override;//串口通信QString m_portName;QString m_response;int m_waitTimeout = 0;QMutex m_mutex;bool m_quit = false;
};

初始化串口类

void SlaveThread::startSlave(const QString &portName, int waitTimeout, const QString &response)
{const QMutexLocker locker(&m_mutex);//初始化线程锁m_portName = portName;//设置串口号m_waitTimeout = waitTimeout;//设置串口的等待时间m_response = response;//设置串口的相应参数if (!isRunning())//判断线程是否运行start();//启动线程
}

打开串口并发送数据

void SlaveThread::run()
{bool currentPortNameChanged = false;// 加锁以安全地访问和更新线程间共享的数据m_mutex.lock();// 获取并记录当前配置与上次是否有变QString currentPortName;if (currentPortName != m_portName) { // 检查端口名是否变更currentPortName = m_portName;currentPortNameChanged = true; // 标记端口变更}int currentWaitTimeout = m_waitTimeout; // 获取超时时间QString currentResponse = m_response; // 获取响应信息m_mutex.unlock(); // 解锁,释放互斥锁QSerialPort serial; // 创建QSerialPort对象用于串口通信// 主循环,持续运行直到收到退出信号while (!m_quit) {// 如果端口设置发生变更if (currentPortNameChanged) {serial.close(); // 关闭旧端口serial.setPortName(currentPortName); // 设置新端口名// 尝试打开新的串口,失败则发送错误信号并退出if (!serial.open(QIODevice::ReadWrite)) {emit error(tr("无法打开端口 %1, 错误码 %2").arg(currentPortName).arg(serial.error()));return;}}// 等待读取请求数据if (serial.waitForReadyRead(currentWaitTimeout)) {// 读取所有可用的请求数据QByteArray requestData = serial.readAll();// 处理可能的分包情况,继续读取直到没有更多数据到来while (serial.waitForReadyRead(10))requestData += serial.readAll();// 准备响应数据并发送QByteArray responseData = currentResponse.toUtf8();serial.write(responseData);// 等待响应数据写入完成,成功则发送请求信号,否则发送写入超时信号if (serial.waitForBytesWritten(m_waitTimeout)) {QString request = QString::fromUtf8(requestData);emit request(request);} else {emit timeout(tr("等待写入响应超时 %1").arg(QTime::currentTime().toString()));}} else {// 如果等待读取请求超时,发送读取超时信号emit timeout(tr("等待读取请求超时 %1").arg(QTime::currentTime().toString()));}// 再次加锁,检查配置是否更新m_mutex.lock();if (currentPortName != m_portName) {currentPortName = m_portName;currentPortNameChanged = true;} else {currentPortNameChanged = false;}currentWaitTimeout = m_waitTimeout; // 更新超时时间currentResponse = m_response; // 更新响应信息m_mutex.unlock(); // 解锁}
}

析构函数

SlaveThread::~SlaveThread()
{m_mutex.lock();m_quit = true;m_mutex.unlock();wait();
}

窗口设置

#ifndef DIALOG_H
#define DIALOG_H#include "slavethread.h"#include <QDialog>QT_BEGIN_NAMESPACEclass QLabel;
class QLineEdit;
class QComboBox;
class QSpinBox;
class QPushButton;QT_END_NAMESPACEclass Dialog : public QDialog
{Q_OBJECTpublic:explicit Dialog(QWidget *parent = nullptr);private slots:void startSlave();void showRequest(const QString &s);void processError(const QString &s);void processTimeout(const QString &s);void activateRunButton();private:int m_transactionCount = 0;QLabel *m_serialPortLabel = nullptr;QComboBox *m_serialPortComboBox = nullptr;QLabel *m_waitRequestLabel = nullptr;QSpinBox *m_waitRequestSpinBox = nullptr;QLabel *m_responseLabel = nullptr;QLineEdit *m_responseLineEdit = nullptr;QLabel *m_trafficLabel = nullptr;QLabel *m_statusLabel = nullptr;QPushButton *m_runButton = nullptr;SlaveThread m_thread;
};#endif // DIALOG_H

窗口函数实现

#include "dialog.h"// 引入所需Qt模块的头文件
#include <QComboBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSerialPortInfo>
#include <QSpinBox>// 定义Dialog类,继承自QWidget
Dialog::Dialog(QWidget *parent) :QDialog(parent),// 初始化UI组件m_serialPortLabel(new QLabel(tr("Serial port:"))), // 串口标签m_serialPortComboBox(new QComboBox),              // 串口选择组合框m_waitRequestLabel(new QLabel(tr("Wait request, msec:"))), // 等待请求时间标签m_waitRequestSpinBox(new QSpinBox),             // 等待请求时间微调框m_responseLabel(new QLabel(tr("Response:"))),     // 响应标签m_responseLineEdit(new QLineEdit(tr("Hello, I'm Slave."))), // 响应文本框,默认值m_trafficLabel(new QLabel(tr("No traffic."))),    // 流量信息标签m_statusLabel(new QLabel(tr("Status: Not running."))), // 状态标签m_runButton(new QPushButton(tr("Start")))      // 开始按钮
{// 设置等待请求时间的范围和默认值m_waitRequestSpinBox->setRange(0, 10000);m_waitRequestSpinBox->setValue(10000);// 获取并添加所有可用的串口名到组合框中const auto infos = QSerialPortInfo::availablePorts();for (const QSerialPortInfo &info : infos)m_serialPortComboBox->addItem(info.portName());// 创建主网格布局,并将UI组件添加到布局中auto mainLayout = new QGridLayout;mainLayout->addWidget(m_serialPortLabel, 0, 0);mainLayout->addWidget(m_serialPortComboBox, 0, 1);mainLayout->addWidget(m_waitRequestLabel, 1, 0);mainLayout->addWidget(m_waitRequestSpinBox, 1, 1);mainLayout->addWidget(m_runButton, 0, 2, 2, 1); // 横跨两行mainLayout->addWidget(m_responseLabel, 2, 0);mainLayout->addWidget(m_responseLineEdit, 2, 1, 1, 3); // 横跨三列mainLayout->addWidget(m_trafficLabel, 3, 0, 1, 4); // 横跨四列mainLayout->addWidget(m_statusLabel, 4, 0, 1, 5); // 横跨五列setLayout(mainLayout); // 设置对话框的主布局// 设置窗口标题,并让串口选择框获得焦点setWindowTitle(tr("Blocking Slave"));m_serialPortComboBox->setFocus();// 连接信号与槽函数// 当按下开始按钮时,启动从机线程connect(m_runButton, &QPushButton::clicked, this, &Dialog::startSlave);// 当线程发出请求信号时,显示该请求connect(&m_thread, &SlaveThread::request, this,&Dialog::showRequest);// 处理错误信号connect(&m_thread, &SlaveThread::error, this, &Dialog::processError);// 处理超时信号connect(&m_thread, &SlaveThread::timeout, this, &Dialog::processTimeout);// 当串口选择、等待时间或响应文本改变时,激活或禁用开始按钮connect(m_serialPortComboBox, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),this, &Dialog::activateRunButton);connect(m_waitRequestSpinBox, &QSpinBox::textChanged, this, &Dialog::activateRunButton);connect(m_responseLineEdit, &QLineEdit::textChanged, this, &Dialog::activateRunButton);
}// 启动从机线程的槽函数
void Dialog::startSlave()
{m_runButton->setEnabled(false); // 禁用开始按钮// 更新状态标签,显示当前连接的端口信息m_statusLabel->setText(tr("Status: Running, connected to port %1.").arg(m_serialPortComboBox->currentText()));// 启动SlaveThread线程,传递配置参数m_thread.startSlave(m_serialPortComboBox->currentText(),m_waitRequestSpinBox->value(),m_responseLineEdit->text());
}// 显示请求的槽函数
void Dialog::showRequest(const QString &s)
{// 更新流量信息,显示当前交易的请求和响应m_trafficLabel->setText(tr("Traffic, transaction #%1:""\n\r-request: %2""\n\r-response: %3").arg(++m_transactionCount) // 交易计数加一.arg(s)                  // 请求内容.arg(m_responseLineEdit->text())); // 预设响应
}// 处理错误的槽函数
void Dialog::processError(const QString &s)
{// 重新激活开始按钮,更新状态标签显示错误信息activateRunButton();m_statusLabel->setText(tr("Status: Not running, %1.").arg(s));m_trafficLabel->setText(tr("No traffic."));
}// 处理超时的槽函数
void Dialog::processTimeout(const QString &s)
{// 更新状态标签,显示超时信息m_statusLabel->setText(tr("Status: Running, %1.").arg(s));m_trafficLabel->setText(tr("No traffic."));
}// 激活或禁用开始按钮的槽函数
void Dialog::activateRunButton()
{m_runButton->setEnabled(true); // 根据输入状态,决定是否启用开始按钮
}

在这里插入图片描述

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

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

相关文章

STL库--string

目录 string的定义 string中内存的访问 string常用函数实例解析 string的定义 定义string的方式跟基本类型相同&#xff0c;只需要在string后跟上变量名即可&#xff1a; string str; 如果要初始化&#xff0c;可以直接给string类型的变量进行赋值&#xff1a; string s…

Vue3+vite项目中使用mock模拟接口

安装依赖 分别安装vite-plugin-mock跟mockjs两个插件 npm install -D vite-plugin-mock mockjs vite.config.ts中添加配置&#xff0c;主要是红色标记的配置 注意此处如果配置出错可能是vite-plugin-mock依赖的版本有问题&#xff0c;重新安装一下依赖指定版本即可&#xf…

Pytorch环境配置2.0.1+ Cuda11.7

查找cuda、cudnn、Pytorch(GPU)及cuda和NVIDIA显卡驱动对应关系 查询可支持的最高cuda版本 nvidia-smi查看支持的cuda的版本 CUDA版本对应表 我的显卡驱动是Driver Version&#xff1a;535.40.&#xff0c;那么左边对应的CUDA都可以兼容 右上角为CUDA 版本&#xff0c;可以看…

Ubuntu配置Git

安装git sudo apt install git 查看是否安装成功 git --version 配置git 用github上注册的用户名和邮箱地址&#xff0c;配置git git config --global user.name "username" git config --global user.email "usernameemail.com" 重启ubuntu查看…

Collection(一)[集合体系]

说明&#xff1a;Collection代表单列集合&#xff0c;每个元素&#xff08;数据&#xff09;只包含一个值。 Collection集合体系&#xff1a; Collection<E> 接口 (一&#xff09;List<E> 接口 说明&#xff1a;添加的元素是有序、可重复、有索引。 1. ArrayLi…

vue3中的toRaw API

文章目录 什么是toRaw API&#xff1f;为什么需要toRaw&#xff1f;如何使用toRaw&#xff1f;实际应用场景 这两天在写项目的时候&#xff0c;发现了一个之前没用过的api&#xff0c;于是上网查了一下&#xff0c;发现这个api还是挺常用&#xff0c;所以在这记录一下 什么是t…

【Postman接口测试】第二节.Postman界面功能介绍(上)

文章目录 前言一、Postman前言介绍二、Postman界面导航说明三、使用Postman发送第一个请求四、Postman 基础功能介绍 4.1 常见类型的接口请求 4.1.1 查询参数的接口请求 4.1.2 表单类型的接口请求 4.1.3 上传文件的表单请求 4.1.4 JSON 类…

HCIP-Datacom-ARST自选题库__BGP/MPLS IP VPN简答【3道题】

1.在BGP/MPLSIPVPN场景中&#xff0c;如果PE设备收到到达同一目的网络的多条路由时&#xff0c;将按照定的顺序选择最优路由。请将以下内容按照比较顺序进行排序。 2.在如图所示的BGP/MPLSIP VPN网络中&#xff0c;管理员准备通过Hub-Spoke组网实现H站点对VPM流量的集中管控&am…

C# 配置文件设置详解

文章目录 1. 配置文件在 C# 项目中的作用和重要性2. 不同类型的配置文件app.configconfig.exejson 3. 创建和修改配置文件文件位置添加内容修改内容保存和加载 4. 读取和写入配置文件app.config 文件读取config.exe 文件写入JSON 文件读写 5. 示例代码演示6. 配置文件在安全性方…

【kubernetes】关于k8s集群的污点、容忍、驱逐以及k8s集群故障排查思路

目录 一、污点(Taint) 1.1污点介绍 1.2污点的组成格式 1.3当前 taint effect 支持如下三个选项&#xff1a; 1.4污点的增删改查 1.4.1验证污点的作用——NoExecute 1.4.2验证污点的作用——NoSchedule 1.4.3 验证污点的作用——PreferNoSchedule 1.5污点的配置与管理…

php反序列化学习(1)

1、php面向对象基本概念 类的定义&#xff1a; 类是定义了一件事物的抽象特征&#xff0c;它将数据的形式以及这些数据上的操作封装住在一起。&#xff08;对象是具有类类型的变量&#xff0c;是对类的实例&#xff09; 构成&#xff1a; 成员变量&#xff08;属性&#xf…

基于开源项目HAL STM32F4 +DSP库跑SVPWM开环速度测试

HAL STM32F4 ARM DSP库跑SVPWM开环速度测试 ✨本篇硬件电路和代码来源于此开源项目&#xff1a;https://github.com/MengYang-x/STM3F401-FOC/tree/main&#x1f4cd;硬件电路和项目介绍&#xff0c;立创开源广场&#xff1a;https://oshwhub.com/shadow27/tai-yang-neng-wu-re…

走进智慧仓储:3D可视化工厂园区革新物流新纪元

在快节奏的现代生活中&#xff0c;物流仓储行业扮演着至关重要的角色。随着科技的飞速发展&#xff0c;传统仓储模式正面临一场前所未有的变革。今天&#xff0c;就让我们一起看看3D可视化技术如何为物流行业带来前所未有的便利与效率。 什么是3D可视化工厂园区&#xff1f; 3…

【最新区块链论文录用资讯】CCF A—INFOCOM 2024 共17篇

Conference&#xff1a;IEEE International Conference on Computer Communications CCF level&#xff1a;CCF A Categories&#xff1a;计算机网络 Year&#xff1a;2024 Num&#xff1a;17 A Generic Blockchain-based Steganography Framework with High Capacity via …

Python: 使用pyotp实现OTP一次性密码验证

使用pyotp实现OTP一次性密码验证 OTP的基本原理 生成一个共享秘钥作为随机数的种子服务端通过种子计算出当前的密码客户端也通过相同的种子计算出当前的密码验证客户端生成的密码和服务端生成的密码是否匹配 服务端和客户端计算的方式一样 共享密钥 时间因子 算法 > 密…

多个文本如何一键导出二维码?在线批量生码的制作方法

当存在多条文本数据并且需要将每条数据生成单独的二维码来使用&#xff0c;很多小伙伴可能还在用一个一个来制作的方法&#xff0c;在二维码生成器上将文本转二维码。这种方式操作起来比较的繁琐&#xff0c;需要浪费大量的时间&#xff0c;那么有什么方法可以简化这个过程吗&a…

YOLOv10代码详细介绍(附录训练教程和权重)

前言 YOLOv10 是清华大学研究人员在 UltralyticsPython 清华大学的研究人员在 YOLOv10软件包的基础上&#xff0c;引入了一种新的实时目标检测方法&#xff0c;解决了YOLO 以前版本在后处理和模型架构方面的不足。通过消除非最大抑制&#xff08;NMS&#xff09;和优化各种模型…

[从零开发JS应用] 如何在VScode中配置Javascript环境,常见的调试方法有哪些?

一、安装VSCode和Node.js 记录环境配置&#xff1a;本文配置的环境主要针对单独JS文件的断点调试&#xff0c;主要是为了调试LeetCode里面的代码。 首先在官网下载对应的版本&#xff1a;https://nodejs.org/en/ 开始安装&#xff0c;可以自定义选择安装路径。 这里选择Add Pa…

【亲测,安卓版】快速将网页网址打包成安卓app,一键将网页打包成app,免安装纯绿色版本,快速将网页网址打包成安卓apk

背景&#xff1a;部分客户需求将自己网站打包成app&#xff0c;供用户在浏览器安装使用、 网页网址快速生成app 准备材料操作流程第一步&#xff1a;打开HBuilder X新建项目第二步创建Wap2App项目第三步修改App图标第四步发布app第五步查看apk 准备材料 1.需要打包的网页 2.ap…

页面加载不出来,报错[@umijs/runtime] load component failed

问题描述 页面加载不出来数据&#xff0c;一直在旋转&#xff0c;控制台输出内容如下&#xff1a; 原因分析&#xff1a; 之前页面是没有问题的&#xff0c;在写当前页面突然出现页面加载不出来&#xff0c;控制台报错&#xff0c;主要是页面引入了这行代码报错 import { …