Buffer circular para sistemas embarcados

Buffer circular para sistemas embarcados
Este post faz parte da série Estruturas de controle de memória

Olá caro leitor. Com este artigo pretendemos expor um recurso muito legal de ser implementado em software embarcado que pode ser utilizado para as mais diversas aplicações, em especial para gerenciamento de dados vindos de controladores de comunicação, o Buffer Circular.

Buffer circular, deixe que ele ordene os dados por você

O buffer circular é conhecido por desenvolvedores experientes por ser uma estrutura de dados que possui uma política de inserção e retirada de dados do tipo FIFO, que em tradução livre significa: primeiro a entrar, primeiro a sair.

Em termos práticos, os pacotes de dados que são inseridos nesse buffer serão ordenados por chegada, de forma que a retirada será feita nessa mesma ordem. O grande diferencial do buffer circular está no ponto em que virtualmente não ocorre overflow (estouro) da área de dados reservada para depositar as variáveis, pois caso o controle de inserção encontre o fim da área de dados, ele automaticamente aponta para o começo dessa mesma área e sobrescreve o conteúdo mais antigo.

Com isso problemas conhecidos como exceções geradas pelo processador que ocorrem quando tentamos depositar dados numa área de memória maior do que o que foi previamente alocada deixa de ocorrer. A figura abaixo demonstra como funciona de forma intuitiva (e canônica um buffer circular).

Buffer circular para sistemas embarcados
Fugura 1: Buffer Circular

A figura ainda aponta que os controladores de inserção (índices) se movimentam na mesma direção, de forma que o índice de remoção “persegue” o de inserção, e ambos respeitam a regra, e ao chegar no final da área de memória, voltam ao início. De forma adicional controles de estouro da quantidade de dados escritos ou retirados podem ser implementados para prevenção de perda de conteúdo.

Interessante, mas onde posso usar o buffer circular?

O comportamento do buffer circular é ideal para implementação de qualquer estrutura de dados que seja estaticamente alocada e que se comporte como FIFO. Como exemplo, mailboxes e filas podem ser implementados utilizando o buffer circular como kernel. O professor Rodrigo Almeida já deu um uso especial do buffer circular, construção de um gerenciador de tarefas para uso em um sistema operacional de tempo real. Para acessar o seu artigo sobre isso clique aqui.

Nos meus artigos sobre DSP e controle digital, você, leitor, perceberá que a implementação dos filtros e equações de diferenças ocorrem na forma de buffers circulares. As aplicações não param por aí, buffers circulares podem ser utilizados para armazenamento temporário de dados vindos de controladores de comunicação. O melhor exemplo disso é a recepção de dados de uma UART, conforme ilustrado na figura abaixo:

Buffer Circular: UART
Figura 2: Buffer Circular: UART

Com o buffer circular, podemos colocar nossa UART apenas para fazer o que ela deve fazer, receber dados. Com isso, a cada byte recebido, este é colocado no buffer circular. Dessa forma aplicações que precisem dos dados da UART, podem fazer o acesso a eles ou mesmo aguardar o preenchimento do buffer para ler os dados depois. Além disso essa abordagem mantém a linha temporal de chegada dos dados, ou seja, dados que chegam primeiro serão processados primeiro.

Quero um exemplo de Buffer Circular!

A ideia deste artigo é resolver um problema apresentando um recurso para isso, logo falar sobre as melhores implementações de buffer circular fogem ao seu escopo. Nos arquivos buffer_circ.c e .h o leitor vai encontrar uma implementação básica do buffer circular. Ele contém as rotinas para inserção, remoção e checagem de buffer cheio ou vazio. Além disso prove uma macro para declarar uma estrutura de buffer inicializada. Vejam os arquivos:

/**
 * @brief Simple circular buffer implementation with basic management functions
 */

#ifndef __BUFFER_CIRC_H
#define __BUFFER_CIRC_H

#define BUFFER_SIZE 128

/** circular buffer management structure */
typedef struct {
  unsigned char data[BUFFER_SIZE];
  unsigned int  items;
  unsigned int  wr_ptr;
  unsigned int  rd_ptr;
} buffer_circ_t;



/**
 * @brief insert a stream data with size lenght to the buffer
 */
int buffer_insert(buffer_circ_t *b,void *data, unsigned int size);

/**
 *  @brief retrieves a stream of dat with specified size
 */
int buffer_retrieve(buffer_circ_t *b, void *data, unsigned int size);

/**
 *  @brief check if buffer is already full
 */
int buffer_full(buffer_circ_t *b);

/**
 * @brief check if a data stream with specified size will full the buffer
 */
int buffer_will_full(buffer_circ_t *b, unsigned int size);

/**
 * @brief makes the buffer empty
 */
int buffer_flush(buffer_circ_t *b);

/**
 * @brief  check if buffer is empty
 */
int buffer_empty(buffer_circ_t *b);

/** declare a initialized circular buffer */
#define CIRCULAR_BUFFER_DECLARE(name)  \
        buffer_circ_t name = {         \
          .data = {0},                 \
          .items = 0,                  \
          .wr_ptr = 0,                 \
          .rd_ptr = 0,                 \
        }

#endif

A constante BUFFER_SIZE determina em bytes a quantidade de memória reservada para uso, podendo ser alterada de acordo com a necessidade do usuário.

#include "string.h"
#include "buffer_circ.h"


int buffer_insert(buffer_circ_t *b,void *data, unsigned int size)
{
  int ret = -1;
  unsigned char *ptr = (unsigned char *)data;

  if(b == NULL) {
    /* check your buffer parameter */
    return(ret);
  }

  if(size + b->items <= BUFFER_SIZE){

    /* the buffer has enough room, insert
     * stream.
     */
    unsigned int i;
    for(i = 0; i < size; i++) {
        b->data[wr_ptr] = *ptr++;

        /* increment the input index in form  if it
         * reach the buffer end, its placed in it
         * initial address again
         */
        wr_ptr =  (wr_ptr + 1) % BUFFER_SIZE;
        b->items++;
    }
    ret = 0;
  }

  return(ret);
}
int buffer_retrieve(buffer_circ_t *b,  void *data, unsigned int size)
{
  int ret = 0;
  unsigned char *ptr = (unsigned char *)data;

  if(b == NULL) {
    /* check your buffer parameter */
    return((ret = -1));
  }

  /* if the size requested fits on
   * current contents, extract
   * the data stream
   */
  unsigned int i;
  for(i = 0; (i < size) && (b->items != 0); i++) {
      *ptr++ = b->data[rd_ptr];
      rd_ptr =  (rd_ptr + 1) % BUFFER_SIZE;
      ret++;
      b->items--;
  }

  return(ret);
}
int buffer_full(buffer_circ_t *b)
{
  int ret = 0;
  if(b->items ==  BUFFER_SIZE) {
    ret = 1;
  }
  return(ret);
}

int buffer_will_full(buffer_circ_t *b, unsigned int size)
{
  int ret = 0;

  if(b->items + size > BUFFER_SIZE ) {
    ret = 1;
  }
  return(ret);
}
int buffer_flush(buffer_circ_t *b)
{
  int ret = 0;

  if(b != NULL) {
    b->wr_ptr = b->rd_ptr;
    b->items = 0;
  } else {
    ret = -1;
  }

  return(ret);
}
int buffer_empty(buffer_circ_t *b)
{
  return((b->items == 0? 1: 0));
}

Reparem nas linhas 28 e 53 desse arquivo, essa operação é chamada de incremento circular. Sua função é incrementar o índice em uma unidade, e, em caso de encontro com fim da área de memória, o resultado dessa operação será o índice 0, ou seja, o início da área reservada.

Conclusão

Buffers circulares são recursos interessantes, seguros e evitam problemas de vazamento de memória. Por exemplo, são poderosos podendo servir de base para estruturas de dados mais complexas, são versáteis, suas aplicações vão de sistemas operacionais a processamento digital de sinais. O exemplo acima possui funções de operações básicas, além de servir de ponto de partida para implementações mais complexas.

Links úteis

Clique aqui para acessar repositório no Github com código e exemplo

Estruturas de controle de memória

Fila circular para sistemas embarcados
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
8 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Fernando Rocha
Fernando Rocha
29/09/2016 22:20

Muito interessante o artigo!
Creio que o incremento circular dos índices de leitura e escrita do buffer se encontra nas linhas 28 e 53.

Felipe Neves
Felipe Neves
Reply to  Fernando Rocha
30/09/2016 14:22

Obrigado Fernando, artigo corrigido 🙂

Felipe

Leonardo Verona da Silva
Leonardo Verona da Silva
28/09/2016 15:33

Boa tarde, receio não ter entendido o porque do segundo IF na função “buffer_retrieve” presente no arquivo .C. Por favor me corrija se eu estiver errado, mas você copiou esse teste da função “buffer_insert” que inseri dados no buffer e esse mesmo teste verifica se com o número atual de itens + o número de itens a serem “Guardados” no Buffer não irá causar um Overflow. Neste caso eu consigo compreender o teste. No caso da função “buffer_retrieve” esse teste não teria lógica ao meu ver, visto que se o meu buffer é de 10 posições de memória (BUFFER_SIZE =… Leia mais »

Felipe Neves
Felipe Neves
Reply to  Leonardo Verona da Silva
28/09/2016 17:18

Leonardo, obrigado pela leitura, você está corretísssimo, subi a versão de desenvolvimento e atualizei apenas o github. Atualizei o código que está inline no artigo.

O teste realmente não faz sentido, o que se pode ocorrer é se o usuário passar um size maior do que o existente a retirada de bytes vai se repetir até o número de items caia a zero.

Felipe

Zeh Ortigoza
Zeh Ortigoza
20/09/2016 10:07

“Com isso problemas conhecidos como “memory leak” que ocorre quando
tentamos depositar dados numa área de memória maior do que o que foi
previamente alocada deixa de ocorrer”

Na verdade seria “buffer overflow”

Felipe Neves
Felipe Neves
Reply to  Zeh Ortigoza
28/09/2016 17:16

Zeh, obrigado pela leitura.

Voce esta correto, buffer overflow é um tipo de vazamento de memória. A unica diferença sutil é que o oveflow mais se refere a ultrapassagem do limite reservado logicamente para um dado stream ao passo que o memory leak é a consequência desse evento.

Zeh Ortigoza
Zeh Ortigoza
Reply to  Felipe Neves
28/09/2016 17:26

memory leak é quando você aloca memória dinâmica(heap) e nunca libera ela.

buffer overflow é quando você esta acessando mais memória do que você alocou, alterando a memória de outra variável do seu processo ou causando um crash no aplicativo quando você tenta acessar um endereço de memória que o sistema operacional não alocou para o seu processo.

Felipe Neves
Felipe Neves
Reply to  Zeh Ortigoza
29/09/2016 10:59

Zeh, obrigado pelo comentário, a confusão de conceitos foi minha 🙂

O Artigo será corrigido.

Felipe

Home » Software » Buffer circular para sistemas embarcados

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: