sábado, 14 de junho de 2014

ISR em assembly para Arduinos

Durante o desenvolvimento do emulador de teclado para o TK90X consegui incorporar um pouco de código assembly para ser compilado no Arduino. Aproveitando a deixa fiz algumas experiências destinadas ao projeto de um adaptador para ser conectado sobre a PPI.

Adaptador PPI-MEGA8

O Código resultante ficou em 15 ciclos de máquina (contando os 4+3 da latência e do Jump no vetor de interrupção).


char Keymap[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};


ISR (PCINT1_vect, ISR_NAKED) {
  asm volatile (                    // 7 até aqui
   "in __tmp_reg__,__SREG__ \n\t"   // 1 Salva registrador de Status  
   "ldi r26,lo8(Keymap) \n\t"       // 1 Ponteiro X = endereço de Keymap
   "ldi r27,hi8(Keymap)\n\t"        // 1 
   "in r19,%0 \n\t"                 // 1 Amostra bits da porta C da PPI (PC0..PC3)
   "andi r19,0x0f \n\t"             // 1 limpa bits 4..7
   "add r26,r19 \n\t"               // 1 soma com ponteiro 
   "ld r19,X \n\t"                  // 1 lê coluna correspondente do mapa de teclas
   "out %1,r19 \n\t"                // 1 escreve na porta B da PPI
                                    // até aqui 15 instruções 
   "out __SREG__,__tmp_reg__ \n\t"  // restaura registrador de Status
   "reti \n\t" 
  
   :: "I" (_SFR_IO_ADDR(PINC)), "I" (_SFR_IO_ADDR(PORTB)) ); 
   
}

Porém algumas considerações permitem reduzir este tempo em alguns ciclos:

Já que apenas os primeiros 4 pinos da Porta C estão conectados, podemos deixar os pinos restantes como saídas e escrever o valor ZERO nos bits PORTC.5 e PORTC.6. Desta maneira, ao se ler o estado da porta C os bits 4 a 8 serão sempre zero, o que significa que podemos economizar um ciclo da instrução andi r19,0x0f

Como o início da RAM dos ATMega88 é em 0x100, podemos declarar a variável Keymap antes de qualquer outra e assim economizamos as instruções ldi r26,lo8(Keymap) e add r26,r19 . Em vez disso, bastar ler a porta C diretamente em r26.

Se reservarmos o registro r27 para ser usado como uma variável, o compilador não deve usar esse registro e assim podemos economizar mais outra instrução: ldi r27,hi8(Keymap) (fazendo é claro a inicialização de r27 em outro trecho de código).

Assim a contagem de ciclos fica igual a 15-1-1-1-1 =11 ciclos.

A 8MHz ou seja, com clock interno isso consome 1,375us
A 16Mhz que é o clock padrão do Arduino são 687,5ns
A 20MHz velocidade máxima oficial são 550ns
A 27MHz em overclock são  407ns

Uma última otimização é colocar o código diretamente no vetor de interrupção e assim economizar mais 3 ciclos.

Assim a contagem de ciclos fica igual a 11-3 =8 ciclos!!

Em termos de tempo isso significa
A 8MHz ou seja, com clock interno isso consome 1,0 us
A 16Mhz que é o clock padrão do Arduino são 500ns
A 20MHz velocidade máxima oficial são 400ns
A 27MHz em overclock são apenas  296,3 ns


Nenhum comentário: