stm32 串口接收不定长度数据及黏包处理 + 串口DMA接收 您所在的位置:网站首页 拓扑数据结构的优缺点分析图 stm32 串口接收不定长度数据及黏包处理 + 串口DMA接收

stm32 串口接收不定长度数据及黏包处理 + 串口DMA接收

2023-12-01 22:38| 来源: 网络整理| 查看: 265

1.不定长度数据 为什么会存在串口接收不定长度数据呢?

首先,在通信双方进行数据传输的时候,由于不同的设备在实现控制,数据采样时,发送的数据指令字节数量存在着差异,就产生了串口接收不定长度数据一说,那么stm32串口是如何实现接收不定长度数据的呢? 串口接收数据一般会采用串口中断方式自动接收,要想接收不定长度数据,就需要让单片机在接收完成一帧数据之后,自动告知系统数据已经接收完成了,这个过程其实都是通过单片机的中断机制实现的,stm32单片机串口接收有一个空闲中断机制,只要我们在配置中断的时候,串口接收的空闲中断IDLE打开,就可实现接收不定长度的数据,让单片机在接收完成之后,触发中断通知用户进行中断处理。 那么,我们应该怎么去实现不定长度数据的接收过程呢?下面我来讲讲,不定长度数据接收的实现思路: 首先,我们需要要定义一个接收数据的缓冲区,一般用数组接收数据,数据缓冲区的大小要足够大才不会造成数据溢出。比如我们在通信中,要发送的一帧数据最大的字节数为32个字节,在很短的时间内(如 1ms),连续发送指令帧数在10帧左右的数据,而接收方处理一帧数据需要用1ms才能执行完这帧数据的相关操作,那么这10帧数据在这个时间内是不能得到处理的,因此我们需要定义的缓冲区大小为10*32=320字节,为了进一步留有余量,我们会将缓冲红区大小定义为512字节,如 netBuf【512】。 接着,我们需要在串口初始化时开启串口的空闲中断和接收中断。然后在有中断产生时,我们需要在串口中断函数里判断是空闲中断还是正常接收一个字节数据引起的接收中断,如果是正常接收字节的中断,那么我们需要把接收到的这个字节数据存放到缓冲数组中,并定义一个全局变量recLen来统计接收的字节数,每当接收一个字节时recLen 增加1,如果是IDLE空闲中断,表示串口数据已经接收完成了,我们需要在IDEL中断处理函数中设置一个数据接收完成标志位表示已经完整的接收到一帧数据了,如:RecFlag=1; 这样通过串口空闲中断的通知,我们就知道了数据什么时候数据接收完成,并且也知道接收了多少个字节的数据,这就实现了不定长度数据的接收。

2.串口数据的黏包 什么是数据黏包呢?为什么会发生数据黏包呢?

我们知道,在通信中,数据发送方如果在很短的时间内连续发送多帧数据,在数据的传输过程中由于线路延时,或者发送端发送多个数据包的时间延时很小,导致几个数据包几乎同时到达接收端(数据包到达接收端的时间间隔小于一个字节时间),这样单片机接收数据时就会将这几帧数据都存在了数缓冲区,对接收方来说,在取取数据的时候,识别到的接收字节数其实是多帧数据的总字节数,而不是一帧数据的字节长度,这就是数据的黏包由来。 而接收方处理每一帧数据都是要花费一定的时间的,比如我们在上面说的发送发在5ms 内发送了10帧数据,而接收方处理一帧数据需要用1ms才能执行完这帧数据的相关操作,那么这10帧数据在这个时间内是不能得到处理的,对接收方来说,接收到第一帧数据后,还没来得及处理,第二帧数据就来了,出现这种情况我们只能先将数据保存在一个数组缓冲区里,实现数据边接收边处理,这里我们说的数据处理一般包括:数据接收、传存、识别(看是不是发给自己的数据)、校验、解析、执行等过程。

3.如何实现黏包数据的解析 串口数据发生黏包了,应该怎么处理呢?

想要实现串口数据黏包的解析,这就需要通信双方定义一定的数据格式协议来实现了。一般来说,实现数据黏包解析,最主要的是通信双方要定义好数据头和数据长度的数据格式,比如说我们定义的通信数据格式为:数据头(2byte)+发送者ID(1byte)+接收者ID(1byte)+ 命令码(1byte)+ 数据长度(1byte)+CRC校验(2byte)。定义好数据格式后,收发双方在发送数据时都要严格按照这个协议的数据格式来发送数据。 这样数据协议的格式定义好以后就很容易在数据接收后实现黏包数据的解析了,我只需要在取提取数据包的时候,按照协议约定数据头和数据段长度来识别一帧数据的范围,例如我们定义的数据头为:0xaa 0x55 这两个数据,在解析接收缓冲数组时,我们只需要去检测识别 aa 55 这连个数据,就可以判断为这是一帧数据的起始字节,然后再根据数据长度字节,提取出 n byte 的数据段和 2 byte 的校验值,就可以完成一个数据包的提取。

4.stm32 DMA接收串口数据

关于stm32 DMA 的知识点,这里就不做分析了,简单来说串口使用DMA来接收数据,可以减轻CPU的负担,DMA可以理解为CPU的助手,只要启用的DMA接收数据,当有数据来的时候,CPU就会通知DMA去把数据接收并保存起来,这过程CPU是全权交个DMA来处理的,CPU就不在取干预这个接收过程了,这样就有空余的时间去处理更重要的事情了,所以使用DMA 可以提高CPU的效率。 DMA 的介绍论坛里有很多帖子写的非常好,大家可以自己去搜索。

5.具体实现方法 我们以stm32f103ze 为例子,用 cubemx HAL 库开发。cubemx 配置过程如下:

(1)新建cubemx工程 选择stm32f103ze芯片 (2)开启外部高速时钟HSE,配置好系统时钟树 图1-1:时钟配置1 图1-2:时钟配置2 (3)配置串口1 ,启用DMA传输,使能中断 图:1-3 串口配置 图1-4:串口中断配置 DMA 接收配置成循环模式,数据位宽默认为8位。 图1-5:DMA接收方式配置1 图1-6:DMA接收方式配置2 (4)设置好工程名称和保存位置,选择自己用的开发工具和版本,然后生成工程代码就可以开始编写我们自己的代码了。 图1-7: 工程配置1 图1-8:工程配置2

6.串口数据黏包解析演示

按照上面定制的协议,通信数据格式为:数据头(2byte)+发送者ID(1byte)+接收者ID(1byte)+ 命令码(1byte)+ 数据长度(1byte)+CRC校验(2byte)。 下面我们就根据约定的协议格式来定义具体的数据头,例如我们定义一个命令来查询单片机PH值传感器1的数值是多少。 数据包定义如下: 数据头:0xAA55 发送者ID:0x01 接收者ID:0x02 命令码: 0x01 //查询PH值传感器1命令 随意约定命令代码 0x02 //响应PH查询命令 0x03 //设置传感器PH值上限命令 0x10 //设置成功 **数据长度:**xx CRC校验: 0xB1B5 // CRC 16 Modbus 那么发送端发送的查询数据的命令为:aa 55 01 02 01 00 b5 b1 接收查询命令响应的数据发送格式也要按照约定的数据格式发送出去: 响应数据为:aa 55 02 01 02 02 00 07 12 91

如此约定了数据格式,如果真的发生粘包的情况,解析数据也很方便了,我们只需要找到数据头的标志0xAA55,然后读取该数据包代表数据长度的字节存放的存储位置,就可以得到数据长度,比如数据包:aa 55 02 01 02 02 00 07 12 91 数据长度的位置就在AA这个字节之后的第五个字节,假设此时AA字节在缓冲数组的位置为RecBuf[i],那么长度字节存放的位置就是RecBuf[i+5],取出RecBuf[i+5]中的数据为2,说明该字节之后有2个字节的数据,再加上CRC的2个字节,我们就需要在RecBuf[i+5]之后还要取出4个字节的数据,才能完整的取出这个小数据包:aa 55 02 01 02 02 00 07 12 91 需要注意的是我们再拆包的过程中要重复考虑其中存在的问题,比如: 数据长度错误时的数量,找不到数据头时循环变量的修正等。 图1-9:PH值查询指令 **命令2:**设置PH值上限指令 aa 55 01 02 03 02 00 0b 57 5b //设置PH上限为 11 【返回数据】:aa 55 02 01 10 00 49 a5 // 返回设置成功指令 0x10 图1-10:PH值设置指令 接下来,我们测试一下数据粘包的情况,我们把之前的2个命令合在一起发送出去,红色数据用来模拟有干扰的情况下,数据出错了。 a2 55 aa 55 01 02 01 00 b5 b1 32 15 8e 20 aa 55 01 02 03 02 00 0b 57 5b 68 21 结果如下: 在串口收到这一帧数据后,调用拆包函数进行数据解析,按照数据头逐个进行比较只要找到正确的数据头,就按照约定的数据长度去取数据,取到一个小包数据后进行CRC校验,校验正确后才执行命令。第一个小包执行完成后接着去取下一个包,然后执行,直到把接收到的数据解析并执行完成。 图1-11:黏包处理结果 中断接收方式: 采用中断方式,只需要在宏定义 #define UsartDMA 0/1 配置成0 就可以了,数据处理结果跟用DMA方式一样,这里不再贴图。

7.代码实现 (1)编写myusart.h 文件 在myusart.h 文件中我们主要定义串口数据接收的结构体类型 #ifndef _MYUSART_H_ #define _MYUSART_H_ #include "main.h" #include "usart.h" #include "string.h" #include "stdio.h" #define UsartBufSize 512 #define UsartDMA 1 // 是否启用串口DMA 1启用 0停止 typedef struct { uint8_t *ReadPtr; //读书节指针 uint8_t RecFlag; //接收标志 uint8_t ProFinsh; //数据处理完成标志 uint8_t RecBuf[UsartBufSize]; //数据缓冲区 uint16_t InAllLen; //缓冲区存放数据长度 uint16_t CFraLen; //当前帧长度 最近接受的帧长度 uint16_t ReadNBytes; //需要读取的字节数 UART_HandleTypeDef *huart; //串口号 } NetDat_TypeDef; void NetDateRec(NetDat_TypeDef *NetDat); void My_Usart_Init(UART_HandleTypeDef *huart,NetDat_TypeDef *NetDat); #endif (2)编写myusart.c文件 #include "myusart.h" #include "netdatpro.h" uint16_t temp,lastTemp=0; extern UART_HandleTypeDef huart1; extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart1_tx; NetDat_TypeDef Usart1_NetDate; //重定向c库函数printf到USARTx struct __FILE //标准库需要的支持函数 { int handle; }; int fputc(int ch, FILE *f) { while((huart1.Instance->SR&0X40)==0);//循环发送,直到发送完毕 huart1.Instance->DR = (uint8_t)ch ; return (ch); } //网络数据接收函数 void NetDateRec(NetDat_TypeDef *NetDat) { #if UsartDMA == Enable // static uint16_t temp=0,lastTemp=0; if(__HAL_UART_GET_FLAG(NetDat->huart,UART_FLAG_IDLE))//检测是否有IDE中断 { NetDat->RecFlag = 1;//接受完成标志位置1 // HAL_UART_DMAStop(NetDat->huart);//停止DMA接收,每次来新数据时都会从缓存 } // ..... } 完整代码,请搜索:stm32 HAL库 串口DMA接收不定长度数据及粘包处理 链接:https://wenku.baidu.com/view/33f0e8ba4a35eefdc8d376eeaeaad1f346931191


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有