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

【Qt开发】QSerialPort串口配置、发送、接收回调函数 多线程接收的串口类封装

【Qt开发】QSerialPort串口配置、发送、接收回调函数 多线程串口类的封装

文章目录

  • QSerialPort串口
    • 串口配置
    • 串口扫描
    • 串口复位和初始化
    • 串口接收RX
    • 串口发送TX
    • 串口基本类封装
  • 串口多线程接收
    • 多线程串口类封装
  • 最终效果
  • 附录:C语言到C++的入门知识点(主要适用于C语言精通到Qt的C++开发入门)
    • C语言与C++的不同
    • C++中写C语言代码
    • C语言到C++的知识点
    • Qt开发中需要了解的C++基础知识
      • namespace
      • 输入输出
      • 字符串类型
      • class类
        • 构造函数和析构函数(解析函数)
        • 类的继承

QSerialPort串口

使用QSerialPort需要在pro上面进行配置

QT       += serialport

即可使用串口库

此Qt的串口库具有高度兼容性 不仅可以在Windows、Linux上面使用 还可以在ARM上面使用
所以选择此串口库要比用C++系统的串口库更方便移植

串口配置

串口的相关参数无非就是波特率、数据长度、校验码、停止位、是否硬件流等
这里可以随意定义
其中 数据长度指的是DATA域长度
不像某些嵌入式开发中 数据长度是DATA+校验码长度

public:QSerialPort SerialPort;QList<QString> SerialPort_List;QString SerialPort_Name;uint32_t BaudRate;uint8_t DataBits;uint8_t StopBits;QSerialPort::Parity Parity;QSerialPort::FlowControl FlowControl;

通过相关函数即可配置
如:

bool Ctrl_SerialPort(bool OpenNotClose){if(OpenNotClose){SerialPort.setPortName(SerialPort_Name);SerialPort.setBaudRate(BaudRate);SerialPort.setFlowControl(FlowControl);switch(DataBits){case 5:SerialPort.setDataBits(QSerialPort::Data5);break;case 6:SerialPort.setDataBits(QSerialPort::Data6);break;case 7:SerialPort.setDataBits(QSerialPort::Data7);break;case 8:SerialPort.setDataBits(QSerialPort::Data8);break;default:break;}SerialPort.setParity(Parity);switch(StopBits){case 1:SerialPort.setStopBits(QSerialPort::OneStop);break;case 2:SerialPort.setStopBits(QSerialPort::TwoStop);break;case 3:SerialPort.setStopBits(QSerialPort::OneAndHalfStop);break;default:break;}if (!SerialPort.open(QIODevice::ReadWrite)){return false;}Open_Flag=true;return true;}Open_Flag=false;SerialPort.close();return true;}

串口扫描

通过遍历QSerialPortInfo::availablePorts()即可获得当前设备的所有串口号

    void Scan_SerialPort(void){foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){SerialPort_List.append(info.portName());}}

串口复位和初始化

初始化时 可以用SerialPort.setReadBufferSize(RX_Buf_Size);函数指定RX缓冲区大小 建议与我们自己设置的一致

复位则是用flushclear函数
前者会等待数据传输完毕后清空 后者直接中断传输清空
所以如果想中断串口 就用clear

串口接收RX

readreadAll等函数即可实现读取
一般采用信号槽连接到readyRead()来进行读取

connect(&this->SerialPort, SIGNAL(readyRead()),this, SLOT(SerialPort_RX()));

