您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > stm32-HAL库-串口DMA接收不定长度数据及粘包处理
1串口接收不定长度数据及数据粘包解析的实现1、如何让串口接收不定长度数据想让Stm32串口接收不定长度数据,这就需要我们开启串口空闲中断(IDLE)方式,所谓串口空闲中断指的是stm32的数据总线在接收数据的过程中,如果总线在接收一个字节所需要的时间内没有再接收到数据,单片机就会判定此时数据已经接收完成了,这时单片机会自动触发空闲中断IDLE标志位,引发空闲中断,我们只需要进入中断取数据就可以了。使用IDLE空闲中断我们就可以用串口接收任意长度的数据了。2、串口接收不定长度数据的实现思路我们实现串口接收不定长度数据的思路是:首先我们要定义一个接收数据的缓冲区,一般用数组接收数据,在串口初始化时要开启串口的空闲中断和接收中断。然后在有中断产生时,我们需要在串口中断函数里判断是空闲中断还是正常接收一个字节数据引起的接收中断,如果是正常接收字节的中断,那么我们需要把接收到的这个字节数据存放到缓冲数组中,如果是IDLE空闲中断,表示串口数据已经接收完成了,我们需要在IDEL中断处理函数中设置一个数据接收完成标志位表示已经完整的接收到一帧数据了,如:RecFlag=1;3、数据粘包解析的实现思路数据粘包是多个数据包发送时由于线路延时,或者发送端发送多个数据包的时间延时很小,导致几个数据包几乎同时到达接收端(数据包到达接收端的时间间隔小于一个字节时间),这样单片机接收数据时就会将这几个数据包当做一帧数据来接收存放。那么我们如何将这几个数据包合成的一帧数据拆解成几个数据包呢?其实,实现的方法也很简单,这就需要我们在发送端和接收端的数据格式上做一个统一的约定,约定了统一的数据发送格式在发送数据时就严格按照这个格式来发送。一般来说约定的格式我们要明确规定数据头和数据长度。然后我们再根据定义的数据头是什么数据,在这一帧数据中逐个去判断当前数据是不是数据头,如果是就说明这个是一个小数据包的开始位置,在根据数据长度就可以解析出一个数据包了。例如,我们约定发送的数据格式为:数据头(2byte)+发送者ID(1byte)+接收者ID(1byte)+命令码(1byte)+数据长度(1byte)+CRC2校验(2byte)。这样我们定义了标准的数据格式就容易拆包了。4、下面我们就根据约定的数据发送格式来定义具体的数据头,例如我们定义一个数据包命令来查询接收命令的单片机PH值传感器1的数据是多少。数据包定义如下:数据头:0xAA55发送者ID:0x01接收者ID:0x02命令码:0x01//查询PH值传感器1命令随意约定命令代码0x02//响应PH查询命令0x03//设置传感器PH值上限命令0x10//设置成功数据长度:xxCRC校验:0xB1B5//CRC16Modbus那么发送端发送的查询数据为:aa5501020100b5b1接收查询命令响应的数据发送格式也要按照约定的数据格式发送出去:响应数据为:aa550201020200071291如此约定了数据格式,如果真的发生粘包的情况,解析数据也很方便了,我们只需要找到数据头的标志0xAA55,然后读取该数据包代表数据长度的字节存放的存储位置,就可以得到数据长度,比如数据包:aa550201020200071291数据长度的位置就在AA这个字节之后的第五个字节,假设此时AA字节在缓冲数组的位置为RecBuf[i],那么长度字节存放的位置就是RecBuf[i+5],取出RecBuf[i+5]中的数据为2,说明该字节之后有2个字节的数据,再加上CRC的2个字节,我们就需要在RecBuf[i+5]之后还要取出4个字节的数据,才能完整的取出这个小数据包:aa550201020200071291需要注意的是我们再拆包的过程中要重复考虑其中存在的问题,比如:数据长度错误时的数量,找不到数据头时循环变量的修正等。5、基于cubemxHAL库的实现方法(1)新建cubemx工程选择stm32f103ze芯片(2)开启外部高速时钟HSE,配置好系统时钟树3(3)配置串口1,启用DMA传输,使能中断45DMA接收配置成循环模式,数据位宽默认为8位DMA发送配置成正常模式,数据位宽默认为8位6(4)设置好工程名称和保存位置,选择自己用的开发工具和版本,然后生成工程代码就可以开始编写我们自己的代码了。6、我们先看结果首先,我们先测试单个命令发送(没有粘包)命令1:查询PH指令aa5501020100b5b1收到查询指令后,进行解析然后执行查询命令,向主机返回PH数据【返回数据】:aa550201020200071291//返回PH值等于77命令2:设置PH值上限指令aa5501020302000b575b//设置PH上限为11【返回数据】:aa550201100049a5//返回设置成功指令0x108接下来,我们测试一下数据粘包的情况我们把之前的2个命令合在一起发送出去,红色数据用来模拟有干扰的情况下,数据出错了。a255aa5501020100b5b132158e20aa5501020302000b575b6821结果如下:在串口收到这一帧数据后,调用拆包函数进行数据解析,按照数据头逐个进行比较只要找到正确的数据头,就按照约定的数据长度去取数据,取到一个小包数据后进行CRC校验,校验正确后才执行命令。第一个小包执行完成后接着去取下一个包,然后执行,直到把接收到的数据解析并执行完成。9中断接收方式:采用中断方式,只需要在宏定义#defineUsartDMA0/1配置成0就可以了,数据处理结果跟用DMA方式一样,这里不再贴图。7、代码实现(1)编写myusart.h文件在myusart.h文件中我们主要定义串口数据接收的结构体类型#ifndef_MYUSART_H_#define_MYUSART_H_#includemain.h#includeusart.h#includestring.h#includestdio.h#defineUsartBufSize51210#defineUsartDMA1//是否启用串口DMA1启用0停止typedefstruct{uint8_t*ReadPtr;//读书节指针uint8_tRecFlag;//接收标志uint8_tProFinsh;//数据处理完成标志uint8_tRecBuf[UsartBufSize];//数据缓冲区uint16_tInAllLen;//缓冲区存放数据长度uint16_tCFraLen;//当前帧长度最近接受的帧长度uint16_tReadNBytes;//需要读取的字节数UART_HandleTypeDef*huart;//串口号}NetDat_TypeDef;voidNetDateRec(NetDat_TypeDef*NetDat);voidMy_Usart_Init(UART_HandleTypeDef*huart,NetDat_TypeDef*NetDat);#endif(2)编写myusart.c文件#includemyusart.h#includenetdatpro.huint16_ttemp,lastTemp=0;externUART_HandleTypeDefhuart1;externDMA_HandleTypeDefhdma_usart1_rx;externDMA_HandleTypeDefhdma_usart1_tx;NetDat_TypeDefUsart1_NetDate;//重定向c库函数printf到USARTxstruct__FILE//标准库需要的支持函数{inthandle;};intfputc(intch,FILE*f){while((huart1.Instance-SR&0X40)==0);//循环发送,直到发送完毕huart1.Instance-DR=(uint8_t)ch;return(ch);}//网络数据接收函数voidNetDateRec(NetDat_TypeDef*NetDat)11{#ifUsartDMA==Enable//staticuint16_ttemp=0,lastTemp=0;if(__HAL_UART_GET_FLAG(NetDat-huart,UART_FLAG_IDLE))//检测是否有IDE中断{NetDat-RecFlag=1;//接受完成标志位置1//HAL_UART_DMAStop(NetDat-huart);//停止DMA接收,每次来新数据时都会从缓存起始地址开始存放lastTemp=temp;temp=hdma_usart1_rx.Instance-CNDTR;//获取DMA中未传输的数据个数,NDTR寄存器分析见下面if((lastTemp==0&&temp==sizeof(NetDat-RecBuf))||lastTemp==temp)//lastTemp==temp前2次都是一次接满缓冲区NetDat-CFraLen=sizeof(NetDat-RecBuf);elseif(lastTemp==0)//首次进入NetDat-CFraLen=sizeof(NetDat-RecBuf)-temp;//计算当前接收的帧长度elseNetDat-CFraLen=lastTemp-temp;//计算当前接收的帧长度if(NetDat-CFraLensizeof(NetDat-RecBuf))//数据从缓冲区溢出NetDat-CFraLen=lastTemp+sizeof(NetDat-RecBuf)-temp;//有溢出了修正接收到的字节数if(temp!=sizeof(NetDat-RecBuf))//缓存没满满的时候temp等于缓冲区大小NetDat-InAllLen=sizeof(NetDat-RecBuf)-temp;//总计数减去未传输的数据个数,得到已经接收的数据个数elseNetDat-InAllLen=sizeof(NetDat-RecBuf);//缓存刚好满时修正总数为缓存大小NetDat-ReadNBytes=NetDat-InAllLen;//计算需要读取的字节数if(NetDat-InAllLenNetDat-CFraLen)//接收总长度小于当前帧长度时缓冲区溢出NetDat-ReadNBytes=NetDat-InAllLen+sizeof(NetDat-RecBuf);//修正要需要读取的字节数HAL_UART_Receive_DMA(NetDat-huart,NetDat-RecBuf,sizeof(NetDat-RecBuf));//开启DMA接收NetDat-ProFinsh=0;__HAL_UART_CLEAR_IDLEFLAG(NetDat-huart);//清除标志位}#elseuint8_ttemp=0;staticunsignedintnum;//接收计数staticuint8_tRecTimes=0;//查询是否发生了空闲中断12if(__HAL_UART_GET_FLAG(NetDat-huart,UART_FLAG_IDLE)!=RESET&&__HAL_UART_GET_IT_SOURCE(NetDat-huart,UART_IT_IDLE)!=RESET){NetDat-CFraLen=num;//发生空闲中断,将数据长度写入到结构体NetDat-ReadNBytes=num;//发生空闲中断,将数据长度写入到结构体num=0;RecTimes=0;NetDat-RecFlag=1;NetDat-ProFinsh=0;//待处理__HAL_UART_CLEAR_IDLEFLAG(NetDat-huart);//清除空闲中断}elseif(__HAL_UART_GET_IT_SOURCE(NetDat-huart,UART_IT_RXNE)!=RESET)//帧传输未完成,按字节接收{#ifdefSTM32F0__HAL_UART_CLEAR
本文标题:stm32-HAL库-串口DMA接收不定长度数据及粘包处理
链接地址:https://www.777doc.com/doc-7417862 .html