Índice:

AVR Assembler Tutorial 3: 9 etapas
AVR Assembler Tutorial 3: 9 etapas

Vídeo: AVR Assembler Tutorial 3: 9 etapas

Vídeo: AVR Assembler Tutorial 3: 9 etapas
Vídeo: AVR Assembly Tutorial: Part 1 (Basic Commands) 2024, Julho
Anonim
AVR Assembler Tutorial 3
AVR Assembler Tutorial 3

Bem-vindo ao tutorial número 3!

Antes de começar, quero fazer uma observação filosófica. Não tenha medo de experimentar os circuitos e o código que estamos construindo nesses tutoriais. Troque os fios, adicione novos componentes, remova componentes, altere linhas de código, adicione novas linhas, exclua linhas e veja o que acontece! É muito difícil quebrar alguma coisa e se o fizer, quem se importa? Nada do que estamos usando, incluindo o microcontrolador, é muito caro e é sempre educativo ver como as coisas podem falhar. Você não apenas descobrirá o que não fazer na próxima vez, mas, mais importante, saberá por que não fazer. Se você for como eu, quando você era criança e ganhou um brinquedo novo, não demorou muito para que você o tivesse em pedaços para ver o que o fazia funcionar certo? Às vezes, o brinquedo acabava irreparavelmente danificado, mas não era grande coisa. Permitir que uma criança explore sua curiosidade até o ponto de brinquedos quebrados é o que a transforma em um cientista ou um engenheiro em vez de um lavador de pratos.

Hoje vamos fazer a fiação de um circuito muito simples e depois nos aprofundar na teoria. Desculpe por isso, mas precisamos das ferramentas! Eu prometo que vamos compensar isso no tutorial 4, onde faremos uma construção de circuito mais séria e o resultado será muito legal. No entanto, a maneira como você precisa fazer todos esses tutoriais é muito lenta e contemplativa. Se você simplesmente arar, construir o circuito, copiar e colar o código e executá-lo, então, com certeza, funcionará, mas você não aprenderá nada. Você precisa pensar sobre cada linha. Pausa. Experimentar. Inventar. Se você fizer isso dessa forma, no final do 5º tutorial, você estará construindo coisas legais e não precisará mais de aulas particulares. Caso contrário, você estará simplesmente observando, em vez de aprender e criar.

Em qualquer caso, chega de filosofia, vamos começar!

Neste tutorial, você precisará:

  1. sua placa de prototipagem
  2. um LED
  3. conectando fios
  4. um resistor em torno de 220 a 330 ohms
  5. O Manual do Conjunto de Instruções: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. A folha de dados: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. um oscilador de cristal diferente (opcional)

Aqui está um link para a coleção completa de tutoriais:

Etapa 1: Construindo o Circuito

Construindo o Circuito
Construindo o Circuito

O circuito neste tutorial é extremamente simples. Basicamente, iremos escrever o programa "blink", então tudo que precisamos é o seguinte.

Conecte um LED ao PD4, a um resistor de 330 ohms e, a seguir, ao aterramento. ou seja, PD4 - LED - R (330) - GND

e é isso!

A teoria vai ser um trabalho árduo embora …

Etapa 2: Por que precisamos dos comentários e do arquivo M328Pdef.inc?

Acho que devemos começar mostrando por que o arquivo de inclusão e os comentários são úteis. Nenhum deles é realmente necessário e você pode escrever, montar e fazer upload do código da mesma maneira sem eles e ele funcionará perfeitamente bem (embora sem o arquivo de inclusão você possa receber algumas reclamações do montador - mas sem erros)

Aqui está o código que iremos escrever hoje, exceto que eu removi os comentários e o arquivo de inclusão:

.device ATmega328P

.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 out 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 out 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b: sbi 0x0b, 0x04 rcall c cbi 0x0b, 0x04 rcall c rjmp bc: clr r17 d: cpi r17, 0x1e brne d ret e: inc r17 cpi r17, 0x3d brne PC + 2 clr r17 reti

bem simples, certo? Haha. Se você montou e carregou este arquivo, você fará com que o LED pisque a uma taxa de 1 piscada por segundo, com a piscada durando ½ segundo e a pausa entre as piscadas durando ½ segundo.

No entanto, olhar para este código dificilmente é esclarecedor. Se você fosse escrever um código como esse e quisesse modificá-lo ou reaproveitá-lo no futuro, seria difícil.

Então, vamos colocar os comentários e incluir o arquivo de volta para que possamos entender um pouco.

Etapa 3: Blink.asm

Aqui está o código que discutiremos hoje:

;************************************

; escrito por: 1o_o7; encontro:; versão: 1.0; arquivo salvo como: blink.asm; para AVR: atmega328p; frequência de clock: 16 MHz (opcional); ***********************************; Função do programa: ---------------------; conta os segundos piscando um LED;; PD4 - LED - R (330 ohm) - GND;; --------------------------------------.nolist.include "./m328Pdef.inc".list; ==============; Declarações:.def temp = r16.def overflows = r17.org 0x0000; local da memória (PC) do manipulador de redefinição rjmp Redefinir; jmp custa 2 ciclos de cpu e rjmp custa apenas 1; portanto, a menos que você precise pular mais de 8k bytes; você só precisa do rjmp. Alguns microcontroladores, portanto, apenas; tem rjmp e não jmp.org 0x0020; localização da memória do manipulador de estouro Timer0 rjmp overflow_handler; vá aqui se ocorrer uma interrupção de estouro do timer0; ============ Redefinir: ldi temp, 0b00000101 out TCCR0B, temp; defina os bits do seletor de relógio CS00, CS01, CS02 para 101; isso coloca o Timer Counter0, TCNT0 no modo FCPU / 1024; portanto, ele atinge a CPU freq / 1024 ldi temp, 0b00000001 sts TIMSK0, temp; definir o bit de habilitação de interrupção de estouro do temporizador (TOIE0); do Registro da máscara de interrupção do temporizador (TIMSK0) sei; habilitar interrupções globais - equivalente a "sbi SREG, I" clr temp out TCNT0, temp; inicializar o Timer / Contador para 0 sbi DDRD, 4; definir PD4 para saída; ========================; Corpo principal do programa: blink: sbi PORTD, 4; acende o LED no retardo rcall do PD4; o atraso será de 1/2 segundo cbi PORTD, 4; desligue o LED em PD4 rcall delay; o atraso será de ½ segundo rjmp piscando; loop de volta para o atraso de início: overflows de clr; definir overflows para 0 sec_count: cpi overflows, 30; compare o número de transbordamentos e 30 brne sec_count; ramificar para voltar para sec_count se não for igual a ret; se 30 overflows tiverem ocorrido, volte para piscar overflow_handler: inc overflows; adicione 1 aos estouros estouros de cpi variáveis, 61; compare com 61 brne PC + 2; Contador de programa + 2 (pula a próxima linha) se não for igual a estouros de clr; se 61 overflows ocorreram, reinicie o contador para zero reti; retornar da interrupção

Como você pode ver, meus comentários são um pouco mais breves agora. Uma vez que sabemos quais são os comandos no conjunto de instruções, não precisamos explicar isso nos comentários. Precisamos apenas explicar o que está acontecendo do ponto de vista do programa.

Estaremos discutindo o que tudo isso faz parte por parte, mas primeiro vamos tentar obter uma perspectiva global. O corpo principal do programa funciona da seguinte maneira.

Primeiro configuramos o bit 4 do PORTD com "sbi PORTD, 4", isso envia um 1 para o PD4 que coloca a tensão em 5V naquele pino. Isso acenderá o LED. Em seguida, pulamos para a sub-rotina "delay" que conta 1/2 segundo (explicaremos como isso acontece mais tarde). Em seguida, voltamos a piscar e limpar o bit 4 no PORTD, que define PD4 para 0 V e, portanto, desliga o LED. Em seguida, atrasamos mais 1/2 segundo e, em seguida, voltamos ao início do blink novamente com "rjmp blink".

Você deve executar este código e ver se ele faz o que deveria.

