User Tools

Site Tools


Sidebar

Laboratorul 2: Întreruperi, Timere

Capitole utile din Datasheet ATmega324

  • 1. Pin Configurations
    • secțiunea 1.1 - pag. 2
  • 7. AVR CPU Core
    • secțiunea 7.3 - pag. 11
    • secțiunea 7.7 - pag. 16
  • 12. Interrupts
    • tabelul 12-1 - pag. 61
  • 13. External Interrupts
    • secțiunea 13.1 - pag. 67
    • secțiunile 13.2.4-13.2.9 - pag. 69
  • 16. 16-bit Timer/Counter1 and Timer/Counter3 with PWM
    • secțiunile 16.1-16.3 - pag. 111
    • secțiunea 16.5 - pag. 117
    • secțiunea 16.7 - pag. 120
    • secțiunile 16.9.1-16.9.2 - pag. 123
    • secțiunea 16.11 - pag. 132

Capitolele sunt din Datasheet ATmega324, document care îl aveți și pe desktop-ul calculatorului din laborator. Nu garantăm aceeași ordine a capitolelor în cazul utilizării altui document!

Acest laborator are ca scop familiarizarea voastră cu lucrul cu întreruperile hardware și cu timere-le prezente în microcontroller-ul Atmega324. Vom folosi timere-le doar pentru a număra, nu și pentru a genera semnal PWM. Această funcționalitate va fi studiată și utilizată în laboratorul următor.

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.

Tabela de vectori de întrerupere pentru ATmega324Fig. 1: Tabela de vectori de întrerupere pentru ATmega324

După cum se observă din figure 1, 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.

Pini întreruperi externe pe capsula ATmega324Fig. 2: Pini întreruperi externe pe capsula ATmega324

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 vor fi luate în considerare. Valoarea acestui flag trebuie să fie 1 dacă vrem ca microcontroller-ul sa le trateze. 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 recepția unui frame pe interfața seriala, și dorim să activăm întreruperile unui timer).

1.1. Tratarea unei întreruperi

Tratarea unei întreruperi pentru ATmega324Fig. 3: Tratarea unei întreruperi pentru ATmega324

Să presupunem că programul nostru primește întreruperea externă INT0 în timp ce execută instrucțiunea ldi R16,0xFF. După rularea 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 în 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

Registrul SREGFig. 4: Registrul SREG

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 bitului IVSEL

Registrul MCUCRFig. 5: Registrul MCUCR

External Interrupt Mask Register (EIMSK)

  • biții INT2:0 controlează dacă întreruperile externe sunt activate
  • pt oricare bit INT2:0, dacă este 1 și bitul I din SREG 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)

Î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
Tab. 1: Exemple de întreruperi

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

Întreruperile pot fi declarate cu anumite flag-uri - Click AICI pentru mai multe detalii

Întreruperile pot fi declarate cu anumite flag-uri - Click AICI pentru mai multe detalii

Întreruperile pot fi declarate cu anumite flag-uri - Click AICI pentru mai multe detalii

#include <avr/interrupt.h>
 
ISR(vector, flag)
{
    ...
}

Macro-ul ISR definește rutina de tratare pentru perifericul respectiv, salvează SREG și apelează instrucțiunea reti la ieșirea din rutină. Rutinele pot fi definite cu următoarele flag-uri:

  • ISR_BLOCK: comportamentul default, în care întreruperile globale sunt dezactivate atunci când se intră într-o întrerupere
  • ISR_NOBLOCK: facilitează existența întreruperilor imbricate (poate fi util dacă se dorește ca o altă întrerupere să fie întârziată)
  • ISR_NAKED: omite prologul și epilogul rutinei de tratare, adică salvarea SREG și apelul reti()
  • ISR_ALIASOF: rutina este identică cu cea a altui vector de întrerupere
#include <avr/interrupt.h>
 
ISR(INT0_vect)
{
    ...
}
 
ISR(INT1_vect, ISR_ALIASOF(INT0_vect))

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:

  1. Registrul numărător (Timer Counter, TCNT) - măsoară efectiv intervalele de timp și este incrementat automat cu o frecvență dată.
  2. Prescaler-ul - are rolul de a diviza în funcție de necesitățile aplicației frecvența de ceas.
  3. 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ă.

Componentele și funcționarea unui timer pentru ATmega324Fig. 6: Componentele și funcționarea unui timer pentru ATmega324

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 3 unități de timer: două de numărare de opt biți și una 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

Timere-le oferă mai multe moduri de funcționare (cu mici diferențe între Timer/Counter0, Timer/Counter1 și Timer/Counter2), 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

În table 2 sunt prezentate cele două moduri pe care le vom folosi în acest laborator.

Mod Descriere Imagine contor Proprietăți
Normal * pornește de la 0
* numără până 0xFFFF
Modul Normal * frecvența este fixată la câteva valori predefinite, date de frecvența ceasului și de prescaler
CTC
Clear Timer on Compare
* pornește de la 0
* numără până când se atinge un prag:
OCRnA - pentru timer-ele 0 și 2
OCR1A sau ICR1 pentru timer-ul 1
Modul CTC * frecvența este variabilă, determinată de valoarea pragului, frecvența ceasului și de prescaler
Tab. 2: Descrierea modurilor de funcționare

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 (ex: Normal).

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 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

Î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 registrele TCCRnA 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 DescriptionTCCR0A

  1. găsim modul CTC ca având biții 0 1 0 pe biții WGM2..0
  2. aflăm că modul 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;

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 DescriptionTCCR2B

  1. găsim tabelul pentru valorile CS..
  2. aflăm că prescaler-ul 256 corespunde biților 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);

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 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ă.

La folosirea compilatorului de C secvența de scriere/citire a unui registru pe 16 biți este implementată automat de către acesta. Pentru a folosi această facilitate utilizați macro-ul aferent registrului fără terminația L sau H (pentru byte-ul LOW, respectiv HIGH).

2.6. Calculator

3. Exerciții

Scopul exercițiilor este să vă familiarizeze cu funcționarea timere-lor. La fiecare exercițiu veți putea vizualiza funcționarea acestora pentru o mai bună înțelegere a lor.

  1. (1p). Rulați exemplul din scheletul de laborator. Ce mod de funcționare folosește timer-ul? Dar prescaler?
    • Pe ecranul LCD-ului se pot vizualiza valorile TCNT1 și OCR1A.
    • Limita din dreapta ecranului reprezintă 0xFFFF.
  2. (2p). Activați întreruperea externă 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.
    • Registrele I/O de care aveți nevoie sunt PCICR și PCMSKn; găsiți explicații în datasheet, la capitolul External Interrupts→Register Description.
    • Va trebui să adăugați definiția handler-ului de întrerupere.
    • Nu uitați să configurați pinul PB3 ca ieșire pentru a putea controla LED-ul.
  3. (1p). Modificați valoarea din registrul de comparare pentru timer-ul 1, OCR1A, în funcție de butoanele legate la PB2 și PD6 (butonul PB2 scade valoarea, butonul PD6 crește valoarea).
    • Faceți modificările în incrementuri de 1000.
  4. (3p). Dorim ca led-ul conectat la PD7 să funcționeze după următoarea regulă: dintr-o perioadă de 4 secunde, 1 secundă led-ul va sta aprins, iar 3 secunde stins. Pentru a implementa acest comportament, vom avea nevoie de al doilea registru de comparație al timer-ului 1, 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.
    • Avem nevoie de al doilea prag, OCR1B, deoarece in modul de funcționare configurat pentru timer, valoarea TOP (pragul superior până la care poate număra) coincide cu pragul OCR1A. Astfel, nu putem genera mai mult de un eveniment.
    • Timer-ul poate genera o întrerupere de output compare pentru pragul OCR1B așa cum generează și pentru pragul OCR1A.
    • Găsiți această întrerupere, activați-o și implementați rutina de tratare a întreruperii pentru a obține comportamentul specificat în enunț.
  5. (1p). Modificați valoarea OCR1A cu ajutorul butoanelor PB2 și PD6 în timpul funcționării numărătorului. Explicați comportamentul actual și modificați codul astfel încat să respectăm regula de la exercițiul anterior (led-ul să stea aprins un sfert din perioadă) indiferent de valoarea lui OCR1A (TOP).
  6. (3p). Schimbați timer-ul în modul de funcționare CTC cu TOP la ICR1, și activați întreruperea de overflow. Folosiți handler-ele de întreruperi pentru a stinge LED-ul PD5 atunci când contorul face overflow, respectiv pentru a-l aprinde când contorul ajunge la pragul OCR1A. Setați valoarea lui OCR1A la 20000 si valoarea lui ICR1 la 50000.
    • Va trebui să adăugați definiția handler-ului de întrerupere pentru overflow.
    • Nu uitați să configurați pinul PD5 ca ieșire pentru a putea controla LED-ul.
    • Puteți dezactiva LED-urile PB3 și PD7 pentru a observa mai bine comportamentul.
    • Ce observați că se întampla cu LED-ul? De ce?
    • Ce valoarea trebuie sa îi atribuim lui ICR1 pentru a obține comportamentul dorit în enunț?
  7. (1p + bragging rights). În directorul scheletului se generează fișierul lab2.lss, reprezentând codul ASM asociat laboratorului. Numărați ciclii de procesor necesari rulării codului din handler-ul întreruperii de overflow a timer-ului 1. Modificați codul sursă C și folosiți ISR_NAKED pentru a reduce durata handler-ului.

4. Resurse

5. Linkuri utile

lab/lab2.txt · Last modified: 2019/04/06 16:56 by andrei [dot] voinescu [at] upb [dot] ro