读取后遍历readAll然后再调用回调即可

    void Scan_SerialPort(void){foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){SerialPort_List.append(info.portName());}}virtual void SerialPort_RX_Callback(MY_SerialPort *port){Q_UNUSED(port);// qDebug()<<port->RX_Data;}

这里的回调是虚函数 其实不算回调 但是用起来一样

串口发送TX

串口的发送其实是在Qt的gui线程中执行 如果线程卡死 则会导致gui卡死或串口无法发送
发送函数为write 此方法返回发送的数据个数
但是该函数是立即返回 所以并不是返回多少就已经发了多少
如果发送未结束就又执行了发送 则会导致发送完成后再发送一遍
所以为了杜绝这种现象 可以在发送前进行清空

 uint32_t SerialPort_TX_Sequence(uint8_t *buf,uint32_t size){if(Open_Flag){return SerialPort.write((char *)buf,size);}return 0;}uint32_t SerialPort_TX(uint8_t *buf,uint32_t size){if(Open_Flag){SerialPort.clear(QSerialPort::Output);SerialPort.write((char *)buf,size);}return 0;}

串口基本类封装

class MY_SerialPort;
class MY_SerialPort : public QObject
{Q_OBJECT
public slots:virtual void SerialPort_RX(void){foreach (const char i,SerialPort.readAll()){RX_Data=i;SerialPort_RX_Callback(this);}}
public:QSerialPort SerialPort;QList<QString> SerialPort_List;QString SerialPort_Name;uint32_t BaudRate;uint8_t DataBits;uint8_t StopBits;QSerialPort::Parity Parity;QSerialPort::FlowControl FlowControl;bool Open_Flag;uint8_t RX_Data;uint8_t *RX_Buf;uint32_t RX_Buf_Size;uint32_t RX_Flag;void Scan_SerialPort(void){foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){SerialPort_List.append(info.portName());}}void Clean_RX(void){RX_Flag=0;RX_Data=0;memset(RX_Buf,0,RX_Buf_Size);}void Reset_SerialPort(void){Scan_SerialPort();BaudRate=115200;DataBits=8;StopBits=1;Parity=QSerialPort::NoParity;FlowControl = QSerialPort::NoFlowControl;if(Open_Flag){SerialPort.clear();SerialPort.flush();}Clean_RX();}bool Ctrl_SerialPort(bool OpenNotClose){if(OpenNotClose){SerialPort.setPortName(SerialPort_Name);SerialPort.setBaudRate(BaudRate);SerialPort.setFlowControl(FlowControl);switch(DataBits){case 5:SerialPort.setDataBits(QSerialPort::Data5);break;case 6:SerialPort.setDataBits(QSerialPort::Data6);break;case 7:SerialPort.setDataBits(QSerialPort::Data7);break;case 8:SerialPort.setDataBits(QSerialPort::Data8);break;default:break;}SerialPort.setParity(Parity);switch(StopBits){case 1:SerialPort.setStopBits(QSerialPort::OneStop);break;case 2:SerialPort.setStopBits(QSerialPort::TwoStop);break;case 3:SerialPort.setStopBits(QSerialPort::OneAndHalfStop);break;default:break;}if (!SerialPort.open(QIODevice::ReadWrite)){return false;}Open_Flag=true;return true;}Open_Flag=false;SerialPort.close();return true;}uint32_t SerialPort_TX_Sequence(uint8_t *buf,uint32_t size){if(Open_Flag){return SerialPort.write((char *)buf,size);}return 0;}uint32_t SerialPort_TX(uint8_t *buf,uint32_t size){if(Open_Flag){SerialPort.clear(QSerialPort::Output);SerialPort.write((char *)buf,size);}return 0;}virtual void SerialPort_RX_Callback(MY_SerialPort *port){Q_UNUSED(port);// qDebug()<<port->RX_Data;}MY_SerialPort(uint32_t Buf_Size=256){SerialPort_Name="";Open_Flag=false;RX_Buf_Size = Buf_Size;RX_Buf = new uint8_t[RX_Buf_Size];SerialPort.setReadBufferSize(RX_Buf_Size);Reset_SerialPort();connect(&this->SerialPort, SIGNAL(readyRead()),this, SLOT(SerialPort_RX()));}~MY_SerialPort(){Open_Flag=false;SerialPort.close();delete RX_Buf;}
};

串口多线程接收

在上面 我们由于用到了信号槽连接 所以得继承QObject
那么如果用多线程方式 就直接采用QObject下面的moveToThread方法即可

这里我用的多线程封装也是由我之前的函数来的 参考:
【Qt开发】多线程QThread(通过QObject::moveToThread)和QMutex互斥锁的配置和基本函数

不过 在调用多线程的时候 先前我们普通方法所使用的readyRead()会直接无效(如果加上去 不会触发接收 并且发送也会出问题)

if(Thread_Flag){this->moveToThread(&workerThread);this->stopWork(false);isCanStart=true;connect(&workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));connect(&workerThread, SIGNAL(finished()), &workerThread, SLOT(deleteLater()));connect(this, SIGNAL(Start_RX_Thread()), this, SLOT(RX_Thread()));}else{connect(&this->SerialPort, SIGNAL(readyRead()),this, SLOT(SerialPort_RX()));}

那么我们的多线程接收就不能通过readyRead()信号
只能在打开串口时开启多线程 然后在多线程循环里面完成
打开串口后打开线程

if(this->Thread_Flag){startThread();}

同样 关闭串口前要先关闭线程

stopThread(true);

通过发送信号转到多线程的函数槽

    bool startThread(void){if(!isCanRun && isCanStart){isCanStart=false;workerThread.start();emit this->Start_RX_Thread();return true;}return false;}

在多线程函数里面 设置互斥锁
通过SerialPort.waitForReadyRead(1);监控是否有数据可读 可读则执行回调

void RX_Thread(void){this->isCanRun=true;//执行while(isCanRun){QMutexLocker locker(&this->lock);SerialPort.waitForReadyRead(1);SerialPort_RX_Thread_Callback();}isCanStart=true;}

多线程串口类封装

这里的回调我都设置的是虚函数 跟上文一样 方便更改

class MY_SerialPort_Thread;
class MY_SerialPort_Thread : public QObject
{Q_OBJECT
private:QMutex lock;bool isCanRun;bool isCanStart;bool Thread_Flag;
public slots:void SerialPort_RX(void){SerialPort_RX_Ready_Callback();}void RX_Thread(void){this->isCanRun=true;//执行while(isCanRun){QMutexLocker locker(&this->lock);SerialPort.waitForReadyRead(1);SerialPort_RX_Thread_Callback();}isCanStart=true;}signals:void Start_RX_Thread(void);public:QSerialPort SerialPort;QList<QString> SerialPort_List;QString SerialPort_Name;uint32_t BaudRate;uint8_t DataBits;uint8_t StopBits;QSerialPort::Parity Parity;QSerialPort::FlowControl FlowControl;bool Open_Flag;uint8_t RX_Data;uint8_t *RX_Buf;uint32_t RX_Buf_Size;uint32_t RX_Flag;public:QThread workerThread;void Stop_Port(uint8_t TX_RX_All){switch(TX_RX_All){case 0:SerialPort.clear(QSerialPort::Output);break;case 1:SerialPort.clear(QSerialPort::Input);break;default:SerialPort.clear();SerialPort.flush();break;}}void Scan_SerialPort(void){foreach (const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){SerialPort_List.append(info.portName());}}void Clean_RX(void){RX_Flag=0;RX_Data=0;memset(RX_Buf,0,RX_Buf_Size);}void Reset_SerialPort(void){Scan_SerialPort();BaudRate=115200;DataBits=8;StopBits=1;Parity=QSerialPort::NoParity;FlowControl = QSerialPort::NoFlowControl;if(Open_Flag){Stop_Port(2);}Clean_RX();}bool Ctrl_SerialPort(bool OpenNotClose){if(OpenNotClose){SerialPort.setPortName(SerialPort_Name);SerialPort.setBaudRate(BaudRate);SerialPort.setFlowControl(FlowControl);switch(DataBits){case 5:SerialPort.setDataBits(QSerialPort::Data5);break;case 6:SerialPort.setDataBits(QSerialPort::Data6);break;case 7:SerialPort.setDataBits(QSerialPort::Data7);break;case 8:SerialPort.setDataBits(QSerialPort::Data8);break;default:break;}SerialPort.setParity(Parity);switch(StopBits){case 1:SerialPort.setStopBits(QSerialPort::OneStop);break;case 2:SerialPort.setStopBits(QSerialPort::TwoStop);break;case 3:SerialPort.setStopBits(QSerialPort::OneAndHalfStop);break;default:break;}if (!SerialPort.open(QIODevice::ReadWrite)){return false;}Open_Flag=true;if(this->Thread_Flag){startThread();}return true;}stopThread(true);Open_Flag=false;SerialPort.close();        return true;}uint32_t SerialPort_TX_Sequence(uint8_t *buf,uint32_t size){if(Open_Flag){return SerialPort.write((char *)buf,size);}return 0;}uint32_t SerialPort_TX(uint8_t *buf,uint32_t size){if(Open_Flag){SerialPort.clear(QSerialPort::Output);return SerialPort.write((char *)buf,size);}return 0;}virtual void SerialPort_RX_Ready_Callback(void){foreach (const char i,SerialPort.readAll()){RX_Data=i;SerialPort_RX_Callback(this);}}virtual void SerialPort_RX_Thread_Callback(void){foreach (const char i,SerialPort.readAll()){RX_Data=i;SerialPort_RX_Callback(this);}}virtual void SerialPort_RX_Callback(MY_SerialPort_Thread *port){Q_UNUSED(port);qDebug()<<port->RX_Data;}void stopWork(bool Wait_Flag){this->isCanRun = false;if(Wait_Flag){QMutexLocker locker(&this->lock);}}bool startThread(void){if(!isCanRun && isCanStart){isCanStart=false;workerThread.start();emit this->Start_RX_Thread();return true;}return false;}bool stopThread(bool Wait_Flag){if(workerThread.isRunning()){stopWork(Wait_Flag);return true;}return false;}void closeThread(void){stopThread(false);this->workerThread.quit();}MY_SerialPort_Thread(uint32_t Buf_Size=256,bool ThreadNotNormal=false){SerialPort_Name="";Open_Flag=false;RX_Buf_Size = Buf_Size;RX_Buf = new uint8_t[RX_Buf_Size];SerialPort.setReadBufferSize(RX_Buf_Size);this->Thread_Flag = ThreadNotNormal;Reset_SerialPort();if(Thread_Flag){this->moveToThread(&workerThread);this->stopWork(false);isCanStart=true;connect(&workerThread, SIGNAL(finished()),this, SLOT(deleteLater()));connect(&workerThread, SIGNAL(finished()), &workerThread, SLOT(deleteLater()));connect(this, SIGNAL(Start_RX_Thread()), this, SLOT(RX_Thread()));}else{connect(&this->SerialPort, SIGNAL(readyRead()),this, SLOT(SerialPort_RX()));}}~MY_SerialPort_Thread(){Open_Flag=false;SerialPort.close();delete RX_Buf;closeThread();}
};

最终效果

通过一个按钮发送数据 然后TX和RX短接

void sonwindow::on_pushButton_3_clicked()
{uint8_t buf[10]={0xAA,0x55,0x01,0x02,0xFF,0x0D,0x0A,0x00,0x00,0xFF};qDebug()<<UART0->SerialPort_TX(buf,5);
}

转到回调直接打印当前数据

    virtual void SerialPort_RX_Callback(MY_SerialPort_Thread *port){Q_UNUSED(port);qDebug()<<port->RX_Data;}

效果:
在这里插入图片描述

附录:C语言到C++的入门知识点(主要适用于C语言精通到Qt的C++开发入门)

C语言与C++的不同

C语言是一门主要是面向工程的语言
C++则是面向对象

C语言中 某些功能实现起来较为繁琐
比如结构体定义:

一般写作:

typedef struct stu_A
{
}A;

也可以写作:

typedef struct 
{
}A;

但 大括号后面的名称是不可省去的

不过 C++的写法就比较简单
除了支持上述写法外

也支持直接声明

typedef struct A
{
}

另外 C++是完全支持C语言库和语法的
不过C++里面的库也有些很方便的高级功能用法 只不过实现起来可能不如C的速度快

再者 C语言与C++的编译流程不一样
C语言没有函数重载 所以给编译器传参就是直接传函数名称
但是C++除了传函数名称外 还会穿函数的参数、类型等等 以实现函数重载

C++中写C语言代码

上文提到 C++可以完全兼容C的写法
但是编译流程也还是不一样
所以如果在编译层面进行C语言代码编译 则通常用以下方法:

extern "C"
{
...
}

表面大括号内的内容用C的方法进行编译

另外 如果还是用C++的编译器 但要实现C语言函数 则需要用到C语言的库

在C语言中 我们一般用如下方法导入库

#include <stdio.h>

此方法同样适用于C++ 但是C++可以更方便的写成去掉.h的方式
比如:

#include <iostream>

在C++中 为了调用C语言的库 可以采用在原库名称前加一个"c"的方式导入
如:

#include <cstdio>

这样就可以使用printf等函数了 甚至比C++的std方法更快

C语言到C++的知识点

在这里插入图片描述

Qt开发中需要了解的C++基础知识

namespace

C++面向对象的特性下诞生的一个名称
表示某个函数、变量在某个集合下 用作namespace
比如 <iostream>库中的关键字cin在std下 则写作std::cin
std就是namespace
::表示某空间下的某某
前面是空间名称 后面是变量、函数名称

using namespace可以告诉编译器以下都用xx名称空间
比如:

using namespace std;
cout<<"a";

如果没有告诉编译器所使用的空间名称 则要写成:

std::cout<<"a";

同样 可以自定义某一段代码属于哪个空间:

namespace xx
{
...
}

输入输出

在C++中 用iostream作为输入输出流的库

#include <iostream>

用cin和cout关键字进行输入和输出
如:

using namespace std;
int a=0;
cin>>a; //输入到acout<<a;  //输出a

类比scanf和printf
同样 还有一个关键字endl表示换行
cout和cin的传参是不固定的
由编译器自行裁定

字符串类型

在C语言中 常用char *表示字符串
但是在C++中 可以直接用string类型
比如:

char * s="456";
string str="123";

由于cout的特性 这两种字符串都可以直接打印
但如果使用C语言中printf的打印方式时 采用%s方式打印字符串 则不能传入string类型

class类

C++的核心就是class
同Python等支持面向对象的语言一样
可以理解成一个支持函数、继承、自动初始化、销毁的结构体
在class类中 有private私有、public公有变量
前者只能内部访问 后者可以外部调用使用
如:

class A
{
public:
int a;
private:
int b;
}

a可以用A.a的方式方位 b则外部无法访问

构造函数和析构函数(解析函数)

构造函数可以理解成对类的初始化 反之析构函数则是退出时进行销毁前的函数
两者需要与类的名称相同 析构函数则在前面加一个~表示非
如:

class A
{
public:
int a;
A();
~A();
private:
int b;
}A::A()
{
...
}A::~A()
{
...
}

构造函数可以定义传参 析构函数则不行

类的继承

如果有两个类A和B 想让A里面包含B 则可以写作继承的写法
继承后 A类的变量可以直接调用B下面的成员
如:

class B
{
int b;
}
class A: public B
{
int a;
}

在定义A后 可以访问到B的成员b 当然 继承也可以私有

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【python基础】—离线环境下,在linux中安装python包(以sqlalchemy为例)
  • SpringBoot获取不到Nacos配置信息报错,Nacos鉴权
  • 基于Python的上市公司年报数字化词频统计:深入解析与实战
  • 倒计时:可添加可删除的倒计时函数
  • 从零开始手写STL库:Map
  • C++:list类(迭代器)
  • Spring SSM框架--MVC
  • [数据集][目标检测]手钳检测数据集VOC+YOLO格式141张1类别
  • 一:《Python基础语法汇总》— 数据类型与输入输出
  • sql总结
  • C++入门——21特殊的类
  • PostgreSQL下载、安装(Windows 10/11 64位)详细教程【超详细,保姆级教程!!!】
  • python——常见创建型设计模式
  • 仿RabbitMq实现简易消息队列正式篇(路由匹配篇)
  • BFS解决单源最短路问题
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • EOS是什么
  • flutter的key在widget list的作用以及必要性
  • IDEA 插件开发入门教程
  • Java教程_软件开发基础
  • jQuery(一)
  • js数组之filter
  • magento 货币换算
  • PHP面试之三:MySQL数据库
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • react-native 安卓真机环境搭建
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 安装python包到指定虚拟环境
  • 服务器从安装到部署全过程(二)
  • 简单数学运算程序(不定期更新)
  • 世界上最简单的无等待算法(getAndIncrement)
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • - 转 Ext2.0 form使用实例
  • Linux权限管理(week1_day5)--技术流ken
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • ​十个常见的 Python 脚本 (详细介绍 + 代码举例)
  • #Datawhale AI夏令营第4期#AIGC文生图方向复盘
  • #NOIP 2014#Day.2 T3 解方程
  • #pragma预处理命令
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • (12)Hive调优——count distinct去重优化
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (Oracle)SQL优化技巧(一):分页查询
  • (web自动化测试+python)1
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (层次遍历)104. 二叉树的最大深度
  • (简单) HDU 2612 Find a way,BFS。
  • (三)mysql_MYSQL(三)
  • (五)MySQL的备份及恢复
  • (转)Linq学习笔记