E aí está! Isso é tudo que esse código faz fisicamente. A mecânica interna do que o microcontrolador está fazendo está um pouco mais envolvida e é por isso que estamos fazendo este tutorial. Portanto, vamos discutir cada seção por vez.

Etapa 4: Diretivas do Assembler.org

Já sabemos o que as diretivas de assembler.nolist,.list,.include e.def fazem em nossos tutoriais anteriores, então vamos primeiro dar uma olhada nas 4 linhas de código que vêm depois disso:

.org 0x0000

jmp Reset.org 0x0020 jmp overflow_handler

A instrução.org informa ao montador onde, em "Memória do programa", colocar a próxima instrução. À medida que seu programa é executado, o "Contador de programa" (abreviado como PC) contém o endereço da linha atual em execução. Portanto, neste caso, quando o PC está em 0x0000, ele verá o comando "jmp Reset" residindo naquele local da memória. A razão pela qual queremos colocar o jmp Reset nesse local é porque quando o programa começa, ou o chip é reiniciado, o PC começa a executar o código neste local. Assim, como podemos ver, acabamos de dizer a ele para "pular" imediatamente para a seção chamada "Reinicializar". Por que fizemos isso? Isso significa que as duas últimas linhas acima estão apenas sendo puladas! Porque?

Bem, é aí que as coisas ficam interessantes. Agora você vai ter que abrir um visualizador de pdf com a folha de dados completa do ATmega328p que indiquei na primeira página deste tutorial (é por isso que é o item 4 na seção "você vai precisar"). Se sua tela for muito pequena, ou se você já tiver muitas janelas abertas (como é o meu caso), você pode fazer o que eu faço e colocá-lo em um Ereader ou em seu telefone Android. Você o usará o tempo todo se planeja escrever código assembly. O legal é que todos os microcontollers são organizados de maneiras muito semelhantes e, portanto, quando você se acostumar a ler as planilhas de dados e codificá-las, descobrirá que é quase trivial fazer o mesmo para um microcontrolador diferente. Portanto, estamos realmente aprendendo a usar todos os microcontroladores de uma certa forma e não apenas o atmega328p.

Ok, vá para a página 18 na folha de dados e dê uma olhada na Figura 8-2.

É assim que a memória do programa no microcontrolador é configurada. Você pode ver que ele começa com o endereço 0x0000 e é separado em duas seções; uma seção de flash de aplicativo e uma seção de flash de inicialização. Se você consultar rapidamente a página 277, tabela 27-14, verá que a seção de flash do aplicativo ocupa os locais de 0x0000 a 0x37FF e a seção de flash de inicialização ocupa os locais restantes de 0x3800 a 0x3FFF.

Exercício 1: Quantas localizações existem na memória do programa? Ou seja, converter 3FFF em decimal e adicionar 1, pois começaremos a contar em 0. Como cada localização de memória tem 16 bits (ou 2 bytes) de largura, qual é o número total de bytes de memória? Agora converta para kilobytes, lembrando que há 2 ^ 10 = 1024 bytes em um kilobyte. A seção do flash de inicialização vai de 0x3800 a 0x37FF, quantos kilobytes são? Quantos kilobytes de memória restam para usarmos para armazenar nosso programa? Em outras palavras, quão grande pode ser nosso programa? Finalmente, quantas linhas de código podemos ter?

Tudo bem, agora que sabemos tudo sobre a organização da memória do programa flash, vamos continuar com nossa discussão sobre as declarações.org. Vemos que a primeira localização de memória 0x0000 contém nossa instrução para pular para nossa seção rotulada de Reset. Agora vemos o que a instrução ".org 0x0020" faz. Diz que queremos que a instrução da próxima linha seja colocada na localização de memória 0x0020. A instrução que colocamos lá é um salto para uma seção em nosso código que rotulamos de "overflow_handler" … agora, por que diabos exigiríamos que esse salto fosse colocado na localização de memória 0x0020? Para descobrir, vamos para a página 65 na folha de dados e damos uma olhada na Tabela 12-6.

A Tabela 12-6 é uma tabela de "Vetores de Reinicialização e Interrupção" e mostra exatamente para onde o PC irá quando receber uma "interrupção". Por exemplo, se você olhar para o vetor número 1. A "fonte" da interrupção é "RESET", que é definido como "Pino externo, Reinicialização da ligação, Reinicialização do Brown-out e reinicialização do sistema Watchdog", o que significa, se houver Se essas coisas acontecerem com nosso microcontrolador, o PC começará a executar nosso programa na localização de memória do programa 0x0000. E quanto à nossa diretiva.org, então? Bem, colocamos um comando na localização de memória 0x0020 e se você olhar a tabela, verá que se ocorrer um estouro de Timer / Counter0 (vindo de TIMER0 OVF), ele executará tudo o que estiver na localização 0x0020. Então, sempre que isso acontecer, o PC irá pular para o ponto que rotulamos de "overflow_handler". Legal certo? Você verá em um minuto porque fizemos isso, mas primeiro vamos terminar esta etapa do tutorial com um aparte.

Se quisermos tornar nosso código mais limpo e organizado, devemos realmente substituir as 4 linhas que estamos discutindo atualmente pelas seguintes (consulte a página 66):

.org 0x0000

rjmp Reset; PC = 0x0000 reti; PC = 0x0002 reti; PC = 0x0004 reti; PC = 0x0006 reti; PC = 0x0008 reti; PC = 0x000A… reti; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022… reti; PC = 0x0030 reti; PC = 0x0032

Assim, se uma determinada interrupção ocorrer, ela será apenas "reti", que significa "retorno da interrupção" e nada mais acontecerá. Mas se nunca "Ativarmos" essas várias interrupções, elas não serão usadas e podemos colocar o código do programa nesses locais. Em nosso programa "blink.asm" atual, vamos apenas habilitar a interrupção de estouro timer0 (e, claro, a interrupção de reset que está sempre habilitada) e por isso não vamos nos preocupar com as outras.

Como podemos "habilitar" a interrupção de estouro timer0 então? … Esse é o assunto da nossa próxima etapa neste tutorial.

Etapa 5: Timer / Contador 0

Temporizador / Contador 0
Temporizador / Contador 0

Dê uma olhada na foto acima. Este é o processo de tomada de decisão do "PC" quando alguma influência externa "interrompe" o fluxo do nosso programa. A primeira coisa que ele faz quando recebe um sinal externo de que uma interrupção ocorreu é verificar se configuramos o bit de "ativação de interrupção" para aquele tipo de interrupção. Se não tivermos, ele simplesmente continuará a executar nossa próxima linha de código. Se tivermos definido esse bit de ativação de interrupção em particular (de modo que haja um 1 nessa localização de bit em vez de um 0), ele irá então verificar se ativamos ou não "interrupções globais", caso contrário, irá novamente para a próxima linha de código e continuar. Se também habilitamos as interrupções globais, ele irá para o local da memória de programa daquele tipo de interrupção (como mostrado na Tabela 12-6) e executará qualquer comando que colocamos lá. Então, vamos ver como implementamos tudo isso em nosso código.

A seção rotulada Redefinir de nosso código começa com as duas linhas a seguir:

Redefinir:

temp ldi, 0b00000101 out TCCR0B, temp

Como já sabemos, isso carrega em temp (ou seja, R16) o número imediatamente seguinte, que é 0b00000101. Em seguida, ele grava esse número no registro chamado TCCR0B usando o comando "out". O que é esse cadastro? Bem, vamos para a página 614 da folha de dados. Isso está no meio de uma tabela que resume todos os registros. No endereço 0x25 você encontrará TCCR0B. (Agora você sabe de onde veio a linha "out 0x25, r16" na minha versão não comentada do código). Vemos pelo segmento de código acima que definimos o 0º bit e o 2º bit e limpamos todo o resto. Olhando para a tabela, você pode ver que isso significa que definimos CS00 e CS02. Agora, vamos para o capítulo na folha de dados chamado "Cronômetro / Contador 0 de 8 bits com PWM". Em particular, vá para a página 107 desse capítulo. Você verá a mesma descrição do registro "Timer / Counter Control Register B" (TCCR0B) que acabamos de ver na tabela de resumo de registro (então poderíamos ter vindo direto aqui, mas eu queria que você visse como usar as tabelas de resumo para referência futura). A folha de dados continua a fornecer uma descrição de cada um dos bits nesse registro e o que eles fazem. Vamos pular tudo isso por enquanto e virar a página para a Tabela 15-9. Esta tabela mostra a "Descrição do bit de seleção do relógio". Agora olhe para baixo na tabela até encontrar a linha que corresponde aos bits que acabamos de definir nesse registro. A linha diz "clk / 1024 (do prescaler)". O que isso significa é que queremos Timer / Counter0 (TCNT0) a uma taxa que é a frequência da CPU dividida por 1024. Como temos nosso microcontrolador alimentado por um oscilador de cristal de 16 MHz, significa que a taxa que nossa CPU executa as instruções é 16 milhões de instruções por segundo. Portanto, a taxa que nosso contador TCNT0 marcará é 16 milhões / 1024 = 15.625 vezes por segundo (tente com bits de seleção de relógio diferentes e veja o que acontece - lembra-se de nossa filosofia?). Vamos manter o número 15625 em nossa mente para mais tarde e passar para as próximas duas linhas de código:

temp IDI, 0b00000001

sts TIMSK0, temp

Isso define o 0º bit de um registro chamado TIMSK0 e limpa todo o resto. Se você der uma olhada na página 109 da folha de dados, verá que TIMSK0 significa "Timer / Counter Interrupt Mask Register 0" e nosso código definiu o bit 0, que é denominado TOIE0, que significa "Timer / Counter0 Overflow Interrupt Enable" … Lá! Agora você vê do que se trata. Agora temos o "conjunto de bits de ativação de interrupção" como queríamos desde a primeira decisão em nossa imagem no topo. Portanto, agora tudo o que temos a fazer é habilitar "interrupções globais" e nosso programa será capaz de responder a esse tipo de interrupção. Iremos habilitar interrupções globais em breve, mas antes de fazermos isso você pode ter se confundido com alguma coisa.. por que diabos eu usei o comando "sts" para copiar para o registrador TIMSK0 em vez do usual "out"?

Sempre que você me vir use uma instrução que você não viu antes, a primeira coisa que você deve fazer é ir para a página 616 da folha de dados. Este é o "Resumo do conjunto de instruções". Agora encontre a instrução "STS" que usei. Ele diz que pega um número de um registro R (usamos R16) e "Armazenar direto na SRAM" local k (em nosso caso, fornecido por TIMSK0). Então, por que tivemos que usar "sts" que leva 2 ciclos de clock (veja a última coluna na tabela) para armazenar em TIMSK0 e nós só precisávamos "out", que leva apenas um ciclo de clock, para armazenar em TCCR0B antes? Para responder a essa pergunta, precisamos voltar à nossa tabela de resumo de registro na página 614. Você vê que o registro TCCR0B está no endereço 0x25, mas também em (0x45) certo? Isso significa que é um registro na SRAM, mas também é um certo tipo de registro denominado "porta" (ou registro i / o). Se você olhar a tabela de resumo de instrução ao lado do comando "out", verá que ele pega valores dos "registradores de trabalho" como R16 e os envia para uma PORTA. Portanto, podemos usar "out" ao gravar em TCCR0B e economizar um ciclo de clock. Mas agora procure TIMSK0 na tabela de registro. Você vê que ele tem o endereço 0x6e. Isso está fora do intervalo de portas (que são apenas as primeiras localizações 0x3F da SRAM) e, portanto, você deve voltar a usar o comando sts e levar dois ciclos de clock da CPU para fazê-lo. Por favor, leia a Nota 4 no final da tabela de resumo das instruções na página 615 agora. Observe também que todas as nossas portas de entrada e saída, como PORTD, estão localizadas na parte inferior da tabela. Por exemplo, PD4 é o bit 4 no endereço 0x0b (agora você vê de onde vieram todas as coisas 0x0b no meu código não comentado!).. ok, pergunta rápida: você mudou o "sts" para "out" e viu o que acontece? Lembre-se de nossa filosofia! quebre! não acredite apenas na minha palavra.

Ok, antes de prosseguirmos, vá para a página 19 da folha de dados por um minuto. Você vê uma imagem da memória de dados (SRAM). Os primeiros 32 registradores na SRAM (de 0x0000 a 0x001F) são os "registradores de trabalho de uso geral" R0 a R31 que usamos o tempo todo como variáveis em nosso código. Os próximos 64 registradores são as portas de E / S até 0x005f (ou seja, aquelas sobre as quais estávamos falando que têm esses endereços sem colchetes ao lado deles na tabela de registro que podemos usar o comando "out" em vez de "sts"). a próxima seção da SRAM contém todos os outros registros na tabela de resumo até o endereço 0x00FF e, por último, o resto é a SRAM interna. Agora, rapidamente, vamos voltar para a página 12 por um segundo. Lá você verá uma tabela dos "registros de trabalho de uso geral" que sempre usamos como nossas variáveis. Você vê a linha espessa entre os números R0 a R15 e, em seguida, R16 a R31? É por essa linha que sempre usamos R16 como o menor e falarei mais sobre isso no próximo tutorial, onde também precisaremos dos três registradores de endereço indireto de 16 bits, X, Y e Z. Não vou entrar nisso ainda, já que não precisamos disso agora e estamos ficando atolados o suficiente aqui.

Volte uma página para a página 11 da folha de dados. Você verá um diagrama do registro SREG no canto superior direito? Você vê que o bit 7 desse registro é chamado de "I". Agora desça a página e leia a descrição do Bit 7…. yay! É o bit de habilitação de interrupção global. Isso é o que precisamos definir para passar pela segunda decisão em nosso diagrama acima e permitir interrupções de estouro de temporizador / contador em nosso programa. Portanto, a próxima linha do nosso programa deve ser:

sbi SREG, eu

que define o bit denominado "I" no registro SREG. No entanto, em vez disso, usamos a instrução

sei

em vez de. Esse bit é definido com tanta frequência em programas que eles apenas criaram uma maneira mais simples de fazê-lo.

OK! Agora temos as interrupções de estouro prontas para que nosso "jmp overflow_handler" seja executado sempre que ocorrer.

Antes de prosseguirmos, dê uma olhada rápida no registro SREG (Registro de Status) porque é muito importante. Leia o que cada uma das bandeiras representa. Em particular, muitas das instruções que usamos definirão e verificarão esses sinalizadores o tempo todo. Por exemplo, mais tarde estaremos usando o comando "CPI" que significa "comparar imediato". Dê uma olhada na tabela de resumo de instrução para esta instrução e observe quantos sinalizadores ela define na coluna "sinalizadores". Essas são todas as sinalizações no SREG e nosso código irá configurá-las e verificá-las constantemente. Você verá exemplos em breve. Finalmente, a última parte desta seção de código é:

clr temp

out TCNT0, temp sbi DDRD, 4

A última linha aqui é bastante óbvia. Ele apenas define o 4º bit do Registrador de Direção de Dados para PortD, fazendo com que PD4 seja OUTPUT.

O primeiro define a variável temp para zero e, em seguida, copia isso para o registro TCNT0. TCNT0 é nosso Timer / Contador0. Isso o define como zero. Assim que o PC executar esta linha, o timer0 começará em zero e contará a uma taxa de 15.625 vezes a cada segundo. O problema é o seguinte: TCNT0 é um registrador de "8 bits", certo? Então, qual é o maior número que um registro de 8 bits pode conter? Bem, 0b11111111 é isso. Este é o número 0xFF. Qual é 255. Então você vê o que acontece? O cronômetro está zunindo aumentando 15.625 vezes por segundo e toda vez que chega a 255 ele "transborda" e volta a 0 novamente. Ao mesmo tempo que volta a zero, ele envia um sinal de interrupção de estouro do temporizador. O PC capta isso e você sabe o que ele faz agora, certo? Sim. Ele vai para a localização 0x0020 da Memória de Programa e executa a instrução que encontra lá.

Excelente! Se você ainda está comigo, então você é um super-herói incansável! Vamos continuar…

Etapa 6: manipulador de estouro

Portanto, vamos supor que o registrador timer / counter0 acabou de estourar. Agora sabemos que o programa recebe um sinal de interrupção e executa 0x0020 que diz ao Contador de Programa, PC para saltar para o rótulo "overflow_handler". O seguinte é o código que escrevemos após esse rótulo:

overflow_handler:

inc overflows cpi overflows, 61 brne PC + 2 clr overflows reti

A primeira coisa que ele faz é incrementar a variável "overflows" (que é o nosso nome para o registro de trabalho de uso geral R17) e então "compara" o conteúdo de overflows com o número 61. A maneira que a instrução cpi funciona é que ela simplesmente subtrai os dois números e se o resultado for zero ele define o sinalizador Z no registro SREG (eu disse a você que veríamos esse registro o tempo todo). Se os dois números forem iguais, a bandeira Z será 1; se os dois números não forem iguais, será 0.

A próxima linha diz "brne PC + 2" que significa "branch if not equal". Essencialmente, ele verifica o sinalizador Z em SREG e se NÃO for um (ou seja, os dois números não são iguais, se eles fossem iguais, o sinalizador zero seria definido) o PC se ramifica para PC + 2, o que significa que pula o próximo linha e vai direto para "reti" que retorna da interrupção para qualquer lugar que estivesse no código quando a interrupção chegou. Se a instrução brne encontrasse um 1 no bit de flag zero, ela não se desviaria e, em vez disso, apenas continuaria para a próxima linha, o que clr overflows redefinindo-o para 0.

Qual é o resultado líquido de tudo isso?

Bem, vemos que toda vez que há um estouro do cronômetro, esse manipulador aumenta o valor de "estouro" em um. Portanto, a variável "overflows" está contando o número de overflows conforme eles ocorrem. Sempre que o número chega a 61, nós o redefinimos para zero.

Agora, por que diabos faríamos isso?

Vamos ver. Lembre-se de que nossa velocidade de clock para nosso CPU é de 16 MHz e nós a "pré-escalamos" usando TCCR0B para que o cronômetro só conte a uma taxa de 15.625 contagens por segundo, certo? E toda vez que o cronômetro atinge a contagem de 255, ele transborda. Isso significa que ele transborda 15625/256 = 61,04 vezes por segundo. Estamos acompanhando o número de overflows com nossa variável "overflows" e estamos comparando esse número com 61. Assim, vemos que "overflows" será igual a 61 uma vez a cada segundo! Portanto, nosso manipulador irá redefinir "overflows" para zero uma vez a cada segundo. Portanto, se simplesmente monitorássemos a variável "estouro" e anotássemos cada vez que ela fosse zerada, estaríamos contando segundo a segundo em tempo real (Observe que no próximo tutorial mostraremos como obter uma atraso em milissegundos da mesma forma que a rotina de "atraso" do Arduino funciona).

Agora, "tratamos" das interrupções de estouro do cronômetro. Certifique-se de entender como isso funciona e, em seguida, passe para a próxima etapa, onde faremos uso desse fato.

Etapa 7: Atraso

Agora que vimos que nossa rotina de manipulador de interrupção de estouro de temporizador "overflow_handler" definirá a variável "overflows" para zero uma vez a cada segundo, podemos usar esse fato para projetar uma sub-rotina de "atraso".

Dê uma olhada no seguinte código do nosso atraso: rótulo

atraso:

clr overflows sec_count: cpi overflows, 30 brne sec_count ret

