环形缓冲区驱动
一、环形缓冲区驱动
(一)、基本概念
1、申请内存
2、写数据
3、读数据
要素:缓冲区长度,缓冲区内存申请,写地址,读地址
(二)、环形缓冲区的原理
环形缓冲区也是一块连续的内存(没啥特殊的)
1、申请内存空间
#define BUFFER_SIZE 8
char ring_buffer[BUFFER_SIZE];
这里通过数组的形式申请了 8个char类型长度(8个字节)的连续内存空间
2、写数据
定义一个变量pw用来保存写地址
int pw;
写数据的逻辑:
将要写的数据写入写指针所指向的内存空间,这里就是1个字节。随后,写指针后移1位。
ring_buffer[pw] = data;
pw = pw + 1;
但是这有一个缺陷,当写位置移动到最后一个位置,即pw=7时,此时如果继续往下写,pw+1=8,将发生越界。
所以应该用如下代替:
ring_buffer[pw] = data;
pw = (pw + 1) % BUFFER_SIZE;
3、读数据
定义一个变量pr用来保存写地址
int pr;
读数据的逻辑:
读出读指针所指向的内存空间的数据,这里就是1个字节。随后,读指针后移1位。
data = ring_buffer[pr];
pr = pr + 1;
但是这同样有一个缺陷,当读位置移动到最后一个位置,即pr=7时,此时如果继续往下写,pr+1=8,将发生越界。
所以应该用如下代替:
data = ring_buffer[pr];
pr = (pr + 1) % BUFFER_SIZE;
4、判断环形缓冲区是否为空
判断pw和pr是否相等。
因为数据被读后相应的内存单元会被清空,pw与pr相等意味着pr前面的内存空间已经被全部读出为空了,而pw后面的内存空间还没有杯写,那就意味着整个缓冲区为空。
5、判断环形缓冲区是否写满
判断(pw+1)%BUFFER_SIZE与pr是否相等。
这里,当pw=7、pr=0时,然后往第7处内存单元中写入数据,然后pw = (pw+1)%BUFFER_SIZE = 0 = pr,但此时的pw=pr与上面的含义不同,不能将pw=pr作为缓冲区写满的标志。应该判断(pw+1)%BUFFER_SIZE与pr是否相等。
以上图为例,pr=0、pw=7,即只有第7个内存单元为空,当向其中继续写入数据时,pw = (pw+1)%BUFFER_SIZE = 0 = pr,此时环形缓冲区确实已经被写满。
再举个例子,若pr=1、pw=7,即第0和第7个内存单元为空,下面将会往第7个内存单元写入数据,
pw=(pw+1)%BUFFER_SIZE=0 != pr,即环形缓冲区未写满,此时也确实未满。那么现在继续写数据,
pw=(pw+1)%BUFFER_SIZE=1 = pr,即环形缓冲区已写满,此时也确实写满了。
如果已经写满,则将要写入的数据丢弃。
(三)、代码实现
1、driver_buffer.h
在driver_buffer.h中对环形缓冲区抽象出一个结构体
//抽象出环形缓冲区结构体
typedef struct{
uint8_t *fifo; //用指针动态分配RingBuffer//效果等同于uint8_t buffer[128];
uint16_t pw; //写位置
uint16_t pr; //读位置
uint16_t buf_size;//Buffer大小
}RingBuffer, *ptRingBuffer;
环形缓冲区结构体只是用来表示一个环形缓冲区对象的结构体,而不是真正的环形缓冲区空间。
真正的环形缓冲区内存空间是结构体内部的uint8_t *fifo指针所指向的动态内存空间。
对六个主要函数的声明:
int Driver_Buffer_Init(ptRingBuffer buffer, uint16_t size);
int Driver_Buffer_Write(ptRingBuffer buffer, const uint8_t data);
int Driver_Buffer_WriteBytes(ptRingBuffer buffer, const uint8_t *data_stream, uint8_t len);
int Driver_Buffer_Read(ptRingBuffer buffer, uint8_t *data);
int Driver_Buffer_ReadBytes(ptRingBuffer buffer, uint8_t *data_stream, uint8_t len);
int Driver_Buffer_Clean(ptRingBuffer buffer);
2、driver_buffer.c
补充知识:
动态存储分配函数malloc()
函数原型是void *malloc(unsigned size)
在内存的动态存储去中分配一连续空间,其长度为size。
若申请成功,则返回指向所分配内存空间的起始地址的指针;
若申请内存不成功,则返回NULL(值为0)。
malloc()的返回值为(void * )类型。在具体使用中,将malloc()的返回值转换为特定指针类型,赋给一个指针。计数动态存储分配函数calloc()
函数原型是void *calloc(unsigned n,unsigned size)
在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0。要注意该函数原型是void *calloc(unsigned n,unsigned size),与malloc()函数不一样
char型初始化函数memset()
头文件:<string.h> 或 <memory.h>
函数原型:void *memset(void *s , int ch , size_t n )
memset(结构体/数组名 , 用于替换的ASCII码对应字符 , 前n个字符 );
memset(结构体/数组名 , "用于替换的字符“ , 前n个字符 );
函数解释:将s中的前n个字节用ch替换并且返回s
函数作用:在一段内存块中填充某一个给定的值,常用于较大的对结构体和数组的清零操作。
对上述六个函数的实现:
①Driver_Buffer_Init(ptRingBuffer buffer, uint16_t size)函数:
/*
函数名:Driver_Buffer_Init
功能:初始化一个指定的环形缓冲区
输入参数:buffer->指向目标缓冲区;size->表示缓冲区分配的内存大小,单位是字节
输出参数:无
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_Init(ptRingBuffer buffer, uint16_t size)
{
if(buffer == NULL || size == 0) return -1;//buffer所指向的结构体应不为空,size应不为0
if(buffer->fifo == NULL)//buffer所指向的结构体内的fifo指针成员应不为空
{
buffer->fifo = (uint8_t*)malloc(size);//申请动态内存空间,并把所申请的内存空间的首地址赋给fifo指针。
if(buffer->fifo == NULL)
{
printf("Malloc %d bytes failed.\r\n", size);
return -1;
}
}
buffer->pw = buffer->pr = 0;//不管buffer所指向的缓冲区结构体的fifo指针成员是否为空
buffer->buf_size = size; //都将写指针、读指针、缓冲区大小置0。
return 0;
}
②Driver_Buffer_Write(ptRingBuffer buffer, const uint8_t data)函数:
/*
函数名:Driver_Buffer_Write
功能:往指定的环形缓冲区写入一个字节的数据
输入参数:buffer->指向目标环形缓冲区;data->写入的数据
输出参数:无
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_Write(ptRingBuffer buffer, const uint8_t data)
{
if(buffer == NULL || buffer->fifo==NULL) return -1;//buffer所指向的结构体应不为空,fifo指针成员应不为空
int i = (buffer->pw + 1) % buffer->buf_size;//定义一个变量使其等于“(pw+1)%BUFFER_SIZE”
if(i != buffer->pr) // i不等于pr说明环形缓冲区未被写满
{
buffer->fifo[buffer->pw] = data; //向其中写入数据,这里之所以可以这么写,
//是因为fifo是指针变量,而数组名本身就是数组的首地址,也是一个指针,
//所以可以这么写。
buffer->pw = i;//令pw = (pw+1) % BUFFER_SIZE;即写地址后移。
return 0;
}
//否则说明环形缓冲区已被写满,返回-1
return -1;
}
③Driver_Buffer_WriteBytes(ptRingBuffer buffer, const uint8_t *data_stream, uint8_t len)函数:
/*
函数名:Driver_Buffer_WriteBytes
功能:往指定的环形缓冲区写入多个字节的数据
输入参数:buffer->指向目标缓冲区;data_stream->写入的数据流;len->写入的数据个数,单位是字节
输出参数:无
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_WriteBytes(ptRingBuffer buffer, const uint8_t *data_stream, uint8_t len)
{
int i = 0;
if(buffer == NULL || buffer->fifo==NULL)return -1;//buffer所指向的结构体应不为空,fifo指针成员应不为空
if(data_stream == NULL) return -1;//数据流指针也应不为空
if(len == 0) return -1;//长度不为0
for(i=0; i<len; i++)
{
if(Driver_Buffer_Write(buffer, data_stream[i]) != 0)break;
//如果向buffer环形缓冲区写入一个字节数据的返回值不为0 (写入的数据是data_stream数组的第i个字节)
//表示写入第i个字节写入失败了。
//并跳出循环。
//否则如果向buffer环形缓冲区写入一个字节数据的返回值为0
//表示写入第i个字节写入成功了。
}
//由于从0-(len-1)的范围内,只要有某一字节写入失败就跳出循环,除非len长度的所有字节都写入成功了会结束循环
//也就是说当前的i表示的是当前已经成功写入的字节数
return i; //返回的是 成功写入的字节数i。
}
这里仍然需要注意的是该函数的入口参数const uint8_t *data_stream,定义成指针的形式,就可以灵活的定义一个字符串,长度不固定,而不用固定为一个char型数组的形式,而data_stream[i]的用法是因为data_stream是一个指针,而数组名也是一个地址,指针。
④Driver_Buffer_Read(ptRingBuffer buffer, uint8_t *data)函数:
/*
函数名:Driver_Buffer_Read
功能:从指定的环形缓冲区读出一个字节的数据
输入参数:buffer->指向目标缓冲区;
输出参数:data->读出的数据
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_Read(ptRingBuffer buffer, uint8_t *data)
{
if(buffer == NULL || buffer->fifo==NULL)return -1;
if(data == NULL) return -1;
if(buffer->pr == buffer->pw) return -1; //读地址和写地址相等,表示当前环形缓冲区为空,不能读,返回-1。
*data = buffer->fifo[buffer->pr];//读出读指针所指向的字节,读到data指针所指向的内存单元
buffer->pr = (buffer->pr + 1) % buffer->buf_size;//令pr=(pr+1)%BUFFER_SIZE,即读指针后移
return 0;
}
读一个字节函数与写一个字节函数的代码和逻辑基本一致。
⑤Driver_Buffer_ReadBytes(ptRingBuffer buffer, uint8_t *data_stream, uint8_t len)函数:
/*
函数名:Driver_Buffer_ReadBytes
功能:往指定的环形缓冲区读出多个字节的数据
输入参数:buffer->指向目标缓冲区;len->读出的数据个数,单位是字节
输出参数:data_stream->读出的数据流
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_ReadBytes(ptRingBuffer buffer, uint8_t *data_stream, uint8_t len)
{
int i = 0;
if(buffer == NULL || buffer->fifo==NULL) return -1;
if(data_stream == NULL) return -1;
if(len == 0) return -1;
for(i=0; i<len; i++)
{
if(Driver_Buffer_Read(buffer, &data_stream[i]) != 0) break;
//循环读出buffer中的数据存到data_stream数据流中
//只要中途某个字节读失败(即遇到环形缓冲区为空的情况),就会跳出循环
}
//否则返回i的值,此时i为已经读出的字节数
return i;
}
⑥Driver_Buffer_Clean(ptRingBuffer buffer)函数:
/*
函数名:Driver_Buffer_Clean
功能:清除指定的环形缓冲区
输入参数:buffer->指向目标缓冲区
输出参数:无
返回值:-1->表示错误;0->表示成功
*/
int Driver_Buffer_Clean(ptRingBuffer buffer)
{
if(buffer == NULL || buffer->fifo==NULL) return -1;
memset(buffer->fifo, 0, buffer->buf_size);//清空fifo所指向的连续内存空间,全部置0
buffer->pw = buffer->pr = 0;//写指针和读指针都置0
return 0;
}
训练营导航:www.100ask.net