当前位置: 首页 > 科技观察

STM32串口开发环形缓冲区

时间:2023-03-21 18:41:29 科技观察

01简介在上一篇《stm32 串口详解》中,我们讲解了串口的基本应用,利用串口中断接收数据,利用串口中断发送返回包(一般,不间断的形式可以用来发送返回包,在数据接收不频繁的应用中。中断串口接收保证串口数据的及时响应,可以不间断的方式发送返回包).下面的《STM32使用DMA接收串口数据》和?文章分别讲解了如何使用DMA辅助串口收发数据。使用DMA的好处是串口不需要CPU就可以收发数据,减轻了CPU的负担。有用。除了以上两种场景,还有一种应用场景:串口接收数据长度位置,频率未知,不需要实时处理。如果采用上述方案,接收到一帧数据并立即处理,则传入的数据包在处理过程中“丢失”了。这时候就需要一个缓冲队列来解决这个问题。02Buffer缓冲区,顾名思义,就是用来缓冲数据的。缓冲区最简单的实现方式是定义多个数组,接收一包数据到数组A,并将接收到的数据的地址替换为数组B,并且每个数据都有一个标志字节来表示数组是否接收到数据,接收数据的处理是否完成。上述方案是完全可行的,但有缺点:①缓冲的数据组数固定,变量较多,代码结构不是很清晰。②接收到的数据长度可能大于数组大小,也可能小于数组大小。不灵活,长时间接收数据容易出错,内存利用率低。一个很好的解决这个问题的方法是:ringbuffer。环形缓冲区是一个带有“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中的可读数据,“尾指针”指向环形缓冲区中的可写缓冲区空间。缓冲区的数据读写可以通过移动“头指针”和“尾指针”来实现。一般情况下,应用程序读取环形缓冲区的数据只影响“头指针”,而串口接收的数据只影响“尾指针”。当串口接收到新的数组时,会将数组保存在环形缓冲区中,同时将“尾指针”加1,保存下一条数据;当应用程序读取数据时,将“头指针”加1,读取到下一个数据。当“尾指针”超过数组大小时,“尾指针”又指向数组首元素,这样就形成了“环形缓冲区”!有效数据区在“头指针”和“尾指针”之间。如下图所示,ringbuffer其实就是一个数组,先“剪”,再“直”。环形缓冲区的特点如下图所示:2.当缓冲区用完需要存储新的数据时,丢弃最旧的数据,保存最新的数据。03代码实现环形缓冲区的实现非常简单,只需要几个简单的接口。首先需要创建一个环形缓冲区#defineRINGBUFF_LEN(500)//定义最大接收字节数500#defineRINGBUFF_OK1#defineRINGBUFF_ERR0typedefstruct{uint16_tHead;uint16_tTail;uint16_tLenght;uint8_tRing_data[RINGBUFF_LEN];}RingBuff_t;当我们发现环形缓冲区“overblown”,即缓冲区已满,但仍有数据需要缓冲时,我们只需要修改RINGBUFF_LEN的宏定义,增加缓冲区的大小即可。环形缓冲区的初始化/***@briefRingBuff_Init*@paramvoid*@returnvoid*@note初始化环形缓冲区*/voidRingBuff_Init(void){//初始化相关信息ringBuff.Head=0;ringBuff.Tail=0;ringBuff.Lenght=0;}主要是清空环形缓冲区的头尾和长度,表示没有存储数据。写入环形缓冲区/***@briefWrite_RingBuff*@paramuint8_tdata*@returnFLASE:环形缓冲区已满,写入失败;TRUE:写入成功*@notewriteuint8_ttypedatatoringbuffer*/uint8_tWrite_RingBuff(uint8_tdata){if(ringBuff.Lenght>=RINGBUFF_LEN)//判断buffer是否满{returnRINGBUFF_ERR;}ringBuff.Ring_data[ringBuff.Tail]=数据;ringBuff.Tail=(ringBuff.Tail+1)%RINGBUFF_LEN;//防止非法访问ringBuff.Lenght++;returnRINGBUFF_OK;}这个接口是写一个字节到ringbuffer。这里注意:可以根据自己的实际应用修改为一次缓冲多个字节。并且这样就做了缓冲区满报错和防止非法穿越的处理。您可以修改它以在缓冲区已满时覆盖最早的数据。ringbuffer的读取/***@briefRead_RingBuff*@paramuint8_t*rData,用于保存读取的数据*@returnFLASE:ringbuffer中没有数据,读取失败;TRUE:读取成功*@note从ringbuffer区读取到一个u8类型的数据*/uint8_tRead_RingBuff(uint8_t*rData){if(ringBuff.Lenght==0)//判断不为空{returnRINGBUFF_ERR;}*rData=ringBuff.Ring_data[ringBuff.Head];//进阶先出FIFO,出bufferheadringBuff.Head=(ringBuff.Head+1)%RINGBUFF_LEN;//防止非法访问ringBuff.Lenght--;returnRINGBUFF_OK;}读取也很简单,同样是读取一个Byte,可以修改为读取多个字节。04验证光说不练假动作,下面我们来验证一下上面代码的可行性。串口中断函数voidUSART1_IRQHandler(void){if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)){Write_RingBuff(USART_ReceiveData(USART1));USART_ClearFlag(USART1,USART_FLAG_RXNE);}}在主循环中,读取buffer中的数据,然后发出来,因为是简单的demo,加了个延时模拟CPU处理其他任务。while(1){if(Read_RingBuff(&data))//从环形缓冲区读取数据{USART_SendData(USART1,data);}SysCtlDelay(1*(SystemCoreClock/3000));}校验,每100ms发送一次数据。结果表明不存在丢包问题。如果你的应用场景串口通信速率快,数据量大或者处理速度慢导致丢包,建议增加RINGBUFF_LEN的宏定义,增加缓存空间。本文转载自微信公众号《知乎编程》,可通过以下二维码关注。转载本文请联系知编程公众号。