队列,环形缓冲区实现与应用:适用于GD32串口编程或嵌入式底层驱动开发

发布于:2025-06-20 ⋅ 阅读:(16) ⋅ 点赞:(0)

环形缓冲区实现与应用:从基础到实践

在嵌入式系统和实时数据处理场景中,环形缓冲区(Circular Buffer)是一种非常常用的的数据结构,它能有效地管理数据的读写操作,尤其适用于数据流的临时存储与转发。

今天,我们就来深入探讨如何实现一个简单高效的环形缓冲区,并将其应用到模拟的 UART 通信场景中。

这篇文章将带您从零开始构建一个实用的环形缓冲区,并展示其在数据传输中的应用。环形缓冲区基本原理环形缓冲区是一种固定大小的数组结构,通过两个指针(读指针和写指针)来追踪数据的读写位置。当缓冲区的空间被占满时,根据不同的模式可选择覆盖旧数据或者阻塞写入操作。这种数据结构的优势在于无需频繁的内存分配和释放操作,能高效地利用有限的内存资源。

项目结构搭建
为了实现环形缓冲区,我们构建了一个简单的项目结构:

project/├── src/│  
            ├── main.c              // 主程序
            │   └── ring_buffer.c       // 环形缓冲区实现        
            ├── include/│ 
              └── ring_buffer.h       // 环形缓冲区头文件
              └── Makefile                // 项目的Makefile

这种清晰的项目结构有助于我们更好地组织和管理代码。

环形缓冲区实现头文件
定义在ring_buffer.h中,我们首先定义了环形缓冲区的结构体、错误码、写入模式以及相关的函数声明:

#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#include <stdint.h>// 环形缓冲区结构体

