【百问网】7天物联网智能家居实战 Day3_1

环形缓冲区驱动


一、环形缓冲区驱动

(一)、基本概念

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()的返回值转换为特定指针类型,赋给一个指针。

img

计数动态存储分配函数calloc()
函数原型是void *calloc(unsigned n,unsigned size)
在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0。

img

要注意该函数原型是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值