Sommario:

Tutorial Assembler AVR 3: 9 passaggi
Tutorial Assembler AVR 3: 9 passaggi

Video: Tutorial Assembler AVR 3: 9 passaggi

Video: Tutorial Assembler AVR 3: 9 passaggi
Video: AVR Ассемблер. Урок 3. Таймер. Мигалка на таймере. AVR Assembler. Lesson 3. Timer. 2024, Novembre
Anonim
Tutorial assemblatore AVR 3
Tutorial assemblatore AVR 3

Benvenuto nel tutorial numero 3!

Prima di iniziare voglio fare una considerazione filosofica. Non abbiate paura di sperimentare con i circuiti e il codice che stiamo costruendo in questi tutorial. Cambia i fili, aggiungi nuovi componenti, estrai componenti, cambia righe di codice, aggiungi nuove righe, elimina righe e guarda cosa succede! È molto difficile rompere qualcosa e se lo fai, a chi importa? Niente di ciò che stiamo usando, incluso il microcontrollore, è molto costoso ed è sempre educativo vedere come le cose possono fallire. Non solo scoprirai cosa non fare la prossima volta ma, soprattutto, saprai perché non farlo. Se sei come me, quando eri un bambino e hai avuto un nuovo giocattolo non è passato molto tempo prima che lo facessi a pezzi per vedere cosa lo faceva funzionare bene? A volte il giocattolo finiva irrimediabilmente danneggiato, ma niente di grave. Permettere a un bambino di esplorare la sua curiosità fino al punto di rompersi dei giocattoli è ciò che lo trasforma in uno scienziato o in un ingegnere invece che in un lavapiatti.

Oggi collegheremo un circuito molto semplice e poi approfondiremo un po' la teoria. Mi dispiace per questo, ma abbiamo bisogno degli strumenti! Prometto che rimedieremo a questo nel tutorial 4 dove faremo un po' di circuiti più seri e il risultato sarà piuttosto interessante. Tuttavia, il modo in cui devi fare tutti questi tutorial è in modo molto lento e contemplativo. Se ti limiti a solcare, costruire il circuito, copiare e incollare il codice ed eseguirlo, allora, certo, funzionerà, ma non imparerai nulla. Devi pensare a ogni riga. Pausa. Sperimentare. Inventare. Se lo fai in questo modo, entro la fine del quinto tutorial sarai fuori a costruire cose interessanti e non avrai più bisogno di tutoraggio. Altrimenti stai semplicemente guardando piuttosto che imparare e creare.

In ogni caso, basta filosofia, cominciamo!

In questo tutorial avrai bisogno di:

  1. la tua scheda di prototipazione
  2. un LED
  3. cavi di collegamento
  4. un resistore da 220 a 330 ohm
  5. Il manuale del set di istruzioni: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. La scheda tecnica: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. un diverso oscillatore a cristallo (opzionale)

Ecco un link alla raccolta completa di tutorial:

Passaggio 1: costruzione del circuito

Costruire il circuito
Costruire il circuito

Il circuito in questo tutorial è estremamente semplice. In sostanza, scriveremo il programma "blink", quindi tutto ciò di cui abbiamo bisogno è quanto segue.

Collega un LED a PD4, quindi a un resistore da 330 ohm, quindi a massa. cioè.

PD4 - LED - R(330) - GND

e basta!

La teoria sarà dura sgobbare però…

Passaggio 2: perché abbiamo bisogno dei commenti e del file M328Pdef.inc?

