Laboratorul 2: Întreruperi, Timere

Acest laborator are ca scop familiarizarea voastră cu lucrul cu întreruperile hardware și cu timer-ele prezente în microcontroller-ul Atmega328p. 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 laboratoarele următoare.

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.

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 PCINT0 Pin Change Interrupt Request 0
5 0008 PCINT1 Pin Change Interrupt Request 1
6 000A PCINT2 Pin Change Interrupt Request 2
7 000C WDT Watchdog Time-out Interrupt
8 000E TIMER2_COMPA Timer/Counter2 Compare Match A
9 0010 TIMER2_COMPB Timer/Counter2 Compare Match B
10 0012 TIMER2_OVF Timer/Counter 2 Overflow
11 0014 TIMER1_CAPT Timer/Counter1 Capture Event
12 0016 TIMER1_COMPA Timer/Counter1 Compare Match A
13 0018 TIMER1_COMPB Timer/Counter1 Compare Match B
14 001A TIMER1_OVF Timer/Counter1 Overflow
15 001C TIMER0_COMPA Timer/Counter0 Compare Match A
16 001E TIMER0_COMPB Timer/Counter0 Compare Match B
17 0020 TIMER0_OVF Timer/Counter0 Overflow
18 0022 SPI_STC SPI Serial Transfer Complete
19 0024 USART0_RX USART0 Rx Complete
20 0026 USART0_UDRE USART0 Data Register Empty
21 0028 USART0_TX USART0 Tx Complete
22 002A ADC ADC Conversion Complete
23 002C EE_READY EEPROM Ready
24 002E ANALOG_COMP Analog Comparator
25 0030 TWI Two-Wire Serial Interface
26 0032 SPM_READY Store Program Memory Ready

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. Utilizarea întreruperilor

În general, pașii parcurși pentru a activa o întrerupere sunt următorii:

Pornim mecanismul de tratare a întreruperilor

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();

Configurăm perifericul care va trimite întreruperile

// exemplu de configurare pentru Timer 1 în mod CTC, care va genera întreruperi cu frecvența de 2Hz
OCR1A = 31249;            // compare match register 16 MHz/256/2 Hz - 1
TCCR1B |= (1 << WGM12);   // CTC mode
TCCR1B |= (1 << CS12);    // 256 prescaler 

Scriem rutina de tratare a întreruperii

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 generate de Timer 1 în mod CTC, codul va arăta în felul următor:

// implementare rutină de tratare a întreruperii TIMER1_COMPA
ISR(TIMER1_COMPA_vect) {
  // cod întrerupere de la Timer1
}

Activăm întreruperea și testăm programul

// activare întrerupere TIMER1_COMPA
TIMSK1 |= (1 << OCIE1A);
// activare întreruperi la nivel global
sei();

1.2. Lucrul cu întreruperi

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

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.

Componentele și funcționarea unui timer pentru ATmega328p pot fi descrise în linii mari de cele trei unități din figura de mai jos:

Componentele și funcționarea unui timer pentru ATmega324

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

Timer-ele sunt prevăzute cu mai multe canale astfel încât se pot desfășura diferite număratori în paralel. ATmega328p 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 laboratoarele următoare.

2.2. Moduri de funcționare

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:

  • valorile până 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 următorul tabel 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ă la 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 0/2, OCR1A sau ICR1 pentru Timer 1)
Modul CTC frecvența este variabilă, determinată de valoarea pragului, frecvența ceasului și de prescaler

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 (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 (activați întreruperile)
Timer1

16 biți
TCNT1 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)
OCR1A , OCR1B Registre prag pe 16 biți ale timer-ului 1 (la fel ca la timer0)
TIMSK1, TIFR1 (la fel ca la timer0)
ICR1 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 14 (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);

Configurarea întreruperilor

Pentru un timer deja configurat, pentru a activa î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 configure_timer1() {
  // exemplu de configurare pentru Timer 1 în mod CTC
  // care va genera întreruperi cu frecvența de 2Hz
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 31249;            // compare match register 16MHz/256/2Hz-1
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
}
 
void init_timer1() {
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
}
 
void setup() {
  // dezactivăm întreruperile globale
  cli();  
  configure_timer1();
  init_timer1();
  // activăm întreruperile globale
  sei();
}
 
void loop() {
 
}

2.5. Calculator

Î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. 16 MHz pentru Arduino UNO) și de modul de funcționare ales. Un exemplu de calcul este prezentat mai jos:

  • calcul frecvență întreruperi în funcție de frecvența ceasului și a pragului de numărare:

f_int = f_clk / (prescaler * (tc + 1))

  • calcul prag de numărare al timer-ului pentru a obține frecvența dorită:

