1. Întreruperi
O întrerupere hardware reprezintă un semnal sincron sau asincron de la un periferic ce semnalizează apariția unui eveniment care trebuie tratat de către procesor. Tratarea întreruperii are ca efect suspendarea firului normal de execuție al unui program și lansarea în execuție a unei rutine de tratare a întreruperii (RTI).
Întreruperile hardware au fost introduse pentru a se elimina buclele pe care un procesor ar trebui să le facă în așteptarea unui eveniment de la un periferic. Folosind un sistem de întreruperi, perifericele pot comunica cu procesorul, acesta din urma fiind liber să-și ruleze programul normal în restul timpului și să își întrerupă execuția doar atunci când este necesar.
Înainte de a lansa în execuție o RTI, procesorul trebuie sa aibă la dispoziție un mecanism prin care să salveze starea în care se afla în momentul apariției întreruperii. Aceasta se face prin salvarea într-o memorie, de cele mai multe ori organizată sub forma unei stive, a registrului contor de program (Program Counter), a registrelor de stare precum și a tuturor variabilelor din program care sunt afectate de execuția RTI. La sfârșitul execuției RTI starea anterioară a registrelor este refăcută și programul principal este reluat din punctul de unde a fost întrerupt.
Pentru a asocia o întrerupere cu o anumită rutină din program, procesorul folosește tabela vectorilor de întrerupere (TVI), ilustrată în figura de mai jos. În această tabelă, fiecărei întreruperi îi este asociată adresa rutinei sale de tratare, la care programul va face salt în cazul apariției acesteia. Aceste adrese sunt predefinite și sunt mapate în memoria de program într-un spatiu contiguu care alcătuiește TVI. Adresele întreruperilor în TVI sunt setate în funcție de prioritatea lor: cu cât adresa este mai mică cu atât prioritatea este mai mare.
După cum se observă din figura 1, pe lângă întreruperile componentelor interne uC-ului(timer-e, interfețe seriale, convertor analog-digital), există și 3 linii pentru întreruperi de la periferice externe: INT0, INT1, INT2. Semnalele pentru aceste întreruperi externe vin pe pinii INT0, INT1 și INT2 (portul D pinii 2, 3 respectiv portul B pinul 2).
Microcontroller-ul oferă posibilitatea mascării întreruperilor prin scrierea pe 0 a bit-ului I din registrul de status (SREG). Valoarea inițială a acestui bit este 0, deci chiar dacă întreruperile unui periferic sunt activate din unul din registrele acestuia, ele nu sunt luate în considerare doar dacă flag-ul este 1. Totodată, la apariția unei întreruperi, în afară de salvarea stării, procesorul dezactivează întreruperile, iar la revenirea din rutina de tratare le reactivează. Activarea lor poate fi realizată forțat și din handler-ul de întrerupere (de exemplu suntem în handler-ul pt reset, și dorim să activăm întreruperile unui timer).
1.1. Tratarea unei intreruperi
Să presupunem că programul nostru primește întreruperea externă INT0 în timp ce execută instructiunea
ldi R16,0xFF
. După efectuarea instrucțiunii, registrul contor program (PC
) este automat salvat în stivă și apoi inițializat la valoarea corespunzătoare adresei lui INT0 ($002). Acest lucru se traduce prin saltul programului de la adresa curentă ($123) la adresa $002. Aici, programul găsește instrucțiunea jmp $2FF
care-i permite sa sară la rutina efectivă de tratare a întreruperii. La sfârșitul oricărei RTI trebuie să existe instrucțiunea reti
care reface din stivă registrul PC
(programul sare înapoi la adresa de unde rămăsese înainte de întrerupere).
În timpul execuției unei întreruperi, bitul
I
din SREG
este setat la 0 și resetat la ieșire, adică orice altă întrerupere care poate apărea în timpul întreruperii curente nu va fi luată in seamă. Cu toate acestea, utilizatorul poate să reseteze bitul I
din software, permițând astfel execuția unei întreruperi în întrerupere.1.2. Registre
Descrierea completă a acestor registre o găsiți în datasheet în capitolele Interrupts, External Interrupts.
Status Register (SREG)
- conține flag-uri setate in urma operațiilor unității aritmetice logice
- conține flag-ul
I
de activare/dezactivare întreruperi - nu este salvat la apariția unei întreruperi !!!
- descris în datasheet în capitolul AVR CPU Core
MCU Control Register (MCUCR)
- bitul
IVSEL
controlează unde se plasează vectorii de întreruperi (0 - începutul memoriei Flash, 1 - începutul secțiunii de Boot Loader din Flash) - bitul
IVCE
activează scrierea bituluiIVSEL
External Interrupt Mask Register (EIMSK)
- biții
INT2:0
controlează dacă întreruperile externe sunt activate - pt oricare bit
INT2:0
, dacă este 1 și bitulI
dinSREG
este 1 atunci sunt activate întreruperile externe pe pinul corespunzător.
1.3. Lucrul cu întreruperi în avr-gcc
avr-libc oferă interfața din avr/interrupt.h pentru definirea rutinelor de tratare a întreruperilor. Tabela de vectori specifică fiecărui microcontroller (în cazul nostru pentru ATMega324) este declarată în header-ul de IO specific (ex: iom324.h)
Definirea rutinei de tratare se face cu macro-ul ISR:
#include <avr/interrupt.h> ISR(INT0_vect) { ... }
Reguli de programare în context întrerupere:
- Nu există o valoare de return! La închierea execuției handler-ului, procesorul execută din nou instrucțiuni de unde rămăsese înainte de declanșarea întreruperii
- Datorită faptului că handler-ul întârzie orice altă activitate din main și inhibă execuția altor întreruperi, se dorește ca timpul de execuție să fie cât mai mic!
- La folosirea unor variabile comune în mai multe handler-e de întrerupere și/sau main trebuie avut în vedere accesul concurent la acestea (în special la variabilele de 16/32 biți a căror modificare necesită mai mulți cicli de procesor).
- Variabilele comune trebuiesc marcate ca
volatile
pentru ca accesele la acestea să nu fie optimizate de către compilator
Alte wrapper-e (în jurul unor instrucțiuni ale core-ului AVR) oferite de interfață sunt:
- sei() - setează pe 1 bitul I din SREG, activând întreruperile globale. Apelează instrucțiunea assembler sei
- cli() - setează pe 0 bitul I din SREG, dezactivând întreruperile globale. Apelează instrucțiunea assembler cli
- reti() - întoarcerea dintr-o rutină de tratare a intreruperii, activează întreruperile globale. Ar trebui sa fie ultima instrucțiune apelată într-o rutină care folosește flag-ul ISR_NAKED.
Întreruperile întâlnite care nu au o rutină de tratare vor cauza o întrerupere de reset!
2.Timer
2.1. Principiul de funcționare al unui Timer
Timerul/Counterul, după cum îi spune și numele, oferă facilitatea de a măsura intervale fixe de timp și de a genera întreruperi la expirarea intervalului măsurat. Un timer, odată inițializat va funcționa independent de unitatea centrală (core μP). Acest lucru permite eliminarea buclelor de delay din programul principal.
Principiul de funcționare a unui Timer poate fi descris în linii mari de cele trei unități din figure 6:
-
Registrul numărător (Timer Counter, TCNT) - măsoară efectiv intervalele de timp și este incrementat automat cu o frecvență dată.
-
Prescaler-ul - are rolul de a diviza în funcție de necesitățile aplicației frecvența de ceas și odată cu divizarea să incrementeze registrul
TCNT
.
-
La fiecare incrementare a
TCNT
are loc o comparație între acest registru și o valoare stocată în registrul OCRn
. Această valoare poate fi încarcată de către programator prin scrierea registrului OCRn
. Dacă are loc egalitatea se generează o întrerupere, în caz contrar incrementarea continuă.
Timerele sunt prevăzute cu mai multe canale astfel încât se pot desfășura diferite număratori în paralel. ATmega324 este prevăzut cu 4 unități de timer: două de opt biți și două de numărare pe șaisprezece biți.
Timerele pot funcționa și în moduri PWM, astfel încat să genereze pe un pin de ieșire un semnal. Mai multe detalii veți afla în laboratorul viitor.
Timerul/Counterul, după cum îi spune și numele, oferă facilitatea de a măsura intervale fixe de timp și de a genera întreruperi la expirarea intervalului măsurat. Un timer, odată inițializat va funcționa independent de unitatea centrală (core μP). Acest lucru permite eliminarea buclelor de delay din programul principal.
Principiul de funcționare a unui Timer poate fi descris în linii mari de cele trei unități din figure 6:
- Registrul numărător (Timer Counter, TCNT) - măsoară efectiv intervalele de timp și este incrementat automat cu o frecvență dată.
- Prescaler-ul - are rolul de a diviza în funcție de necesitățile aplicației frecvența de ceas și odată cu divizarea să incrementeze registrul
TCNT
. - La fiecare incrementare a
TCNT
are loc o comparație între acest registru și o valoare stocată în registrulOCRn
. Această valoare poate fi încarcată de către programator prin scrierea registruluiOCRn
. Dacă are loc egalitatea se generează o întrerupere, în caz contrar incrementarea continuă.
Timerele sunt prevăzute cu mai multe canale astfel încât se pot desfășura diferite număratori în paralel. ATmega324 este prevăzut cu 4 unități de timer: două de opt biți și două de numărare pe șaisprezece biți.
Timerele pot funcționa și în moduri PWM, astfel încat să genereze pe un pin de ieșire un semnal. Mai multe detalii veți afla în laboratorul viitor.
2.2. Moduri de funcționare
Timer/Counter0 și Timer/Counter2 au patru moduri de funcționare (dintre care vom detalia 3, pentru cel de-al patrulea puteți citi datasheet-ul), ce se diferențiaza prin:
-
valorile pâna la care se face incrementarea
-
felul în care se numără (doar crescător, sau alternativ crescător/descrescător)
-
când se face resetarea contorului
Definiții care apar în datasheet:
-
BOTTOM: capătul inferior din intervalul de numărare
-
TOP: capătul superior al intervalului de numărare
-
MAX: limita superioară a numărării, 255 (0xff) pentru 8 biți, 65535 (0xffff) pentru 16 biți. TOP poate fi MAX în anumite moduri de funcționare ( de exemplu, Fast PWM).
Timer/Counter0 și Timer/Counter2 au patru moduri de funcționare (dintre care vom detalia 3, pentru cel de-al patrulea puteți citi datasheet-ul), ce se diferențiaza prin:
- valorile pâna la care se face incrementarea
- felul în care se numără (doar crescător, sau alternativ crescător/descrescător)
- când se face resetarea contorului
Definiții care apar în datasheet:
- BOTTOM: capătul inferior din intervalul de numărare
- TOP: capătul superior al intervalului de numărare
- MAX: limita superioară a numărării, 255 (0xff) pentru 8 biți, 65535 (0xffff) pentru 16 biți. TOP poate fi MAX în anumite moduri de funcționare ( de exemplu, Fast PWM).
2.3. Registre
Timer | Registre | Rol |
---|---|---|
Timer0 8 biți | TCNT0 | Registrul contor al timer-ului 0 (cel care numără) |
TCCR0A ,TCCR0B | Registre de control ale timer-ului 0 (aici veți activa diverși biți pentru configurarea timer-ului) | |
OCR0A ,OCR0B | Registre prag pentru timer-ul 0 (prag al numărătorii la care stuff happens, în funcție de configurare) | |
TIMSK0 ,TIFR0 | Registre cu biți de activare întreruperi timer 0 / flag-uri de activare (aici activați întreruperile) | |
Timer1 Timer3 (nume cu '3') 16 biți | TCNT1H/L =TCNT1H + TCNT1L | Registrul contor al timer-ului 1 (la fel ca la timer0, doar că pe 16 biți) |
TCCR1A , TCCR1B ,TCCR1C | Registre control ale timer-ului 1 (la fel ca la timer0) | |
OCR1AH/L , OCR1BH/L | Registre prag pe 16 biți ale timer-ului 1 (la fel ca la timer0) | |
TIMSK1 , TIFR1 | (la fel ca la timer0) | |
ICR1H/L | Registru folosit pentru a reține valoarea contorului la apariția unui eveniment extern pe pin-ul ICP ( nu vom folosi la laborator, poate folosiți voi la proiect - unlikely) | |
Timer2 8 biți | aceleași registre ca la Timer0 | Diferența față de Timer-ul 0 este posibilitatea folosirii unui oscilator extern separat pentru Timer-ul 2, pe pinii TOSC1 și TOSC2 |
ASSR , GTCCR | Registre ce țin de funcționarea asicronă a acestui timer față de restul sistemului |
Întreruperile sunt activate doar dacă bitul
I
din SREG
este setat (întreruperile sunt activate global)2.4. Lucrul cu Timer-ul
Setarea modului de funcționare
Pentru a configura un mod de funcționare, vom seta:
- biții
WGM
din timer-ul respectiv (care se găsesc în registreleTCCRnA
din datasheet, la secțiunile aferente timerelor, “Register Description”) - pragul de numărare
De exemplu, ca să setăm timer-ul 0 să numere până la 5, ne vom uita în datasheet la capitolul 15 (8-bit Timer/Counter0 with PWM) - secțiunea Register Description →TCCR0A
- găsim modul
CTC
ca având biții0 1 0
pe bițiiWGM2..0
- aflăm că modul
CTC
numără până laOCRA
Presupunând că plecăm de la un registru complet neinițializat (0 este valoarea default pentru majoritatea biților), avem următorul cod:
TCCR0A |= (1 << WGM01); OCR0A = 5;
Setarea prescaler-ului
Pentru setarea prescaler-ului se vor modifica biții tip
CS..
din registrul TCCRnB
al timer-ului respectiv.
De exemplu, pentru setarea valorii de prescaler 256 pentru timer-ul 2, vom urmări în datasheet capitolul 17 (8-bit Timer/Counter2 with PWM) - secțiunea Register Description → TCCR2B
- găsim tabelul pentru valorile
CS..
- aflăm că prescaler-ul 256 corespunde biților
1 1 0
pentru bițiiCS22 CS21 CS20
Presupunând că plecăm de la un registru complet neinițializat (0 este valoarea default pentru majoritatea biților), avem următorul cod:
TCCR2B |= (1 << CS22) | (1 << CS21);
Setarea întreruperilor
Pentru un timer deja configurat, pentru a seta întreruperile trebuie doar să activăm bitul corespunzător din
TIMSKx
De exemplu, pentru pragul A al timer-ului 1 vom scrie:
ISR(TIMER1_COMPA_vect) { // cod întrerupere ... } void init_timer1() { ... TIMSK1 |= (1 << OCIE1A); } int main() { sei(); // activăm primirea întreruperilor ... ... init_timer1(); // apelăm funcția de inițializare ... }
2.5. Accesarea registrelor pe 16 biți
TCNT1, OCR1A/B si ICR1 sunt registre de 16 biti care pot fi accesate de catre AVR CPU cu ajutorul bus- ului de date de 8 biti. Un registru de 16 biti poate sa sa fie accesat folosind doua operatii, fie de scriere, fie de citire pe 8 biti. Pentru a executa o scriere de 16 biti, byte-ul HIGH trebuie sa fie scris inaintea byte-ului LOW (bug de compilator, nu feature hardware).
2.6. Calculator
Referinte externe:
http://cs.curs.pub.ro/wiki/pm/lab/lab2