As fitas de LED RGB “endereçáveis”, baseadas no chip WS2812B, são bastante populares para a geração de efeitos luminosos. Estas fitas estão disponíveis em versões para operação a 5V ou 12V, com ou sem proteção para uso externo e em diferentes densidades (número de LEDs por metro). Aprenda neste artigo a controlar uma fita de led com a Raspberry Pi Pico.
Do ponto de vista de programação, a interface destas fitas apresenta alguns desafios, principalmente para microcontroladores mais modestos. É utilizado um único sinal de dados, com a definição dos bits e seus valores sendo feita por temporizações bastante curtas. A rigor, os LEDs não são endereçáveis: não é possível atualizar um LED isoladamente. Para atualizar um LED é preciso atualizar também todos os LEDs anteriores a ele na fita, o que aumenta a quantidade de bytes a enviar. Considerando que são 3 bytes para cada LED (um para cada cor), a memória para armazenar o estado de toda uma fita com 100 ou mais LEDs pode ser significativa.
Neste post vamos ver como alguns recursos do Raspberry Pi Pico facilitam o controle de uma fita de LED RGB.
Material Utilizado:
Para reproduzir o exemplo que vamos montar você vai precisar de:
- Raspberry Pi Pico
- Fita de LED RGB WS2812 5050 5M
- Jumpers Macho-Macho x40 Unidades
- Protoboard 830 Pontos
- Cabo Micro USB 1,8 metros
Tratando o Protocolo de Comunicação do LED WS2812B
Como dito na introdução, o envio de cada bit exige temporizações relativamente curtas:
- O início da sequência de bits é marcado por um nível baixo de pelo menos 50 microsegundos.
- O envio de um bit “0” é composto de 400 nanosegundos de nível alto seguido de 850 nanosegundos de nível baixo.
- O envio de um bit “1” é composto de 850 nanosegundos de nível alto seguido de 400 nanosegundos de nível baixo.
- Os tempos dos bits precisam ter uma precisão de 150 nanosegundos.
As bibliotecas mais populares para uso com a IDE Arduino recorrem a códigos em assembly (com interrupções inibidas) para conseguir gerar estes tempos em microcontroladores ATmega como os usados no Arduino Uno. Para microcontroladores mais rápidos, elas utilizam interrupções de relógio, o que causa a pausa frequente da execução do programa principal.
Tipicamente é armazenada uma única cópia do estado de todos os LEDs. Para produzir um efeito ou animação, esta cópia é alterada e depois enviada à fita. Ao final do envio o processo se repete.
Como a Raspberry Pi Pico pode melhorar a atualização?
Em primeiro lugar, ele dispõe do recurso de I/O Programado (o PIO). O PIO pode ser programado para gerar automaticamente o sinal com os tempos corretos a partir da escrita dos três bytes, sem interferência no processamento. O programa para isso é um dos exemplos fornecido pela Raspberry Pi Foundation e pode ser visto no github oficial: https://github.com/raspberrypi/pico-examples/blob/master/pio/ws2812/ws2812.pio (se o código parecer confuso, a documentação do SDK explica linha a linha a programação da PIO).
A carga dos bytes no PIO pode ser também feita sem interferência direta do processador, através do recurso de Acesso Direto à Memória (DMA). O DMA é programado com o endereço inicial e o tamanho dos dados a transferir e faz a transferência dos dados em paralelo à execução normal. O final da transferência pode ser sinalizado por uma interrupção.
Como o Raspberry Pi Pico possui generosos 264k de Ram, podemos guardar duas cópias da imagem dos LEDs. Enquanto atualizamos uma cópia, a outra é enviada à fita (via DMA+PIO). Com isso a geração do próximo estado e o envio à fita são feitos em paralelo, possibilitando uma atualização mais frequente.
Fita de led com Raspberry Pi Pico | Um Exemplo Prático
Neste exemplo vamos usar uma fita com 150 LEDs RGB, com alimentação de 5V. A ligação da fita à alimentação e ao Raspberry Pi Pico é a seguinte:
Vamos usar uma alimentação separada de 5V para a fita ao invés de usar os 5V da USB devido ao consumo dos LEDs. A Raspberry Pi Pico deve ser alimentado normalmente pela USB.
O nosso programa vai ser escrito em linguagem C. A programação da Raspberry Pi Pico em C é descrita no manual do SDK e a preparação do ambiente é descrita no artigo Programando a Raspberry Pi Pico da Rosana Guse (e na documentação oficial).
O programa (fitaLED.c) é o seguinte:
/** * Controle de LEDs RGB usando PIO e DMA, com duplo buffer * Baseado nos exemplos do SDK da Raspberry Pi Pico */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "hardware/clocks.h" #include "ws2812.pio.h" // GPIO conectado à fita const int PIN_TX = 0; // Número de LEDs na fita #define NLEDS 150 // PIO e state machine para controlar os LEDs static PIO pio; static int sm; // Imagens dos LEDs static uint32_t fitaEd[NLEDS]; // imagem para edição static uint32_t fitaAt[NLEDS]; // imagem para atualização // Monta um valor de 32 bits com as intensidades das cores // ggggggggrrrrrrrrbbbbbbbb00000000 static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) { return ((uint32_t) (r) << 16) | ((uint32_t) (g) << 24) | ((uint32_t) (b) << 8); } // Atualiza os LEDs na fita static void atualizaFita() { uint dma_chan = 0; // Copia para a área que será enviada memcpy (fitaAt, fitaEd, sizeof(fitaAt)); // garante que a transferência anterior terminou dma_channel_wait_for_finish_blocking(dma_chan); // espera pio ter tratado tudo while (!pio_sm_is_tx_fifo_empty(pio, sm)) { sleep_us(10); } // para a state machine pio_sm_set_enabled(pio, sm, false); // Configura o DMA // - incrementar endereço origem (memória) // - não incrementar endereço destino (fila da PIO) // - PIO indica quando fazer a transferência dma_channel_config c = dma_channel_get_default_config(dma_chan); channel_config_set_read_increment(&c, true); channel_config_set_write_increment(&c, false); channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true)); dma_channel_configure(dma_chan, &c, &pio->txf[sm], // destino fitaAt, // origem NLEDS, // tamanho true // começar imediatamente ); // Dispara a transferência sleep_us(300); // Para indicar inicio pio_sm_set_enabled(pio, sm, true); // liga a state machine } // Limpa a imagem dos leds static void apagaLEDS() { memset (fitaEd, 0, sizeof(fitaEd)); atualizaFita(); } // Inicia os LEDs void iniciaLEDs() { // Cria um techo em cor verde/azulada int i = 0; fitaEd[i++] = urgb_u32(0, 5, 2); fitaEd[i++] = urgb_u32(0, 10, 4); fitaEd[i++] = urgb_u32(0, 20, 8); fitaEd[i++] = urgb_u32(0, 40, 16); fitaEd[i++] = urgb_u32(0, 20, 8); fitaEd[i++] = urgb_u32(0, 10, 4); fitaEd[i++] = urgb_u32(0, 5, 2); // O resto fica em cor vermelha clara while (i < NLEDS) { fitaEd[i++] = urgb_u32(3, 0, 1); } // Roda os LEDs void rodaLEDs() { uint32_t aux = fitaEd[0]; for (int i = 1; i < NLEDS; i++) { fitaEd[i-1] = fitaEd[i]; } fitaEd[NLEDS-1] = aux; } // Programa principal int main() { // Aloca e inicia uma PIO pio = pio0; sm = 0; uint offset = pio_add_program(pio, &ws2812_program); ws2812_program_init(pio, sm, offset, PIN_TX, 800000, false); apagaLEDS(); // Apresenta a animação iniciaLEDs(); while (1) { rodaLEDs(); atualizaFita(); sleep_ms(10); // uma pequena pausa para ficar mais visível } }
As informações para compilar este programa serão colocadas em um arquivo CMakeLists.txt:
cmake_minimum_required(VERSION 3.13) include(pico_sdk_import.cmake) project(fitaLED_project) pico_sdk_init() add_executable(fitaLED fitaLED.c ) pico_generate_pio_header(fitaLED ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) target_link_libraries(fitaLED PRIVATE pico_stdlib hardware_pio hardware_dma ) pico_add_extra_outputs(fitaLED)
As instruções abaixo para gerar o executável usando a linha de comando são para Linux (veja mais detalhes nas referências acima) e supõem que você instalou o SDK dentro do seu ‘home’, num diretório chamado pico. Crie debaixo deste diretório pico o diretório fitaLED e coloque dentro dele os arquivos fitaLED.c e CMakeLists.txt listados acima e execute os seguintes comandos:
cd ~/pico/fitaLED cp ~/pico/pico-sdk/external/pico_sdk_import.cmake . cp ~/pico//pico-examples/pio/ws2812/ws2812.pio . mkdir build cd build export PICO_SDK_PATH=../../pico-sdk cmake .. make
Ao final terá sido criado no diretório build, entre outros, o arquivo fitaLED.uf2. Aperte o botão BOOT da Pi Pico, conecte ao micro e solte o botão. O micro vai reconhecer a placa como um pendrive. Copie o arquivo fitaLED.uf2 para este drive, a placa irá reiniciar e executar o programa.
O vídeo abaixo mostra o resultado.
Conclusão
Os recursos de PIO e DMA do Raspberry Pi Pico permitem realizar a atualização dos LEDs RGB sem um consumo elevado de processamento da CPU. Com isto a CPU pode ser usada para o que realmente importa: gerar a próxima imagem. O resultado são animações mais sofisticadas com altas taxas de atualização.
Estas técnicas podem também ser usadas com outros arranjos de LEDs WS2812B, como anéis e painéis.
Então, você vai usar isso no seu próximo projeto? Se sim, mostre ele para gente na Comunidade Maker no Facebook.
Dúvidas ou comentários? É só colocar aqui embaixo!