typedef struct 
{    
uint8_t *buffer;    // 数据存储的缓冲区    
uint32_t in_index;  // 写入指针    
uint32_t out_index; // 读取指针    
uint32_t length;    // 当前缓冲区中的元素个数    
ring_buffer_mode_t mode; // 缓冲区的写入模式} 
ring_buffer_t;// 错误码定义

#define RINGBUFF_OK     0    // 成功
#define RINGBUFF_ERR    1    // 错误
#define RINGBUFF_EMPTY  2    // 缓冲区为空
#define RINGBUFF_FULL   3    // 缓冲区满// 写入模式枚举

typedef enum {    
RINGBUFF_OVERWRITE,    // 缓冲区满时覆盖最旧数据    
RINGBUFF_NO_OVERWRITE  // 缓冲区满时返回错误
} ring_buffer_mode_t;// 函数声明

void ring_buffer_init(ring_buffer_t *buffer, uint8_t *data, ring_buffer_mode_t mode);

uint8_t ring_buffer_write(ring_buffer_t *buffer, uint8_t data);

uint8_t ring_buffer_read(ring_buffer_t *buffer);

int read_data_to_array(ring_buffer_t *buffer, uint8_t *data, uint32_t data_len);

#endif // RING_BUFFER_H

源文件实现在ring_buffer.c
我们实现了环形缓冲区的各项功能:

#include "ring_buffer.h"// 初始化环形缓冲区
void ring_buffer_init(ring_buffer_t *buffer, uint8_t *data, ring_buffer_mode_t mode) 
{    
buffer->buffer = data;    
buffer->in_index = 0;    
buffer->out_index = 0;    
buffer->length = 0;    
buffer->mode = mode;
}// 写入数据到环形缓冲区

uint8_t ring_buffer_write(ring_buffer_t *buffer, uint8_t data) 
{    
uint32_t next_in_index = (buffer->in_index + 1) % RINGBUFF_LEN;  // 使用数组的固定大小    
if (buffer->length == RINGBUFF_LEN) {        
if (buffer->mode == RINGBUFF_NO_OVERWRITE) {            return RINGBUFF_FULL;  // 缓冲区已满,且不允许覆盖        
} 
else 
{            
// 覆盖最旧的数据            
buffer->out_index = (buffer->out_index + 1) % RINGBUFF_LEN;            
buffer->length--;        
}    
}    

buffer->buffer[buffer->in_index] = data;    
buffer->in_index = next_in_index;    
buffer->length++;    
return RINGBUFF_OK;
}

// 从环形缓冲区读取数据
uint8_t ring_buffer_read(ring_buffer_t *buffer) 
{    
if (buffer->length == 0) 
{        
return '\0';  // 缓冲区为空,返回 '\0'    
}    

uint8_t data = buffer->buffer[buffer->out_index];    
buffer->out_index = (buffer->out_index + 1) % RINGBUFF_LEN;    
buffer->length--;    
return data;
}// 从缓冲区读取数据到数组

int read_data_to_array(ring_buffer_t *buffer, uint8_t *data, uint32_t data_len) 
{    
uint32_t index = 0;    

while (buffer->length > 0 && index < data_len) 
{        
uint8_t byte = ring_buffer_read(buffer);        

if (byte != '\0') 
{            
data[index++] = byte;        
}    
}    
return index;  // 返回实际读取的字节数

}

主程序与模拟应用

main.c中,我们模拟了一个简单的 UART 通信场景,展示如何使用环形缓冲区实现数据的接收和读取:

#include "ring_buffer.h"
#include <stdio.h>

#define RINGBUFF_LEN 128  // 固定缓冲区长度

uint8_t uart_ring_buffer[RINGBUFF_LEN];
ring_buffer_t uart_buffer;// 模拟 UART 中断接收函数,模拟数据写入缓冲区

void uart_interrupt_receive(uint8_t data) 
{    
ring_buffer_write(&uart_buffer, data);
}

int main() 
{    
// 初始化环形缓冲区,使用覆盖模式    
ring_buffer_init(&uart_buffer, uart_ring_buffer, RINGBUFF_OVERWRITE);    // 模拟 UART 中断接收数据    
uart_interrupt_receive('A');    uart_interrupt_receive('B');    uart_interrupt_receive('C');    uart_interrupt_receive('D');    uart_interrupt_receive('E');    uart_interrupt_receive('F');    // 读取数据并打印    
uint8_t data[10];    
int bytes_read = read_data_to_array(&uart_buffer, data, sizeof(data));    // 打印读取的数据    
printf("Read %d bytes: ", bytes_read);    

for (int i = 0; i < bytes_read; i++) 
{        
printf("%c ", data[i]);    
}    

return 0;
}

Makefile 构建
为了方便编译和构建项目,我们提供了简单的Makefile

makefile
CC = gccCFLAGS = -Wall -Wextra -std=c99
SRC = src/main.c src/ring_buffer.c
OBJ = $(SRC:.c=.o)
EXEC = ring_buffer_demo
all: $(EXEC)$(EXEC): $(OBJ)    
$(CC) $(OBJ) -o $(EXEC)%.o: %.c    
$(CC) $(CFLAGS) -c $< -o 
$@clean:    rm -f $(OBJ) $(EXEC)

关键点总结

  1. 环形缓冲区:我们定义了一个基于固定大小数组的环形缓冲区结构体,通过in_indexout_index来追踪写入和读取的位置。
  2. 中断模拟:在uart_interrupt_receive函数中,我们模拟了 UART 中断接收数据的过程,并将接收到的数据写入环形缓冲区。
  3. 数据读取:在主函数中,我们使用read_data_to_array函数将环形缓冲区中的数据读取到普通数组中,并通过printf打印输出结果。
  4. 扩展性:如果需要支持多线程场景,可以加入互斥锁来确保缓冲区访问的安全性。
  5. 优化建议:在高频率中断场景下,可以进一步优化数据结构和访问模式以提高性能。总结通过这个简单的环形缓冲区实现,我们了解了其基本原理和应用场景。

这个项目不仅适用于学习目的,还能作为实际项目中的基础模块进行扩展和优化。希望这篇文章能帮助您更好地理解和使用环形缓冲区技术。


网站公告

今日签到

点亮在社区的每一天
去签到