sexta-feira, maio 20, 2016

Geração de Vídeo com Microcontrolador AVR - Parte 3

Para podermos fazer algo mais sofisticado, vamos trocar o microcontrolador por um ATmega328.


A troca do microcontrolador afeta pouco o hardware, são os mesmos componentes ligados em pinos diferentes. Para simplificar a mexida do software, usei PB0 e PB1 para a geração de vídeo (no ATtiny44 usei PA0 e PA1, portanto os bits são os mesmos, só mudam os endereços das portas):


Para rodar o software do post anterior no ATmega328 basta:
  • Alterar no Makefile o modelo do processador e o valor dos fuses.
  • Trocar PORTA por PORTB e DDRA por DDRB. No código assembler, trocar OUT 0x1B,R30 por OUT 0x05,R30.
  • Trocar TIM1_COMPA_vect por TIMER1_COMPA_vect (isto me custou um bom tempo, pois com TIM1_COMPA_vect compila sem erros mas não funciona).
Obviamente queremos fazer algo melhor com o ATmega328: mapear a tela na Ram. Como ele "só" tem 2K bytes, não dá para mapear toda a tela do exemplo anterior (144x200 = 3600 bytes). A minha decisão foi usar apenas a região de 96x150 pontos no centro da tela, o que requer 1800 bytes.

Cada linha passa a ser mapeada em 12 bytes, o que é acertado na macro byteblast. O delay antes desta macro, na rotina de interrupção do timer, precisa ser aumentado para compensar os 24 pontos iniciais que não serão enviados (outra alternativa seria alterar byteblast para enviar bytes fixos). Na vertical, basta alterar as definições de primeiralinha e ultimalinha. Por último, pontLinha deve apontar para o byte correto. O código da rotina de interrupção fica assim:
ISR (TIMER1_COMPA_vect)
{
    static uint8_t syncON = VID_0V;
    static uint8_t syncOFF = VID_03V;
    
    // Gera o pulso de sync
    VID_PORT = syncON;
    linhaAtual++;
    if (linhaAtual == iniSyncV)
    {
        // Sync vertical é invertido
        syncON = VID_03V;
        syncOFF = VID_0V;
    }
    if (linhaAtual == fimSyncV)
    {
        // Voltar ao sync normal
        syncON = VID_0V;
        syncOFF = VID_03V;
    }
    if (linhaAtual == fimFrame)
    {
        // Fim do frame
 linhaAtual = 1;
        pontLinha = tela;
    }
    _delay_us(2);   // Aguarda fim do tempo do pulso
    VID_PORT = syncOFF;

    // Gerar a imagem
    if ((linhaAtual <= ultLinha) && (linhaAtual >= primLinha))
    {
 _delay_us(18);  // tempo para centrar a linha

 byteblast();
        
        pontLinha += nBytesLinha;
    }      
}
Com isto a tela passa a apresentar o que estiver no vetor tela. As rotinas abaixo permitem acessar um ponto:
uint8_t lePto (uint8_t x, uint8_t y)
{
    return tela[y*nBytesLinha + (x >> 3)] & (0x80 >> (x & 7));
}

void acendePto (uint8_t x, uint8_t y)
{
    tela[y*nBytesLinha + (x >> 3)] |= (0x80 >> (x & 7));
}

void apagaPto (uint8_t x, uint8_t y)
{
    tela[y*nBytesLinha + (x >> 3)] &= ~(0x80 >> (x & 7));
}
Com esta infraestrutura, podemos montar uma pequena demo: o logotipo do Garoa Hacker Clube (um guarda chuva) com chuva caindo sobre ele. O logotipo é copiado da Flash para Ram no início da execução. A chuva é composta de 48 pingos em colunas alternadas. Um vetor de 48 bytes contém a linha atual de cada pingo. Este vetor é iniciado de forma pseudo-aleatória:
for (i = 0; i < NCHUVA; i++)
{
    chuva[i] = rand() & 0x1F;
}
A cada retraço seis pingos são atualizados:
  • O pingo é apagado na posição atual
  • A linha é incrementada
  • Se a nova posição estiver ocupada ou fora da tela, voltar o pingo para a primeira linha
  • Se a nova posição estiver livre, desenhar o pingo nela
O código fica assim:
for (j = 0; j < 6; j++)
{
    apagaPto ((i << 1)+1, chuva[i]);
    chuva[i]++;
    if ((chuva[i] == nLinhas) || lePto ((i << 1)+1, chuva[i]))
    {
        chuva[i] = 0
    }
    acendePto ((i << 1)+1, chuva[i]);
    if (++i == NCHUVA)
        i = 0;
}
O vídeo abaixo mostra o programa em funcionamento.


O projeto completo pode ser baixado dos arquivos do blog (aquivo vidbpchuva.zip)

Nenhum comentário: