一套qt c++的串口通信

news2025/6/2 5:17:38

实现了创建线程使用串口的功能

具备功能:
1.线程使用串口
2.定时发送队列内容,防止粘包
3.没处理接收粘包,根据你的需求来,handleReadyRead函数中,可以通过m_receiveBuffer来缓存接收,然后拆分数据来处理

源码

serialportmanager.h

#ifndef SERIALPORTMANAGER_H
#define SERIALPORTMANAGER_H

#include <QObject>
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread> // 添加 QThread 头文件
#include <QMetaObject> // 添加 QMetaObject 头文件
#include <QQueue> // 添加 QQueue 头文件
#include <QTimer> // 添加 QTimer 头文件

class SerialPortManager : public QObject
{
    Q_OBJECT
public:
    explicit SerialPortManager(QObject *parent = nullptr);
    ~SerialPortManager();

    // 打开串口 (调用将在工作线程执行)
    Q_INVOKABLE bool openPort(const QString &portName, int baudRate, QSerialPort::DataBits dataBits = QSerialPort::Data8,
                              QSerialPort::Parity parity = QSerialPort::NoParity, QSerialPort::StopBits stopBits = QSerialPort::OneStop);
    // 关闭串口 (调用将在工作线程执行)
    Q_INVOKABLE void closePort();
    // 写入数据 (调用将在工作线程执行)
    Q_INVOKABLE qint64 writeData(const QByteArray &data);
    Q_INVOKABLE qint64 writeString(const QString &str);

    //带参数的
    Q_INVOKABLE qint64 writeCommnd(const QString &baseCommad,QStringList strParas);

    // 检查串口是否打开 (调用将在工作线程执行)
    Q_INVOKABLE bool isOpen();

    // 获取可用串口列表 (静态方法,主线程安全)
    static QStringList availablePorts();

signals:
    void dataReceived(const QByteArray &data);
    void portError(const QString &errorDescription);
    void portOpened();
    void portClosed();
    // 内部信号,用于触发工作线程中的初始化
    void initSerialPortSignal();

public slots:
    void _closePortSlot();
private slots:
    // 这些槽函数将在工作线程中执行
    void _openPortSlot(const QString &portName, int baudRate, QSerialPort::DataBits dataBits,
                       QSerialPort::Parity parity, QSerialPort::StopBits stopBits, bool *result);

    void _writeDataSlot(const QByteArray &data, qint64 *result);
    void _isOpenSlot(bool *result) const;

    void handleReadyRead();
    void handleError(QSerialPort::SerialPortError error);
    void _initSerialPort(); // 在工作线程中初始化串口对象
    void _cleanupSlot();    // 在工作线程退出前执行清理
    void sendDataFromQueue(); // 定时器超时槽函数,发送队列中的数据

private:
    QSerialPort *m_serialPort; // 将在工作线程中创建和使用
    QThread *m_workerThread;   // 串口操作的工作线程
    QQueue<QByteArray> m_dataQueue; // 数据发送队列
    QTimer *m_sendTimer; // 发送定时器
    QByteArray m_receiveBuffer; // 接收数据缓冲区
};

#endif // SERIALPORTMANAGER_H

serialportmanager.cpp

#include "serialportmanager.h"
#include <QDebug> // 用于调试

SerialPortManager::SerialPortManager(QObject *parent) : QObject(nullptr), // 关键: 初始化时设置父对象为nullptr,以允许moveToThread
    m_serialPort(nullptr), // 初始化为 nullptr,将在工作线程中创建
    m_workerThread(new QThread())  // m_workerThread 创建时不指定父对象,其生命周期将单独管理
{
    Q_UNUSED(parent); // 如果传递了parent,明确表示它在这里没有被使用来设置父子关系
    // 将 SerialPortManager 对象本身移动到工作线程
    this->moveToThread(m_workerThread);

    // 连接信号以在工作线程启动后初始化串口
    connect(m_workerThread, &QThread::started, this, &SerialPortManager::_initSerialPort);
    // 连接信号以在工作线程结束时自动删除 QThread 对象
    connect(m_workerThread, &QThread::finished, m_workerThread, &QObject::deleteLater);

    // 启动工作线程
    m_workerThread->start();
}

SerialPortManager::~SerialPortManager()
{
    if (m_workerThread->isRunning()) {
        // 连接 cleanupSlot 以在线程完成前执行清理
        // 使用 Qt::DirectConnection 确保 _cleanupSlot 在 m_workerThread 中执行
        connect(m_workerThread, &QThread::finished, this, &SerialPortManager::_cleanupSlot, Qt::DirectConnection);

        // 请求退出线程
        m_workerThread->quit();
        // 等待线程结束,m_workerThread 会通过之前连接的 deleteLater 自行删除
        if (!m_workerThread->wait(5000)) { // 增加等待时间以确保清理完成
            qWarning() << u8"SerialPortManager: 工作线程未及时完成,尝试终止。";
            m_workerThread->terminate(); // 强制终止作为最后手段
            m_workerThread->wait();      // 等待终止完成
        }
        qDebug() << u8"SerialPortManager: 工作线程已完成。";
    } else {
        // 如果线程没有运行,但 m_workerThread 对象仍然存在,也应该安排删除
        // (虽然理论上 connect m_workerThread finished to deleteLater 应该处理了,但作为安全措施)
        if (m_workerThread) {
             m_workerThread->deleteLater();
        }
    }
    qDebug() << u8"SerialPortManager 已销毁。";
}

void SerialPortManager::_cleanupSlot()
{
    if (m_serialPort && m_serialPort->isOpen()) {
        m_serialPort->close();
        qDebug() << u8"串口在 _cleanupSlot 中关闭。";
    }
}

void SerialPortManager::_initSerialPort()
{
    // 这个函数在 m_workerThread 中执行
    m_serialPort = new QSerialPort(this); // parent 为 this,确保随 SerialPortManager 销毁
    connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortManager::handleReadyRead);
    connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialPortManager::handleError);

    m_sendTimer = new QTimer(this); // parent 为 this,确保随 SerialPortManager 销毁
    connect(m_sendTimer, &QTimer::timeout, this, &SerialPortManager::sendDataFromQueue);
    m_sendTimer->start(100); // 0.1秒 (100毫秒) 执行一次

    qDebug() << u8"SerialPortManager 在线程中初始化:" << QThread::currentThreadId();
}


bool SerialPortManager::openPort(const QString &portName, int baudRate, QSerialPort::DataBits dataBits,
                               QSerialPort::Parity parity, QSerialPort::StopBits stopBits)
{
    if (QThread::currentThread() == m_workerThread) {
        // 如果已经在工作线程,直接调用
        bool result = false;
        _openPortSlot(portName, baudRate, dataBits, parity, stopBits, &result);
        return result;
    }

    bool result = false;
    // 使用 BlockingQueuedConnection 确保调用完成并获取结果
    QMetaObject::invokeMethod(this, "_openPortSlot", Qt::BlockingQueuedConnection,
                              Q_ARG(QString, portName),
                              Q_ARG(int, baudRate),
                              Q_ARG(QSerialPort::DataBits, dataBits),
                              Q_ARG(QSerialPort::Parity, parity),
                              Q_ARG(QSerialPort::StopBits, stopBits),
                              Q_ARG(bool*, &result));
    return result;
}

void SerialPortManager::_openPortSlot(const QString &portName, int baudRate, QSerialPort::DataBits dataBits,
                                    QSerialPort::Parity parity, QSerialPort::StopBits stopBits, bool *result)
{
    if (!m_serialPort) {
        *result = false;
        emit portError(u8"串口对象未初始化。");
        return;
    }
    if (m_serialPort->isOpen()) {
        m_serialPort->close();
    }
    m_serialPort->setPortName(portName);
    m_serialPort->setBaudRate(baudRate);
    m_serialPort->setDataBits(dataBits);
    m_serialPort->setParity(parity);
    m_serialPort->setStopBits(stopBits);
    m_serialPort->setFlowControl(QSerialPort::NoFlowControl);

    if (m_serialPort->open(QIODevice::ReadWrite)) {
        emit portOpened();
        *result = true;
    } else {
        emit portError(m_serialPort->errorString());
        *result = false;
    }
}

