通用环形缓冲区 LwRB 使用指南
Posted 研究是为了理解
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通用环形缓冲区 LwRB 使用指南相关的知识,希望对你有一定的参考价值。
什么是 LwRB?
LwRB
是一个开源、通用环形缓冲区库,为嵌入式系统进行了优化。源码点击这里(Github)。
LwRB 特性
- 使用 ANSI
C99
编写 FIFO
(先进先出)- 无动态内存分配,数据是静态数组
- 只有单个任务写并且单个任务读时,线程是安全的
- 只有单个中断写并且单个中断读时,中断是安全的
- 支持内存间的 DMA 操作,实现缓冲区和应用程序内存之间零拷贝
- 对于读数据,提供
peek
(窥读,读数据但不改变读指针) 、skip
(跳读,向前移动读指针,将指定长度的数据标记为已读)函数,对于写数据,提供advance
(跳写,向前移动写指针,比如 DMA 硬件向环形缓冲区写入了 10 字节,应用程序需要调用此函数更新写指针)函数。 - 支持事件通知
临界条件
定义 LwRB
的 读指针为 r
,在读写操作时使用 r
,但仅在读操作时修改 r
;
定义 LwRB
的 写指针为 w
,在读写操作时使用 w
,但仅在写操作时修改 w
;
定义 LwRB
的 环形缓冲区大小为 s
,所有操作都会使用,决不会修改 s
。
- 环形缓冲区可以容纳的最大字节数总是为
s - 1
。这要求在初始化时,环形缓冲区的大小要比实际存储数据多一个字节; w
、r
指针总是指向下一个可写、可读位置;- 当
w == r
时,环形缓冲区为空; - 当
w == r - 1
时,环形缓冲区为满;
常规 API 函数
初始化函数
uint8_t lwrb_init(LWRB_VOLATILE lwrb_t* buff, void* buffdata, size_t size)
用法举例:
#define QUEUE_MAX_SIZE (1024)
lwrb_t format_rb;
uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];
void init_format(void)
lwrb_init(&format_rb, format_data, sizeof(format_data));
这里需注意环形缓冲区数组 format_data
的定义格式:
uint8_t format_data[sizeof(int) * QUEUE_MAX_SIZE + 1];
规定写入环形缓冲区或者从环形缓冲区读出的最小数据单位是 数据项
。数据项
可能为 1 个字节,也可能为多个字节。
这里的例子数据项
为 int
类型数据。
首先用宏 QUEUE_MAX_SIZE
定义需要保存的最大数据项
数目,这里定义 1024
个数据项
。
环形缓冲区定义为 uint8_t
类型的数组。
环形缓冲区数组大小可以用以下公式确定:
环形缓冲区数组大小
=
数据项大小
∗
数据项个数
+
1
环形缓冲区数组大小 = 数据项大小 * 数据项个数 + 1
环形缓冲区数组大小=数据项大小∗数据项个数+1
这里 + 1
是因为 LwRB
的实现特性决定的,详见 临界条件 一节。
从环形缓冲区读数据
size_t lwrb_read(LWRB_VOLATILE lwrb_t* buff, void* data, size_t btr)
该函数最多读取 btr
字节的数据(如果有的话),数据从环形缓冲区拷贝到 data
指向的数组。函数返回实际读取的字节数。
用法举例:
lwrb_read(&format_rb, data_buf, sizeof(int));
向环形缓冲区写数据
size_t lwrb_write(LWRB_VOLATILE lwrb_t* buff, const void* data, size_t btw)
该函数最多写入 btw
字节的数据(如果可以的话),数据从 data
指向的数组拷贝到环形缓冲区。函数返回实际写入的字节数。
用法举例:
lwrb_write(&format_rb, data_buf, sizeof(int));
从环形缓冲区窥读数据
size_t lwrb_peek(LWRB_VOLATILE lwrb_t* buff, size_t skip_count, void* data, size_t btp)
函数 lwrb_peek
也可以从环形缓冲区读取最多 btp
字节的数据,数据被拷贝到 data
指向的数组。但是它和 lwrb_read
函数有两点不同:
- 第一,
lwrb_read
函数读取数据后会移动读指针,也就是读取数据后,环形缓冲区会将这些数据移除掉;而lwrb_peek
函数不移动读指针,这就意味着读取数据后,环形缓冲区不会将这些数据移除掉,就好像只是到环形缓冲区中看看这些数据都是什么样子,并不把数据拿出来,所以称为窥读。 - 第二,相比
lwrb_read
函数,lwrb_peek
函数多了一个skip_count
参数。这个参数允许用户先跳过skip_count
指定的字节数,再开始读取。
lwrb_peek
对一些场景十分有用。举一个我用到的例子:
设备与上位机通讯故障后,会将本地采集的不定长数据缓存起来,缓存的格式是:
然后将这些数据存储到环形缓冲区。等到设备与上位机恢复通讯,再将这些数据去除长度字段后上传给上位机。为了数据的可靠传输,设备必须等到上位机确认数据已经收到,才能将这些数据删除掉。
所以在程序设计中,先窥读长度字段,确认长度字段合法后,再窥读剩余数据。
因为使用窥读,所以数据仍保存在环形缓冲区中,直到上位机确认数据已经收到后,再将这些数据从环形缓冲区中删除(会用到尚未介绍的 API 函数)。代码如下:
#define DATA_LEN_NUM 2 //长度字段占用的字节数
#define DATA_SEND_BUF_NUM 100 //数据字段最大字节数
read_count = lwrb_peek(&resume_rb_s, 0, resume_read_buf, DATA_LEN_NUM); //窥读长度字段
/*环形缓冲区空处理*/
if(read_count != DATA_LEN_NUM)
//处理
return 0;
len = to_uint16_low_first(resume_read_buf); //长度字段
/*长度字段合法性检查*/
if(len == 0 || len > DATA_SEND_BUF_NUM)
ASSERT(0);
//错误处理
return 0;
lwrb_peek(&resume_nvrb_s, DATA_LEN_NUM, resume_read_buf, len); //跳过长度字段窥读数据部分
//其它处理
从环形缓冲区跳读数据
size_t lwrb_skip(LWRB_VOLATILE lwrb_t* buff, size_t len)
该函数最多读取 len
字节的数据(如果有的话),数据并不会被保存到用户层,而是直接丢弃掉(环形缓冲区会删除这些数据),就像跳过了这些数据,所以称为跳读。
跳读一般有两个用处:
- 第一,和窥读(
lwrb_peek
)函数配合使用,就如窥读举例使用的场景:先窥读出数据,传送给上位机;上位机确认接收后,用跳读将这些数据丢弃掉。 - 第二,使用硬件(比如 DMA )直接读取环形缓冲区数组后,需要用跳读将这些数据丢弃掉。这会在零拷贝一节中讲解。
以上是关于通用环形缓冲区 LwRB 使用指南的主要内容,如果未能解决你的问题,请参考以下文章