Programele de până acum au avut o structură previzibilă: inițializare, apoi o buclă infinită în care procesorul întreabă mereu aceleași lucruri. Structura funcționează atâta timp cât lumea din jur are răbdare să fie întrebată. Problema apare când nu mai are: un buton apăsat câteva zecimi de milisecundă, un octet care sosește pe linia serială înainte să fi fost citit precedentul, un impuls scurt de la un senzor de turație. Mecanismul care rezolvă exact această problemă se numește întrerupere - soneria de la ușă, în locul plimbărilor repetate până acolo ca să verificăm dacă a venit cineva.
1Obiectul și structura cursului6 min
Vom urmări întreruperea pas cu pas: ce se întâmplă mecanic în procesor când apare, cum se scrie corect o rutină de tratare, ce este tabelul vectorilor, ce surse are ATmega328P, cum funcționează prioritatea pe AVR și, cel mai important pentru practică, de ce comunicarea dintre o rutină de tratare și programul principal ascunde două capcane pe care le rezolvă doar disciplina, nu talentul.
- Citirea unei intrări se face din PINx, niciodată din PORTx (Cursul 04)
- Eșantionarea unui pin poate rata un impuls mai scurt decât durata unei bucle de interogare (Cursul 04) - exact problema pe care o rezolvă acest curs
- Să explicați de ce interogarea ciclică poate pierde evenimente scurte, cu o cifră concretă
- Să enumerați, în ordine, operațiile pe care procesorul le execută la acceptarea unei întreruperi
- Să scrieți o rutină de tratare corectă, scurtă, fără întârzieri și fără operații lente
- Să explicați ce înseamnă prioritate pe AVR și de ce nu există preempțiune între întreruperi
- Să recunoașteți și să rezolvați o variabilă neprotejată cu volatile și o condiție de cursă
2Interogarea ciclică și alternativa întreruperilor12 min
Metoda clasică fără întreruperi se numește interogare ciclică (polling): în bucla principală, programul verifică periodic starea fiecărei surse care îl interesează.
while (1) {
stare_noua = (PIND & (1<<PD2)) ? 1 : 0;
if (stare_veche == 1 && stare_noua == 0) PORTB ^= (1<<PB5);
stare_veche = stare_noua;
citeste_senzor_temperatura(); /* dureaza cateva milisecunde */
actualizeaza_afisajul(); /* dureaza si mai mult */
}
Programul are două defecte pe care nicio rescriere nu le poate corecta. Primul: risipa de timp - procesorul citește PIND de zeci de mii de ori pe secundă, iar în marea majoritate a cazurilor răspunsul e „nu s-a întâmplat nimic"; într-o aplicație pe baterie, un procesor care întreabă încontinuu nu poate fi trecut niciodată în regim de consum redus. Al doilea, mai grav: pierderea informației - între două verificări ale butonului, programul execută citirea senzorului și actualizarea afișajului; dacă acestea durează cinci milisecunde, un impuls mai scurt poate cădea în întregime în acel interval și dispare fără urmă.
Un senzor optic generează un impuls de 40 µs la fiecare rotație. Bucla principală, pe un ATmega328P la 16 MHz, are nevoie de 1200 de cicluri pentru un parcurs complet.
Vezi rezolvarea
Un ciclu: T = 1/16×10⁶ = 62,5 ns. Un parcurs complet al buclei: 1200×62,5 ns =
75 µs.
Impulsul de 40 µs e mai scurt decât intervalul dintre două citiri consecutive - poate începe și se poate termina complet între două priviri ale programului. Detecția nu e imposibilă, dar e aleatoare: turația măsurată va fi greșită imprevizibil. Pentru detecție sigură prin interogare, bucla ar trebui să se închidă în mai puțin de 640 de cicluri - foarte puțin loc pentru restul aplicației.
3Caracteristici ale sistemului de întreruperi7 min
Întreruperea e generată ca răspuns la un efect fizic al unui periferic: schimbarea unui pin, încheierea unei perioade de timp, terminarea unei transmisii seriale, sfârșitul unei conversii analog-numerice. Caracterul lor comun e asincronismul - se produc la momente necorelate cu poziția curentă a programului. Tocmai de aici vine câștigul de eficiență: programul nu mai trebuie construit în jurul momentelor în care evenimentele s-ar putea produce.
Tratarea presupune o subrutină dedicată, rutina de tratare, invocată automat de hardware, niciodată explicit din program. Apariția evenimentului setează un bistabil numit indicator de întrerupere (flag) - exact ce rezolvă problema impulsurilor scurte: impulsul poate dispărea fizic, dar bitul care îl marchează rămâne setat până e tratat sau șters.
4Principiul de funcționare, pas cu pas13 min
Punctul de plecare e programul principal (MPF), cu contorul de program (PC) conținând adresa instrucțiunii următoare. Presupunem că un periferic setează indicatorul și cele trei condiții sunt îndeplinite.
Succesiunea de operații
1. Procesorul termină instrucțiunea aflată în curs - o garanție de corectitudine, nu un detaliu: oprirea la jumătate ar lăsa o scriere parțială sau un rezultat necalculat. Întreruperea nu e luată în calcul niciodată la mijlocul unei instrucțiuni, doar la granița dintre două. O instrucțiune lungă adaugă întârziere la răspuns.
2. Salvează PC pe stivă (push(PC)) - adresa de revenire exactă. Pe
ATmega328P, doi octeți, SP scade cu două. Dacă stiva e coruptă, revenirea duce programul într-un loc
arbitrar din Flash.
3. Dezactivează automat întreruperile, ștergând bitul I din SREG - echivalentul de „a scoate din priză soneria" cât timp discutăm cu curierul. Fără acest pas, o a doua întrerupere ar sosi imediat, ar salva încă o adresă, ar fi întreruptă la rândul ei - stiva ar crește necontrolat.
4. Încarcă în PC adresa vectorului corespunzător, din tabelul vectorilor de întrerupere.
5. Execută rutina de tratare - programul principal e complet oprit, nu rulează în paralel, nu rulează mai încet, nu rulează deloc.
6. Se încheie cu reti (return from interrupt) - extrage adresa de pe stivă în
PC, apoi redeschide întreruperile, setând la loc bitul I. Diferența față de ret
e exact această reactivare.
Programul ar reveni corect la adresă, dar ar rula mai departe cu întreruperile stinse - sistemul ar părea „blocat" după prima întrerupere, fără niciun mesaj de eroare. E o greșeală de sintaxă imposibil de comis în C (macroul ISR generează întotdeauna reti), dar reală în asamblare.
Latența răspunsului
Timpul scurs între eveniment și prima instrucțiune utilă a rutinei se numește latență - nu e nulă, nici constantă. Componente: terminarea instrucțiunii curente, recunoașterea cererii și salvarea PC, saltul din tabelul vectorilor, prologul rutinei (salvarea contextului).
ATmega328P la 16 MHz. Recunoașterea cererii durează 4 perioade de ceas, saltul din tabelul vectorilor 3 perioade. Estimați latența minimă.
Vezi rezolvarea
T = 1/16×10⁶ = 62,5 ns. Recunoaștere + salvare PC: 4×62,5 = 250 ns.
Salt: 3×62,5 = 187,5 ns. Total: ~437,5 ns.
La acest interval se adaugă terminarea instrucțiunii în curs (până la patru perioade) și prologul generat de compilator (pe o rutină cu mai multe variabile, poate depăși douăzeci de perioade). Concluzie: latența reală e de ordinul unei microsecunde, nu al zecilor de nanosecunde, și variază de la o apariție la alta - pentru măsurători precise nu ne bazăm pe momentul intrării în rutină, ci pe o valoare capturată direct de hardware (captura pe temporizator).
5Rutina de tratare a întreruperii11 min
Rutina de tratare, ISR (Interrupt Service Routine), se scrie ca orice funcție, dar se comportă diferit.
Salvarea și restaurarea contextului
Hardware-ul salvează doar PC. Tot restul - registrul SREG cu indicatorii de condiție (zero, transport, semn, depășire) și registrele de lucru - rămâne la dispoziția rutinei.
Programul principal execută o comparație, urmează un salt condiționat pe baza ei. Dacă între cele două apare o întrerupere, iar rutina execută orice operație aritmetică, indicatorii sunt rescriși - la revenire, saltul se ia pe baza calculului din rutină, nu pe baza comparației originale. Eroarea depinde de momentul exact al întreruperii, deci apare o dată la mii de execuții.
Ansamblul SREG + registrele folosite se numește context. În C, macroul ISR()
din avr/interrupt.h generează automat prologul și epilogul: salvează SREG, salvează
exact registrele pe care corpul le folosește efectiv, execută corpul, restaurează totul în ordine
inversă și încheie cu reti.
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(INT0_vect)
{
PORTB ^= (1 << PB5); /* corpul rutinei: o singura operatie */
}
INT0_vect nu e un parametru oarecare, ci numele simbolic care leagă funcția de
poziția din tabel. Scris greșit, compilatorul acceptă codul fără plângere - rutina nu
e apelată niciodată. La apariția evenimentului, programul sare într-o intrare netratată și se
resetează (vezi secțiunea despre tabelul vectorilor).
Reguli de scriere
Pe durata rutinei, programul principal e oprit și, pe AVR, celelalte întreruperi sunt blocate. Orice ciclu consumat inutil e un ciclu în care aplicația nu răspunde la nimic altceva.
_delay_ms,
delay) - opresc întregul sistem. Fără operații lente - serial, EEPROM, virgulă mobilă,
LCD, toate durează milisecunde. Nu returnează valori, nu primește parametri - nu există cine să le
transmită sau să le preia, apelul vine din hardware, nu din program. Tiparul central: rutina doar
constată evenimentul, programul principal îl prelucrează.6Tabelul vectorilor de întrerupere8 min
Legătura dintre sursa fizică și rutina de tratare e o structură de date fixă în memorie: tabelul vectorilor de întrerupere. Pe AVR se află la începutul Flash-ului, de la adresa zero.
Pe ATmega328P (32 KB), fiecare intrare conține o instrucțiune jmp completă (două
cuvinte), pentru că distanța până la rutină poate depăși raza unui salt relativ. Vectorii se află
deci din două în două cuvinte: RESET la 0x0000, INT0 la 0x0002, INT1 la 0x0004.
E punctul de pornire după alimentare, resetare externă sau expirarea watchdog-ului. Consecință utilă: dacă o întrerupere apare fără rutină scrisă pentru ea, intrarea implicită trimite execuția la vectorul de RESET, și programul repornește de la început. Un microcontroler care se resetează la intervale aparent aleatoare este, foarte des, unul cu o sursă de întrerupere activată și netratată.
| Adresă | Sursă | Semnificație |
|---|---|---|
| 0x0000 | RESET | alimentare, resetare externă, watchdog |
| 0x0002 | INT0 | întrerupere externă 0 (PD2) |
| 0x0004 | INT1 | întrerupere externă 1 (PD3) |
| 0x0006-0x000A | PCINT0-2 | schimbare de nivel pe porturile B, C, D |
| 0x0012, 0x001A, 0x0020 | TIMERn_OVF | depășire la numărătoarele 0-2 |
| 0x0024, 0x0028 | USART_RX, USART_TX | recepție / transmisie încheiată |
| 0x002A | ADC | conversie analog-numerică încheiată |
Regularitatea tabelului nu e întâmplătoare: sursele care necesită reacție rapidă și sunt legate direct de exterior (întreruperile externe) se află în capul tabelului, cele lente (legate de memorie) la sfârșit - poziția în tabel are un rol suplimentar, discutat la priorități.
7Surse de întrerupere: externe și interne9 min
Sursele se împart în externe (evenimentul vine de pe un pin) și interne (evenimentul e produs de un modul din interiorul cipului).
Întreruperi externe
Detecția se face în două moduri. Activ pe nivel (level-triggered): cererea există în permanență cât timp linia e în starea activă - dacă rutina se termină și linia e încă jos, cererea reapare imediat, execuție la nesfârșit până sursa eliberează linia. Activ pe front (edge-triggered): cererea apare o singură dată, la tranziție (crescător, căzător, sau oricare) - modul folosit în majoritatea aplicațiilor, pentru că evenimentul e prin natura lui punctual.
ATmega328P are două întreruperi externe complete, INT0 (PD2) și INT1 (PD3), configurate în
EICRA prin perechile ISC01/ISC00 și ISC11/ISC10: 00 nivel jos, 01 orice
schimbare, 10 front căzător, 11 front crescător. Activare locală prin
EIMSK, indicatori în EIFR (INTF0, INTF1).
Perifericele de comunicație (USART, SPI, I2C) generează întreruperi la date gata de transfer sau primite. La 115200 b/s, un caracter sosește la ~87 µs - fără întrerupere, programul ar trebui să verifice registrul de stare cu o frecvență cel puțin dublă, imposibil de conciliat cu orice altă activitate.
Întreruperi interne
Numărătoarele generează depășire (overflow, perioadă fixă, dictată de dimensiune și divizor) sau comparare (perioadă aleasă liber, prin registrul de comparare). ADC-ul generează o întrerupere la finalul conversiei - soluția firească pentru achiziții: se pornește conversia, procesorul face altceva zeci de microsecunde, rutina preia rezultatul.
La majoritatea surselor, indicatorul se șterge automat de hardware la intrarea în rutină. Dacă tratăm prin interogare, fără rutină, ștergerea manuală se face scriind unu în bit, nu zero. La alte surse (recepția USART), indicatorul se șterge singur la citirea registrului de date - dacă rutina uită să citească UDR0, indicatorul rămâne setat și întreruperea reapare la nesfârșit.
8Priorități și imbricarea întreruperilor9 min
Prioritatea decide ordinea de tratare când apar mai multe cereri simultan, sau când o cerere nouă sosește în timpul tratării alteia. Poate fi hardware (fixată prin construcție) sau software (aleasă de programator, prin registru de configurare) - arhitecturile pe 32 de biți oferă de regulă un controler cu nivele programabile.
Un mecanism comun tuturor arhitecturilor e masca de întrerupere: dezactivarea temporară a unor surse, indiferent de prioritate. O sursă mascată nu dispare - indicatorul ei continuă să fie setat, iar la ridicarea măștii, cererea acumulată e tratată imediat.
Cum stau lucrurile pe AVR
Prioritatea contează numai când două sau mai multe cereri sunt în așteptare simultan, în momentul în care procesorul e gata să accepte o întrerupere - atunci se tratează întâi cea cu vectorul mai mic, cealaltă rămâne în așteptare și e tratată imediat după. Nicio cerere nu se pierde.
Odată intrată în rutină, cu bitul I șters automat, nicio altă întrerupere nu o poate întrerupe,
oricât de mare i-ar fi prioritatea. O cerere INT0 sosită în timpul tratării unei întreruperi ADC nu
scoate procesorul din rutina ADC - așteaptă cuminte până la reti. Consecință practică:
pe AVR prioritatea contează mult mai puțin decât durata rutinelor - cea mai bună garanție de
răspuns rapid pentru o sursă critică nu e poziția ei în tabel, ci faptul că toate celelalte rutine
sunt scurte.
Reactivarea manuală a întreruperilor în interiorul unei rutine (sei() sau
ISR_NOBLOCK) produce imbricare - rutine care se pot întrerupe reciproc. Aproape
întotdeauna o idee proastă, din trei motive: consumul de stivă crește cu fiecare nivel de
imbricare, pe o memorie de 2 KB; riscul de recursivitate, dacă indicatorul propriei surse nu
e tratat înainte de reactivare; și complexitatea rezultată face imposibil de verificat toate
întrețeserile posibile ale execuției. Soluția corectă la o rutină prea lungă nu e imbricarea, ci
scurtarea ei.
9Variabile partajate și condiții de cursă11 min
Comunicarea cu programul principal se face exclusiv prin variabile globale - și ascunde două capcane distincte.
Optimizarea compilatorului și volatile
Dacă vede o variabilă globală citită într-o buclă, dar niciodată modificată acolo sau în funcțiile apelate din ea, compilatorul conclude că valoarea nu se schimbă - o citește o singură dată, o păstrează într-un registru, folosește registrul la toate iterațiile. Raționament corect pentru codul vizibil, greșit în realitate, pentru că rutina de tratare o modifică fără ca vreun apel să apară în program.
volatile uint8_t eveniment = 0; /* fara volatile, bucla nu se termina niciodata */
ISR(INT0_vect) { eveniment = 1; } /* rutina doar constata */
int main(void) {
configureaza_intreruperea(); sei();
while (1) {
if (eveniment) { /* programul principal il prelucreaza */
eveniment = 0;
PORTB ^= (1 << PB5);
}
}
}
volatile. Costul e o mică pierdere de viteză, nesemnificativă
față de riscul unei bucle care așteaptă la nesfârșit un indicator deja setat.Condiția de cursă
Nu se rezolvă cu volatile. Registrele AVR sunt de opt biți - o variabilă pe 16 biți
cere două citiri succesive, iar între ele există o granița de instrucțiune unde o întrerupere poate
fi acceptată.
impulsuri are valoarea 255 = 0x00FF. Programul citește octetul inferior (0xFF).
Exact atunci sosește un impuls, rutina incrementează variabila la 256 = 0x0100. Programul citește
octetul superior (0x01).
Vezi rezolvarea
Valoarea recompusă: 0x01FF = 511. Numărul 511 nu a existat niciodată - variabila a
avut 255, apoi 256, dar niciodată 511. Programul a citit jumătate dintr-o valoare veche și jumătate
dintr-una nouă - o eroare de peste 100%, apărută o dată la câteva mii de citiri, care trece de toate
testele și se manifestă la client.
Soluție: citire atomică, prin dezactivarea temporară a întreruperilor, cu restaurarea stării anterioare (nu reactivare necondiționată):
#include <util/atomic.h>
uint16_t citeste_impulsuri(void) {
uint16_t copie;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { copie = impulsuri; }
return copie;
}
Pe durata secțiunii critice sistemul e surd la orice eveniment - o secțiune lungă produce
exact aceleași probleme ca o rutină de tratare lungă. Se copiază valoarea în interior, se
prelucrează după ieșire. Notă utilă: pe AVR, citirea/scrierea unei variabile de opt biți e
atomică prin construcție (o singură instrucțiune) - dar contor++ nu e atomic nici pe
opt biți, fiind citire+incrementare+scriere, trei instrucțiuni separate.
10Aplicație completă cu sistemul de întreruperi11 min
Aplicație completă: LED-ul comută starea la fiecare apăsare a unui buton, folosind INT0, pe frontul crescător.
Montajul și inițializarea
Butonul pe PD2 (singurul pin cu INT0), legat între pin și VCC, cu pull-down extern de ~10 kΩ - repaus la nivel jos, apăsare la nivel înalt (front crescător). LED-ul pe PB5, cu rezistență de limitare calculată ca în Cursul 04.
static void init_porturi(void) {
DDRB = (1 << DDB5); PORTB = 0x00; /* PB5 iesire, LED stins */
DDRD = 0x00; PORTD = 0x00; /* PD2 intrare, fara pull-up (extern) */
}
static void init_intrerupere(void) {
EICRA |= (1 << ISC01) | (1 << ISC00); /* front crescator pe INT0 */
EIFR |= (1 << INTF0); /* sterge un eventual indicator ramas */
EIMSK |= (1 << INT0); /* activare locala */
}
ISR(INT0_vect) { PORTB ^= (1 << PB5); } /* comuta LED-ul; nimic altceva */
int main(void) {
init_porturi(); init_intrerupere();
sei(); /* activare globala, dupa toata initializarea */
while (1) { /* liber pentru orice altceva */ }
}
sei() se apelează întotdeauna la sfârșitul inițializării: dacă
întreruperile ar fi active mai devreme, o rutină s-ar putea executa înainte ca structurile de date
pe care le folosește să fi fost inițializate.Varianta Arduino
volatile bool cerere_comutare = false;
void trateaza_buton() { cerere_comutare = true; } /* doar marcheaza */
void setup() {
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_BUTON, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN_BUTON), trateaza_buton, RISING);
}
void loop() {
if (cerere_comutare) {
cerere_comutare = false;
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
}
}
Diferența nu e doar de sintaxă: aici rutina nu comandă direct ieșirea, doar ridică un indicator,
iar comutarea se face în loop() - pentru că digitalRead/
digitalWrite nu sunt operații elementare (traduc pin în port+mască, verifică PWM
activ), durează câteva microsecunde, mult pentru o rutină de tratare. Același tipar: rutina
constată, bucla prelucrează.
11Problema oscilațiilor de contact6 min
Programul de mai sus funcționează perfect în simulare. Pe un montaj real, LED-ul va comuta uneori o dată, alteori de trei sau cinci ori la o singură apăsare - oscilația de contact (Cursul 04), văzută de sistemul de întreruperi la fel de fidel ca orice alt front.
const unsigned long INTERVAL_MIN = 40; /* milisecunde */
volatile bool cerere_comutare = false;
volatile unsigned long ultimul_moment = 0;
void trateaza_buton() {
unsigned long acum = millis(); /* poate fi citita, nu avanseaza in rutina */
if (acum - ultimul_moment >= INTERVAL_MIN) {
cerere_comutare = true;
ultimul_moment = acum;
}
}
Prima formă rămâne corectă și atunci când numărătorul de milisecunde depășește valoarea maximă și revine la zero - scăderea în aritmetică fără semn dă oricum diferența corectă. A doua formă produce, la momentul depășirii, un buton care nu mai răspunde pentru un interval lung.
Alternativa hardware, un filtru RC eventual urmat de trigger Schmitt, nu consumă timp de procesor, dar cere o componentă în plus pentru fiecare buton - decizia depinde de câte butoane are montajul și cât de critic e timpul de procesor.
millis() se bazează pe o întrerupere de numărător. Deoarece
întreruperile sunt dezactivate în interiorul unei rutine de tratare, valoarea returnată de
millis() nu avansează pe durata execuției acesteia - poate fi citită (utilă pentru a
marca momentul evenimentului), dar nu poate măsura o durată în interiorul rutinei, și cu atât mai
puțin poate fi folosită pentru așteptare. delay() plasat într-o rutină de tratare nu se
termină niciodată.12Erori frecvente4 min
- „O rutină de tratare mai lungă nu strică, doar face ceva mai mult." Fals - pe durata ei, programul principal e complet oprit și, pe AVR, toate celelalte întreruperi sunt blocate. Orice ciclu inutil e un ciclu în care aplicația nu răspunde la nimic altceva, inclusiv la evenimente critice. Rutina doar constată evenimentul (indicator, citire, incrementare); prelucrarea se face în bucla principală.
- „O variabilă globală citită și în întrerupere, și în bucla principală nu are nevoie de nimic
special dacă e mică."
Fals - fără
volatile, compilatorul poate păstra o copie învechită într-un registru, iar bucla nu vede niciodată actualizarea. Pentru variabile mai mari de un octet, chiar cuvolatile, citirea nu e atomică și poate recompune o valoare care nu a existat niciodată. Declarațivolatileorice variabilă partajată; protejați citirile pe mai mult de un octet cuATOMIC_BLOCK. - „Dacă o sursă are prioritate mai mare, poate întrerupe orice altă rutină în curs." Fals pe AVR - nu există preempțiune. Odată intrată într-o rutină, nicio altă întrerupere nu o poate întrerupe, indiferent de prioritate; ea așteaptă cuminte până la reti. Nu vă bazați pe prioritate pentru răspuns rapid - garanția reală vine din faptul că toate rutinele sunt scurte.
13Rezumat și glosar5 min
Interogarea ciclică poate pierde evenimente mai scurte decât durata unei iterații a buclei;
întreruperea rezolvă asta printr-un indicator hardware care memorează evenimentul chiar dacă
semnalul fizic a dispărut deja. La acceptarea unei cereri, procesorul termină instrucțiunea curentă,
salvează PC pe stivă, dezactivează global întreruperile, sare la vectorul din tabel, execută rutina
și revine cu reti, care reactivează întreruperile - spre deosebire de ret.
O rutină de tratare trebuie să fie scurtă, fără întârzieri și fără operații lente: constată
evenimentul, lasă prelucrarea pentru bucla principală. Pe AVR, prioritatea e dată exclusiv de poziția
vectorului în tabel și contează doar între cereri simultane în așteptare - nu există preempțiune
între rutine în curs de execuție. Comunicarea prin variabile globale cere volatile
pentru corectitudinea vizibilității și, pentru variabile mai mari de un octet, secțiuni critice
pentru citiri atomice - altfel apar condiții de cursă, greu de reprodus și devastatoare când
apar.
14Întrebări de verificare7 min
- Explicați de ce interogarea ciclică nu poate garanta detecția unui eveniment scurt, chiar dacă bucla e optimizată. Ce mărime trebuie comparată cu durata evenimentului?
- Enumerați, în ordine, operațiile procesorului AVR între acceptarea unei cereri și prima instrucțiune a rutinei. Care sunt hardware și care generate de compilator?
- Care e diferența dintre ret și reti? Ce se întâmplă dacă o rutină se încheie din greșeală cu ret?
- Ce se înțelege prin contextul procesorului, și de ce trebuie salvat la intrarea în rutină? Dați un exemplu concret de eroare produsă de nesalvarea SREG.
- Un ATmega328P se resetează la intervale neregulate, deși programul nu conține nicio instrucțiune de resetare, iar watchdog-ul e dezactivat. Formulați o explicație plauzibilă.
- Pe AVR, INT0 are prioritate mai mare decât recepția USART. O cerere INT0 sosește în timp ce se execută rutina de recepție. Ce se întâmplă, și de ce?
- Un numărător de 16 biți e incrementat într-o rutină și citit în programul principal. Construiți o secvență de evenimente în care programul obține o valoare care nu a existat niciodată.
15Direcții de aprofundare2 min
Cursul următor folosește exact mecanismul de întreruperi discutat aici, aplicat unui periferic specific: modulele de tip temporizator/numărător - baza timpului, generarea de PWM și captura evenimentelor externe cu precizie de ciclu de ceas.
Montajul, inițializarea și rutina de tratare din acest curs devin montaj real în Laboratorul 03.