Capitole utile din Datasheet ATmega324
Acest laborator are ca scop familiarizarea voastră cu lucrul cu întreruperile hardware și cu timer-ele prezente în microcontroller-ul Atmega324. Vom folosi timer-ele doar pentru a număra, nu și pentru a genera semnal PWM. Această funcționalitate va fi studiată și utilizată în laboratorul următor.
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.
<tabcaption table1 center | Tabela de vectori de întrerupere pentru ATmega324>
Vector no. | Program address | Source | Interrupt definition |
---|---|---|---|
1 | 0000 | RESET | External Pin, Power-on Reset, Brown-out Reset, Watchdog Reset and JTAG AWR Reset |
2 | 0002 | INT0 | External Interrupt Request 0 |
3 | 0004 | INT1 | External Interrupt Request 1 |
4 | 0006 | INT2 | External Interrupt Request 2 |
5 | 0008 | PCINT0 | Pin Change Interrupt Request 0 |
6 | 000A | PCINT1 | Pin Change Interrupt Request 1 |
7 | 000C | PCINT2 | Pin Change Interrupt Request 2 |
8 | 000E | PCINT3 | Pin Change Interrupt Request 3 |
9 | 0010 | WDT | Watchdog Time-out Interrupt |
10 | 0012 | TIMER2_COMPA | Timer/Counter2 Compare Match A |
11 | 0014 | TIMER2_COMPB | Timer/Counter2 Compare Match B |
12 | 0016 | TIMER2_OVF | Timer/Counter 2 Overflow |
13 | 0018 | TIMER1_CAPT | Timer/Counter1 Capture Event |
14 | 001A | TIMER1_COMPA | Timer/Counter1 Compare Match A |
15 | 001C | TIMER1_COMPB | Timer/Counter1 Compare Match B |
16 | 001E | TIMER1_OVF | Timer/Counter1 Overflow |
17 | 0020 | TIMER0_COMPA | Timer/Counter0 Compare Match A |
18 | 0022 | TIMER0_COMPB | Timer/Counter0 Compare Match B |
19 | 0024 | TIMER0_OVF | Timer/Counter0 Overflow |
20 | 0026 | SPI_STC | SPI Serial Transfer Complete |
21 | 0028 | USART0_RX | USART0 Rx Complete |
22 | 002A | USART0_UDRE | USART0 Data Register Empty |
23 | 002C | USART0_TX | USART0 Tx Complete |
24 | 002E | ANALOG_COMP | Analog Comparator |
25 | 0030 | ADC | ADC Conversion Complete |
26 | 0032 | EE_READY | EEPROM Ready |
27 | 0034 | TWI | Two-Wire Serial Interface |
28 | 0036 | SPM_READY | Store Program Memory Ready |
29 | 0038 | USART1_RX | USART1 Rx Complete |
30 | 003A | USART1_UDRE | USART1 Data Register Empty |
31 | 003C | USART1_TX | USART1 Tx Complete |
32 | 003E | TIMER3_CAPT | Timer/Counter3 Capture Event |
33 | 0040 | TIMER3_COMPA | Timer/Counter3 Compare Match A |
34 | 0042 | TIMER3_COMPB | Timer/Counter3 Compare Match B |
35 | 0044 | TIMER3_OVF | Timer/Counter3 Overflow |
</tabcaption>
După cum se observă din <imgref irq_table>, pe lângă întreruperile componentelor interne uC-ului (timer-e, interfețe seriale, convertor analog-digital), există și câteva linii pentru întreruperi de la periferice externe: INT0-INT2 și PCINT0-PCINT3. Diferența dintre aceste două tipuri de întreruperi externe este dată de capabilitățile suportate și de granularitatea lor. Semnalele pentru întreruperile INTn vin pe portul D pinii 2, 3 respectiv portul B pinul 2 și pot genera o întrerupere la tranziție (crescătoare, descrescătoare sau ambele) sau pe nivel 0, în funcție de configurarea întreruperii. Întreruperile PCINTn se declanșează la ambele tranziții (de unde și numele Pin Change INTerrupt) și câte 8 pini sunt multiplexați pe o singură întrerupere. Semnalele de întrerupere PCINT se pot activa individual, însă pentru a afla exact ce semnal a declanșat o anumită întrerupere trebuie verificat registrul PINn corespunzător. Dacă mai multe semnale se declanșează în același timp, ele nu vor putea fi deosebite.
<imgcaption irq_pins center|Pini întreruperi externe pe capsula ATmega324></imgcaption>
În general, pașii parcurși pentru a activa o întrerupere sunt următorii:
Mecanismul de întreruperi trebuie activat explicit (bitul I - Global Interrupt Enable din registrul SREG
). Pentru a seta și reseta acest bit, există și două funcții ajutătoare:
// activează întreruperile sei(); // dezactivează întreruperile cli();
// întreruperi externe EICRA |= (1 << ISC00); // set INT0 to trigger on ANY logic change // întreruperi de tip pin change (activare vector de întreruperi) PCICR |= (1 << PCIE1); // enable the pin change interrupt, set PCIE1 to enable PCMSK1 scan // alte întreruperi
În exemplu, pentru INT0
, INT1
și INT2
, configurarea se face din registrul EICRA
(External Interrupt Control Register A, secțiunea 13.2.1, pagina 67 din datasheet), în timp ce pentru întreruperi de tip pin change se folosește registrul PCICR
.
Rutinele de tratare a întreruperii trebuie menționate în memorie la anumite adrese fixe (în vectorul de întreruperi). Pentru a face acest lucru folosim macro-ul de ISR(), care primește ca parametru tipul de întrerupere ce este tratată.
De exemplu, pentru întreruperile de tip INT0
și respectiv PCINT1
, codul va arăta în felul următor:
ISR(INT0_vect) { // cod întrerupere externă } ISR(PCINT1_vect){ // cod întrerupere de tip pin change if ((PINB & (1 << PB1)) == 0){ // întrerupere generată de pinul PB1 } }
În cazul de față, pentru activarea întreruperilor externe, se configurează registrul External Interrupt Mask Register (EIMSK
) astfel:
INT2:0
controlează dacă întreruperile externe (INT0-INT2) sunt activate
Pentru celelalte întreruperi, se configurează registrul corespunzător (e.g. PCMSK1
pentru activarea întreruperilor de tip pin change pe fiecare pin)
// întrerupere externă EIMSK |= (1 << INT0); // Turns on INT0 // întrerupere de tip pin change PCMSK1 |= (1 << PCINT9); // Turns on PCINT9 (PB1) // alte întreruperi // ... // activare întreruperi la nivel global sei();
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)
<tabcaption table1 center | Exemple de întreruperi>
Întrerupere | Vector | Descriere |
---|---|---|
TIMER1_COMPA | TIMER1_COMPA_vect | Compare Match pragul A pe timer-ul 1 |
TIMER1_COMPB | TIMER1_COMPB_vect | Compare Match pragul B pe timer-ul 1 |
TIMER1_OVF | TIMER1_OVF_vect | Overflow pe contorul timer-ului 1 |
PCINT1 | PCINT1_vect | Eveniment de “pin change” pe portul B |
… | … | … |
</tabcaption>
Definirea rutinei de tratare se face cu macro-ul ISR:
#include <avr/interrupt.h> ISR(INT0_vect) { // ... }
Reguli de programare în context întrerupere:
volatile
pentru ca accesele la acestea să nu fie optimizate de către compilatorAlte wrapper-e (în jurul unor instrucțiuni ale core-ului AVR) oferite de interfață sunt:
Timer-ul/Counter-ul 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ă (UCP). 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 <imgref timer>:
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ă.<imgcaption timer center|Componentele și funcționarea unui timer pentru ATmega324></imgcaption>
Timer-ele sunt prevăzute cu mai multe canale astfel încât se pot desfășura diferite număratori în paralel. ATmega324 este prevăzut cu 3 unități de timer: două de pe 8 biți și una de 16 biți.
Timer-ele pot funcționa și în moduri PWM, astfel încat să genereze pe un pin de ieșire un semnal de comandă cu tensiune (medie) variabilă. Mai multe detalii veți afla în laboratorul viitor.
Timer-ele oferă mai multe moduri de funcționare (cu mici diferențe între Timer/Counter0, Timer/Counter1 și Timer/Counter2), ce se diferențiaza prin:
În <tabref tabelmoduri> sunt prezentate cele două moduri pe care le vom folosi în acest laborator.
<tabcaption tabelmoduri center | Descrierea modurilor de funcționare>
</tabcaption>
Definiții care apar în datasheet:
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 se poate declansa intreruperea, î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 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 sau ca prag pentru unul din modurile CTC |
|
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 |
I
din SREG
este setat (întreruperile sunt activate global)
Pentru a configura un mod de funcționare, vom seta:
WGM
din timer-ul respectiv (care se găsesc în registrele TCCRnA
din datasheet, la secțiunile aferente timerelor, “Register Description”)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
CTC
ca având biții 0 1 0
pe biții WGM2..0
CTC
numără până la OCRA
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;
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
CS..
1 1 0
pentru biții CS22 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);
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 // ... }
TCNT1, OCR1A/B si ICR1 sunt registre de 16 biți care pot fi accesate de către AVR CPU cu ajutorul bus-ului de date de 8 biți. Un registru de 16 biți poate să fie accesat numai folosind două operatii, fie de scriere, fie de citire pe 8 biți. Pentru a executa o scriere de 16 biți, byte-ul HIGH trebuie să fie scris înaintea byte-ului LOW. Intern, scrierea byte-ului HIGH nu actualizează imediat registrul, el fiind salvat într-o locație temporară. Scrierea byte-ului LOW determină actualizarea întregului registru într-un singur ciclu, folosind valoarea HIGH salvată în zona temporară.
În general, pentru configurarea unui Timer, este necesară alegerea unui prescaler și a unei limite de numărare, în funcție de perioada dorită și de frecvența de lucru a procesorului (e.g. 12 MHz pentru placa de laborator) și de modul de funcționare ales. Un exemplu de calcul este prezentat mai jos:
// calculate the frequency of the interrupt (f) from the timer configuration and clock speed (f_clk) f_int = f_clk / (prescaler * (tc + 1)) // calculate the target timer count (tc) for the required interrupt frequency tc = f_clk / (prescaler * f_int) - 1
Alegerea prescaler-ului se face astfel încât să se poată obține frecvența (exactă) dorită a întreruperilor. De exemplu, pentru o frecvență de 1Hz (f_clk = 12MHz) se obțin următoarele variante pentru determinarea prescaler-ului și a limitei de numărare a timer-ului:
Prescaler | Limită counter | Timer 1 (16 bit) | Timer 0,2 (8 bit) | Obs. |
---|---|---|---|---|
1 | 11999999 | Invalid (16 bit) | Invalid (8 bit) | overflow |
8 | 1499999 | Invalid (16 bit) | Invalid (8 bit) | overflow |
64 | 187499 | Invalid (16 bit) | Invalid (8 bit) | overflow |
256 | 46874 | Valid | Invalid (8 bit) | Se poate folosi doar Timer 1 |
1024 | 11717.75 | Invalid | Invalid | Nu este exact/divizibil |
*Obs: Timer2 poate fi configurat și cu prescaler de 32 sau 128 (secțiunea 17.11.2, pag. 160 din datasheet)
Există însă calculatoare care pot fi utile pentru determinarea rapidă a valorilor pentru registrele de configurare ale unui Timer precum:
Scopul exercițiilor este să vă familiarizeze cu funcționarea întreruperilor și timer-elor. La fiecare exercițiu veți putea vizualiza funcționarea acestora pentru o mai bună înțelegere a lor.
TCNT1
și OCR1A
.PCINTn
necesară pentru butoanele legate la PB2
și PD6
. În handler-ul de întrerupere, aprindeți LED-ul legat la PB3
la apăsarea lui PB2
și stingeți-l la apăsarea lui PD6
.PCICR
și PCMSKn
; găsiți explicații în datasheet, la capitolul External Interrupts → Register Description.PB3
ca ieșire pentru a putea controla LED-ul.PB2
, cronometrul va fi activat cu o durată de 10 secundePB3
PD6
, cronometrul va fi oprit și va fi reactivat doar la apăsarea lui PB2
OCR1A
pentru a genera întreruperi cu perioada de o secundă.PB2
să funcționeze după următoarea regulă: dintr-o perioadă de o secundă, LED-ul va sta aprins 200 ms. Pentru a implementa acest comportament, vom avea nevoie de al doilea registru de comparație al Timer1, OCR1B
. Calculați valorile care ar trebui scrise în registrele OCR1A
și OCR1B
și implementați/modificați rutinele de tratare a întreruperilor astfel încat să obținem acest comportament.PD4
).PD4
, cu o durată de 2 secunde PD6
, cronometrul și alarma vor fi dezactivate