![]() |
É 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
- }
- 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