基本概念
环形缓冲区一种遵循FIFO(先进先出)的存储空间,它的主要特点是首尾相连形成了闭环。这种结构允许数据在达到缓冲区末尾时自动回绕到起始位置,从而实现数据的循环存储和管理。下图是一种形象理解。
通俗地来讲,它也是一种线性表,由一个数组和两个表示读写状态的指针构成。在这个线性表被定义地第一时间,读指针与写指针指向同一个数组元素;当我们写入了一个数据之后,读指针会依旧指向第一个元素但写指针会按照写入方向依次指向下一个元素。如果写指针的下一个指向是读指针所在的元素,代表这个环形缓冲区已经写满。
功能与优势
- 存储和管理数据:环形缓冲区可以存储一定数量的数据,并在数据满后通过覆盖最早的数据来继续存储新数据,实现“先进先出”(FIFO)的数据管理方式。
- 高效利用空间:通过循环存储,环形缓冲区能够高效地利用有限的缓存空间,避免数据的丢失,并减少内存分配和释放的开销。
- 快速读写操作:环形缓冲区的插入和删除操作通常只涉及头指针和尾指针的移动,时间复杂度为O(1),这使得它在实时或高性能应用中具有显著优势。
- 支持多线程和并发操作:通过适当的同步机制,如互斥锁或信号量,环形缓冲区可以支持多个读取者和写入者并发地访问和操作数据。
操作方法与代码示例
利用结构体构建环形buff
#define RingBuffer_Size 8
typedef struct {
int w;
int r;
int buffer[RingBuffer_Size];
}RingBuffer_Type;
初始化环形缓冲区域
让读指针与写指针指向同一个数组元素
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type *rb)
{
rb->w = 0;
rb->r = 0;
for (int i = 0; i < RingBuffer_Size; i++)
rb->buffer[i] = 0;
}
判断环形buffer状态
判断是否满:写入的下一位是否位读
/*判断buffer是否填满*/
int RingBuffer_Is_Full(RingBuffer_Type* rb)
{
return (((rb->w) + 1) % RingBuffer_Size == (rb->r));
}
判断是否空:读和写的指向在同一位
/*判断buffer是否为空*/
int RingBuffer_Is_Empty(RingBuffer_Type* rb)
{
return(rb->r==rb->w);
}
对环形buff进行写入
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data)
{
if (RingBuffer_Is_Full(rb)) //先判断是否为满
return -1;
rb->buffer[rb->w] = rb_data; //对写位置进行了写入
rb->w = ((rb->w) + 1) % RingBuffer_Size; //写位置加一的同时对数组大小进行取模防止数组越界
return 0;
}
对环形buff进行读取
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type *rb, int *rb_data)
{
if (RingBuffer_Is_Empty(rb)) //判断是否为空
return -1;
*rb_data = rb->buffer[rb->r]; //读取读指针指向的元素
rb->r = (rb->r + 1) % RingBuffer_Size; //读指针加一的同时防止数组溢出
return 0;
}
控制台实验
大家可以用C语言编译器先试一试体会一下。
RingBuff.c
#include <iostream>
#include "RingBuffer.h"
using namespace std;
RingBuffer_Type g_rb;
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type *rb)
{
rb->w = 0;
rb->r = 0;
for (int i = 0; i < RingBuffer_Size; i++)
rb->buffer[i] = 0;
}
/*判断buffer是否填满*/
int RingBuffer_Is_Full(RingBuffer_Type* rb)
{
return (((rb->w) + 1) % RingBuffer_Size == (rb->r));
}
/*判断buffer是否为空*/
int RingBuffer_Is_Empty(RingBuffer_Type* rb)
{
return(rb->r==rb->w);
}
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data)
{
if (RingBuffer_Is_Full(rb))
return -1;
rb->buffer[rb->w] = rb_data;
rb->w = ((rb->w) + 1) % RingBuffer_Size;
return 0;
}
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type *rb, int *rb_data)
{
if (RingBuffer_Is_Empty(rb))
return -1;
*rb_data = rb->buffer[rb->r];
rb->r = (rb->r + 1) % RingBuffer_Size;
return 0;
}
/*在控制台打印环形Buffer*/
void RingBuffer_Printf(RingBuffer_Type* rb)
{
int rb_data;
int pbuffer[RingBuffer_Size] = {};
if (!RingBuffer_Read(rb,&rb_data))/* C/C++函数的形参列表中不能含有类型名 */
{
for (int x = 0; x < RingBuffer_Size - 1; x++)
{
pbuffer[x] = (rb->buffer[x]);
printf("%d\r\n", pbuffer[x]);
}
}
}
int main()
{
int Prime_Number[] = {2,3,5,6,11,13,17};//环形buffer需要自己保留一位
int size;
size = sizeof(Prime_Number) / sizeof(Prime_Number[0]);
/*printf("size=%d",size);*/
RingBuffer_Init(&g_rb);
for(int j=0;j<size;j++)
RingBuffer_Write(&g_rb, Prime_Number[j]);
//for (int x = 0; x < RingBuffer_Size-1; x++)
// printf("%d\r\n", g_rb.buffer[x]);
RingBuffer_Printf(&g_rb);
system("pause");
return 0;
}
RingBuff.h
#ifndef __RingBuffer_H
#define __RingBuffer_H
#define RingBuffer_Size 8
typedef struct {
int w;
int r;
int buffer[RingBuffer_Size];
}RingBuffer_Type;
extern RingBuffer_Type g_rb;
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type* rb);
/*判断buffer是否填满*/
int RingBuffer_Is_Full(RingBuffer_Type* rb);
/*判断buffer是否为空*/
int RingBuffer_Is_Empty(RingBuffer_Type* rb);
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data);
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type* rb, int* rb_data);
/*在控制台打印环形Buffer*/
void RingBuffer_Printf(RingBuffer_Type* rb);
#endif
单片机应用实操
假设我们遇到这样一个场景,用按键操作GUI实现人机交互,如果不考虑引入操作系统,那么为了及时响应势必会使用GPIO输入中断。中断在清理标志位的过程中势必会覆盖原有按键值的内存,为了记录按键值我们可以引入环形buffer。
前置实验请参考拙作:利用软件定时器实现按键消抖-CSDN博客
几乎可以原封不动地进行库移植。
RingBuffer.c
#include "RingBuffer.h"
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type *rb)
{
rb->w = 0;
rb->r = 0;
for (int i = 0; i < RingBuffer_Size; i++)
rb->buffer[i] = 0;
}
/*判断buffer是否填满*/
static int RingBuffer_Is_Full(RingBuffer_Type* rb)
{
return (((rb->w) + 1) % RingBuffer_Size == (rb->r));
}
/*判断buffer是否为空*/
static int RingBuffer_Is_Empty(RingBuffer_Type* rb)
{
return(rb->r==rb->w);
}
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data)
{
if (RingBuffer_Is_Full(rb))
return -1;
rb->buffer[rb->w] = rb_data;
rb->w = ((rb->w) + 1) % RingBuffer_Size;
return 0;
}
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type *rb, int *rb_data)
{
if (RingBuffer_Is_Empty(rb))
return -1;
*rb_data = rb->buffer[rb->r];
rb->r = (rb->r + 1) % RingBuffer_Size;
return 0;
}
RingBuffer.h
#ifndef __RingBuffer_H
#define __RingBuffer_H
#include <stdint.h>
#define RingBuffer_Size 100 //限制buffer尺寸为100
typedef struct circle_buf{
uint32_t r;
uint32_t w;
uint8_t buffer[RingBuffer_Size];
}RingBuffer_Type,*pRingBuffer;
/*初始化环形缓冲区的方法是让
读写标记指针都指向同一个线性表元素*/
void RingBuffer_Init(RingBuffer_Type* rb);
/*写环形Buffer*/
int RingBuffer_Write(RingBuffer_Type* rb, int rb_data);
/*读环形Buffer*/
int RingBuffer_Read(RingBuffer_Type* rb, int* rb_data);
#endif
SoftTimer.c
应用层还是要改一改的
#include "SoftTimer.h" //为了按键设计的软件定时器
int g_key_cnt = 0;
RingBuffer_Type g_keybufs;
void key_timeout_func(void *args);
soft_timer key_timer = {~0, NULL, key_timeout_func};
static void key_timeout_func(void *args)
{
g_key_cnt++;
key_timer.timeout = ~0;
uint8_t key_val=0; /*按下按键0x11,松开按键0xFF*/
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)
key_val = 0x11;
else
key_val = 0xff;
RingBuffer_Write(&g_keybufs,key_val);
}
void mod_timer(soft_timer *pTimer, uint32_t delayTICK)
{
pTimer->timeout = HAL_GetTick() + delayTICK;
}
void check_timer(void)
{
if (key_timer.timeout <= HAL_GetTick())
{
key_timer.func(key_timer.args);
}
}
SoftTimer.h
#ifndef __SoftTimer_H
#define __SoftTimer_H
#include <stdint.h>
#include <stdio.h>
#include "RingBuffer.h"
#include "gpio.h"
typedef struct software_timer {
uint32_t timeout;
void * args;
void (*func)(void *);
}soft_timer;
extern int g_key_cnt;
extern uint32_t HAL_GetTick(void);
extern soft_timer key_timer;
extern RingBuffer_Type g_keybufs;
void mod_timer(soft_timer *pTimer, uint32_t timeout);
void check_timer(void);
#endif
main.c
编译下载之后可以发现按键值被有效地存储了。
/* USER CODE BEGIN 2 */
RingBuffer_Init(&g_keybufs);
OLED_Init();
OLED_Clear();
// OLED_PrintString(0, 4, "Key ISR cnt = ");
OLED_PrintString(0, 0, "Cnt : ");
int len = OLED_PrintString(0, 2, "Key val : ");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// OLED_PrintSignedVal(14, 4, g_key_cnt);
OLED_PrintSignedVal(len, 0, g_key_cnt);
int key_val = 0;
if (0 == RingBuffer_Read(&g_keybufs,&key_val))
{
OLED_ClearLine(len, 2);
OLED_PrintHex(len, 2, key_val, 1);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */