Ping-pong Buffer para sistemas embarcados

Ping-pong Buffer

Olá caro leitor. Neste novo artigo vou apresentar a vocês mais uma estrutura de controle de memória bastante presente, leve e eficiente em sistemas embarcados, o Ping-pong Buffer. Nos últimos artigos publicados mostramos o buffer circular e seu derivado, a fila circular, e apresentamos onde eles podem ser úteis para resolver os problemas do dia a dia na bancada. Com o Ping-pong (sem tradução livre compatível) não é diferente. Sua política de inserção e remoção de dados pode ajudar principalmente em aplicações que envolvem processamento digital de sinais em não-tempo-real, ou em tempo real com maior permissão de latência (por exemplo, DSP utilizando processadores de baixo custo).

 

 

O que é o Ping-pong Buffer?

 

O Ping-pong Buffer não se trata de um derivado do buffer circular (embora algumas implementações possam utilizar ele como base), mas sim de uma estrutura nova. Sua política de trabalho segue algo similar ao: "Dividir para conquistar". O que quer dizer que o tratamento dos dados são divididos em dois grupos, os dados em captura, e os dados em processamento. A ideia então é possuir uma área de memória divida logicamente em duas partes iguais, com dois canais independentes de acesso (os chamados "switches"). Dessa forma, o canal "Ping" tem por função receber uma cadeia de bytes produzida por uma fonte (por exemplo amostras vindas de um conversor A/D) até que seu espaço de memória seja totalmente preenchido. Já o canal "Pong" tem por função ler a memória a ele reservado previamente, cheia pelo canal "Ping" e realizar algum processamento relevante. Vejamos a figura abaixo:

 

ping pong buffer
Figura 1: Política de trabalho do Ping-pong Buffer

 

A figura 1 ilustra perfeitamente a política de trabalho da qual falamos a pouco, de forma que quando o canal "Ping" está cheio e o canal "Pong" vazio (essa operação é gerenciada na grande maioria das vezes pelo usuário), uma operação de sincronização é realizada e o canal Ping aponta a memória antes destinada ao canal Pong, e para este último, lhe resta apontar para o canal que era anteriormente do "Ping" com os novos dados disponíveis para processamento. Enquanto isso, o novo canal Ping cuida de preecher a memória com novos dados. Essa estratégia permite economizar o uso de CPU, uma vez que a tarefa de aplicação só será colocada em modo ativo quanto novos dados estiverem disponíveis, sendo possível assim gerar uma linha de delay de acordo com tamanho de memória reservada, multiplicada pela taxa de preenchimento do buffer.

 

Vejam a figura abaixo comparando o processamento amostra a amostra vindo de um conversor A/D contra a um processamento em blocos utilizando um Ping-pong Buffer:

 

processo ping pong buffer
Figura 2: Comparação do processamento amostra a amostra de um conversor A/D contra a um processamento em blocos utilizando um Ping-pong Buffer.

 

Na figura acima temos dois casos de uso para processamento de um sinal que chega de um conversor A/D. No caso 1, o da esquerda, uma rotina de interrupção acessa o registrador de dados e escreve em uma região previamente alocada, de forma que a aplicação tem que processa-la para não gerar distorção. Porém, a tarefa será executada a cada captura do A/D. No caso 2, à direita, um periférico de transferência automática de memória captura as amostras e as escreve em uma área de memória previamente alocada, e uma interrupção será gerada apenas quando essa área for preenchida, reduzindo muito o uso da CPU que só irá executar a aplicação quando o efeito de buffer cheio for gerado (temporalmente em menor escala do que a cada ocorrência de uma interrupção para o A/D).

 

 

Legal esse caso de uso do A/D, mas onde uma coisa conecta com outra?

 

Vamos à parte que mais gosto desses artigos, o uso prático. A grande vantagem do buffer apresentado é que ele consegue otimizar ambos os casos de uso do exemplo apresentado acima. No caso 1, o nosso ping-pong buffer serviria como uma área de memória a ser preenchida para coletar várias amostras. Com isso a rotina de interrupção torna-se leve, tendo apenas que inserir amostra a amostra no buffer e quando esse estiver cheio, podendo enviar um sinal acordando a aplicação, que por sua vez troca os canais, e processa o novo buffer enquanto o outro enche. Esse caso inclusive é perfeito em microcontroladores de baixo custo, que não possuem um periférico automático de transferência de memória como o DMA. Eu redigi esse artigo que demonstra o caso 1 otimizado com essa técnica.

 

O segundo caso de uso acaba por ser ainda mais otimizado, pois com o DMA a rotina de interrupção do A/D simplesmente deixa de ser necessária. O DMA nesse caso tem por função receber o endereço de memória onde os dados serão depositados e automaticamente, a cada nova amostra do A/D, esta será copiada para o canal "Ping", e ao preenchimento completo dele, uma interrupção será gerada, onde os dados ficariam acessíveis para processamento os canais de acesso então se invertem e o novo endereço de memória é enviado a o DMA que se encarrega de preencher o novo bloco. A figura abaixo ilustra bem o que ocorre:

 

processo ping pong buffer com dma
Figura 3: Ping-pong buffer e DMA

 

Um terceiro caso que vale a pena citar para o uso de um Ping-pong buffer é o tratamento de imagens para controladores de display (os populares TFT). Com o uso de um Ping-pong buffer é possível enviar ao display um framebuffer já processado através do canal "Pong", enquanto o processador utiliza o canal "Ping" para realizar operações como desenhar ou reposicionar objetos na tela. O mesmo caso aplica-se ao uso de conversor D/A para geração de sinais, vejam mais uma figurinha que ilustra esse caso:

 

Exemplo de Ping-pong buffer para tratamento de imagens
Figura 4: Exemplo de Ping-pong buffer para tratamento de imagens

 

 

Implementação básica de um Ping-pong buffer

 

Nesse exemplo de implementação de um ping-pong buffer tentei ser um pouco mais amplo, provendo a clássica macro que declara um ping-pong devidamente inicializado e pronto para uso. As rotinas de insert e retrieve estão disponíveis  e automaticamente gerenciam onde os canais "Ping" e "Pong" devem acessar a memória reservada. A função ppbuf_get_full_signal é responsável pela operação de sincronização. Assim, quando seu retorno for true, a aplicação que chama essa função tem a opção de consumir o evento, e quanto isso ocorrem os canais Ping e Pong se invertem, podendo num novo ciclo ser inicializado.

 

Vejamos a interface do nosso Ping-pong Buffer:

 

 

As funções ppbuf_dma_xxx são para uso exclusivo com DMA, um certo cuidado deve se ter ao utilizá-las pois ela devolve o endereço de memória do canal "Ping" ou "Pong" correspondente para ser passado ao DMA. Junto com ppbuf_dma_force_swap, que força a troca de canal de forma assíncrona. Vejamos a implementação:

 

 

A implementação possui pouco a se comentar, operações de posicionamento de memória, e novamente a eficiência para cópia dos dados em processadores sem DMA está condicionada à implementação interna da função memcpy, geralemente otimizada para IDE, Compilador ou arquitetura.

 

 

Conclusão

 

A estrutura Ping-pong buffer é uma estrutura leve e eficiente para captura e processamento de dados de forma quase que simultânea. Sua estratégia de captura de dados, enquanto outra sequência é processada, a torna ideal para uso com processamento de sinais e imagens, evitando frequência elevada de eventos periódicos relacionadas a novas amostras que chegam do (ou vão para o) hardware. Espero que seja mais um objeto útil ao leitor. Bons projetos!

 

 

Links úteis

 

Acesse aqui o repositório do Github contendo os arquivos e uma aplicação de exemplo simples que ensina a alocar, inserir e retirar os dados.

Outros artigos da série

<< Fila circular para sistemas embarcadosPilha Estática - Uma estrutura leve para sistemas embarcados >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Felipe Neves
Engenheiro de sistemas embarcados apaixonado pelo que faz, já trabalhou em diversos setores de tecnologia nos últimos 14 anos com destaque para Defesa, Automação, Agricultura, Wearables, Robótica e mais recentemente Semicondutores. Possui sangue maker, tendo paixão por construir e compatilhar suas próprias coisas e explorar novos sabores dentro do mundo "embedded". Atualmente trabalha como Engenheiro de Software Senior na Espressif, sim aquela do ESP32 e do ESP8266. Tem interesse em tópicos que envolvam Robótica, Controle de Movimento, DSP e Sistemas de Tempo Real.

Deixe um comentário

avatar
 
  Notificações  
Notificar