Protopanda

Protopanda

Telegram channel: https://t.me/mockdiodes Telegram chat: https://t.me/protopandachat

Protopanda é uma plataforma open source (firmware e hardware) para controlar protogens. A ideia é ser simples o suficiente para que você só precise de um pouco de conhecimento técnico para fazê-lo funcionar, mas ao mesmo tempo flexível para que uma pessoa com o mínimo conhecimento de Lua possa fazer quase de tudo.

  1. Features
  2. Alimentação
  3. Painéis
  4. Tela e Expressões
  5. Tiras de LED
  6. Bluetooth
  7. Hardware
  8. Montando os eletronicos
  9. Imprimindo e montando as peças
  10. Programação em Lua

Features

Alimentação

TLDR: Use um power bank de pelo menos 20W com PD e USB-C.

Existem dois modos: um que alimenta diretamente em 5V via USB e outro que possui gerenciamento de energia (conversor buck), que requer de 6,5V até 12V. Este segundo modo só é habilitado através de alterações físicas na PCB.

Cada painel HUB75 pode consumir até 2A no brilho máximo, então alimentar diretamente via USB em 5V pode ser problemático. Por isso, esta versão com o regulador ativa o PD (Power Delivery) no USB, solicitando 9V 3A, o que fornece energia suficiente para ambos os painéis. Infelizmente, esta versão consome muito mais energia.

Na maioria dos casos, você não estará operando os painéis no brilho máximo nem com todos os LEDs em branco, então a versão em 5V é a recomendada.da suporta tiras de LED, e há uma porta dedicada a elas. A saída também é de 5V, a mesma dos painéis. Como os LEDs são do tipo WS2812B, eles podem consumir até 20 mA por LED a 100% de brilho.

Painéis

Os painéis utilizados também são conhecidos como painéis HUB75. Eles são controlados pela biblioteca hub75 do mrcodetastic, e estes são os painéis recomendados.
Painéis HUB75
Eles são multiplexados, o que significa que apenas alguns LEDs estão ligados por vez. É rápido o suficiente para não ser percebido pelo olho humano, mas sob luz solar direta, é difícil tirar uma boa foto sem efeito de tearing.
Tearing capturado na câmera

A resolução é de 64 pixels de largura e 32 pixels de altura. Com dois painéis lado a lado, a área total é de 128x32 pixels. A profundidade de cor é de 16 bits, no formato RGB565, o que significa vermelho (0-32), verde (0-64) e azul (0-32).

Buffer Duplo

Para evitar outro tipo de tearing, quando um quadro está sendo desenhado enquanto o quadro está sendo alterado, habilitamos o uso de buffer duplo. Isso significa que desenhamos os pixels na memória, mas eles não aparecem imediatamente na tela. Quando chamamos flipPanelBuffer(), a memória em que desenhamos é enviada para o DMA para ser constantemente exibida no painel. Então, o buffer que usamos para desenhar muda. Isso aumenta o uso de memória, mas é um preço necessário a pagar.

Tela e Expressões

O Protopanda usa imagens do cartão SD e alguns arquivos JSON para construir as sequências de animação. Todas as imagens devem ser PNG; posteriormente, são decodificadas para um formato bruto e armazenadas no arquivo bulk de quadros.

Carregando Frames

Para carregar os quadros (frames), você precisa adicioná-los ao cartão SD e especificar suas localizações no arquivo animation.json:

{
  "frames": [
    {"pattern": "/expressions/angry/angry%d.png","flip_left": false,"flip_right": true,"from": 5,"to": 9,"name": "frames_angry"},
    {"pattern": "/expressions/angry/angry%d transition.png","flip_left": false,"flip_right": true,"from": 1,"to": 4,"name": "frames_angry_transition"},
    {"pattern": "/expressions/blink/blink%d.png","flip_left": false,"flip_right": true,"from": 1,"to": 8,"name": "frames_blink"},
  ]
}

Modificar o arquivo animation.json adicionando ou removendo arquivos forçará o sistema a reconstruir o arquivo de bulk de frames.

Cada elemento no array frames pode ser tanto o caminho do arquivo quanto um objeto que descreve múltiplos arquivos. Você pode usar esta página para ajudar.

Expressões

Uma vez que os frames são carregados e a execução começa, é trabalho dos scripts Lua gerenciar as expressões.
As expressões são armazenadas em expressions.json na raiz do cartão SD.

// Início do JSON //
{
  "frames": [],
  "expressions": [
    {"name": "normal", "frames": "frames_normal", "animation": [1,2,1,2,1,2,3,4,3], "duration": 250},
    {"name": "sus", "frames": "frames_amogus", "animation": "auto", "duration": 200},
    {"name": "noise", "frames": "frames_noise", "animation": "auto", "duration": 5, "onEnter": "ledsStackCurrentBehavior()  ledsSegmentBehavior(0, BEHAVIOR_NOISE) ledsSegmentBehavior(1, BEHAVIOR_NOISE)", "onLeave": "ledsPopBehavior()"},
    {"name": "boop", "frames": "frames_boop", "animation": [1,2,3,2], "duration": 250},
    {"name": "boop_begin", "frames": "frames_boop_transition", "animation": [1,2,3], "duration": 250, "transition": true},
    {"name": "boop_end", "frames": "frames_boop_transition", "animation": [3,2,1], "duration": 250, "transition": true}
  ],
  "scripts": [],
  "boop": {}
}

Expression Properties

Pilha de Expressões

As expressões são armazenadas em uma pilha. Quando você adiciona uma animação que não se repete, ela pausa a animação atual e executa até o final da nova animação. Se você adicionar duas ao mesmo tempo, a última será executada. Quando terminar, a anterior será executada.

Arquivo Bulk

Mesmo com o cartão SD, mudar os quadros não é tão rápido. A interface do cartão SD não é rápida o suficiente. Para acelerar, as imagens são decodificadas de PNG para dados brutos de pixels no formato RGB565 e armazenadas na flash interna. Todos os quadros são armazenados em um único arquivo chamado arquivo bulk. Isso é feito de forma que os quadros são armazenados sequencialmente e, mantendo o arquivo aberto, a velocidade de transferência é acelerada, atingindo 60 FPS.

Toda vez que você adiciona ou modifica um novo quadro, é necessário reconstruir esse arquivo. Isso pode ser feito no menu ou chamando a função Lua composeBulkFile.

Modo managed

As animações são processadas pelo Núcleo 1, então você não precisa perder tempo precioso nos scripts Lua atualizando-as. É possível mudar o quadro usando scripts Lua... Mas também é um desperdício. Então deixe isso para o outro núcleo, e você só precisa se preocupar em selecionar quais expressões deseja!

Durante o modo gerenciado, o desenho dos quadros é tratado pelo Núcleo 1.
alt text

Tiras de LED

O Protopanda suporta o protocolo de LED endereçável WS2812B e fornece um sistema simples para definir alguns comportamentos para a tira/matriz.
alt text
alt text

Bluetooth

Des da versão 2.0, o protopanda suporta quase qualquer dispositivo BLE (bluetooth low energy) que tenha HID. Porém, é possivel criar 'drivers' usando Lua. Por padrão, o protopanda suporta: * https://github.com/mockthebear/ble-fursuit-paw * https://pt.aliexpress.com/item/1005008459884910.html * https://pt.aliexpress.com/item/1005009845485445.html

Porém teoricamente, um joystick que roda em low energy deve ser suportador atraves de keybinds

Keybind

Atualmente as keybinds padrão são:

{
  "keybinds":{
    "joystick.right_hat=5": "BUTTON_LEFT",
    "joystick.right_hat=3": "BUTTON_DOWN",
    "joystick.right_hat=1": "BUTTON_RIGHT",
    "joystick.right_hat=7": "BUTTON_UP",
    "joystick.buttons.4": "BUTTON_CONFIRM",
    "joystick.buttons.1": "BUTTON_BACK",

    "beauty.buttons.4": "BUTTON_LEFT",
    "beauty.buttons.1": "BUTTON_DOWN",
    "beauty.buttons.3": "BUTTON_RIGHT",
    "beauty.buttons.2": "BUTTON_UP",
    "beauty.buttons.5": "BUTTON_CONFIRM",
    "beauty.buttons.6": "BUTTON_BACK"

  }
}

Todas as keybinds mapeiam para input do controle do protopanda.

Hardware

O Protopanda foi projetado para rodar no ESP32-S3-N16R8, uma versão com 16 MB de flash, 384 kB de ROM, 512 kB de RAM e 8 MB de PSRAM octal. É necessário essa versão com mais espaço e PSRAM para ter RAM suficiente para rodar os painéis, BLE e Lua simultaneamente.

No hardware, há uma porta para os dados HUB75, um conector para cartão SD, dois terminais parafusados para a saída de 5V, os pinos de alimentação, uma porta I2C e um pino para tira de LED.

Diagrama

Diagrama

Diagrama

Portas

Portas

Portas

Esquemático

Diagrama

Diagrama

Dois Núcleos

O Protopanda utiliza e abusa dos dois núcleos do ESP32.
* Núcleo 0
Por padrão, o Núcleo 0 é projetado principalmente para gerenciar o Bluetooth. Quando não está fazendo isso, ele gerencia as animações e, quando o Modo Gerenciado está ativo, também cuida da atualização da tela de LED.
* Núcleo 1
O segundo núcleo lida com tarefas não relacionadas à tela. Ele possui a rotina que verifica o nível de energia, atualiza as entradas, lê os sensores e chama a função Lua onLoop.

Montando o seu protopanda

Sei que fazer uma PCB do zero, usar componentes SDM é complicado. Porém, você pode usar peças que dá para comprar no aliexpresse montar uma versão reduzida do protopanda.

Diagrama

Diagrama

Peças

Imprimindo e montando as peças

Guia aqui

Programação em Lua

Lua functions reference

Script Lua mínimo

-- Script Lua mínimo em init.lua  

function onSetup()  
  -- Esta função é chamada uma vez. Aqui você pode iniciar o BLE, começar a escanear, configurar o painel, definir o modo de energia, carregar bibliotecas e preparar as tiras de LED e até ligar a energia.  
  -- Todas as chamadas aqui são feitas durante o SETUP, rodando no núcleo 0.  
end  

function onPreflight()  
  -- Aqui, todas as chamadas Lua são feitas a partir do núcleo 1. Você pode até deixar esta função em branco.  
  -- O Núcleo 0 só começará a gerenciar após 100 ms (o bip final).  
end  

function onLoop(dt)  
  -- Esta função será chamada em loop.  
  -- O parâmetro dt é a diferença em MS entre o início do último quadro e o atual. Útil para armazenar tempo decorrido.  
end  

Ciclar expressões a cada segundo

local expressions = dofile("/lualib/expressions.lua")  
local changeExpressionTimer = 1000 -- 1 segundo  

function onSetup()  
  setPanelMaxBrightness(64)  
  panelPowerOn() -- O brilho sempre começa em 0  
  gentlySetPanelBrightness(64)  
end  

function onPreflight()  
  setPanelManaged(true)  
  expressions.Next()  
end  

function onLoop(dt)  
  changeExpressionTimer = changeExpressionTimer - dt  
  if changeExpressionTimer <= 0 then  
    changeExpressionTimer = 1000 -- 1 segundo  
    expressions.Next()  
  end  
end