嵌入式开发|高效缓冲器设计(C语言)

一、前言

        嵌入式开发中我们要时刻保持代码的高效与整洁。在嵌入式的开发中缓冲器是非常常用的,比如串口的数据,MCU处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如果要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。

                                                 

       环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

二、代码

   经典的环形缓冲器源代码可以参考linux内核中的kernel/kfifo.c,巧夺天工的kfifo:Linux Kernel中的无锁环形缓冲,下面代码是移植的nordic ble SDK中的,相对比较简单,使用方便。

#include "cola_fifo.h"
#include "utils.h"
static __inline uint32_t fifo_length(cola_fifo_t * p_fifo)
{
    uint32_t tmp = p_fifo->read_pos;
    return p_fifo->write_pos - tmp;
}


#define FIFO_LENGTH() fifo_length(p_fifo)  /**< Macro for calculating the FIFO length. */


/**@brief Put one byte to the FIFO. */
static __inline void fifo_put(cola_fifo_t * p_fifo, uint8_t byte)
{
    p_fifo->p_buf[p_fifo->write_pos & p_fifo->buf_size_mask] = byte;
    p_fifo->write_pos++;
}


/**@brief Look at one byte in the FIFO. */
static __inline void fifo_peek(cola_fifo_t * p_fifo, uint16_t index, uint8_t * p_byte)
{
    *p_byte = p_fifo->p_buf[(p_fifo->read_pos + index) & p_fifo->buf_size_mask];
}


/**@brief Get one byte from the FIFO. */
static __inline void fifo_get(cola_fifo_t * p_fifo, uint8_t * p_byte)
{
    fifo_peek(p_fifo, 0, p_byte);
    p_fifo->read_pos++;
}


uint32_t cola_fifo_init(cola_fifo_t * p_fifo, uint8_t * p_buf, uint16_t buf_size)
{
    // Check buffer for null pointer.
    if (p_buf == NULL)
    {
        return 0;
    }

    // Check that the buffer size is a power of two.
    if (!IS_POWER_OF_TWO(buf_size))
    {
        return 0;
    }

    p_fifo->p_buf         = p_buf;
    p_fifo->buf_size_mask = buf_size - 1;
    p_fifo->read_pos      = 0;
    p_fifo->write_pos     = 0;

    return 1;
}


uint32_t cola_fifo_put(cola_fifo_t * p_fifo, uint8_t byte)
{
    if (FIFO_LENGTH() <= p_fifo->buf_size_mask)
    {
        fifo_put(p_fifo, byte);
        return 1;
    }

    return 0;
}


uint32_t cola_fifo_get(cola_fifo_t * p_fifo, uint8_t * p_byte)
{
    if (FIFO_LENGTH() != 0)
    {
        fifo_get(p_fifo, p_byte);
        return 1;
    }

    return 0;

}


uint32_t cola_fifo_peek(cola_fifo_t * p_fifo, uint16_t index, uint8_t * p_byte)
{
    if (FIFO_LENGTH() > index)
    {
        fifo_peek(p_fifo, index, p_byte);
        return 1;
    }

    return 0;
}


uint32_t cola_fifo_flush(cola_fifo_t * p_fifo)
{
    p_fifo->read_pos = p_fifo->write_pos;
    return 1;
}


uint32_t cola_fifo_read(cola_fifo_t * p_fifo, uint8_t * p_byte_array, uint32_t  p_size)
{
    //VERIFY_PARAM_NOT_NULL(p_fifo);
    //VERIFY_PARAM_NOT_NULL(p_size);
    if(p_fifo == NULL)
    {
        return 0;
    }
    if(p_size == 0)
    {
        return 0;
    }
    const uint32_t byte_count    = fifo_length(p_fifo);
    const uint32_t requested_len = (p_size);
    uint32_t       index         = 0;
    uint32_t       read_size     = MIN(requested_len, byte_count);

    (p_size) = byte_count;

    // Check if the FIFO is empty.
    if (byte_count == 0)
    {
        return 0;
    }

    // Check if application has requested only the size.
    if (p_byte_array == NULL)
    {
        return 0;
    }

    // Fetch bytes from the FIFO.
    while (index < read_size)
    {
        fifo_get(p_fifo, &p_byte_array[index++]);
    }

    (p_size) = read_size;

    return p_size;
}


uint32_t cola_fifo_write(cola_fifo_t * p_fifo, uint8_t const * p_byte_array, uint32_t  p_size)
{
    //VERIFY_PARAM_NOT_NULL(p_fifo);
    //VERIFY_PARAM_NOT_NULL(p_size);
    if(p_fifo == NULL)
    {
        return 0;
    }
    if(p_size == 0)
    {
        return 0;
    }
    const uint32_t available_count = p_fifo->buf_size_mask - fifo_length(p_fifo) + 1;
    const uint32_t requested_len   = (p_size);
    uint32_t       index           = 0;
    uint32_t       write_size      = MIN(requested_len, available_count);

    (p_size) = available_count;

    // Check if the FIFO is FULL.
    if (available_count == 0)
    {
        return 0;
    }

    // Check if application has requested only the size.
    if (p_byte_array == NULL)
    {
        return 0;
    }

    //Fetch bytes from the FIFO.
    while (index < write_size)
    {
        fifo_put(p_fifo, p_byte_array[index++]);
    }

    (p_size) = write_size;

    return p_size;
}

三、引申

        环形串口缓冲器的缺点是同一块buffer中只能有一个缓冲器工作,如果当前申请的缓冲区没有在使用那么也会占用内存,因此在这将缓冲器的设计思想做一下引申,一个可以同时多个缓冲器共用同一个buffer的例子。

实现原理:

1.申请一块固定长度的数组用于缓冲区。如:uint8_t buffer[2048],申请2K的总缓冲区

2.将总的缓冲区分成2的整数幂字节大小的块。如每块128字节,可分成16块。

3.使用位图方式管理每块,uint32_t bitmap,32位整形最大可管理32块内存块。

4.0-127字节标号块0,128-255字节标号块2.......,以此类推标号。

5.每次可申请一块的大小,每次申请空闲且标号最小的块,这样可保证每次都是用低位空间。

6.每块最高位字节记录下一块标号,这样可将多个块连城一个大块

7.实现多通道,每个通道包括,最大字节长度,当前字节说,当前写入的块号,当前写入处于某个块的位置,当前读出块号,当前读出块位置。

缺点:多个缓冲器公用同一个buffer,在读写数据时需要加锁。

                     

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页