WS2812 LED Redondo Luz com Raspberry Pi Pico

Tempo de leitura: 11 minutes

Esta é a segunda entrada na série de tutoriais Raspberry Pi Pico dedicada a explorar as capacidades do novo microcontrolador Pico da Raspberry Pi Foundation. A primeira entrada contém muitos dos fundamentos de ‘introdução’ necessários para fazer a interface com o Raspberry Pi Pico e, portanto, é recomendado que os novos usuários retornem à Parte I antes de continuar com este projeto. Nesta segunda entrada, um LED RGB WS2812 é controlado por meio do sistema de E/S programável (PIO) no microcontrolador Pico. O código e os métodos usados ​​para controlar o WS2812 são baseados no Raspberry Pi Pico Micropython SDK, o projeto intitulado “Usando PIO para conduzir um conjunto de anel NeoPixel (LEDs WS2812).” Uma máquina de estado é usada no Pico para controlar a matriz de LED WS2812, que permite aos usuários testar uma variedade de algoritmos que afetam a luz do anel. Os mapeamentos de luz serão posteriormente capazes de emular os efeitos do LED semelhantes aos demonstrados pelos dispositivos Amazon Alexa ou Google Home. É fornecido um diagrama de ligação universal que permite que qualquer número de LEDs seja conectado ao Pico, que testamos até 60 LEDs.

 

Lista de peças e Ligação

Um anel de luz LED de 16 pixels é recomendado ao usar apenas o microcontrolador Raspberry Pi Pico. Se um anel de luz LED de pixel maior for usado, uma fonte de alimentação externa deve ser usada para reduzir o requisito de carga da porta USB. É por isso que uma fonte externa geral de 5 V é usada no diagrama de fiação para este projeto – ela permitirá que os usuários usem quase qualquer anel de luz LED com qualquer número de pixels (com algumas ressalvas relacionadas ao endereçamento dos pixels). A lista completa de componentes usados neste tutorial é fornecida abaixo:

  • Raspberry Pi Pico
  • Anel de luz LED RGB de 16 pixels
  • Fonte de alimentação 5V 3A
  • Raspberry Pi 4 Computer

Observe que a fonte de 5 V 3A foi escolhida para lidar com anéis luminosos que contêm até 85 pixels (com base na corrente máxima medida de 35mA para cada LED). Fomos capazes de testar 60 LEDs com a fonte de alimentação 5V/3A sem problemas.

Um diagrama de pinagem para o Raspberry Pi Pico pode ser encontrado aqui. O diagrama de ligação entre o Raspberry Pi Pico e o anel de luz LED WS2812 RGB é fornecido abaixo:

Observe o uso do pino DI como pino de controle para o LED WS2812. O 5V é fornecido pela fonte externa, e o Raspberry Pi, o anel luminoso e a fonte externa compartilham um aterramento. O pino DO no anel luminoso permite que os usuários conectem vários conjuntos de LEDs, mas não será usado ou discutido posteriormente neste tutorial. Tecnicamente, o VBUS no Pico (pino 40) é capaz de fornecer 0,5 A (USB 2.0) ou 1,0 A (USB 3.0), portanto, o anel de luz LED de 16 pixels pode ser alimentado através do pino VBUS, no entanto, é mais seguro para usar USB 3.0 ou uma fonte externa.

Abaixo está uma foto da saída de amperagem do multímetro do anel de luz LED de 16 pixels com brilho máximo para todos os 16 LEDs:

Com base na medição de 0,55A do anel de luz LED com brilho máximo (mostrado acima) – o máximo médio para cada LED é aproximado de ≈35mA. Na próxima seção, a estrutura de código MicroPython para controlar os LEDs será apresentada junto com uma rotina simples que cria uma cor de pixel giratória. O Raspberry Pi Pico será usado junto com Thonny IDE para programar o Ring Light de 16 pixels em tempo real.

 

Controle MicroPython do WS2812

O anel de luz LED de 16 pixels será controlado usando o esquema descrito no documento de introdução Raspberry Pi Pico MicroPython, onde um tutorial intitulado “Usando PIO para conduzir um conjunto de anel NeoPixel (LEDs WS2812)” contém um script que usaremos para criar uma máquina de estado no RPi Pico. A máquina de estado será usada para controlar os LEDs no anel luminoso usando apenas um único pino no Pico (GPIO13 como cabeado acima). Um script de exemplo MicroPython completo também pode ser encontrado no repositório NeoPixel Ring do Raspberry Pi Pico no GitHub. Alguns dos algoritmos a seguir não estão contidos nesse repositório, mas estão incluídos no repositório GitHub para este tutorial:

O código para iniciar a máquina de estado no pino #13 GPIO do RPi Pico é fornecido abaixo:

import array, time
from machine import Pin
import rp2

############################################
# RP2040 PIO e configurações de pinos
########################################1####
#
# Configuração do anel LED WS2812
led_count = 16 # number of LEDs in ring light
PIN_NUM = 13 # pin connected to ring light
brightness = 0.5 # 0.1 = darker, 1.0 = brightest

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
             autopull=True, pull_thresh=24) # Configuração PIO

# define os parâmetros WS2812
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()


# Crie o StateMachine com o programa ws2812, produzindo no pino pré-definido
# na frequência de 8 MHz
state_mach = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))

# Ative a máquina de estado
state_mach.active(1)

Este trecho de código é o cabeçalho para todos os algoritmos a seguir. A variável led_count = 16 afirma que usaremos um anel de luz LED WS2812 de 16 pixels. PIN_NUM = 13 define GPIO13 como nosso pino de controle, e o brilho é usado para diminuir os LEDs (1 = brilho máximo). O state_mach hospeda a máquina de estado no GPIO13 nas alocações contidas no ws2812. Por fim, a última linha do código acima ativa a máquina de estado e aguarda a implementação das alterações.

Outro trecho de código é fornecido abaixo, que faz um loop de um LED de uma única cor ao redor da luz do anel de 16 pixels:

import array, time
from machine import Pin
import rp2

############################################
# RP2040 PIO e configurações de pinos
############################################
#
# Configuração do anel LED WS2812
led_count = 16 # number of LEDs in ring light
PIN_NUM = 13 # pin connected to ring light
brightness = 0.5 # 0.1 = darker, 1.0 = brightest

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
             autopull=True, pull_thresh=24) # PIO configuration

# define WS2812 parameters
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()


# Crie o StateMachine com o programa ws2812, produzindo no pino pré-definido
# na frequência de 8 MHz
state_mach = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))

# Ative a máquina de estado
state_mach.active(1)

# Gama de LEDs armazenados em uma matriz
pixel_array = array.array("I", [0 for _ in range(led_count)])
#
############################################
# Funções para coloração RGB
############################################
#
def update_pix(brightness_input=brightness): # escurecendo as cores e atualizando a máquina de estado (state_mach)
    dimmer_array = array.array("I", [0 for _ in range(led_count)])
    for ii,cc in enumerate(pixel_array):
        r = int(((cc >> 8) & 0xFF) * brightness_input) # Vermelho de 8 bits esmaecido para brilho
        g = int(((cc >> 16) & 0xFF) * brightness_input) # Verde de 8 bits esmaecido para o brilho
        b = int((cc & 0xFF) * brightness_input) # Azul de 8 bits esmaecido para o brilho
        dimmer_array[ii] = (g<<16) + (r<<8) + b # Cor de 24 bits esmaecida para brilho
    state_mach.put(dimmer_array, 8) # atualize a máquina de estado com novas cores
    time.sleep_ms(10)

def set_24bit(ii, color): # definir cores para o formato de 24 bits dentro do pixel_array
    pixel_array[ii] = (color[1]<<16) + (color[0]<<8) + color[2] # set 24-bit color

#
############################################
# Loops e chamadas principais
############################################
#
color = (255,0,0) # looping color
blank = (255,255,255) # color for other pixels
cycles = 5 # number of times to cycle 360-degrees
for ii in range(int(cycles*len(pixel_array))+1):
    for jj in range(len(pixel_array)):
        if jj==int(ii%led_count): # no caso de passarmos pelo número de pixels na matriz
            set_24bit(jj,color) # pinte e faça um loop em um único pixel
        else:
            set_24bit(jj,blank) # desligar outros
    update_pix() # update pixel colors
    time.sleep(0.05) # wait 50ms

Abaixo está um vídeo de demonstração do código acima, mostrando o LED de loop único:

Circuito de LED único com anel de luz de 16 pixels

 

 

 

O acima é uma das rotinas mais simples que podemos fazer com o anel de luz de 16 pixels.

Outro algoritmo bastante simples que podemos tentar é uma rotina de LED do tipo ‘respiração’ que envolve ir do brilho mínimo ao máximo e voltar ao brilho mínimo. Isso resulta em uma “respiração” baseada na luz que se parece com algumas das empregadas pelos dispositivos Amazon Alexa. O código é fornecido a seguir, seguido por outra demonstração GIF:

import array, time
from machine import Pin
import rp2

############################################
# RP2040 PIO e configurações de pinos
############################################
#
# Configuração do anel LED WS2812
led_count = 16 # número de LEDs no anel de luz
PIN_NUM = 13 # pin connected to ring light
brightness = 1.0 # 0.1 = darker, 1.0 = brightest

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
             autopull=True, pull_thresh=24) # configuração PIO

# definir parâmetros WS2812
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()

# Crie o StateMachine com o programa ws2812, produzindo no pino pré-definido
# na frequência de 8 MHz
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))

# Ative a máquina de estado
sm.active(1)

# Gama de LEDs armazenados em uma matriz
ar = array.array("I", [0 for _ in range(led_count)])
#
############################################
# Funções para coloração RGB
############################################
#
def pixels_show(brightness_input=brightness):
    dimmer_ar = array.array("I", [0 for _ in range(led_count)])
    for ii,cc in enumerate(ar):
        r = int(((cc >> 8) & 0xFF) * brightness_input) # Vermelho de 8 bits esmaecido para brilho
        g = int(((cc >> 16) & 0xFF) * brightness_input) # Verde de 8 bits esmaecido para o brilho
        b = int((cc & 0xFF) * brightness_input) # Azul de 8 bits esmaecido para o brilho
        dimmer_ar[ii] = (g<<16) + (r<<8) + b # Cor de 24 bits esmaecida para brilho
    sm.put(dimmer_ar, 8) # update the state machine with new colors
    time.sleep_ms(10)

def pixels_set(i, color):
    ar[i] = (color[1]<<16) + (color[0]<<8) + color[2] # set 24-bit color
        
def breathing_led(color):
    step = 5
    breath_amps = [ii for ii in range(0,255,step)]
    breath_amps.extend([ii for ii in range(255,-1,-step)])
    for ii in breath_amps:
        for jj in range(len(ar)):
            pixels_set(jj, color) # mostrar todas as cores
        pixels_show(ii/255)
        time.sleep(0.02)
#
############################################
# Principais chamadas e loops
############################################
#
# color specifications
red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
yellow = (255,255,0)
cyan = (0,255,255)
white = (255,255,255)
blank = (0,0,0)
colors = [blue,yellow,cyan,red,green,white]

while True: # loop indefinidamente
    for color in colors: # emular LED de respiração (semelhante ao Alexa da Amazon)
        breathing_led(color)
        time.sleep(0.1) # espere entre as cores

 

WS2812 LED de ‘Respiração’

Na próxima seção, um exemplo mais detalhado será explorado criando um emulador de LED Google Home.

 

Emulador de LED Google Home e Amazon Alexa

Neste ponto, o usuário deve estar confortável com a funcionalidade dos LEDs WS2812 e como controlar as luzes de anel com a máquina de estado do Pico. Nesta seção, tentaremos emular a função de rotação de LED quádruplo do Google Home, a curva de LED azul de rotação do Amazon Alexa e a função zipped off do Amazon Alexa.

Abaixo está o script usado para realizar todas as três funções declaradas acima:

import array, time
from machine import Pin
import rp2

############################################
# RP2040 PIO e configurações de pinos
############################################
#
# Configuração do anel LED WS2812
led_count = 16 # número de LEDs no anel de luz
PIN_NUM = 13 # pino conectado ao anel de luz
brightness = 1.0 # 0.1 = darker, 1.0 = brightest

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
             autopull=True, pull_thresh=24) # PIO configuration

# define os parâmetros WS2812
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    label("bitloop")
    out(x, 1)               .side(0)    [T3 - 1]
    jmp(not_x, "do_zero")   .side(1)    [T1 - 1]
    jmp("bitloop")          .side(1)    [T2 - 1]
    label("do_zero")
    nop()                   .side(0)    [T2 - 1]
    wrap()


# Crie o StateMachine com o programa ws2812, produzindo no pino pré-definido
# na frequência de 8 MHz
state_mach = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))

# Ative a máquina de estado
state_mach.active(1)

# Gama de LEDs armazenados em uma matriz
pixel_array = array.array("I", [0 for _ in range(led_count)])
#
############################################
# Funções para coloração RGB
############################################
#
def update_pix(brightness_input=brightness): # escurecendo as cores e atualizando a máquina de estado (state_mach)
    dimmer_array = array.array("I", [0 for _ in range(led_count)])
    for ii,cc in enumerate(pixel_array):
        r = int(((cc >> 8) & 0xFF) * brightness_input) # Vermelho de 8 bits esmaecido para brilho
        g = int(((cc >> 16) & 0xFF) * brightness_input) # Verde de 8 bits esmaecido para o brilho
        b = int((cc & 0xFF) * brightness_input) # Azul de 8 bits esmaecido para o brilho
        dimmer_array[ii] = (g<<16) + (r<<8) + b # Cor de 24 bits esmaecida para brilho
    state_mach.put(dimmer_array, 8) # atualize a máquina de estado com novas cores
    time.sleep_ms(10)

def set_24bit(ii, color): # definir cores para o formato de 24 bits dentro do pixel_array
    color = hex_to_rgb(color)
    pixel_array[ii] = (color[1]<<16) + (color[0]<<8) + color[2] # definir cor de 24 bits
    
def hex_to_rgb(hex_val):
    return tuple(int(hex_val.lstrip('#')[ii:ii+2],16) for ii in (0,2,4))

############################################
# Loops e chamadas principais
############################################
#
# Crie o esquema de rotação de quatro cores do Google Home
google_colors = ['#4285f4','#ea4335','#fbbc05','#34a853'] # cores hexadecimais do Google
cycles = 5 # número de vezes para fazer um ciclo de 360 graus
for jj in range(int(cycles*len(pixel_array))):
    for ii in range(len(pixel_array)):
        if ii%int(len(pixel_array)/4)==0: # Leds de 90 graus apenas
            set_24bit((ii+jj)%led_count,google_colors[int(ii/len(pixel_array)*4)])
        else:
            set_24bit((ii+jj)%led_count,'#000000') # outros pixels em branco
    update_pix() # atualizar cores de pixel
    time.sleep(0.05) # espere entre as mudanças

# Criar roda de rotação Amazon Alexa
amazon_colors = ['#00dbdc','#0000d4'] # cores hexadecimais da Amazon
light_width = 3 # largura da matriz rotativa de led
cycles = 3 # número de vezes que a largura gira 360 graus
for jj in range(int(cycles*len(pixel_array))):
    for ii in range(len(pixel_array)):
        if ii<light_width: 
            set_24bit((ii+jj)%led_count,amazon_colors[0])
        else:
            set_24bit((ii+jj)%led_count,amazon_colors[1]) # outros pixels em branco
    update_pix() # atualizar cores de pixel
    time.sleep(0.03) # espere entre as mudanças
time.sleep(0.5)

# desligue os LEDs usando o desligamento tipo zíper Alexa
for ii in range(int(len(pixel_array)/2)):
    set_24bit(ii,'#000000') # desligue o lado positivo
    set_24bit(int(len(pixel_array)-ii-1),'#000000') # desligue o lado positivo
    update_pix() # atualize
    time.sleep(0.02) # espere

No código acima, o usuário pode notar que as cores hexadecimais estão sendo usadas em oposição aos valores RGB de 0-255. Isso se deve simplesmente à semelhança das cores hexadecimais nas cores da marca. Para o Google, estamos usando quatro das cores de marca especificadas (retiradas do logotipo do Google.com):

Essas quatro cores serão colocadas a 90 graus uma da outra para emular a tela LED do Google Home. Se os usuários não conhecerem as rotinas de LED do Google Home, consulte esta página para referência.

Abaixo está um snippet do código usado para fazer o loop dos quatro LEDs de 90 graus para emular o Google Home no sentido horário:

google_colors = ['#4285f4','#ea4335','#fbbc05','#34a853'] # cores hexadecimais do Google
cycles = 5 # número de vezes para fazer um ciclo de 360 graus
for jj in range(int(cycles*len(pixel_array))):
    for ii in range(len(pixel_array)):
        if ii%int(len(pixel_array)/4)==0: # Leds de 90 graus apenas
            set_24bit((ii+jj)%led_count,google_colors[int(ii/len(pixel_array)*4)])
        else:
            set_24bit((ii+jj)%led_count,'#000000') # outros pixels em branco
    update_pix() # atualizar cores de pixel
    time.sleep(0.05) # espere entre as mudanças

Observe que estamos selecionando apenas quatro LEDs e os colorindo como as quatro cores do Google. Então, nós os giramos ao redor do círculo. Para emular o Alexa, podemos usar métodos semelhantes – todos fornecidos no código completo acima e na página GitHub do projeto. Abaixo está um trecho da função de desligamento usada em dispositivos Amazon Alexa, que é apenas uma ligeira variação do desligamento do estilo ‘perseguição’:

# desligue os LEDs usando o desligamento tipo zíper Alexa
for ii in range(int(len(pixel_array)/2)):
    set_24bit(ii,'#000000') # desligue o lado positivo
    set_24bit(int(len(pixel_array)-ii-1),'#000000') # desligue o lado negativo
    update_pix() # atualizar
    time.sleep(0.02) # espere

Abaixo está um vídeo de demonstração do emulador Google e Amazon criado com o Raspberry Pi Pico e o anel de luz WS2812 de 16 pixels:

Também é perceptível que as cores do LED não correspondem exatamente às mostradas nas telas dos computadores. Isto é devido a várias razões. Primeiro, os LEDs WS2812 são maiores do que os LEDs de exibição e os LEDs usados no Google Home e no Amazon Alexa. Isso resulta em uma cor menos definida, onde o vermelho, o azul e o verde são mais visíveis. Outra razão para as disparidades de cores é o uso da cúpula translúcida, que pode refratar certas cores da luz emitida pelos LEDs WS2812.

 

Conclusão e Continuação

Nesta segunda entrada na série exploratória do Raspberry Pi Pico, um LED Ring Light RGB de 16 pixels foi controlado usando uma máquina de estado em MicroPython. Vários algoritmos diferentes foram delineados para emular o esquema de cores do Google Home, os esquemas de LED do Amazon Alexa e outros métodos exclusivos de matriz de cores. Os LEDs usados ​​são semelhantes aos LEDs RGB WS2812 amplamente disponíveis, que consomem cerca de 35mA cada. O anel de luz de 16 pixels foi capaz de ser alimentado com segurança através do pino VBUS no Pico, que recebe energia diretamente da porta USB 3.0 em um computador Raspberry Pi 4. Este tutorial foi concebido como uma introdução adicional ao Raspberry Pi Pico com MicroPython. Esta é apenas a segunda entrada em uma série, onde nos concentramos em algoritmos exclusivos destinados a empurrar os limites do controle Pico do anel luminoso. Esta série de tutoriais continuará com mais explorações do Pico, com extensões em sensores de leitura, usando diferentes protocolos digitais como UART, SPI, I2C, e outras formas de testar os limites do microcontrolador Pico.