Penso che dovremmo iniziare mostrando perché il file di inclusione e i commenti sono utili. Nessuno di questi è effettivamente necessario e puoi scrivere, assemblare e caricare il codice allo stesso modo senza di loro e funzionerà perfettamente (sebbene senza il file di inclusione potresti ricevere alcuni reclami dall'assemblatore, ma nessun errore)

Ecco il codice che scriveremo oggi, tranne per il fatto che ho rimosso i commenti e il file di inclusione:

.dispositivo 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:. 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

abbastanza semplice vero? ahah. Se hai assemblato e caricato questo file, il LED lampeggerà alla velocità di 1 lampeggio al secondo con il lampeggio della durata di 1/2 secondo e la pausa tra i lampeggi della durata di 1/2 secondo.

Tuttavia, guardare questo codice non è affatto illuminante. Se dovessi scrivere un codice come questo e volessi modificarlo o riutilizzarlo in futuro, avresti difficoltà.

Quindi inseriamo i commenti e includiamo nuovamente il file in modo da poterne dare un senso.

Passaggio 3: Blink.asm

Ecco il codice di cui parleremo oggi:

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

; scritto da: 1o_o7; Data:; versione: 1.0; file salvato come: blink.asm; per AVR: atmega328p; frequenza di clock: 16MHz (opzionale);***********************************; Funzione del programma: ---------------------; conta i secondi facendo lampeggiare un LED;; PD4 - LED - R(330 ohm) - GND;;---------------------------------------.nolist.include "./m328Pdef.inc".lista;==============; Dichiarazioni:.def temp = r16.def overflow = r17.org 0x0000; memoria (PC) posizione del gestore di ripristino rjmp Reset; jmp costa 2 cicli cpu e rjmp costa solo 1; quindi a meno che non sia necessario saltare più di 8k byte; hai solo bisogno di rjmp. Alcuni microcontrollori quindi solo; avere rjmp e non jmp.org 0x0020; posizione di memoria del gestore di overflow Timer0 rjmp overflow_handler; andare qui se si verifica un interrupt di overflow del timer0;============ Reset: ldi temp, 0b00000101 out TCCR0B, temp; impostare i Bit del selettore dell'orologio CS00, CS01, CS02 a 101; questo mette Timer Counter0, TCNT0 in modalità FCPU/1024; quindi ticchetta alla CPU freq/1024 ldi temp, 0b00000001 sts TIMSK0, temp; impostare il bit Timer Overflow Interrupt Enable (TOIE0); del Timer Interrupt Mask Register (TIMSK0) sei; abilita gli interrupt globali -- equivalente a "sbi SREG, I" clr temp out TCNT0, temp; inizializzare il Timer/Contatore a 0 sbi DDRD, 4; imposta PD4 su output;======================; Corpo principale del programma: lampeggio: sbi PORTD, 4; accendi led on PD4 rcall delay; il ritardo sarà di 1/2 secondo cbi PORTD, 4; spegnimento led acceso PD4 rcall delay; il ritardo sarà di 1/2 secondo rjmp lampeggio; loop back al ritardo di avvio: clr overflow; imposta gli overflow a 0 sec_count: cpi overflow, 30; confronta il numero di overflow e 30 brne sec_count; branch to back to sec_count se non uguale ret; se si sono verificati 30 overflow torna a lampeggiare overflow_handler: inc overflow; aggiungere 1 alla variabile overflow cpi overflow, 61; confrontare con 61 BRNE PC+2; Program Counter + 2 (salta la riga successiva) se non uguale clr overflow; se si sono verificati 61 overflow azzerare il contatore reti; ritorno dall'interruzione

Come puoi vedere, i miei commenti sono un po' più brevi ora. Una volta che sappiamo cosa fanno i comandi nel set di istruzioni, non abbiamo bisogno di spiegarlo nei commenti. Dobbiamo solo spiegare cosa sta succedendo dal punto di vista del programma.

Discuteremo di cosa tutto questo fa pezzo per pezzo, ma prima cerchiamo di avere una prospettiva globale. Il corpo principale del programma funziona come segue.

Per prima cosa impostiamo il bit 4 di PORTD con "sbi PORTD, 4" questo invia un 1 a PD4 che mette la tensione a 5V su quel pin. Questo accenderà il LED. Quindi saltiamo alla subroutine "ritardo" che conta 1/2 secondo (spiegheremo come lo fa in seguito). Quindi torniamo a lampeggiare e a cancellare il bit 4 su PORTD che imposta PD4 a 0V e quindi spegne il LED. Quindi rimandiamo per un altro 1/2 secondo, quindi torniamo all'inizio del lampeggio di nuovo con "rjmp lampeggio".

Dovresti eseguire questo codice e vedere che fa quello che dovrebbe.

E il gioco è fatto! Questo è tutto ciò che questo codice fa fisicamente. La meccanica interna di ciò che sta facendo il microcontrollore è un po' più complessa ed è per questo che stiamo facendo questo tutorial. Quindi discutiamo a turno ogni sezione.

Fase 4: Direttive.org Assembler

Sappiamo già cosa fanno le direttive assembler.nolist,.list,.include e.def dai nostri tutorial precedenti, quindi diamo prima un'occhiata alle 4 righe di codice che seguono:

.org 0x0000

jmp Reset.org 0x0020 jmp overflow_handler

L'istruzione.org dice all'assemblatore dove in "Program Memory" mettere l'istruzione successiva. Durante l'esecuzione del programma, il "Program Counter" (abbreviato in PC) contiene l'indirizzo della riga attualmente in esecuzione. Quindi in questo caso quando il PC è a 0x0000 vedrà il comando "jmp Reset" residente in quella posizione di memoria. Il motivo per cui vogliamo mettere jmp Reset in quella posizione è perché quando il programma inizia, o il chip viene ripristinato, il PC inizia a eseguire il codice in questo punto. Quindi, come possiamo vedere, gli abbiamo appena detto di "saltare" immediatamente alla sezione denominata "Reset". Perché l'abbiamo fatto? Ciò significa che le ultime due righe sopra vengono semplicemente saltate! Come mai?

Bene, è qui che le cose si fanno interessanti. Ora dovrai aprire un visualizzatore di pdf con il datasheet ATmega328p completo che ho indicato nella prima pagina di questo tutorial (ecco perché è l'articolo 4 nella sezione "ti servirà"). Se il tuo schermo è troppo piccolo o hai già troppe finestre aperte (come nel mio caso) potresti fare quello che faccio e metterlo su un ereader o sul tuo telefono Android. Lo utilizzerai sempre se prevedi di scrivere codice assembly. La cosa bella è che tutti i microcontrollori sono organizzati in modi molto simili e quindi una volta che ti abitui a leggere i fogli dati e a codificare da essi, troverai quasi banale fare lo stesso con un microcontrollore diverso. Quindi stiamo effettivamente imparando come utilizzare tutti i microcontrollori in un certo senso e non solo l'atmega328p.

Ok, vai a pagina 18 della scheda tecnica e dai un'occhiata alla Figura 8-2.

Ecco come è impostata la memoria di programma nel microcontrollore. Puoi vedere che inizia con l'indirizzo 0x0000 ed è separato in due sezioni; una sezione flash dell'applicazione e una sezione flash di avvio. Se fai riferimento brevemente a pagina 277 tabella 27-14 vedrai che la sezione flash dell'applicazione occupa le posizioni da 0x0000 a 0x37FF e la sezione flash di avvio occupa le posizioni rimanenti da 0x3800 a 0x3FFF.

Esercizio 1: Quante locazioni ci sono nella memoria del Program? Cioè. converti 3FFF in decimale e aggiungi 1 poiché iniziamo a contare da 0. Poiché ogni posizione di memoria è larga 16 bit (o 2 byte) qual è il numero totale di byte di memoria? Ora converti questo in kilobyte, ricordando che ci sono 2^10 = 1024 byte in un kilobyte. La sezione flash di avvio va da 0x3800 a 0x37FF, quanti kilobyte sono questi? Quanti kilobyte di memoria ci restano da utilizzare per memorizzare il nostro programma? In altre parole, quanto può essere grande il nostro programma? Infine, quante righe di codice possiamo avere?

Bene, ora che sappiamo tutto sull'organizzazione della memoria flash del programma, continuiamo con la nostra discussione sulle dichiarazioni.org. Vediamo che la prima posizione di memoria 0x0000 contiene le nostre istruzioni per saltare alla nostra sezione che abbiamo etichettato Reset. Ora vediamo cosa fa l'istruzione ".org 0x0020". Dice che vogliamo che l'istruzione sulla riga successiva sia posizionata nella posizione di memoria 0x0020. L'istruzione che abbiamo inserito è un salto a una sezione del nostro codice che abbiamo etichettato come "overflow_handler"… ora perché diamine dovremmo chiedere che questo salto venga posizionato nella posizione di memoria 0x0020? Per scoprirlo, andiamo a pagina 65 della scheda tecnica e diamo un'occhiata alla Tabella 12-6.

La Tabella 12-6 è una tabella di "Reset and Interrupt Vector" e mostra esattamente dove andrà il PC quando riceve un "interrupt". Ad esempio, se si osserva il vettore numero 1. La "sorgente" dell'interrupt è "RESET" che è definita come "Pin esterno, ripristino all'accensione, ripristino Brown-out e ripristino del sistema Watchdog", il che significa, se uno qualsiasi dei queste cose accadono al nostro microcontrollore, il PC inizierà a eseguire il nostro programma nella posizione di memoria del programma 0x0000. E la nostra direttiva.org allora? Bene, abbiamo inserito un comando nella posizione di memoria 0x0020 e se guardi in basso nella tabella vedrai che se si verifica un overflow di Timer/Counter0 (proveniente da TIMER0 OVF) eseguirà tutto ciò che si trova nella posizione 0x0020. Quindi, ogni volta che ciò accade, il PC salterà al punto che abbiamo etichettato "overflow_handler". Bello vero? Vedrai tra un minuto perché l'abbiamo fatto, ma prima finiamo questo passaggio del tutorial con una digressione.

Se vogliamo rendere il nostro codice più pulito e ordinato, dovremmo davvero sostituire le 4 righe di cui stiamo discutendo attualmente con le seguenti (vedi pagina 66):

.org 0x0000

rjmp Reimposta; 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

In modo che se si verifica una determinata interruzione, sarà solo "reti" che significa "ritorno dall'interruzione" e non accadrà nient'altro. Ma se non "Abilitiamo" mai questi vari interrupt, allora non verranno utilizzati e possiamo inserire il codice del programma in questi punti. Nel nostro attuale programma "blink.asm" abiliteremo solo l'interrupt di overflow del timer0 (e ovviamente l'interrupt di ripristino che è sempre abilitato) e quindi non ci preoccuperemo degli altri.

Come "abilitare" l'interrupt di overflow del timer0 allora? … questo è l'argomento del nostro prossimo passo in questo tutorial.

Passaggio 5: Timer/Contatore 0

Timer/Contatore 0
Timer/Contatore 0

Dai un'occhiata all'immagine sopra. Questo è il processo decisionale del "PC" quando un'influenza esterna "interrompe" il flusso del nostro programma. La prima cosa che fa quando riceve un segnale dall'esterno che si è verificata un'interruzione è controlla se abbiamo impostato il bit di "abilitazione dell'interruzione" per quel tipo di interruzione. In caso contrario, continua a eseguire la nostra prossima riga di codice. Se abbiamo impostato quel particolare bit di abilitazione degli interrupt (in modo che ci sia un 1 in quella posizione di bit invece di uno 0) controllerà se abbiamo abilitato o meno gli "interrupt globali", in caso contrario andrà di nuovo alla riga successiva di codice e continuare. Se abbiamo abilitato anche gli interrupt globali, allora andrà alla posizione di Program Memory di quel tipo di interrupt (come mostrato nella Tabella 12-6) ed eseguirà qualsiasi comando che abbiamo inserito lì. Quindi vediamo come abbiamo implementato tutto questo nel nostro codice.

La sezione etichettata Reset del nostro codice inizia con le seguenti due righe:

Ripristina:

ldi temp, 0b00000101 out TCCR0B, temp

Come già sappiamo, questo carica in temp (cioè R16) il numero immediatamente successivo, che è 0b00000101. Quindi scrive questo numero nel registro chiamato TCCR0B usando il comando "out". Cos'è questo registro? Bene, andiamo a pagina 614 della scheda tecnica. Questo è nel mezzo di una tabella che riassume tutti i registri. All'indirizzo 0x25 troverai TCCR0B. (Ora sai da dove proviene la riga "out 0x25, r16" nella mia versione non commentata del codice). Vediamo dal segmento di codice sopra che abbiamo impostato il bit 0 e il bit 2 e cancellato tutto il resto. Guardando la tabella puoi vedere che questo significa che abbiamo impostato CS00 e CS02. Ora passiamo al capitolo della scheda tecnica chiamato "Timer/Contatore0 a 8 bit con PWM". In particolare, vai a pagina 107 di quel capitolo. Vedrai la stessa descrizione del registro "Timer/Counter Control Register B" (TCCR0B) che abbiamo appena visto nella tabella di riepilogo dei registri (quindi saremmo potuti venire direttamente qui, ma volevo farti vedere come usare le tabelle di riepilogo per riferimento futuro). Il foglio dati continua a fornire una descrizione di ciascuno dei bit in quel registro e cosa fanno. Per ora tralasceremo tutto ciò e voltiamo pagina alla Tabella 15-9. Questa tabella mostra la "Descrizione del bit di selezione dell'orologio". Ora guarda in basso in quella tabella finché non trovi la riga che corrisponde ai bit che abbiamo appena impostato in quel registro. La riga dice "clk/1024 (dal prescaler)". Ciò significa che vogliamo che Timer/Counter0 (TCNT0) ticchetta a una velocità che è la frequenza della CPU divisa per 1024. Poiché abbiamo il nostro microcontrollore alimentato da un oscillatore a cristallo da 16 MHz, significa che la velocità con cui la nostra CPU esegue le istruzioni è 16 milioni di istruzioni al secondo. Quindi la velocità con cui il nostro contatore TCNT0 ticchetta è quindi 16 milioni/1024 = 15625 volte al secondo (prova con diversi bit di selezione dell'orologio e guarda cosa succede - ricordi la nostra filosofia?). Teniamo il numero 15625 nella parte posteriore della nostra mente per dopo e passiamo alle prossime due righe di codice:

ldi temp, 0b0000001

maglie TIMSK0, temp

Questo imposta il bit 0 di un registro chiamato TIMSK0 e cancella tutto il resto. Se dai un'occhiata a pagina 109 della scheda tecnica vedrai che TIMSK0 sta per "Timer/Counter Interrupt Mask Register 0" e il nostro codice ha impostato il bit 0 che si chiama TOIE0 che sta per "Timer/Counter0 Overflow Interrupt Enable" … Là! Ora vedi di cosa si tratta. Ora abbiamo il "bit di abilitazione dell'interruzione impostato" come volevamo dalla prima decisione nella nostra immagine in alto. Quindi ora tutto ciò che dobbiamo fare è abilitare gli "interrupt globali" e il nostro programma sarà in grado di rispondere a questo tipo di interrupt. Abiliteremo gli interrupt globali a breve, ma prima di farlo potresti essere stato confuso da qualcosa.. perché diamine ho usato il comando "sts" per copiare nel registro TIMSK0 invece del solito "out"?

Ogni volta che mi vedi usa un'istruzione che non hai mai visto prima, la prima cosa che dovresti fare è andare a pagina 616 nella scheda tecnica. Questo è il "Riepilogo del set di istruzioni". Ora trova l'istruzione "STS" che è quella che ho usato. Dice che prende un numero da un registro R (abbiamo usato R16) e la posizione "Store direct to SRAM" k (nel nostro caso dato da TIMSK0). Allora perché abbiamo dovuto usare "sts" che richiede 2 cicli di clock (vedi l'ultima colonna nella tabella) per essere archiviato in TIMSK0 e avevamo solo bisogno di "out", che richiede solo un ciclo di clock, da memorizzare in TCCR0B prima? Per rispondere a questa domanda dobbiamo tornare alla nostra tabella riassuntiva dei registri a pagina 614. Vedete che il registro TCCR0B è all'indirizzo 0x25 ma anche a (0x45) giusto? Ciò significa che è un registro in SRAM, ma è anche un certo tipo di registro chiamato "porta" (o registro i/o). Se guardi la tabella di riepilogo delle istruzioni accanto al comando "out" vedrai che prende i valori dai "registri di lavoro" come R16 e li invia a una PORT. Quindi possiamo usare "out" quando scriviamo su TCCR0B e risparmiarci un ciclo di clock. Ma ora cerca TIMSK0 nella tabella dei registri. Vedi che ha l'indirizzo 0x6e. Questo è al di fuori dell'intervallo di porte (che sono solo le prime posizioni 0x3F di SRAM) e quindi devi ricorrere all'uso del comando sts e prendere due cicli di clock della CPU per farlo. Si prega di leggere subito la Nota 4 alla fine della tabella riepilogativa delle istruzioni a pagina 615. Nota anche che tutte le nostre porte di input e output, come PORTD, si trovano nella parte inferiore della tabella. Ad esempio, PD4 è il bit 4 all'indirizzo 0x0b (ora vedi da dove provengono tutte le cose 0x0b nel mio codice non commentato!).. ok, domanda veloce: hai cambiato "sts" in "out" e guarda cosa accade? Ricorda la nostra filosofia! rompilo! non limitarti a credermi sulla parola.

Ok, prima di andare avanti, vai a pagina 19 della scheda tecnica per un minuto. Vedete un'immagine della memoria dati (SRAM). I primi 32 registri in SRAM (da 0x0000 a 0x001F) sono i "registri di lavoro generici" da R0 a R31 che usiamo sempre come variabili nel nostro codice. I successivi 64 registri sono le porte I/O fino a 0x005f (cioè quelle di cui stavamo parlando che hanno accanto quegli indirizzi senza parentesi nella tabella dei registri che possiamo usare il comando "out" invece di "sts") Infine la successiva sezione di SRAM contiene tutti gli altri registri della tabella riassuntiva fino all'indirizzo 0x00FF, ed infine il resto è SRAM interna. Ora, velocemente, passiamo un secondo a pagina 12. Lì vedi una tabella dei "registri di lavoro per scopi generici" che usiamo sempre come nostre variabili. Vedete la linea spessa tra i numeri da R0 a R15 e poi da R16 a R31? Questa linea è il motivo per cui usiamo sempre R16 come il più piccolo e ne parlerò un po' di più nel prossimo tutorial in cui avremo anche bisogno dei tre registri di indirizzi indiretti a 16 bit, X, Y e Z. Non lo farò entra ancora in questo però dato che non ne abbiamo bisogno ora e ci stiamo impantanando abbastanza qui.

Torna indietro di una pagina alla pagina 11 della scheda tecnica. Vedrai uno schema del registro SREG in alto a destra? Vedete che il bit 7 di quel registro si chiama "I". Ora scendi la pagina e leggi la descrizione del Bit 7…. Sìì! È il bit di abilitazione dell'interruzione globale. Questo è ciò che dobbiamo impostare per passare attraverso la seconda decisione nel nostro diagramma sopra e consentire interruzioni di overflow timer/contatore nel nostro programma. Quindi la riga successiva del nostro programma dovrebbe essere:

sbi SREG, io

che imposta il bit denominato "I" nel registro SREG. Tuttavia, piuttosto che questo abbiamo usato l'istruzione

sei

Invece. Questo bit è impostato così spesso nei programmi che hanno semplicemente creato un modo più semplice per farlo.

Va bene! Ora abbiamo gli interrupt di overflow pronti per essere eseguiti in modo che il nostro "jmp overflow_handler" venga eseguito ogni volta che si verifica.

Prima di andare avanti, dai una rapida occhiata al registro SREG (Status Register) perché è molto importante. Leggi cosa rappresenta ciascuna delle bandiere. In particolare, molte delle istruzioni che utilizziamo imposteranno e controlleranno continuamente questi flag. Ad esempio, in seguito utilizzeremo il comando "CPI" che significa "confronto immediato". Dai un'occhiata alla tabella di riepilogo delle istruzioni per questa istruzione e nota quanti flag imposta nella colonna "flags". Questi sono tutti flag in SREG e il nostro codice li imposterà e li controllerà costantemente. Vedrai degli esempi a breve. Infine l'ultimo bit di questa sezione di codice è:

clr temp

out TCNT0, temp sbi DDRD, 4

L'ultima riga qui è abbastanza ovvia. Imposta solo il 4° bit del registro di direzione dei dati per PortD facendo in modo che PD4 sia OUTPUT.

Il primo imposta la variabile temp a zero e poi la copia nel registro TCNT0. TCNT0 è il nostro Timer/Contatore0. Questo lo azzera. Non appena il PC esegue questa riga, il timer0 partirà da zero e conterà con una frequenza di 15625 volte al secondo. Il problema è questo: TCNT0 è un registro "8 bit" giusto? Quindi qual è il numero più grande che può contenere un registro a 8 bit? Bene 0b11111111 lo è. Questo è il numero 0xFF. Che fa 255. Quindi vedi cosa succede? Il timer scorre aumentando di 15625 volte al secondo e ogni volta che raggiunge 255 "trabocca" e torna di nuovo a 0. Contemporaneamente al ritorno a zero invia un segnale di Timer Overflow Interrupt. Il PC ottiene questo e sai cosa fa ormai, vero? Sì. Va alla locazione di memoria di programma 0x0020 ed esegue l'istruzione che vi trova.

Grande! Se sei ancora con me allora sei un supereroe instancabile! Andiamo avanti…

Passaggio 6: gestore dell'overflow

Quindi supponiamo che il registro timer/counter0 sia appena andato in overflow. Ora sappiamo che il programma riceve un segnale di interruzione ed esegue 0x0020 che dice al Program Counter, PC di saltare all'etichetta "overflow_handler" il seguente è il codice che abbiamo scritto dopo quell'etichetta:

overflow_handler:

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

La prima cosa che fa è incrementare la variabile "overflow" (che è il nostro nome per il registro di lavoro generico R17) quindi "confronta" il contenuto degli overflow con il numero 61. Il modo in cui funziona l'istruzione cpi è che semplicemente sottrae i due numeri e se il risultato è zero imposta il flag Z nel registro SREG (ti avevo detto che avremmo visto questo registro tutto il tempo). Se i due numeri sono uguali allora il flag Z sarà un 1, se i due numeri non sono uguali allora sarà uno 0.

La riga successiva dice "brne PC+2" che significa "ramo se non uguale". In sostanza controlla il flag Z in SREG e se NON è uno (cioè i due numeri non sono uguali, se fossero uguali verrebbe impostato il flag zero) il PC si dirama a PC+2, cioè salta il successivo riga e va direttamente a "reti" che ritorna dall'interrupt in qualsiasi posizione si trovasse nel codice quando è arrivato l'interrupt. Se l'istruzione brne trovasse un 1 nel bit del flag zero, non si diramava e invece continuava alla riga successiva che clr overflow reimpostandolo a 0.

Qual è il risultato netto di tutto questo?

Bene, vediamo che ogni volta che c'è un overflow del timer questo gestore aumenta il valore di "overflow" di uno. Quindi la variabile "overflow" conta il numero di overflow man mano che si verificano. Ogni volta che il numero raggiunge 61 lo azzeriamo.

Ora, perché mai dovremmo farlo?

Vediamo. Ricorda che la nostra velocità di clock per la nostra CPU è di 16 MHz e l'abbiamo "prescalata" utilizzando TCCR0B in modo che il timer conti solo a una velocità di 15625 conteggi al secondo, giusto? E ogni volta che il timer raggiunge un conteggio di 255 trabocca. Ciò significa che trabocca 15625/256 = 61,04 volte al secondo. Stiamo tenendo traccia del numero di overflow con la nostra variabile "overflow" e stiamo confrontando quel numero con 61. Quindi vediamo che "overflow" sarà uguale a 61 una volta al secondo! Quindi il nostro gestore ripristinerà "overflow" a zero una volta al secondo. Quindi se dovessimo semplicemente monitorare la variabile "overflow" e prendere nota di ogni volta che si azzera, conteremmo secondo per secondo in tempo reale (nota che nel prossimo tutorial mostreremo come ottenere un ritardo in millisecondi nello stesso modo in cui funziona la routine "ritardo" di Arduino).

Ora abbiamo "gestito" gli interrupt di overflow del timer. Assicurati di capire come funziona e poi passa al passaggio successivo in cui utilizziamo questo fatto.

Passaggio 7: ritardo

Ora che abbiamo visto che la nostra routine "overflow_handler" del gestore di interrupt di overflow del timer imposterà la variabile "overflow" a zero una volta al secondo, possiamo usare questo fatto per progettare una subroutine di "ritardo".

Dai un'occhiata al seguente codice da sotto il nostro ritardo: etichetta

ritardo:

clr overflow sec_count: cpi overflow, 30 brne sec_count ret

Chiameremo questa subroutine ogni volta che avremo bisogno di un ritardo nel nostro programma. Il modo in cui funziona è che prima imposta la variabile "overflow" a zero. Quindi entra in un'area etichettata "sec_count" e confronta gli overflow con 30, se non sono uguali torna all'etichetta sec_count e confronta ancora, e ancora, ecc. finché non sono finalmente uguali (ricorda che per tutto il tempo che sta andando sul nostro gestore di interrupt del timer sta continuando ad incrementare la variabile overflow e quindi cambia ogni volta che andiamo da queste parti. Quando l'overflow è finalmente uguale a 30, esce dal ciclo e ritorna dove abbiamo chiamato ritardo: da. Il risultato netto è un ritardo di 1/2 secondo

Esercizio 2: Modificare la routine overflow_handler come segue:

overflow_handler:

inc trabocca reti

ed eseguire il programma. C'è qualcosa di diverso? Perché o perché no?

Passaggio 8: lampeggia

Infine diamo un'occhiata alla routine di lampeggio:

battito di ciglia:

sbi PORTD, ritardo 4 rcall cbi PORTD, ritardo 4 rcall rjmp lampeggio

Prima accendiamo PD4, poi chiamiamo la nostra subroutine di ritardo. Usiamo rcall in modo che quando il PC arriva a un'istruzione "ret" tornerà alla riga successiva a rcall. Quindi la routine di ritardo ritarda di 30 conteggi nella variabile di overflow come abbiamo visto e questo è quasi esattamente 1/2 secondo, quindi disattiviamo PD4, ritardiamo un altro 1/2 secondo e poi torniamo all'inizio.

Il risultato netto è un LED lampeggiante!

Penso che ora sarai d'accordo sul fatto che "blink" non è probabilmente il miglior programma "ciao mondo" in linguaggio assembly.

Esercizio 3: Modificare i vari parametri del programma in modo che il LED lampeggi a velocità diverse come un secondo o 4 volte al secondo, ecc. Esercizio 4: Modificarlo in modo che il LED sia acceso e spento per periodi di tempo diversi. Ad esempio acceso per 1/4 di secondo e poi spento per 2 secondi o qualcosa del genere. Esercizio 5: Modificare i bit di selezione dell'orologio TCCR0B a 100 e quindi continuare a risalire la tabella. A che punto diventa indistinguibile dal nostro programma "hello.asm" dal tutorial 1? Esercizio 6 (opzionale): se hai un oscillatore a cristallo diverso, come un 4 MHz o un 13.5 MHz o altro, cambia il tuo oscillatore a 16 MHz sulla breadboard per quella nuova e vedere come ciò influisce sulla velocità di lampeggio del LED. Ora dovresti essere in grado di eseguire il calcolo preciso e prevedere esattamente come influenzerà il tasso.

Passaggio 9: conclusione

Per quelli di voi irriducibili che sono arrivati fin qui, congratulazioni!

Mi rendo conto che è piuttosto difficile sgobbare quando stai leggendo e guardando più in alto di quanto stai cablando e sperimentando, ma spero che tu abbia imparato le seguenti cose importanti:

  1. Come funziona la memoria di programma
  2. Come funziona SRAM
  3. Come cercare i registri
  4. Come cercare le istruzioni e sapere cosa fanno
  5. Come implementare gli interrupt?
  6. Come il CP esegue il codice, come funziona l'SREG e cosa succede durante gli interrupt
  7. Come eseguire loop e salti e rimbalzare nel codice
  8. Quanto è importante leggere la scheda tecnica!
  9. Come una volta che sai come fare tutto questo per il microcontrollore Atmega328p sarà una passeggiata relativa per imparare tutti i nuovi controller che ti interessano.
  10. Come modificare il tempo della CPU in tempo reale e utilizzarlo nelle routine di ritardo.

Ora che abbiamo un sacco di teoria fuori mano, siamo in grado di scrivere codice migliore e controllare cose più complicate. Quindi nel prossimo tutorial faremo proprio questo. Costruiremo un circuito più complicato, più interessante e lo controlleremo in modi divertenti.

Esercizio 7: "Rompi" il codice in vari modi e guarda cosa succede! Curiosità scientifica tesoro! Qualcun altro può lavare bene i piatti? Esercizio 8: Assemblare il codice utilizzando l'opzione "-l" per generare un file di elenco. Cioè. "avra -l blink.lst blink.asm" e dai un'occhiata al file della lista. Credito extra: il codice non commentato che ho fornito all'inizio e il codice commentato che discuteremo in seguito differiscono! C'è una riga di codice che è diversa. Puoi trovarlo? Perché questa differenza non ha importanza?

Spero ti sia divertito! Ci vediamo la prossima volta…

Consigliato: