Cuprins:
- Pasul 1: Construirea circuitului
- Pasul 2: De ce avem nevoie de comentarii și de fișierul M328Pdef.inc?
- Pasul 3: Blink.asm
- Pasul 4: Directivele de asamblare.org
- Pasul 5: Temporizator / Contor 0
- Pasul 6: Handler Handler
- Pasul 7: Întârziere
- Pasul 8: clipiți
- Pasul 9: Concluzie
Video: Tutorial AVR Assembler 3: 9 Pași
2024 Autor: John Day | [email protected]. Modificat ultima dată: 2024-01-30 11:41
Bine ați venit la tutorialul nr. 3!
Înainte de a începe, vreau să fac un punct filosofic. Nu vă fie teamă să experimentați cu circuitele și codul pe care îl construim în aceste tutoriale. Schimbați firele, adăugați componente noi, scoateți componentele, schimbați liniile de cod, adăugați linii noi, ștergeți liniile și vedeți ce se întâmplă! Este foarte dificil să spargi ceva și dacă faci, cui îi pasă? Nimic din ce folosim, inclusiv microcontrolerul, nu este foarte scump și este întotdeauna educativ să vedem cum pot eșua lucrurile. Nu numai că veți afla ce să nu faceți data viitoare, dar, mai important, veți ști de ce să nu o faceți. Dacă ești ceva ca mine, când erai copil și ai o jucărie nouă, nu a trecut mult timp până când ai avut-o în bucăți pentru a vedea ce a făcut-o să bifeze corect? Uneori jucăria a ajuns să fie deteriorată iremediabil, dar nu era mare lucru. A permite unui copil să-și exploreze curiozitatea chiar și până la jucăriile sparte este ceea ce îl transformă în om de știință sau inginer în loc de mașină de spălat vase.
Astăzi vom conecta un circuit foarte simplu și apoi vom fi puțin grei în teorie. Ne pare rău, dar avem nevoie de instrumente! Îți promit că vom compensa acest lucru în tutorialul 4, unde vom face câteva construcții de circuite mai serioase, iar rezultatul va fi destul de cool. Cu toate acestea, modul în care trebuie să faceți toate aceste tutoriale este într-un mod foarte lent, contemplativ. Dacă doar arunci, construiește circuitul, copiază și lipesc codul și rulează-l atunci, sigur, va funcționa, dar nu vei învăța nimic. Trebuie să vă gândiți la fiecare linie. Pauză. Experiment. Inventa. Dacă o faceți în acest fel, până la sfârșitul celui de-al cincilea tutorial, nu veți mai construi lucruri interesante și nu veți mai avea nevoie de îndrumare. În caz contrar, pur și simplu priviți, nu învățați și creați.
În orice caz, suficientă filozofie, să începem!
În acest tutorial veți avea nevoie de:
- placa dvs. de prototipare
- un LED
- conectarea firelor
- un rezistor în jur de 220-330 ohmi
- Manualul setului de instrucțiuni: www.atmel.com/images/atmel-0856-avr-instruction-se…
- Fișa tehnică: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
- un oscilator de cristal diferit (opțional)
Iată un link către colecția completă de tutoriale:
Pasul 1: Construirea circuitului
Circuitul din acest tutorial este extrem de simplu. În esență, vom scrie programul „clipire”, așa că tot ce avem nevoie este următorul.
Conectați un LED la PD4, apoi la un rezistor de 330 ohmi, apoi la masă. adică
PD4 - LED - R (330) - GND
și asta este!
Totuși, teoria va fi o slogană dură …
Pasul 2: De ce avem nevoie de comentarii și de fișierul M328Pdef.inc?
Cred că ar trebui să începem prin a arăta de ce sunt utile fișierul include și comentariile. Niciunul dintre ele nu este de fapt necesar și puteți scrie, asambla și încărca cod în același mod fără ele și va funcționa perfect (deși fără fișierul include puteți primi unele reclamații de la asamblare - dar nu există erori)
Iată codul pe care urmează să-l scriem astăzi, cu excepția faptului că am eliminat comentariile și fișierul include:
.dispozitiv 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 0x0c, 0 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
destul de simplu nu? Haha. Dacă ați asamblat și încărcat acest fișier, veți face ca LED-ul să clipească la o rată de 1 clipire pe secundă, cu clipirea care durează 1/2 secundă și pauza între clipiri care durează 1/2 secundă.
Cu toate acestea, privirea la acest cod este greu de iluminat. Dacă ar fi să scrieți un cod ca acesta și ați dori să îl modificați sau să-l reutilizați în viitor, veți avea dificultăți.
Deci, să punem comentariile și să includem fișierul înapoi, astfel încât să putem da un sens.
Pasul 3: Blink.asm
Iată codul pe care îl vom discuta astăzi:
;************************************
; scris de: 1o_o7; Data:; versiune: 1.0; fișier salvat ca: blink.asm; pentru AVR: atmega328p; frecvența ceasului: 16MHz (opțional); *************************************; Funcția programului: ---------------------; numără secunde prin clipirea unui LED;; PD4 - LED - R (330 ohm) - GND;; --------------------------------------.nolist.include "./m328Pdef.inc".list; ==============; Declarații:.def temp = r16.def overflow = r17.org 0x0000; locația memoriei (PC) a handlerului de resetare rjmp Resetare; jmp costă 2 cicluri CPU și rjmp costă doar 1; deci, cu excepția cazului în care trebuie să sari mai mult de 8k octeți; ai nevoie doar de rjmp. Unele microcontrolere, prin urmare, numai; au rjmp și nu jmp.org 0x0020; locația memoriei Timer0 overflow handler rjmp overflow_handler; mergi aici dacă apare o întrerupere de revărsare timer0; ============ Resetare: ldi temp, 0b00000101 out TCCR0B, temp; setați butoanele selectorului de ceas CS00, CS01, CS02 la 101; aceasta pune Timer Counter0, TCNT0 în modul FCPU / 1024; deci bifează la CPU freq / 1024 ldi temp, 0b00000001 STS TIMSK0, temp; setați bitul Timer Overflow Interrupt Enable (TOIE0); din registrul de mască de întrerupere a temporizatorului (TIMSK0) sei; activați întreruperile globale - echivalent cu "sbi SREG, I" clr temp out TCNT0, temp; inițializați temporizatorul / contorul la 0 sbi DDRD, 4; setați PD4 la ieșire; ======================; Corpul principal al programului: clipire: sbi PORTD, 4; aprindere LED pe PD4 rcall întârziere; întârzierea va fi de 1/2 secundă cbi PORTD, 4; opriți LED-ul pentru întârzierea apelului PD4; întârzierea va fi intermitentă de 1/2 secundă; reveniți la întârzierea de pornire: revărsările clr; setați depășirile la 0 sec_count: depășiri CPI, 30; comparați numărul de revărsări și 30 brne sec_count; ramificați înapoi la sec_count dacă nu este egal cu ret; dacă au avut loc 30 de revărsări reveniți la clipire overflow_handler: inc revărsări; adăugați 1 la variabila de revărsare cpi deversări, 61; comparați cu 61 brne PC + 2; Program Contor + 2 (săriți rândul următor) dacă nu este egal cu depășiri de clr; dacă au avut loc 61 de revărsări, resetați contorul la zero reti; întoarce-te de la întrerupere
După cum puteți vedea, comentariile mele sunt puțin mai scurte acum. Odată ce știm ce fac comenzile din setul de instrucțiuni, nu este nevoie să explicăm asta în comentarii. Trebuie doar să explicăm ce se întâmplă din punctul de vedere al programului.
Vom discuta despre ce face toate acestea bucată cu bucată, dar mai întâi să încercăm să obținem o perspectivă globală. Corpul principal al programului funcționează după cum urmează.
Mai întâi setăm bitul 4 din PORTD cu „sbi PORTD, 4”, acesta trimite un 1 la PD4 care pune tensiunea la 5V pe acel pin. Aceasta va aprinde LED-ul. Sărim apoi la subrutina „întârziere” care contează 1/2 o secundă (vom explica cum o face mai târziu). Revenim apoi la clipirea și ștergerea bitului 4 de pe PORTD care setează PD4 la 0V și, prin urmare, oprește LED-ul. Amânăm apoi încă o jumătate de secundă și apoi sărim înapoi la începutul clipirii din nou cu „rjmp clipire”.
Ar trebui să rulați acest cod și să vedeți că face ceea ce ar trebui.
Și iată-l! Asta este tot ceea ce face acest cod fizic. Mecanica internă a ceea ce face microcontrolerul este puțin mai implicată și de aceea facem acest tutorial. Deci, să discutăm pe rând fiecare secțiune.
Pasul 4: Directivele de asamblare.org
Știm deja ce fac directivele de asamblare.nolist,.list,.include și.def din tutorialele noastre anterioare, așa că haideți mai întâi să aruncăm o privire asupra celor 4 linii de cod care vin după aceea:
.org 0x0000
jmp Reset.org 0x0020 jmp overflow_handler
Declarația.org îi spune ansamblorului unde este în „Memoria programului” pentru a pune următoarea declarație. Pe măsură ce programul dvs. se execută, „Program Counter” (abreviat ca PC) conține adresa liniei curente care se execută. Deci, în acest caz, atunci când PC-ul este la 0x0000, va vedea comanda "jmp Reset" care se află în acea locație de memorie. Motivul pentru care dorim să punem jmp Reset în acea locație se datorează faptului că atunci când programul începe sau cipul este resetat, computerul începe să execute cod în acest loc. Deci, după cum putem vedea, tocmai i-am spus să „sară” imediat la secțiunea etichetată „Resetare”. De ce am făcut asta? Asta înseamnă că ultimele două rânduri de mai sus sunt doar ignorate! De ce?
Ei bine, aici lucrurile devin interesante. Acum va trebui să deschideți un vizualizator pdf cu foaia de date completă ATmega328p pe care am arătat-o pe prima pagină a acestui tutorial (de aceea este articolul 4 din secțiunea „veți avea nevoie”). Dacă ecranul dvs. este prea mic sau dacă aveți deja prea multe ferestre deschise (așa cum este cazul meu), puteți face ceea ce fac și îl puteți pune pe un Ereader sau pe telefonul dvs. Android. O veți folosi tot timpul dacă intenționați să scrieți codul de asamblare. Interesant este că toți microcontolerele sunt organizate în moduri foarte asemănătoare și astfel, odată ce vă obișnuiți să citiți fișele tehnice și să le codificați, veți găsi aproape banal să faceți același lucru pentru un microcontroler diferit. Așadar, învățăm cum să folosim toate microcontrolerele într-un sens și nu doar atmega328p.
Bine, treceți la pagina 18 din foaia tehnică și aruncați o privire la Figura 8-2.
Acesta este modul în care este configurată memoria programului din microcontroler. Puteți vedea că începe cu adresa 0x0000 și este separat în două secțiuni; o secțiune flash pentru aplicație și o secțiune flash de încărcare. Dacă vă referiți scurt la pagina 277 tabelul 27-14, veți vedea că secțiunea bliț a aplicației ocupă locațiile de la 0x0000 la 0x37FF și secțiunea bliț de încărcare ocupă locurile rămase de la 0x3800 la 0x3FFF.
Exercițiul 1: Câte locații există în memoria programului? Adică convertiți 3FFF în zecimal și adăugați 1 deoarece începem să numărăm la 0. Deoarece fiecare locație de memorie are o lățime de 16 biți (sau 2 octeți) care este numărul total de octeți de memorie? Acum convertiți acest lucru în kilobyți, amintindu-vă că există 2 ^ 10 = 1024 octeți într-un kilobyte. Secțiunea flash de pornire merge de la 0x3800 la 0x37FF, câți kilobyți este aceasta? Câți kilobiți de memorie rămân pe noi să îi folosim pentru a stoca programul nostru? Cu alte cuvinte, cât de mare poate fi programul nostru? În cele din urmă, câte linii de cod putem avea?
Bine, acum că știm totul despre organizarea memoriei programului flash, să continuăm cu discuția noastră despre declarațiile.org. Vedem că prima locație de memorie 0x0000 conține instrucțiunile noastre de a trece la secțiunea noastră pe care am etichetat-o Reset. Acum vedem ce face declarația „.org 0x0020”. Se spune că dorim ca instrucțiunile de pe următoarea linie să fie plasate la locația de memorie 0x0020. Instrucțiunea pe care am pus-o acolo este un salt la o secțiune din codul nostru pe care am etichetat-o „overflow_handler” … acum de ce naiba am cere ca acest salt să fie plasat în locația de memorie 0x0020? Pentru a afla, ne întoarcem la pagina 65 din foaia de date și aruncăm o privire la Tabelul 12-6.
Tabelul 12-6 este un tabel de "Resetare și întrerupere a vectorilor" și arată exact unde va merge PC-ul atunci când primește o "întrerupere". De exemplu, dacă te uiți la numărul Vector 1. „Sursa” întreruperii este „RESET”, care este definită ca „Pin extern, resetare pornire, resetare Brown-out și resetare sistem Watchdog”, dacă există aceste lucruri se întâmplă cu microcontrolerul nostru, PC-ul va începe să execute programul la locația memoriei de program 0x0000. Dar directiva noastră.org atunci? Ei bine, am plasat o comandă la locația de memorie 0x0020 și dacă vă uitați în jos la masă veți vedea că, dacă se întâmplă un overflow Timer / Counter0 (provenind de la TIMER0 OVF), acesta va executa orice este la locația 0x0020. Deci, ori de câte ori se întâmplă acest lucru, computerul va sări la locul pe care l-am etichetat „overflow_handler”. Cool nu? Veți vedea într-un minut de ce am făcut acest lucru, dar mai întâi să terminăm acest pas al tutorialului cu o parte.
Dacă dorim să ne facem codul mai îngrijit și mai ordonat, ar trebui să înlocuim cu adevărat cele 4 linii pe care le discutăm în prezent cu următoarele (vezi pagina 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
Astfel, dacă apare o întrerupere dată, va fi doar „reti”, ceea ce înseamnă „revenire de la întrerupere” și nu se întâmplă nimic altceva. Dar dacă nu „activăm” aceste diferite întreruperi, atunci acestea nu vor fi utilizate și putem pune codul programului în aceste locuri. În programul nostru actual "blink.asm", vom activa doar întreruperea timer0 overflow (și, bineînțeles, întreruperea de resetare care este întotdeauna activată) și astfel nu ne vom deranja cu ceilalți.
Cum putem „activa” atunci întreruperea timer0 de revărsare? … acesta este subiectul următorului nostru pas din acest tutorial.
Pasul 5: Temporizator / Contor 0
Uitați-vă la imaginea de mai sus. Acesta este procesul decizional al „PC-ului” atunci când o influență exterioară „întrerupe” fluxul programului nostru. Primul lucru pe care îl face atunci când primește un semnal din exterior că a avut loc o întrerupere este să verifice dacă am setat bitul de „activare a întreruperii” pentru acel tip de întrerupere. Dacă nu am făcut-o, atunci continuă să execute următoarea noastră linie de cod. Dacă am setat acel bit de activare a întreruperii (astfel încât să existe un 1 în acea locație de bit în loc de un 0), atunci vom verifica dacă am activat sau nu „întreruperi globale”, dacă nu, va merge din nou la următoarea linie de cod și continuați. Dacă am activat și întreruperile globale, atunci va merge la locația Memorie program a acelui tip de întrerupere (așa cum se arată în Tabelul 12-6) și va executa orice comandă am plasat acolo. Deci, să vedem cum am implementat toate acestea în codul nostru.
Secțiunea Reset etichetată a codului nostru începe cu următoarele două linii:
Resetați:
ldi temp, 0b00000101 out TCCR0B, temp
După cum știm deja, acest lucru încarcă în temp (adică R16) numărul imediat următor, care este 0b00000101. Apoi scrie acest număr în registrul numit TCCR0B folosind comanda „out”. Ce este acest registru? Ei bine, să trecem la pagina 614 din foaia de date. Aceasta se află în mijlocul unui tabel care rezumă toate registrele. La adresa 0x25 veți găsi TCCR0B. (Acum știți de unde a venit linia „out 0x25, r16” în versiunea mea necomentată a codului). Vedem după segmentul de cod de mai sus că am setat bitul 0 și bitul 2 și am șters tot restul. Privind tabelul, puteți vedea că acest lucru înseamnă că am setat CS00 și CS02. Acum permiteți trecerea la capitolul din foaia de date numită „Temporizator 8 / biți 0 cu PWM”. În special, accesați pagina 107 a acelui capitol. Veți vedea aceeași descriere a registrului „Timer / Counter Control Register B” (TCCR0B) pe care tocmai l-am văzut în tabelul rezumat al registrului (așa că am fi putut veni direct aici, dar am vrut să vedeți cum să utilizați tabelele rezumative pentru referință viitoare). Foaia tehnică continuă să ofere o descriere a fiecăruia dintre biții din acel registru și ce fac. Vom sări peste toate acestea pentru moment și vom întoarce pagina la Tabelul 15-9. Acest tabel prezintă „Descrierea bitului de selectare a ceasului”. Acum priviți în jos acel tabel până când găsiți linia care corespunde biților pe care tocmai le-am setat în acel registru. Linia spune „clk / 1024 (de la prescaler)”. Ceea ce înseamnă acest lucru este că vrem ca Timer / Counter0 (TCNT0) să bifeze la o rată care este frecvența CPU împărțită la 1024. Deoarece microcontrolerul nostru este alimentat de un oscilator de cristal de 16 MHz înseamnă că rata pe care CPU-ul nostru execută instrucțiunile este 16 milioane de instrucțiuni pe secundă. Deci rata pe care o va bifa contorul nostru TCNT0 este de 16 milioane / 1024 = 15625 de ori pe secundă (încercați-o cu diferiți biți de selectare a ceasului și vedeți ce se întâmplă - vă amintiți filosofia noastră?). Să păstrăm numărul 15625 în spatele minții noastre pentru mai târziu și să trecem la următoarele două linii de cod:
ldi temp, 0b00000001
sts TIMSK0, temp
Aceasta setează bitul 0 al unui registru numit TIMSK0 și șterge tot restul. Dacă aruncați o privire la pagina 109 din foaia de date, veți vedea că TIMSK0 înseamnă „Timer / Counter Interrupt Mask Register 0” și codul nostru a setat bitul 0, numit TOIE0, care înseamnă „Timer / Counter0 Overflow Interrupt Enable Enable” … Acolo! Acum vedeți despre ce este vorba. Acum avem „setarea bitului de activare a întreruperii” așa cum am dorit de la prima decizie din imaginea noastră din partea de sus. Deci, acum tot ce trebuie să facem este să activăm „întreruperile globale”, iar programul nostru va putea răspunde la acest tip de întreruperi. Vom activa întreruperile globale în scurt timp, dar înainte de a face acest lucru este posibil să fiți confuz de ceva.. de ce naiba am folosit comanda „sts” pentru a copia în registrul TIMSK0 în loc de „out” obișnuit?
Ori de câte ori mă vedeți, folosesc o instrucțiune pe care nu ați mai văzut-o înainte. Acesta este „Rezumatul setului de instrucțiuni”. Acum găsiți instrucțiunea „STS” care este cea pe care am folosit-o. Se spune că ia un număr dintr-un registru R (am folosit R16) și „Stocați direct către SRAM” locația k (în cazul nostru dat de TIMSK0). Deci, de ce a trebuit să folosim „sts” care durează 2 cicluri de ceas (vezi ultima coloană din tabel) pentru a fi stocate în TIMSK0 și am avut nevoie doar de „out”, care durează doar un ciclu de ceas, pentru a fi stocate în TCCR0B înainte? Pentru a răspunde la această întrebare, trebuie să ne întoarcem la tabelul rezumat al registrului de la pagina 614. Vedeți că registrul TCCR0B este la adresa 0x25, dar și la (0x45) nu? Aceasta înseamnă că este un registru în SRAM, dar este și un anumit tip de registru numit „port” (sau registru i / o). Dacă vă uitați la tabelul rezumat al instrucțiunilor de lângă comanda „out” veți vedea că preia valori din „registrele de lucru” precum R16 și le trimite la un PORT. Deci putem folosi „out” atunci când scriem pe TCCR0B și ne putem salva un ciclu de ceas. Dar acum căutați TIMSK0 în tabelul de registre. Vedeți că are adresa 0x6e. Acest lucru se află în afara gamei de porturi (care sunt doar primele locații 0x3F ale SRAM) și, prin urmare, trebuie să vă întoarceți la utilizarea comenzii STS și să luați două cicluri de ceas CPU pentru ao face. Vă rugăm să citiți Nota 4 la sfârșitul tabelului rezumat al instrucțiunilor de la pagina 615 chiar acum. De asemenea, observați că toate porturile noastre de intrare și ieșire, cum ar fi PORTD, sunt situate în partea de jos a tabelului. De exemplu, PD4 este bitul 4 la adresa 0x0b (acum vedeți de unde au venit toate lucrurile 0x0b în codul meu necomentat!).. bine, întrebare rapidă: ați schimbat „sts” la „out” și ați văzut ce se întâmplă? Amintiți-vă filosofia noastră! Rupe-o! nu mă crede doar pe cuvânt.
Bine, înainte de a trece mai departe, treceți la pagina 19 din foaia de date pentru un minut. Vedeți o imagine a memoriei de date (SRAM). Primele 32 de registre din SRAM (de la 0x0000 la 0x001F) sunt „registrele de lucru cu scop general” de la R0 la R31 pe care le folosim tot timpul ca variabile în codul nostru. Următoarele 64 de registre sunt porturile I / O de până la 0x005f (adică cele despre care vorbeam care au acele adrese fără paranteze alături în tabelul de registre pe care le putem folosi comanda „out” în loc de „sts”) următoarea secțiune a SRAM conține toate celelalte registre din tabelul rezumat până la adresa 0x00FF, iar în cele din urmă restul este SRAM intern. Acum, să trecem la pagina 12 pentru o secundă. Acolo vedeți un tabel cu „registrele de lucru cu scop general” pe care le folosim întotdeauna ca variabile. Vedeți linia groasă dintre numerele R0 - R15 și apoi R16 - R31? De aceea, folosim întotdeauna R16 ca fiind cel mai mic și voi intra în el un pic mai mult în următorul tutorial, unde vom avea nevoie și de cele trei registre de adrese indirecte pe 16 biți, X, Y și Z. intrați încă în acest lucru, deși nu avem nevoie de el acum și ne împotmolim suficient aici.
Reveniți la o pagină la pagina 11 din foaia de date. Veți vedea o diagramă a registrului SREG în dreapta sus? Vedeți că bitul 7 din acel registru se numește „eu”. Acum coborâți pagina și citiți descrierea Bit 7…. Yay! Este bitul Global Interrupt Enable. Aceasta este ceea ce trebuie să setăm pentru a trece prin cea de-a doua decizie din diagrama noastră de mai sus și pentru a permite întreruperile temporizatorului / contorului în programul nostru. Deci, următoarea linie a programului nostru ar trebui să citească:
sbi SREG, eu
care setează bitul numit „I” în registrul SREG. Cu toate acestea, mai degrabă decât aceasta am folosit instrucțiunea
sei
in schimb. Acest bit este setat atât de des în programe încât tocmai au făcut o modalitate mai simplă de a o face.
Bine! Acum avem pregătirile pentru întreruperile de deversare, astfel încât „jmp overflow_handler” nostru să fie executat ori de câte ori apare.
Înainte de a trece mai departe, aruncați o privire rapidă la registrul SREG (Status Register), deoarece este foarte important. Citiți ce reprezintă fiecare dintre steaguri. În special, multe dintre instrucțiunile pe care le folosim vor seta și vor verifica aceste steaguri tot timpul. De exemplu, mai târziu vom folosi comanda „CPI” care înseamnă „comparați imediat”. Aruncați o privire la tabelul rezumat al instrucțiunilor pentru această instrucțiune și observați câte steaguri stabilește în coloana „steaguri”. Acestea sunt toate steaguri în SREG, iar codul nostru le va seta și le va verifica în mod constant. Veți vedea exemple în scurt timp. În cele din urmă, ultima parte a acestei secțiuni de cod este:
clr temp
out TCNT0, temp sbi DDRD, 4
Ultima linie aici este destul de evidentă. Setează doar cel de-al 4-lea bit al Registrului de direcție a datelor pentru PortD, determinând ca PD4 să fie OUTPUT.
Primul setează variabila temp la zero și apoi o copiază în registrul TCNT0. TCNT0 este Timer / Counter0 nostru. Aceasta îl stabilește la zero. De îndată ce PC-ul execută această linie, cronometrul 0 va începe la zero și se va număra cu o rată de 15625 de ori pe secundă. Problema este următoarea: TCNT0 este un registru „pe 8 biți” nu? Deci, care este cel mai mare număr pe care îl poate deține un registru pe 8 biți? Ei bine, 0b11111111 este. Acesta este numărul 0xFF. Care este 255. Deci vedeți ce se întâmplă? Cronometrul crește de 15625 ori pe secundă și de fiecare dată când ajunge la 255 „deversează” și revine la 0. În același timp cu revenirea la zero, trimite un semnal de întrerupere a depășirii temporizatorului. PC-ul primește acest lucru și știi ce face până acum nu? Da. Merge la locația Memorie program 0x0020 și execută instrucțiunile pe care le găsește acolo.
Grozav! Dacă ești încă cu mine, atunci ești un supererou neobosit! Sa continuam…
Pasul 6: Handler Handler
Deci, să presupunem că registrul timer / counter0 tocmai a debordat. Acum știm că programul primește un semnal de întrerupere și execută 0x0020, care îi spune programului Contor, PC să treacă la eticheta „overflow_handler”, următorul este codul pe care l-am scris după acea etichetă:
overflow_handler:
inc overflows cpi overflows, 61 brne PC + 2 clr overlows reti
Primul lucru pe care îl face este să incrementeze variabila „depășiri” (care este numele nostru pentru registrul de lucru de uz general R17), apoi „compară” conținutul depășirilor cu numărul 61. Modul în care funcționează instrucțiunea CPI este că pur și simplu scade cele două numere și dacă rezultatul este zero, stabilește steagul Z în registrul SREG (v-am spus că vom vedea acest registru tot timpul). Dacă cele două numere sunt egale, atunci steagul Z va fi 1, dacă cele două numere nu sunt egale, atunci va fi un 0.
Următoarea linie spune „brne PC + 2” care înseamnă „ramură dacă nu este egală”. În esență, verifică steagul Z în SREG și dacă NU este unul (adică cele două numere nu sunt egale, dacă ar fi egale, steagul zero ar fi setat) PC-ul se ramifică la PC + 2, ceea ce înseamnă că omite următorul line și merge direct la „reti” care se întoarce de la întrerupere în orice loc era în cod când a sosit întreruperea. Dacă instrucțiunea brne a găsit un 1 în bitul de semnalizare zero, nu s-ar ramifica și, în schimb, ar continua doar la următoarea linie, care ar clr debordează resetându-l la 0.
Care este rezultatul net al tuturor acestor lucruri?
Ei bine, vedem că de fiecare dată când există o revărsare a temporizatorului, acest handler mărește valoarea „revărsărilor” cu unul. Deci, variabila „deversări” contorizează numărul de revărsări pe măsură ce apar. Ori de câte ori numărul ajunge la 61 îl resetăm la zero.
Acum, de ce în lume am face asta?
Să vedem. Amintiți-vă că viteza de ceas pentru procesorul nostru este de 16 MHz și l-am „prescalat” folosind TCCR0B, astfel încât temporizatorul să conteze doar la o rată de 15625 de conturi pe secundă, nu? Și de fiecare dată când cronometrul atinge un număr de 255, acesta se revarsă. Deci asta înseamnă că deversează 15625/256 = 61,04 ori pe secundă. Urmărim numărul de revărsări cu variabila noastră „revărsări” și comparăm acest număr cu 61. Deci vedem că „revărsările” vor fi egale cu 61 o dată pe secundă! Așadar, handlerul nostru va reseta „revărsările” la zero o dată pe secundă. Deci, dacă ar fi să monitorizăm pur și simplu variabila „revărsări” și să luăm notă de fiecare dată când se resetează la zero, am fi numărat secundar cu secundă în timp real (Rețineți că în următorul tutorial vom arăta cum să obțineți un întârziere în milisecunde în același mod în care funcționează rutina „întârziere” Arduino).
Acum am „gestionat” întreruperile de revărsare a temporizatorului. Asigurați-vă că înțelegeți cum funcționează acest lucru și apoi treceți la pasul următor în care folosim acest fapt.
Pasul 7: Întârziere
Acum, că am văzut că rutina noastră de gestionare a întreruperilor de depășire a temporizatorului „overflow_handler” va seta variabila „depășiri” la zero o dată pe secundă, putem folosi acest fapt pentru a proiecta un subrutină „întârziere”.
Aruncați o privire la următorul cod de sub întârzierea noastră: etichetă
întârziere:
clr overflows sec_count: CPI overflow, 30 brne sec_count ret
Vom apela acest subrutină de fiecare dată când avem nevoie de o întârziere în programul nostru. Modul în care funcționează este că setează mai întâi variabila „deversări” la zero. Apoi intră într-o zonă etichetată „sec_count” și compară debordările cu 30, dacă nu sunt egale, se ramifică înapoi la eticheta sec_count și se compară din nou, din nou, etc. până când sunt în cele din urmă egale (amintiți-vă că tot timpul se întâmplă pe timer-ul nostru de manipulare a întreruperilor continuă să creștem variabile de revărsare și astfel se schimbă de fiecare dată când mergem pe aici. Când debordurile sunt în sfârșit egale cu 30, iese din buclă și revine oriunde am numit întârziere: de la. Rezultatul net este un întârziere de 1/2 secundă
Exercițiul 2: schimbați rutina overflow_handler la următorul:
overflow_handler:
inc revarsă reti
și rulați programul. Este ceva diferit? De ce sau de ce nu?
Pasul 8: clipiți
În cele din urmă, să ne uităm la rutina de clipire:
clipi:
sbi PORTD, 4 rcall delay cbi PORTD, 4 rcall delay rjmp clipesc
Mai întâi pornim PD4, apoi apelăm subrutina noastră de întârziere. Folosim rcall astfel încât, atunci când computerul ajunge la o declarație „ret”, acesta să revină la linia care urmează rcall. Apoi întârzieri de rutină întârzieri pentru 30 de conturi în variabila de revărsare, așa cum am văzut și aceasta este aproape exact 1/2 secundă, apoi oprim PD4, amânăm încă 1/2 secundă și apoi ne întoarcem din nou la început.
Rezultatul net este un LED intermitent!
Cred că acum veți fi de acord că „clipirea” nu este probabil cel mai bun program „hello world” în limbajul asamblării.
Exercițiul 3: Modificați diferiții parametri din program, astfel încât LED-ul să clipească la viteze diferite, cum ar fi o secundă sau de 4 ori pe secundă, etc. De exemplu, pornit timp de 1/4 de secundă și apoi oprit timp de 2 secunde sau ceva asemănător. În ce moment devine indistinct de programul nostru „hello.asm” din tutorialul 1? Exercițiul 6 (opțional): dacă aveți un oscilator de cristal diferit, cum ar fi un 4 MHz sau un 13,5 MHz sau orice altceva, schimbați oscilatorul de 16 MHz pe placa dvs. pentru noua și vedeți cum afectează acest lucru rata de clipire a LED-ului. Acum ar trebui să puteți parcurge calculul precis și să preziceți exact cum va afecta rata.
Pasul 9: Concluzie
Pentru cei dintre voi care au ajuns până acum, Felicitări!
Îmi dau seama că este destul de greu să cazi atunci când citești mai mult și privești în sus decât faci și experimentezi, dar sper că ai învățat următoarele lucruri importante:
- Cum funcționează memoria programului
- Cum funcționează SRAM
- Cum să căutați registre
- Cum să căutați instrucțiuni și să știți ce fac
- Cum se implementează întreruperile
- Cum execută codul CP, cum funcționează SREG și ce se întâmplă în timpul întreruperilor
- Cum se fac bucle și salturi și sări în jurul codului
- Cât de important este să citești fișa tehnică!
- Cum, odată ce știi cum să faci toate acestea pentru microcontrolerul Atmega328p, va fi o plimbare relativă pentru a afla orice noi controlere care te interesează.
- Cum să schimbați timpul procesorului în timp real și să îl utilizați în rutinele de întârziere.
Acum, că avem o mulțime de teorii în afara modului, suntem capabili să scriem un cod mai bun și să controlăm lucruri mai complicate. Deci, următorul tutorial îl vom face exact. Vom construi un circuit mai complicat, mai interesant, și îl vom controla în moduri distractive.
Exercițiul 7: „spargeți” codul în diferite moduri și vedeți ce se întâmplă! Curiozitate științifică copil! Altcineva poate spăla vasele corect? Exercițiul 8: Asamblați codul folosind opțiunea „-l” pentru a genera un fișier listă. Adică „avra -l blink.lst blink.asm” și aruncă o privire asupra fișierului listă. Credit suplimentar: codul necomentat pe care l-am dat la început și codul comentat despre care discutăm mai târziu diferă! Există o linie de cod diferită. Poti sa o gasesti? De ce nu contează această diferență?
Sper ca te-ai distrat! Ne vedem data viitoare …
Recomandat:
Tutorial AVR Assembler 2: 4 pași
Tutorial AVR Assembler 2: Acest tutorial este o continuare a " AVR Assembler Tutorial 1 " Dacă nu ați parcurs Tutorialul 1, ar trebui să vă opriți acum și să faceți asta mai întâi. În acest tutorial vom continua studiul programării limbajului de asamblare al atmega328p u
Tutorial AVR Assembler 1: 5 pași
AVR Assembler Tutorial 1: Am decis să scriu o serie de tutoriale despre cum să scriu programe de limbaj de asamblare pentru Atmega328p care este microcontrolerul utilizat în Arduino. Dacă oamenii rămân interesați, voi continua să renunț la o săptămână sau cam așa până când voi rămâne fără
Tutorial AVR Assembler 6: 3 pași
Tutorial AVR Assembler 6: Bine ați venit la Tutorialul 6! Tutorialul de astăzi va fi unul scurt, în care vom dezvolta o metodă simplă de comunicare a datelor între un atmega328p și altul folosind două porturi care le conectează. Vom lua apoi rolul de zaruri din Tutorialul 4 și din Registr
Tutorial AVR Assembler 8: 4 Pași
AVR Assembler Tutorial 8: Bine ați venit la Tutorialul 8! În acest scurt tutorial vom face o mică deviere de la introducerea de noi aspecte ale programării limbajului de asamblare pentru a arăta cum să mutăm componentele noastre de prototipare într-un „tipărit” separat. placă de circuit
Tutorial AVR Assembler 7: 12 Pași
AVR Assembler Tutorial 7: Bine ați venit la Tutorialul 7! Astăzi vom arăta mai întâi cum să curățați o tastatură și apoi să arătăm cum să utilizați porturile de intrare analogice pentru a comunica cu tastatura. Vom face acest lucru folosind întreruperi și un singur fir ca intrare. Vom conecta tastatura astfel încât să