关于循环缓冲区
循环缓冲区
说明
X | X | X | 1 | 2 | 3 | 4 | 5 | 6 | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | -> | writePointer | -> | end |
7 | 8 | 9 | X | X | X | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | -> | readPointer | -> | end |
后面写不下,回到开头去写。
注意读指针、写指针都只能“向后走”,走到最后就回到开头。
读写应当通过读指针、写指针去做,不然就乱套了。
注意
不允许将整个数组全部使用,否则无法区分当前是没有数据,还是数据已满!
没有数据
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变量。
相关代码
定义
#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改typedef struct
{uint8_t buffer[CIRCULAR_BUFFER_SIZE];uint8_t* writePointer;uint8_t* readPointer;uint8_t* end;//缓冲区真正的结尾
}CircularBuffer;
初始化
/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cb);/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cvoid circularBufferInit(CircularBuffer* cb)
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其实不赋值也无所谓cb->writePointer = cb->buffer;cb->readPointer = cb->buffer;cb->end = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾
};
数据是否存在
/*** @brief 看看循环缓冲区中是否存在数据* @param cb 循环缓冲区指针* @return true 有* @return false 没有*/
bool circularBufferDataExists(CircularBuffer* cb);bool circularBufferDataExists(CircularBuffer* cb)
{return cb->writePointer != cb->readPointer;
}
数据长度
当前存储的数据长度,或者说允许读取的长度
请结合前面的表格理解代码
/*** @brief 得到允许读取的数据长度* @param cb 循环缓冲区指针* @return 允许读取的数据长度* @note 也就是未处理的数据长度*/
size_t circularBufferReadAllow(CircularBuffer* cb);size_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);//}//与上面的代码等价size_t readAllow = (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE;return readAllow;
}
剩余空间
剩余空间,或者说允许写入的长度,注意不允许全部写满
请结合前面的表格理解代码
/*** @brief 得到允许写入的数据长度* @param cb 循环缓冲区指针* @return 允许写入的数据长度*/
size_t circularBufferWriteAllow(CircularBuffer* cb);size_t circularBufferWriteAllow(CircularBuffer* cb)
{//size_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;//}//与上面的代码等价//不允许整个数组都存入数据size_t writeAllow = CIRCULAR_BUFFER_SIZE - (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE - 1;return writeAllow;
}
写入数据
注意,如果end处有写入,写指针就需要回到开头
写指针不需要返回开头的情况
以写入4个为例
原本
X | X | 1 | 2 | 3 | X | X | X | X | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
之后
X | X | 1 | 2 | 3 | 4 | 5 | 6 | 7 | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
写指针需要返回开头的情况
写不下需要返回开头!
以写四个为例
原本
X | X | X | X | X | X | X | 1 | 2 | 3 | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
之后
6 | 7 | X | X | X | X | X | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | readPointer | end |
实现写入的代码
/*** @brief 向缓冲区写入数据* @param cb 循环缓冲区指针* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/
bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length);bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length)
{if (length == 0)return true;if (length > circularBufferWriteAllow(cb))return false;if (cb->writePointer + length <= cb->end)//不需要返回开头的情况{memcpy(cb->writePointer, data, length);cb->writePointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->writePointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(cb->writePointer, data, endLength);//拷贝到后面 memcpy(cb->buffer, data + endLength, beginLength);//拷贝到前面cb->writePointer = cb->buffer + beginLength;}return true;
}
读取数据
以读取5个数据为例
读指针不需要返回开头的情况
读取前
X | X | 1 | 2 | 3 | 4 | 5 | 6 | 7 | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
读取后
X | X | X | X | X | X | X | 6 | 7 | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
读指针需要返回开头的情况
读取前
3 | 4 | 5 | 6 | 7 | X | X | X | X | X | 1 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | readPointer | end |
读取后
X | X | X | 6 | 7 | X | X | X | X | X | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end |
实现读取的代码
/*** @brief 从循环缓冲区中读出数据* @param cb 循环缓冲区指针* @param buffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/
bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length);bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memcpy(buffer, cb->readPointer, length);cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(buffer, cb->readPointer, endLength);//拷贝后面的memcpy(buffer + endLength, cb->buffer, beginLength);//拷贝前面的cb->readPointer = cb->buffer + beginLength;}return true;
}
查找数据
通常是要查找通信协议的数据头
注意,不返回目标的位置,因为读取的操作应当通过读指针实现。
找到后,将读指针指向目标。
结束循环的条件是迭代器指向写指针
以查找1 2 3 4为例
迭代器不需要返回开头的情况
找到前
X | X | X | ? | ? | 1 | 2 | 3 | 4 | ? | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> |
找到后
X | X | X | X | X | 1 | 2 | 3 | 4 | ? | X | X |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readPointer | writePointer | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it |
迭代器需要返回开头的情况
找到前
3 | 4 | ? | X | X | X | X | ? | ? | ? | 1 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | readPointer | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> |
临近尾部要把目标数据拆分成两部分分别比较
3 | 4 | ? | X | X | X | X | X | ? | ? | 1 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | readPointer | end | ||||||||
4 | 1 | 2 | 3 | ||||||||
it | -> |
找到后
3 | 4 | ? | X | X | X | X | X | X | X | 1 | 2 |
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writePointer | readPointer | end | ||||||||
3 | 4 | 1 | 2 | ||||||||
it |
实现查找的代码
/*** @brief 在循环缓冲区中查找特定数据,如果成功移动读指针到目标位置* @param cb 循环缓冲区指针* @param target 目标数据指针* @param length 目标数据长度* @return true 找到了* @return false 没有找到* @note 从读指针的位置开始找,如果找到,会移动读指针,也就是抛弃路径上的数据*/
bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length);bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length)
{if (target == NULL) return false;if (length == 0) return true;if (length > circularBufferReadAllow(cb)) return false;//目标长度超过当前数据长度,错误uint8_t* it = cb->readPointer;//遍历时的迭代器while (it != cb->writePointer){//目前不涉及迭代器返回开头的情况if (it + length <= cb->end){if (memcmp(it, target, length) == 0){cb->readPointer = it;return true;}}else{size_t endLength = cb->end - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0){cb->readPointer = it;return true;}}it++;if (it > cb->end) it = cb->buffer;//回到开头}return false;
}
完整代码
我这里以C实现它
CircularBuffer.h
#pragma once#ifdef __cplusplus
extern "C" {
#endif#include <stdint.h>
#include <string.h>
#include <stdbool.h>#define CIRCULAR_BUFFER_SIZE 12 //缓冲区大小,根据需要修改typedef struct
{uint8_t buffer[CIRCULAR_BUFFER_SIZE];uint8_t* writePointer;uint8_t* readPointer;uint8_t* end;//缓冲区真正的结尾
}CircularBuffer;/*** @brief 初始化一个循环缓冲区* @param cb 缓冲区指针* @note 可以使用此函数清空循环缓冲区*/
void circularBufferInit(CircularBuffer* cb);/*** @brief 得到允许读取的数据长度* @param cb 循环缓冲区指针* @return 允许读取的数据长度* @note 也就是未处理的数据长度*/
size_t circularBufferReadAllow(CircularBuffer* cb);/*** @brief 得到允许写入的数据长度* @param cb 循环缓冲区指针* @return 允许写入的数据长度*/
size_t circularBufferWriteAllow(CircularBuffer* cb);/*** @brief 向缓冲区写入数据* @param cb 循环缓冲区指针* @param data 要写入的数据指针* @param length 要写入的数据的长度* @return true 成功* @return false 失败*/
bool circularBufferWrite(CircularBuffer* cb, uint8_t* data, size_t length);/*** @brief 看看循环缓冲区中是否存在数据* @param cb 循环缓冲区指针* @return true 有* @return false 没有*/
bool circularBufferDataExists(CircularBuffer* cb);/*** @brief 从循环缓冲区中读出数据* @param cb 循环缓冲区指针* @param buffer 读取到的缓冲区* @param length 要求读取的长度* @return true 成功* @return false 失败*/
bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length);/*** @brief 在指定的循环缓冲区中删除数据* @param cb 循环缓冲区指针* @param length 要删除的长度* @return true 成功* @return false 失败* @note 与读取相似,移动读指针即可*/
bool circularBufferDelete(CircularBuffer* cb, size_t length);/*** @brief 在循环缓冲区中查找特定数据,如果成功移动读指针到目标位置* @param cb 循环缓冲区指针* @param target 目标数据指针* @param length 目标数据长度* @return true 找到了* @return false 没有找到* @note 从读指针的位置开始找,如果找到,会移动读指针,也就是抛弃路径上的数据*/
bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_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;cb->end = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;//缓冲区真正的结尾
}size_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);//}//与上面的代码等价size_t readAllow = (cb->writePointer + CIRCULAR_BUFFER_SIZE - cb->readPointer) % CIRCULAR_BUFFER_SIZE;return readAllow;
}size_t circularBufferWriteAllow(CircularBuffer* cb)
{//size_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;//}//与上面的代码等价//不允许整个数组都存入数据size_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, size_t length)
{if (length == 0)return true;if (length > circularBufferWriteAllow(cb))return false;if (cb->writePointer + length <= cb->end)//不需要返回开头的情况{memcpy(cb->writePointer, data, length);cb->writePointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->writePointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(cb->writePointer, data, endLength);//拷贝到后面 memcpy(cb->buffer, data + endLength, beginLength);//拷贝到前面cb->writePointer = cb->buffer + beginLength;}return true;
}bool circularBufferDataExists(CircularBuffer* cb)
{return cb->writePointer != cb->readPointer;
}bool circularBufferRead(CircularBuffer* cb, uint8_t* buffer, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memcpy(buffer, cb->readPointer, length);cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要拷贝的数量size_t beginLength = length - endLength;//开头部分需要拷贝的数量memcpy(buffer, cb->readPointer, endLength);//拷贝后面的memcpy(buffer + endLength, cb->buffer, beginLength);//拷贝前面的cb->readPointer = cb->buffer + beginLength;}return true;
}bool circularBufferDelete(CircularBuffer* cb, size_t length)
{if (length == 0)return true;if (length > circularBufferReadAllow(cb))return false;if (cb->readPointer + length <= cb->end)//不需要返回开头的情况{memset(cb->readPointer, 0x00, length);//不赋值也无所谓cb->readPointer += length;}else//需要返回开头的情况{size_t endLength = cb->end - cb->readPointer + 1;//结尾部分需要无视的数量size_t beginLength = length - endLength;//开头部分需要无视的数量memset(cb->readPointer, 0x00, endLength);//不赋值也无所谓memset(cb->buffer, 0x00, beginLength);//不赋值也无所谓cb->readPointer = cb->buffer + beginLength;}return true;
}bool circularBufferFindAndMove(CircularBuffer* cb, uint8_t* target, size_t length)
{if (target == NULL) return false;if (length == 0) return true;if (length > circularBufferReadAllow(cb)) return false;//目标长度超过当前数据长度,错误uint8_t* it = cb->readPointer;//遍历时的迭代器while (it != cb->writePointer){//目前不涉及迭代器返回开头的情况if (it + length <= cb->end){if (memcmp(it, target, length) == 0){cb->readPointer = it;return true;}}else{size_t endLength = cb->end - it + 1;//结尾部分需要比较的数量size_t beginLength = length - endLength;//开头部分需要比较的数量if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0){cb->readPointer = it;return true;}}it++;if (it > cb->end) it = cb->buffer;//回到开头}return false;
}
后记
如果嵌入式项目中涉及大量数据结构的问题,请使用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
8E%E7%BC%96%E8%AF%91%E6%97%B6%E7%A1%AE%E5%AE%9A%E5%A4%A7%E5%B0%8F%E7%9A%84%E5%AE%B9%E5%99%A8%E5%92%8C%E7%AE%97%E6%B3%95%EF%BC%8C%E9%81%BF%E5%85%8D%E4%BA%86%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%EF%BC%8C%E7%A1%AE%E4%BF%9D%E4%BA%86%E4%BB%A3%E7%A0%81%E5%9C%A8%E8%B5%84%E6%BA%90%E6%9C%89%E9%99%90%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E7%A8%B3%E5%AE%9A%E6%80%A7%E5%92%8C%E5%8F%AF%E9%A2%84%E6%B5%8B%E6%80%A7%E3%80%82)
参考资料
什么是循环缓冲区?-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