STM32串口开发之环形缓冲区

01
简介

在之前的文章《stm32 串口详解》中,我们讲解了串口的基本应用,使用串口中断接收数据,串口中断发送回包(一般可以使用非中断形式发送回包,在数据接收不频繁的应用中。串口接收中断保证串口数据及时响应,使用非中断方式发送回包即可)。

后面的文章《STM32使用DMA接收串口数据》和《STM32使用DMA发送串口数据》讲解了如何使用DMA辅助串口收发数据,使用DMA的好处在于不用CPU即可完成串口收发数据,减轻CPU负担,在串口通信频繁且不想频繁中断的应用中非常有用。

除了上述两种场景,还有一种应用场景:串口接收数据长度位置,频率未知,不要求实时处理的场景。如果采用上述方案,接收一帧数据立即处理,那么在处理的时候来的数据包就“丢失”了。这个时候就需要缓冲队列来解决这个问题。

02
缓冲区

缓冲区看名字就知道,是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否处理完成。

上述方案是完全可行的,但有缺点:

①缓冲数据组数一定,且有多变量,代码结构不太清晰。

②接收数据长度可能大于数组大小,也可能小于数组大小。不灵活,需要接收数据很长时容易出错,且内存利用率低。

解决这个问题的好办法是:环形缓冲区。

环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图

如上面说的,环形缓冲区其实就是一个数组,将其“剪开”,然后“拉直”后如下图

环形缓冲区的特性

1、先进新出。

2、当缓冲区被使用完,且又有新的数据需要存储时,丢掉历史最久的数据,保存最新数据。

03
代码实现

环形缓冲区的实现很简单,只需要简单的几个接口即可。

