Tag Archive for Salto

AVR Assembly – Salto sem Label

posted by Wagner Lipnharski @ 3:07pmThursday 4 April 2013.

Em AVR Assembly, as vezes você precisa fazer o program counter saltar as próximas duas instruções, para frente ou para trás, muitas vezes usados em comparações, ou contadores de tempo, e obviamente precisa usar um label para dizer para onde o program counter tem que pular.

Exemplo de uma rotina simples perdedora de tempo.
        LDI   R20, 10
A1:   DEC  R20
        BRNE A1
        …
R20 é carregado com valor 10 e é decrementado até zero antes de ir em frente.  O “BRNE A1″, diz que se após decrementar R20 na instrução “DEC R20″ resultar em R20 diferente de zero, para saltar para o label A1, que está uma instrução acima e irá decrementar novamente, mantendo o loop até R20 chegar à zero, quando o BRNE não irá saltar e ir em frente.
Mas isso requer o label A1:  na instrução DEC R20.
As vezes o uso de labels simples como esse incomoda o programador, e gostaríamos de só dizer ao assembly para saltar para tantas instruçòes para trás ou para frente, sem usar o label.
No Assembly do AVR isso é possível, usando o PC (Program Counter), e ficaria assim:
        LDI   R20, 10
        DEC  R20
        BRNE PC-1
        …
Acima se vê o “BRNE PC-1″, que significa “BRanch Not Equal”, ou seja, “Salte se o resultado anterior for diferente de zero”, para a posição do Program Counter, ou seja, “aqui”, menos uma instrução, então saltará para a instrução anterior, que é onde se quer.
Pode-se usar a faixa de 7 bits completa, ou seja, PC-127 a PC+126, e obviamente RJMP PC, BRNE PC, BREQ PC, fará a instrução saltar para ela mesma e com isso fazer o AVR entrar em loop, saindo só com uma interrupção (se ativada) ou reset.
Apesar desse truque ser muito útil, tem-se que tomar Extremo cuidado ao usa-lo.  Imagine a seguinte rotina:
        LDI   R20, 10
        SBI PortB,3
        NOP
        CBI PortB,3
        DEC  R20
        BRNE PC-4
Que fará o pino 3 da portaB subir e descer 10 vezes, com um pequeno delay de uma instrução NOP enquanto o pino estiver com nível alto, e o BRNE PC-4 fará o loop de volta para SBI PortB,3, pois é a quarta instrução para trás.
Se inserirmos ou removermos uma instrução nesse loop, mudando a quantidade de instruções entre o “SBI PortB,3″ e a “BRNE PC-4″, o valor “PC-4″ deverá ser mudado de acordo, e é comum esquecermos disso.
Exemplo, imagine que o pulso no pino 3 ficou muito curto e se quer aumentar um tililim, então mudamos a rotina para ter DUAS instruções NOP entre o nível do pino subir e descer.
        LDI   R20, 10
        SBI PortB,3
        NOP
        NOP
        CBI PortB,3
        DEC  R20
        BRNE PC-4
Se não atualizarmos o “PC-4″ para “PC-5″, o que irá ocorrer?  o salto PC-4 cairá no primeiro NOP, e não mais no “SBI PortB,3″, a rotina irá rodar, mas o pino 3 nunca subirá de nível.  Então o correto deveria ser:
        LDI   R20, 10
        SBI PortB,3
        NOP
        NOP
        CBI PortB,3
        DEC  R20
        BRNE PC-5
Também temos que prestar atenção para instruções que usam duas posições de memória, são raras, mas existem.   Digamos que ai no meio exista uma instrução grande, do tipo LDS R21, 0×1234, carregando R21 da posição de memória externa 0×1234.  Essa instrução consome 4 bytes, duas posições de memória, pois precisa de dois bytes só para conter o valor do endereço 0×1234, mais o opcode, mais o código do registrador R21.  Usando o espaço de duas posições na memória de código, essa instrução em especial deve ser contada como “duas posições” e não uma só.
Então observe que se eu trocar um dos NOPs por LDS R21, 0×1234, o PC-5 deverá ser PC-6, porque apesar de ter só cinco instruções de SBI PortB,3 ao BRNE, essas instruções ocuparam seis posições de memória, e o PC-n não conta instruções, mas converte isso para endereço físico de memória.
        LDI   R20, 10
        SBI PortB,3
        NOP
        LDS  R21, 0×1234
        CBI PortB,3
        DEC  R20
        BRNE PC-6