Tag Archive for AVR Assembly

Protocolo RC-5 Infravermelho no AVR

Abaixo o código em assembly mais enxuto para receber RC-5 no AVR (AtMega8) rodando com clock interno de 8MHz.    Esse circuito pode ser implementado num chip pequeno como um AtTiny13, porque mesmo com os 8 pinos, usando dois para +VCC e Terra e um para a entrada do Modulo receptor de IR, ainda sobrariam 5 pinos para mostrar até 32 comandos do Controle Remoto. 

O Código do RC-5 está contido a partir do label IRC5A, e ocupa somente 19 instruções, recebendo o comando do controle remoto e postando nos pinos da porta B.  No caso de usar o AtTiny teria que mudar os pinos de porta utilizados aqui.  Somente 19 instruções, até hoje eu não vi nenhum outro código para decodificar RC-5 que tenha 19 instruções ou menos.

AVR Assembly – Adição com valor imediato

posted by Wagner Lipnharski @ 1:58pm, Thursday 4 April 2013.

O instruçtion set da familia AVR não possui uma instrução de adição em um registrador com um valor imediato.

Por exemplo, você queira somar o valor fixo “6″ no registrador R18, pela lógica seria a instrução ADDI R14, 0×06, ou, ADI R14, 0×06, mas essa instrução não existe, mas existe a instrução ADD R18, Rnn. Para faze-la, se tem que carregar o valor 0×06 em outro registrador, por exemplo, LDI R20, 0×06, e então efetuar a soma ADD R18, R20.

Mas lembrar que somar +6 é o mesmo que subtrair -6, pois “menos com menos resulta em mais”.

Então, ADDI R18, 0×06 é quase o mesmo que SUBI R18, -0×06, e essa instrução existe.

Outro exemplo, 7+1 é o mesmo que 7-255, o resultado sempre será 8.

Lembrar que -6 também pode ser escrito como 0-6, ou seja, 0xFA.

Então, pode-se escrever SUBI R18, -6 ou SUBI R18, 0xFA.

A diferença entre a suposta ADDI R18, 6 e a SUBI R18, 0xFA, é o carry bit.
Se R18 tivesse um conteúdo de 3, ao somar 6 resultaria em 9, sem carry bit, pois o resultado da soma não ultrapassou 255 e não virou o carry bit. Mas ao subrair 254 (-6 ou 0xFA) de 3, o resultado será 2, 1, 0 -1 -2 -3 ….. até chegar ao 9 esperado, na verdade -9, e o sinal negativo ai é o carry bit que ficou ligado. É só ignorar o carry bit, ou prestar a atenção e tomar a atitude correta com relação ao carry bit.

A forma de fazer o carry bit assumir o valor correto após essa “soma” feita subtração, é comparar o resultado com o valor “6″ que deveria ter sido somado.

Então após a SUBI R18, -6, a instrução Compare Imediate “CPI R18, 6″ fará o serviço correto no carry bit. Veja, como o R18 antes da subtração era “3″, somando 6 ela irá para “9″, e o carry bit deveria estar desligado, mas devido à subtração de -6, ele ficou ligado “indevidamente” para o que se destina. Ao comparar o resultado de R18 com 6, e R18 é 9, o carry bit ficará baixo, pois 9 é maior que 6. Nessa comparação de R18 com 6, o carry bit só ficará ligado quando R18 for entre zero e cinco, ou seja, menor que o valor “6″, e isso só irá ocorrer após a suposta soma com “6″, se o R18 original fosse entre 0xF9 e 0xFF, e a suposta soma com 6 fosse efetivamente gerar o carry bit.

Então, para não usar um segundo registrador para fazer a soma imediata, pode-se sim usar o SUBI R18, -6 ou SUBI R18, 0xF9, mas para acertar o carry bit, em seguida teremos que usar CPI R18, 6.

Mas, necessitando fazer uma soma de valor constante (imediato) à um numero que use mais de 8 bits, ou seja, mais um byte, pode-se fazer toda a operação de soma usando o SUBI, e o SBCI (subtract com valor imediato e com carry) e com isso resolve tudo ao contrário e obtendo o valor certo ao final. Por exemplo 3 bytes, R18, R19 e R20, somando o valor 6 e que propagasse o valor nos 24 bits, considerando que R18 seja o byte de menor ordem, ficaria assim:

SUBI R18, -6
SBCI R19, -1
SBCI R20, -1

Ao subtrair -1 de um número qualquer, ele soma um, mas a instrução SBCI irá também considerar o carry bit na subtração. Se o carry bit estiver desligado, ele irá efetivamente subtrair -1, ou seja, somar 1 no registrador R19 ou R20, no exemplo acima. O carry bit só estará desligado se a instrução anterior efetivamente criaria um carry numa soma, tipo, somar 0x00FF + 0×0004 = 0×103, mas na subi ou sbci tal carry não existiria. Então, o SBCI Rxx, -1 irá só subtrair efetivamente -1 do registrador quando o carry bit estiver desligado, e ele só está desligado quando precisa propagar o carry que não ocorreu.

Ou seja, numa subtração de número negativo, o carry bit funciona ao contrário. Se o carry estiver desligado é porque precisa somar 1 no byte à esquerda, se o carry estiver ligado é porque não precisa somar 1.
Então, é o mesmo que seria (mas não existe):

ADI R18, 6
ADCI R19, 0
ADCI R20, 0

Onde o carry bit se propaga para os bytes de mais alta ordem, e transforma, por exemplo, 00|05|FE + 6 em 00|06|04.

A restrição é que devido à definição de bits nas instruções, a instrução SUBI e SBCI só funcionam nos registradores R16 a R31

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