![]() |
| É difícil colocar uma ilustração do firmware... |
No projeto da Make foi usada uma biblioteca da Adafruit para controle dos LEDs. Eu me debrucei sobre os fontes e gerei uma versão especializada para a operação a 20MHz.
Como falei antes, os tempos são bem apertados e é preciso apelar para assembly. O código da biblioteca é muito elegante e parte da ideia que, no envio de um bit, existem três pontos onde o sinal pode ser alterado:
- no início do bit o sinal é colocado em nível alto (H)
- após 450ns o sinal é alterado para nível baixo se for um bit "0" ou mantido em nível alto se for um bit "1" (x)
- após outros 350nS o sinal sempre deve ser colocado em nível baixo (L)
HHHHHHHHHxxxxxxxLLLLLLLLL ^ ^ ^
Indicando que o sinal deve alterado a 0, 9 e 16 ciclos do início. Aí é hora de contar os ciclos de cada instrução, lembrando que um desvio gasta 1 ou 2 ciclos e tomando o cuidado das contagens serem constantes em todos os caminhos possíveis. Um truque adicional, para conseguir a velocidade necessária, é pré-computar as programações para sinal alto e baixo. O resultado, na horrível notação para código assemby do avr-gcc, é isto aqui:
// Rotina para atualizar os LEDs
// 12 LEDs RGB, controlador WS2812B
// bit 0: 400nS high, 850nS low
// bit 1: 800nS high, 450nS low
// Vamos aproveitar as tolerâncias e trabalhar com 450/800
// A rotina abaixo é uma adaptação de código da biblioteca NeoPixel
// da Adafruit https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp
// Garantir mínimo de 50uSeg entre chamadas
static void refresh()
{
volatile uint16_t i = 3*12; // Contador de bytes
volatile uint8_t bit = 8; // Contador de bits
volatile uint8_t *ptr = pixels; // Ponteiro para os bytes
volatile uint8_t b = *ptr++; // Byte atual
volatile uint8_t hi, lo; // Programações do port
volatile uint8_t next; // Próxima transição
volatile uint8_t *port = &LED_PORT;
cli(); // Sem interrupções daqui para frente
hi = *port | LED_BIT;
lo = *port & ~LED_BIT;
next = lo;
if (b & 0x80)
next = hi;
// Assembly para contar os ciclos
// A 20MHz, cada ciclo dura 50nS
// Um bit (1,25uS) corresponde a 25 cliclos
// HHHHHHHHHxxxxxxxLLLLLLLLL
// ^ ^ ^ mudanças em 0, 9 e 16 ciclos
asm volatile(
"head25:" "\n\t" // Clk Pseudocode (T = 0)
"st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2)
"sbrc %[byte], 7" "\n\t" // 1-2 if(b & 128)
"mov %[next], %[hi]" "\n\t" // 1-0 next = hi (T = 4)
"dec %[bit]" "\n\t" // 1 bit-- (T = 5)
"rjmp .+0" "\n\t" // 2 nop nop (T = 7)
"st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 9)
"mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 10)
"breq nextbyte25" "\n\t" // 1-2 if(bit == 0) (from dec above)
"rol %[byte]" "\n\t" // 1 b <<= 1 (T = 12)
"rjmp .+0" "\n\t" // 2 nop nop (T = 14)
"rjmp .+0" "\n\t" // 2 nop nop (T = 16)
"st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 18)
"nop" "\n\t" // 1 nop (T = 19)
"rjmp .+0" "\n\t" // 2 nop nop (T = 21)
"rjmp .+0" "\n\t" // 2 nop nop (T = 23)
"rjmp head25" "\n\t" // 2 -> head25 (next bit out)
"nextbyte25:" "\n\t" // (T = 12)
"ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 13)
"ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 15)
"nop" "\n\t" // 1 nop (T = 16)
"st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 18)
"nop" "\n\t" // 1 nop (T = 19)
"rjmp .+0" "\n\t" // 2 nop nop (T = 21)
"sbiw %[count], 1" "\n\t" // 2 i-- (T = 23)
"brne head25" "\n" // 2 if(i != 0) -> (next byte)
: [port] "+e" (port),
[byte] "+r" (b),
[bit] "+r" (bit),
[next] "+r" (next),
[count] "+w" (i)
: [ptr] "e" (ptr),
[hi] "r" (hi),
[lo] "r" (lo));
sei(); // Permite novamente interrupções
}
Reparem que as interrupções precisam ser desabilitadas durante este trecho crítico. Feito isto, o resto parece muito fácil:- O programa principal fica em loop processando os potenciômetros e atualizando os LEDs.
- O processamento dos potenciômetros consiste em amostrar com o ADC e considerar os valores em faixas:
- Para o modo, existem quatro faixas (apagado, pisca, roda e aceso). Nos modos pisca e roda a posição dentro da faixa determina a velocidade.
- Para a cor temos seis faixas (apagado, vermelho, verde, azul, "cinza" e branco). Para vermelho, verde, azul e "cinza" a posição dentro da faixa determina a intensidade
- A atualização dos LEDs consiste em atualizar um vetor com os bytes a serem enviados para os LEDs. A atualização considera o modo atual, a velocidade é implementada através de delays gerados com a interrupção do timer.
O código completo pode ser examinado nos arquivos do blog, em Lanterna.zip.

Nenhum comentário:
Postar um comentário