关于循环缓冲区
循环缓冲区
说明
后面写不下,回到开头去写。
一般情况
先来看一般的情况。
有数据时
情况1
X | X | X | 1 | 2 | 3 | 4 | 5 | 6 | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
readPointer | writePointer |
情况2
7 | 8 | 9 | X | X | X | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|---|---|---|---|
writePointer | readPointer |
注意
不允许将整个数组全部使用,否则无法区分当前是没有数据,还是数据已满!
没有数据
X | X | X | X | X | X | X | X | X | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
writePointer | |||||||||||
readPointer |
数据充满整个数组
11 | 12 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
writepointer | |||||||||||
readPointer |
当然,这也可以通过加入一个Flag变量去标记,目前到底有没有数据。如果这样的话,每次读写后都应当检查writePointer是否等于readPointer,若相等则根据情况更新Flag变量。
读写时的情况
直接写入的情况
略
返回开头写入的情况
写不下需要返回开头!
以写四个为例
X | X | X | X | X | X | X | 1 | 2 | 3 | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
readPointer | writePointer |
6 | 7 | X | X | X | X | X | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|---|---|---|---|---|
writePointer | readPointer |
直接读取的情况
略
回到开头读取的情况
以读取5个数据为例
3 | 4 | 5 | 6 | 7 | X | X | X | X | X | 1 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|
writePointer | readPointer |
X | X | X | 6 | 7 | X | X | X | X | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
readPointer | writePointer |
实现代码
我这里以C实现它
CircularBuffer.h
#pragma once#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>
#include <string.h>
#include <stdbool.h>#define CIRCULAR_BUFFER_SIZE 64 //缓冲区大小 根据需要修改typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE];uint8_t* writePointer;uint8_t* readPointer;
}CircularBuffer;/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针*/
void circularBufferInit(CircularBuffer* cb);/*** @brief 得到允许读取的数据长度* @param cb 循环缓冲区指针* @return 允许读取的数据长度* @note 也就是未处理的数据长度*/
uint16_t circularBufferReadAllow(CircularBuffer* cb);/*** @brief 得到允许写入的数据长度* @param cb 循环缓冲区指针* @return 允许写入的数据长度*/
uint16_t circularBufferWriteAllow(CircularBuffer* cb);/*** @brief 向缓冲区写入数据* @param cb 循环缓冲区指针* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/
bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, uint16_t length);/*** @brief 看看有没有要处理的数据* @param cb 循环缓冲区指针* @return true 有* @return false 没有*/
bool circularBufferNeedProcessed(CircularBuffer* cb);/*** @brief 从缓冲区中读出数据* @param cb 循环缓冲区指针* @param buffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/
bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, uint16_t length);#ifdef __cplusplus
}
#endif
CircularBuffer.c
#include "CircularBuffer.h"void circularBufferInit(CircularBuffer* cb)
{memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);cb->writePointer = cb->buffer;cb->readPointer = cb->buffer;
}uint16_t circularBufferReadAllow(CircularBuffer* cb)
{//if (cb->writePointer == cb->readPointer)//{// cb->readAllow = 0;//二者相等时没有数据//}//else if (cb->writePointer > cb->readPointer)//{// cb->readAllow = cb->writePointer - cb->readPointer;//}//else//{// cb->readAllow = CIRCULAR_BUFFER_SIZE - (cb->readPointer - cb->writePointer);//}//与上面的代码等价uint16_t readAllow = (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE;return readAllow;
}uint16_t circularBufferWriteAllow(CircularBuffer* cb)
{//uint16_t writeAllow = 0;//if (cb->writePointer == cb->readPointer)//{// writeAllow = CIRCULAR_BUFFER_SIZE - 1;//不允许整个数组都存入数据//} //else if (cb->writePointer > cb->readPointer)//{// writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer - cb->readPointer) - 1;//}//else//{// writeAllow = cb->readPointer - cb->writePointer - 1;//}//与上面的代码等价//不允许整个数组都存入数据uint16_t writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE - 1;return writeAllow;
}bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, uint16_t length)
{if (length > circularBufferWriteAllow(cb))return false;if (cb->writePointer + length < cb->buffer + CIRCULAR_BUFFER_SIZE)//不需要返回开头的情况{memcpy(cb->writePointer, data, length);cb->writePointer += length;}else//需要返回开头的情况{uint16_t endLength = cb->buffer + CIRCULAR_BUFFER_SIZE - cb->writePointer; memcpy(cb->writePointer, data, endLength);//拷贝到后面uint16_t beginLength = length - endLength;memcpy(cb->buffer, data + endLength, beginLength);//拷贝到前面cb->writePointer = cb->buffer + beginLength;}return true;
}bool circularBufferNeedProcessed(CircularBuffer* cb)
{return !(cb->writePointer == cb->readPointer);
}bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, uint16_t length)
{if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length < cb->buffer + CIRCULAR_BUFFER_SIZE)//不需要返回开头的情况{memcpy(buffer, cb->readPointer, length);cb->readPointer += length;}else//需要返回开头的情况{uint16_t endLength = cb->buffer + CIRCULAR_BUFFER_SIZE - cb->readPointer;memcpy(buffer, cb->readPointer, endLength);//拷贝后面的uint16_t beginLength = length - endLength;memcpy(buffer+endLength, cb->buffer, beginLength);//拷贝前面的cb->readPointer = cb->buffer + beginLength;}return true;
}
测试代码
main.c
#include <stdio.h>#include <time.h>#include "CircularBuffer.h"void test1()
{ CircularBuffer cb;uint8_t writeData[CIRCULAR_BUFFER_SIZE * 2]; // 写入数据uint8_t readData[CIRCULAR_BUFFER_SIZE * 2]; // 读取数据// 初始化缓冲区circularBufferInit(&cb);// 准备测试数据for (uint16_t i = 0; i < CIRCULAR_BUFFER_SIZE * 2; i++) {writeData[i] = i % 256; // 数据范围 0x00 到 0xFF}printf("=== Testing Circular Buffer ===\n");// 测试写入超出允许长度的情况printf("\n--- Test 1: Write more than allowed ---\n");if (!circularBufferWrite(&cb, writeData, CIRCULAR_BUFFER_SIZE + 1)) {printf("Write failed as expected.\n");}// 测试写入正常数据printf("\n--- Test 2: Write normal data ---\n");if (circularBufferWrite(&cb, writeData, CIRCULAR_BUFFER_SIZE / 2)) {printf("Wrote %d bytes successfully.\n", CIRCULAR_BUFFER_SIZE / 2);}else {printf("Write failed unexpectedly.\n");}// 检查允许读取的数据长度printf("\n--- Test 3: Check read allowance ---\n");uint16_t readAllow = circularBufferReadAllow(&cb);printf("Allowed to read: %d bytes\n", readAllow);// 测试读取部分数据printf("\n--- Test 4: Read partial data ---\n");if (circularBufferRead(&cb, readData, readAllow)) {printf("Read %d bytes successfully.\n", readAllow);printf("Read data: ");for (uint16_t i = 0; i < readAllow; i++) {printf("%02X ", readData[i]);}printf("\n");}else {printf("Read failed unexpectedly.\n");}// 测试是否需要处理数据printf("\n--- Test 5: Check if data needs processing ---\n");if (circularBufferNeedProcessed(&cb)) {printf("There is still data to process.\n");}else {printf("No data to process.\n");}// 测试再次写入数据(模拟环形覆盖)printf("\n--- Test 6: Write more data to test wrap-around ---\n");if (circularBufferWrite(&cb, writeData + CIRCULAR_BUFFER_SIZE * 7 / 8, CIRCULAR_BUFFER_SIZE * 7 / 8)){printf("Wrote %d bytes successfully.\n", CIRCULAR_BUFFER_SIZE * 7 / 8);}else{printf("Write failed unexpectedly.\n");}// 再次测试是否需要处理数据printf("\n--- Test 7: Check if data needs processing ---\n");if (circularBufferNeedProcessed(&cb)) {printf("There is still data to process.\n");}else{printf("No data to process.\n");}// 测试读取所有剩余数据printf("\n--- Test 8: Read all remaining data ---\n");readAllow = circularBufferReadAllow(&cb);if (circularBufferRead(&cb, readData, readAllow)) {printf("Read %d bytes successfully.\n", readAllow);printf("Read data: ");for (uint16_t i = 0; i < readAllow; i++) {printf("%02X ", readData[i]);}printf("\n");}else {printf("Read failed unexpectedly.\n");}// 最终检查是否还有未处理数据printf("\n--- Final Check: Any data left? ---\n");if (circularBufferNeedProcessed(&cb)) {printf("There is still data to process.\n");}else {printf("No data to process.\n");}}int main(void)
{test1();return 0;
}
后记
如果嵌入式项目中涉及大量数据结构的问题,请使用ETL库
ETL(Embedded Template Library) 是一个专为嵌入式系统和资源受限环境设计的轻量级 C++ 模板库,提供了类似 STL(标准模板库)的容器和算法,但更注重 确定性内存管理、零动态内存分配 和 低开销。
etl::circular_buffer实现了循环缓冲区
参考
嵌入式模板库(ETL):为嵌入式系统量身定制的C++模板库-CSDN博客
参考资料
什么是循环缓冲区?-CSDN博客
https://docs.keysking.com/docs/stm32/example/UART_COMMAND
https://www.bilibili.com/video/BV1p75yzSEt9/?spm_id_from=333.337.search-card.all.click&vd_source=a56c06558fd43f4777ff9ae6c06c3e71