首先需要创建一个环形缓冲区

    #define RINGBUFF_LEN (500) //定义最大接收字节数 500#define RINGBUFF_OK 1 #define RINGBUFF_ERR 0 typedef struct{ uint16_t Head; uint16_t Tail; uint16_t Lenght; uint8_t Ring_data[RINGBUFF_LEN];}RingBuff_t;RingBuff_t ringBuff;//创建一个ringBuff的缓冲区

    当我们发现环形缓冲区被“冲爆”时,也就是缓冲区满了,但是还有待缓冲的数据时,只需要修改RINGBUFF_LEN的宏定义,增大缓冲区间即可。

    环形缓冲区的初始化

      /*** @brief  RingBuff_Init* @param  void* @return void* @note   初始化环形缓冲区*/void RingBuff_Init(void){  //初始化相关信息  ringBuff.Head = 0;  ringBuff.Tail = 0;  ringBuff.Lenght = 0;}

      主要是将环形缓冲区的头,尾和长度清零,表示没有任何数据存入。

      环形缓冲区的写入

        /*** @brief Write_RingBuff* @param uint8_t data* @return FLASE:环形缓冲区已满,写入失败;TRUE:写入成功* @note 往环形缓冲区写入uint8_t类型的数据*/uint8_t Write_RingBuff(uint8_t data){ if(ringBuff.Lenght >= RINGBUFF_LEN) //判断缓冲区是否已满 { return RINGBUFF_ERR; } ringBuff.Ring_data[ringBuff.Tail]=data; ringBuff.Tail = (ringBuff.Tail+1)%RINGBUFF_LEN;//防止越界非法访问 ringBuff.Lenght++; return RINGBUFF_OK;}

        这个接口是写入一个字节到环形缓冲区。这里注意:大家可以根据自己的实际应用修改为一次缓冲多个字节。并且这个做了缓冲区满时报错且防止非法越界的处理,大家可以自行修改为缓冲区满时覆盖最早的数据。

        环形缓冲区的读取

          /*** @brief  Read_RingBuff* @param  uint8_t *rData,用于保存读取的数据* @return FLASE:环形缓冲区没有数据,读取失败;TRUE:读取成功* @note   从环形缓冲区读取一个u8类型的数据*/uint8_t Read_RingBuff(uint8_t *rData){  if(ringBuff.Lenght == 0)//判断非空  {    return RINGBUFF_ERR;  }  *rData = ringBuff.Ring_data[ringBuff.Head];//先进先出FIFO,从缓冲区头出  ringBuff.Head = (ringBuff.Head+1)%RINGBUFF_LEN;//防止越界非法访问  ringBuff.Lenght--;  return RINGBUFF_OK;}

          读取的话也很简单,同样是读取一个字节,大家可以自行修改为读取多个字节。

          04
          验证

          光说不练假把式,下面我们就来验证上面的代码可行性。

          串口中断函数中缓冲数据

            void USART1_IRQHandler(void){ if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)) { Write_RingBuff(USART_ReceiveData(USART1)); USART_ClearFlag(USART1, USART_FLAG_RXNE); }}

            在主循环中,读取缓冲区的数据,然后发送出去,因为是简单的demo,添加了延时模拟CPU处理其他任务。

              while (1)  {    if(Read_RingBuff(&data))            //从环形缓冲区中读取数据    {      USART_SendData(USART1, data);    }    SysCtlDelay(1*(SystemCoreClock/3000));  }

              验证,间隔100ms发送数据。

              结果显示没有出现丢包问题。如果你的应用场景串口通信速率快,数据量大或处理速度慢导致丢包,建议增大RINGBUFF_LEN的宏定义,增大缓冲区间即可。

              KeilIAR的工程文件下载地址:

              PCB和工程代码开源地址:

              https://github.com/strongercjd/STM32F207VCT6

              (提示:公众号不支持外链接,请复制链接到浏览器下载)

              (0)

              相关推荐

              • 串口发送的这几种写法,你用过几种?

                STM32用USART发送字符串,以USART_FLAG_TXE和USART_FLAG_TC怎么用 一:STM32用USART发送字符串 void UART_Send_Message(u8 *Data ...

              • 附源码-终极串口接收(二)

                来源:公众号[鱼鹰谈单片机] 作者:鱼鹰Osprey ID   :emOsprey 前段时间需要写个串口接收程序,一时没找到源码,就想着自己写过一篇文章<终极串口接收方式,极致效率>,看看 ...

              • 怎样用串口发送结构体

                先说解决方案,细节和实现代码都放在正文 下位机:把结构体拆分成8位的整型数据,加上数据包头和包尾,然后按顺序单个单个地发出: 上位机:把串口里的数据读取出来,找到包头,按顺序装填到结构体中,然后使用结 ...

              • 看完这篇,不要说不懂串口通信!

                一.什么是串口通讯? 串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式.尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输. 典型 ...

              • STM32入坑(12)串口发送字节、半字、字、字符串、数组及实现串口控制

                串口发送字节.半字.字.字符串.数组及实现串口控制 简介 串口的配置 1. 配置usart的TX和RX引脚 2. 配置串口模式 3.配置串口的优先级(使用串口中断时) 4.串口初始化 编写发送函数 发 ...

              • STM32系统学习——USART(串口通信)

                串口通信是一种设备间非常常用的串行通行方式,其简单便捷,大部分电子设备都支持. 一.物理层 常用RS-232标准,主要规定了信号的用途.通信接口以及信号的电平标准. "DB9接口" ...

              • STM32串口通信基本原理

                通信方式 并行通信 传输原理:数据各个位同时传输 优点:速度快(一个引脚传输一个位) 缺点:占用引脚资源多 串行通信 传输原理:数据按位顺序传输 优点:占用引脚资源少(一个引脚都可以) 缺点:速度相对 ...

              • NRF24L01无线串口开发板程序详解

                NRF24L01无线串口开发板程序详解

              • stm32串口发送16进制字符和16进制数的区别和具体实现方法

                在调试一个stm32串口通信时,下位机设置好的发送方式采用串口助手接收后已经可以正常离线解包.但是由于需要实时解包并存储,因此写了一个实时解包存储的上位机,通信正常后接收的数据一直有误.经过单步调试, ...

              • STM32 串口DMA接收 Openmv / K210 整数、小数字符串数据 (基于HAL库)

                目录 前言 一.工程配置 二.串口DMA部分代码 1.源文件UART_DMA.c 2.头文件UART_DMA.h 3.stm32f1xx_it.c的修改 4.串口收发DMA测试 三.字符串数字提取代码 ...

              • STM32系列开发-揭开MDK下__main的面纱--非常详解

                之前,是大体了解了MDK下的__main函数所做的事情,一直没有深究,最近突然很想知道,MDK下在程序运行到我们用户定义的main()函数前,到底做了些什么,想看看ARMCC编译器默默地为我们做了哪些 ...

              • STM32串口空闲中断接收不定长数据(DMA方式)

                在使用STM32的串口接收数据的时候,我们常常会使用接收中断的方式来接收数据,常用的是RXNE.这里分享另一种接收数据的方式--IDLE中断(PS:本文的例子运行在STM32F103ZET6上). 一 ...

              • STM32串口IAP分享

                什么是IAP? IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通 ...

              • 用宏高效实现环形缓冲区

                嵌入式专栏 153篇原创内容 公众号 来源 | 小麦大叔 循环缓冲区是嵌入式软件工程师在日常开发过程中的关键组件. 多年来,互联网上出现了许多不同的循环缓冲区实现和示例.我非常喜欢这个模块,可以Git ...

              • STM32串口通信配置(USART1+USART2+USART3+UART4)

                一.串口一的配置(初始化+中断配置+中断接收函数) 1 /*====================================================================== ...