void SerialPortManager::sendDataFromQueue()
{
    if (m_serialPort && m_serialPort->isOpen() && !m_dataQueue.isEmpty()) {
        QByteArray dataToSend = m_dataQueue.dequeue();
        qDebug() << u8"从队列发送数据:" << dataToSend;
        m_serialPort->write(dataToSend);
    }
}

void SerialPortManager::closePort()
{
    if (QThread::currentThread() == m_workerThread) {
        _closePortSlot();
        return;
    }
    QMetaObject::invokeMethod(this, "_closePortSlot", Qt::QueuedConnection);

}

void SerialPortManager::_closePortSlot()
{
    m_dataQueue.clear(); // 清空队列中的剩余数据

    if (m_serialPort && m_serialPort->isOpen()) {
        m_serialPort->close();
        emit portClosed();
    }
}

qint64 SerialPortManager::writeData(const QByteArray &data)
{
    if (QThread::currentThread() == m_workerThread) {
        qint64 result = -1;
        _writeDataSlot(data, &result);
        return result;
    }

    qint64 result = -1;
    QMetaObject::invokeMethod(this, "_writeDataSlot", Qt::BlockingQueuedConnection,
                              Q_ARG(QByteArray, data),
                              Q_ARG(qint64*, &result));
    return result;
}

qint64 SerialPortManager::writeString(const QString &str)
{
    return writeData(str.toUtf8());
}

qint64 SerialPortManager::writeCommnd(const QString &baseCommad, QStringList strParas)
{
    QString strSendData = "<";
    strSendData+=baseCommad;
    for(QString para : strParas)
    {
        strSendData+=" ";
        strSendData +=para;
    }
    strSendData +=">";
    return writeString(strSendData);
}

void SerialPortManager::_writeDataSlot(const QByteArray &data, qint64 *result)
{
    // 将数据添加到队列,而不是直接发送
    m_dataQueue.enqueue(data);
    qDebug() << u8"数据已添加到发送队列,当前队列大小:" << m_dataQueue.size();
    *result = data.size(); // 返回添加到队列的数据大小
}

bool SerialPortManager::isOpen()
{
    if (QThread::currentThread() == m_workerThread) {
        bool localResult = false;
        _isOpenSlot(&localResult);
        return localResult;
    }
    bool result = false;
    // 使用 const_cast 是因为 invokeMethod 的槽函数参数不能是 const 的,但逻辑上是 const
    QMetaObject::invokeMethod(const_cast<SerialPortManager*>(this), "_isOpenSlot", Qt::BlockingQueuedConnection,
                              Q_ARG(bool*, &result));
    return result;
}

void SerialPortManager::_isOpenSlot(bool *result) const
{
    if (m_serialPort) {
        *result = m_serialPort->isOpen();
    } else {
        *result = false;
    }
}

QStringList SerialPortManager::availablePorts()
{
    QStringList portNames;
    const auto infos = QSerialPortInfo::availablePorts();
    for (const QSerialPortInfo &info : infos) {
        portNames.append(info.portName());
    }
    return portNames;
}

void SerialPortManager::handleReadyRead()
{
    if (!m_serialPort || !m_serialPort->isOpen() || !m_serialPort->bytesAvailable())
        return;

    QByteArray data = m_serialPort->readAll();
    if (data.isEmpty()) {
        return;
    }
    emit dataReceived(data);
    }
}

void SerialPortManager::handleError(QSerialPort::SerialPortError error)
{
    if (!m_serialPort) return;
    // NoError 也是一个 error 枚举值,但通常我们只关心实际的错误
    if (error != QSerialPort::NoError) {
        emit portError(m_serialPort->errorString());
    }
}

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

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

相关文章

一篇文章玩转CAP原理

CAP 原理是分布式系统设计的核心理论之一&#xff0c;揭示了系统设计中的 根本性权衡。 一、CAP 的定义 CAP 由三个核心属性组成&#xff0c;任何分布式系统最多只能同时满足其中两个&#xff1a; 一致性&#xff08;Consistency&#xff09; 所有节点在同一时刻看到的数据完全…

Vue-收集表单信息

收集表单信息 Input label for 和 input id 关联, 点击账号标签 也能聚焦 input 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><title>表单数据</title><!-- 引入Vue --><scrip…

vscode开发stm32,main.c文件中出现很多报错影响开发解决日志

本质上为 .vscode/c_cpp_properties.json文件和Makefile文件中冲突&#xff0c;两者没有同步。 将makefile文件中的内容同步过来即可&#xff0c;下面给出一个json文件的模板&#xff0c;每个人的情况不同&#xff0c;针对性修改即可 {"configurations": [{"na…

嵌入式鸿蒙系统中水平和垂直以及图片调用方法

利用openharmony操作的具体现象: 第一:Column 作用:沿垂直方向布局的容器。 第二:常用接口 Column(value?: {space?: string | number}) 参数: 参数名参数类型必填参数描述spacestring | number否纵向布局元素垂直方向间距。 从API version 9开始,space为负数或者ju…

【海康USB相机被HALCON助手连接过后,MVS显示无法连接故障。】

在Halcon里使用助手调用海康USB相机时&#xff0c;如果这个界面点击了【是】 那么恭喜你&#xff0c;相机只能被HALCON调用使用&#xff0c;使用MVS或者海康开发库&#xff0c;将查找不到相机 解决方式&#xff1a; 右键桌面【此电脑】图标 ->选择【管理】 ->选择【设备…

2025年电气工程与轨道交通国际会议:绿色能源与智能交通的创新之路

2025年电气工程与轨道交通国际会议&#xff08;ICEERT 2025&#xff09;是一场电气工程与轨道交通领域的国际盛会&#xff0c;将于2025年在武汉隆重召开。此次会议汇聚了全球顶尖的专家学者和行业精英&#xff0c;共同探讨电气工程与轨道交通的最新研究成果和技术趋势。会议将围…

WPF log4net用法

WPF log4net用法 一、在工程中管理NuGet程序包&#xff0c;找到log4net&#xff0c;点击安装&#xff0c;如下图已成功安装&#xff1b; 二、在工程中右键添加新建项&#xff0c;选择应用程序配置文件&#xff08;后缀为.config&#xff09;,然后设置名称&#xff0c;这里设置…

数字孪生数据监控如何提升汽车零部件工厂产品质量

一、汽车零部件工厂的质量挑战 汽车零部件作为汽车制造的基础&#xff0c;其质量直接关系到整车的性能、可靠性和安全性。在传统的汽车零部件生产过程中&#xff0c;质量问题往往难以在早期阶段被发现和解决&#xff0c;导致生产效率低下、生产成本上升&#xff0c;甚至影响到…

贪心算法实战3

文章目录 前言区间问题跳跃游戏跳跃游戏II用最少数量的箭引爆气球无重叠区间划分字母区间合并区间 最大子序和加油站监控二叉树 前言 今天继续带大家进行贪心算法的实战篇3&#xff0c;本章注意来解答一些运用贪心算法的比较难的问题&#xff0c;大家好好体会&#xff0c;怎么…

实测,大模型谁更懂数据可视化?

大家好&#xff0c;我是 Ai 学习的老章 看论文时&#xff0c;经常看到漂亮的图表&#xff0c;很多不知道是用什么工具绘制的&#xff0c;或者很想复刻类似图表。 实测&#xff0c;大模型 LaTeX 公式识别&#xff0c;出乎预料 前文&#xff0c;我用 Kimi、Qwen-3-235B-A22B、…

Linux入门(十一)进程管理

Linux 中每个执行的程序都称为一个进程&#xff0c;每个进程都分配一个ID号&#xff08;PID&#xff09; 每个进程都可能以两种方式存在&#xff0c;前台&#xff08;屏幕上可以操作的&#xff09;和后台&#xff08;屏幕上无法看到的&#xff09;&#xff0c;一般系统的服务都…

【技能篇】RabbitMQ消息中间件面试专题

1. RabbitMQ 中的 broker 是指什么&#xff1f;cluster 又是指什么&#xff1f; 2. 什么是元数据&#xff1f;元数据分为哪些类型&#xff1f;包括哪些内容&#xff1f;与 cluster 相关的元数据有哪些&#xff1f;元数据是如何保存的&#xff1f;元数据在 cluster 中是如何分布…

Linux研学-环境搭建

一 概述 1 Linux 概述 Linux系统由内核、Shell、文件系统、应用程序及系统库等关键部分组成。内核作为核心&#xff0c;管理硬件资源与系统服务&#xff1b;Shell提供用户与系统交互的命令行界面&#xff0c;让用户能便捷执行操作&#xff1b;文件系统负责数据的存储、组织与管…

Ubuntu系统下可执行文件在桌面单击运行教程

目录 ​编辑 操作环境&#xff1a;这个可执行文件在原目录下还有它的依赖文件 1&#xff0c;方法1&#xff1a;创建启动脚本 操作步骤​&#xff1a; &#xff08;1&#xff09;​​在桌面创建脚本文件​​&#xff08;如 run_main_improve.sh&#xff09;&#xff1a; ​…

Linux之文件进程间通信信号

Linux之文件&进程间通信&信号 文件文件描述符文件操作重定向缓冲区一切皆文件的理解文件系统磁盘物理结构&块文件系统结构 软硬链接 进程间通信匿名管道命名管道system V共享内存 信号 文件 首先&#xff0c;Linux下一切皆文件。对于大量的文件&#xff0c;自然要…

代码随想录算法训练营 Day61 图论ⅩⅠ Floyd A※ 最短路径算法

图论 题目 97. 小明逛公园 本题是经典的多源最短路问题。 在这之前我们讲解过&#xff0c;dijkstra朴素版、dijkstra堆优化、Bellman算法、Bellman队列优化&#xff08;SPFA&#xff09; 都是单源最短路&#xff0c;即只能有一个起点。 而本题是多源最短路&#xff0c;即求多…

改写自己的浏览器插件工具 myChromeTools

1. 起因&#xff0c; 目的: 前面我写过&#xff0c; 自己的一个浏览器插件小工具 最近又增加一个小功能&#xff0c;可以自动滚动页面&#xff0c;尤其是对于那些瀑布流加载的网页。最新的代码都在这里 2. 先看效果 3. 过程: 代码 1, 模拟鼠标自然滚动 // 处理滚动控制逻辑…

python-pptx去除形状默认的阴影

文章目录 效果原理1. 阴影继承机制解析2. XML层操作细节3. 注意事项 扩展应用1. 批量去除阴影2. 复合效果控制 效果 右边这个是直接添加一个形状。可以看到它会默认被赋予一个阴影。 然而&#xff0c;这个东西在特定的场合&#xff0c;其实是我们所不需要的。 那怎么把这个阴…

kuboard自带ETCD存储满了处理方案

一、前言 当运行 ETCD 日志报 Erro: mvcc database space exceeded 时&#xff0c;说明 ETCD 存储不足了&#xff08;默认 ETCD 存储是 2G&#xff09;&#xff0c;配额会触发告警&#xff0c;然后 Etcd 系统将进入操作受限的维护模式。 通过下面命令可以查看 ETCD 存储使用情…

SpringBoot+tabula+pdfbox解析pdf中的段落和表格数据

一、前言 在日常业务需求中&#xff0c;往往会遇到解析pdf文件中的段落或者表格数据的需求。 常见的做法是使用 pdfbox 来做&#xff0c;但是它只能提取文本数据&#xff0c;没有我们在文件页面上面的那种结构化组织&#xff0c;文本通常是散乱的包含各种换行回车空格等格式&a…