tc = f_clk / (prescaler * f_int) - 1

Observăm că trebuie aleasă o valoare convenabilă pentru prescaler (din cele disponibile, ex. 8, 64, 256, 1024) și un prag de numărare (0-255 pentru timer-e pe 8 biți, 0-65535 pentru timer-e pe 16 biți) astfel încât să se obțină frecvența exactă.

Există calculatoare care pot fi utile pentru determinarea rapidă a valorilor pentru registrele de configurare ale unui Timer precum:

3. Afișaje cu 7 segmente

Afișajele LED cu 7 segmente sunt utilizate în multe aplicații ca indicatoare numerice pe panoul frontal. Cele mai comune aplicații sunt calculatoarele, ceasurile digitale, cuptoarele cu microunde, echipamentele electronice de laborator, cum ar fi generatoarele de funcții și contoarele de frecvență. De asemenea, este obișnuit să existe afișaje cu 7 segmente care au un punct ce poate fi activat după cifră.

Un afișaj LED cu 7 segmente este un aranjament de bare LED (a, b, c, d, e, f, g, dp) care pot fi alimentate individual pentru a afișa cifre (și unele caractere). Configurația poate fi fie anod comun, fie catod comun:

  • Anod comun: anozii (pozitiv) tuturor LED-urilor sunt conectați electric la un pin și fiecare catod (negativ) al LED-urilor are propriul pin
  • Catod comun: catozii (negativ) tuturor LED-urilor sunt conectați electric la un pin și fiecare anod (pozitiv) al LED-urilor are propriul pin

Există și variante cu mai mult de o cifră integrată în componenta de afișare. Afișajele cu mai multe cifre sunt motivul pentru care catozii (sau anozii) sunt conectați, astfel încât cifrele să poată fi multiplexate și să nu folosească o grămadă de pini pentru a controla afișajul.

Următorul tabel de căutare poate fi util pentru programarea unui driver de afișare cu 7 segmente:

Simbol hex a b c d e f g
0 0x7e 1 1 1 1 1 1 0
1 0x30 0 1 1 0 0 0 0
2 0x6d 1 1 0 1 1 0 1
3 0x79 1 1 1 1 0 0 1
4 0x33 0 1 1 0 0 1 1
5 0x5b 1 0 1 1 0 1 1
6 0x5f 1 0 1 1 1 1 1
7 0x70 1 1 1 0 0 0 0
8 0x7f 1 1 1 1 1 1 1
9 0x7b 1 1 1 1 0 1 1

4. Exerciții

Alegerea parametrilor pentru timer pentru setările registrelor:

  OCR1A = 31249; //counter
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt

Dacă frecvența de bază a procesorului este de 16 MHz și prescalerul 256 atunci timerul are frecvența de 62500 Hz deci se va incrementa cu 1 la fiecare 1/62500 Hz = 16 μs. O întrerupere va fi generată când valoarea din timer va fi egală cu cea din registrul OCR1A (31249). 16μs×31250 = 0.5s

Task 1 (1p) Rulați exemplul din scheletul de laborator. Ce mod de funcționare folosește timer-ul? Dar prescaler-ul?

Task 2 (2p) Dorim să vizualizăm efectele prescaler-ului asupra frecvenței cu care se incrementează numărul din display-ul cu 7 segmente. Vom folosi butonul legat la PD3 pentru a cicla prin valorile posibile ale prescaler-ului.

Task 3 (3p) Realizați un cronometru de tip “countdown timer”. La finalul numărătorii, LED-ul va trebui să rămână aprins iar la apăsarea butonului se va reseta cronometrul la valoarea maximă. Valoarea maximă a cronometrului va fi de 10 secunde și se va afișa pe display valoarea corespunzătoare (9..0).

Bonus (1p): Aprindeți LED-ul pentru 200 ms la fiecare secundă cât timp cronometrul numără descrescător.

Task 4 (4p) Conectați un buzzer la pinul PD2. Folosind notele muzicale definite în repo-ul arduino-songs sau biblioteca Melody, implementați un cântec la alegere. Atunci când butonul va fi apăsat, notele vor fi ridicate cu o octavă (vor reveni la normal atunci când butonul nu mai este apăsat).

Bonus (1p) Pe măsură ce cronometrul se apropie de zero, viteza de redare va fi încetinită liniar astfel încât atunci când cronometrul ajunge la 0 viteza va fi 0.1 din valoarea normală.

5. Resurse

pinout Arduino UNO

Rezolvare
Arhiva cu soluțiile o puteți descărca de aici: lab2-solved.zip

5. Linkuri utile

pm/lab/lab2-2022.txt · Last modified: 2022/05/01 14:26 by florin.stancu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0