Vamos chamar esta sub-rotina toda vez que precisarmos de um atraso em nosso programa. A forma como funciona é primeiro define a variável "overflows" para zero. Em seguida, ele entra em uma área rotulada "sec_count" e compara os overflows com 30, se eles não forem iguais, ele volta para o rótulo sec_count e compara novamente, e novamente, etc. até que eles sejam finalmente iguais (lembre-se de que o tempo todo isso está acontecendo em nosso manipulador de interrupção do cronômetro continua a incrementar os estouros de variáveis e, portanto, está mudando cada vez que passamos por aqui. Quando os estouros finalmente são iguais a 30, ele sai do loop e retorna para onde chamamos de atraso: de. O resultado líquido é um atraso de 1/2 segundo

Exercício 2: Altere a rotina overflow_handler para o seguinte:

overflow_handler:

inc overflows reti

e execute o programa. Algo diferente? Por que ou por que não?

Etapa 8: Pisque

Finalmente, vamos dar uma olhada na rotina de piscar:

piscar:

sbi PORTD, 4 rcall retardo cbi PORTD, 4 rcall retardo rjmp piscando

Primeiro ligamos o PD4 e, em seguida, chamamos novamente nossa sub-rotina de atraso. Usamos rcall para que, quando o PC chegar a uma instrução "ret", ele volte para a linha após rcall. Em seguida, a rotina de atraso atrasa 30 contagens na variável de estouro, como vimos, e isso é quase exatamente 1/2 segundo, então desligamos o PD4, atrasamos mais 1/2 segundo e, em seguida, voltamos ao início novamente.

O resultado líquido é um LED piscando!

Acho que agora você vai concordar que "blink" provavelmente não é o melhor programa "hello world" em linguagem assembly.

Exercício 3: Altere os vários parâmetros no programa para que o LED pisque em taxas diferentes, como um segundo ou 4 vezes por segundo, etc. Exercício 4: Altere para que o LED fique aceso e apagado por períodos de tempo diferentes. Por exemplo, ligado por 1/4 de segundo e depois desligado por 2 segundos ou algo assim. Exercício 5: Altere os bits de seleção do relógio TCCR0B para 100 e continue subindo na tabela. Em que ponto ele se torna indistinguível de nosso programa "hello.asm" do tutorial 1? Exercício 6 (opcional): Se você tiver um oscilador de cristal diferente, como 4 MHz ou 13,5 MHz ou qualquer outro, troque o oscilador de 16 MHz em sua placa de ensaio para o novo e veja como isso afeta a taxa de intermitência do LED. Agora você deve ser capaz de realizar o cálculo preciso e prever exatamente como isso afetará a taxa.

Etapa 9: Conclusão

Para aqueles de vocês obstinados que chegaram até aqui, parabéns!

Sei que é muito difícil trabalhar muito quando você está lendo e olhando mais para cima do que conectando e experimentando, mas espero que você tenha aprendido as seguintes coisas importantes:

  1. Como funciona a memória do programa
  2. Como funciona a SRAM
  3. Como procurar registros
  4. Como procurar instruções e saber o que elas fazem
  5. Como implementar interrupções
  6. Como o CP executa o código, como funciona o SREG e o que acontece durante as interrupções
  7. Como fazer loops e saltos e pular no código
  8. Como é importante ler a ficha técnica!
  9. Depois de saber como fazer tudo isso com o microcontrolador Atmega328p, será muito fácil aprender todos os novos controladores nos quais você estiver interessado.
  10. Como transformar o tempo da CPU em tempo real e usá-lo em rotinas de atraso.

Agora que temos muita teoria fora do caminho, podemos escrever um código melhor e controlar coisas mais complicadas. Então, no próximo tutorial, faremos exatamente isso. Vamos construir um circuito mais complicado e interessante e controlá-lo de maneiras divertidas.

Exercício 7: "Quebre" o código de várias maneiras e veja o que acontece! Curiosidade científica baby! Alguém mais pode lavar a louça certo? Exercício 8: Monte o código usando a opção "-l" para gerar um arquivo de lista. Ou seja, "avra -l blink.lst blink.asm" e dê uma olhada no arquivo de lista. Crédito extra: O código não comentado que dei no início e o código comentado que discutiremos mais tarde diferem! Existe uma linha de código diferente. Você pode encontrá-lo? Por que essa diferença não importa?

Espero que tenha se divertido! Até a próxima …

Recomendado: