Acest laborator are ca scop familiarizarea voastră cu lucrul cu întreruperile hardware și în particular cu întreruperile externe. Vom folosi întreruperi externe pentru a detecta imediat (în timp real) apăsarea unui buton, independent de programul principal.
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 |
Î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();
În general, pașii parcurși pentru a activa o întrerupere externă INT sau PCINT sunt următorii:
În exemplu, pentru INT0
și INT1
(pinii PD2
și PD3
), configurarea se face din registrul EICRA
(External Interrupt Control Register A, secțiunea 12.2.1, pagina 54 din datasheet, în timp ce pentru întreruperi de tip pin change se folosește registrul PCICR
.
// întreruperi externe EICRA |= (1 << ISC00); // set INT0 to trigger on ANY logic change // întreruperi de tip pin change (activare vector de întreruperi) PCICR |= (1 << PCIE2); // enable the pin change interrupt, set PCIE2 to enable PCMSK2 scan // alte întreruperi
De exemplu, pentru întreruperile de tip INT0
și respectiv PCINT2
, codul va arăta în felul următor:
ISR(INT0_vect) { // cod întrerupere externă PD2 /INT0 // verificare tranziție pe front crescător, descrescător sau oricare // (după cum este configurat INT0) } ISR(PCINT2_vect){ // cod întrerupere de tip pin change if ((PIND & (1 << PD4)) == 0){ // întreruperea a fost generată de pinul PD4 / PCINT20 // verificăm nivelul logic al pinului } // întreruperea a fost generată de alt pin }
În cazul de față, pentru activarea întreruperilor externe, se configurează registrul External Interrupt Mask Register (EIMSK
) astfel:
INT1:0
controlează dacă întreruperile externe (INT0-INT1) sunt activate
Pentru celelalte întreruperi, se configurează registrul corespunzător (e.g. PCMSK2
pentru activarea întreruperilor de tip pin change pe fiecare pin de pe portul D)
// întrerupere externă EIMSK |= (1 << INT0); // Turns on INT0 // întrerupere de tip pin change PCMSK2 |= (1 << PCINT20); // Turns on PCINT20 (PD4) // activare întreruperi globale sei();
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:
Întreruperile se pot împărți în două categorii:
Pe lângă întreruperile componentelor interne uC-ului, există și câteva linii pentru întreruperi de la periferice externe: INT0-INT1 și PCINT0-PCINT2. Diferența dintre aceste două tipuri de întreruperi externe este dată de capabilitățile suportate și de granularitatea lor.
Semnalele pentru întreruperile INTn 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 (Pin Change INTerrupt) se declanșează la ambele tranziții (mai exact, atunci când se face toggle la valoare) ș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.
Deși noi vom lucra în principal cu registre, în Arduino, întreruperile se mai pot configura folosind funcția attachInterrupt(). Funcția primește ca parametru numărul întreruperii externe (pe Arduino UNO avem INT0, INT1), funcția care va fi apelată (echivalent ISR), și modul prin care se face detecția tranziției:
Dezactivarea întreruperilor externe se poate face cu funcția detachInterrupt() Activarea sau dezactivarea temporară a tuturor întreruperilor se poate face cu funcțiile noInterrupts() și interrupts()
int counter = 0; void myCallback() { counter += 1; } void setup() { pinMode(2, INPUT_PULLUP); attachInterrupt(0, myCallback, FALLING); } void loop() { Serial.print("numar apasari: "); Serial.println(counter); delay(1000); }
Folosiți întreruperi externe (INT și/sau PCINT) pentru a detecta apăsarea unui buton conectat la PD2
(pin 2) și a unuia conectat la PD4
(pin 4). Modificați starea unui LED conectat la PD7
(pin 7) în ISR.
Tinkercad - Testați programul folosind următorul montaj:
Arduino - Încărcați codul pe placă și observați comportamentul. La apăsarea butonului, LED-ul își schimbă starea o singură dată? Adăugați o metodă de debouncing pentru a detecta o singură apăsare pe buton:
ISR(INT0_vect) { // cod întrerupere externă PORTD ^= (1 << PD7); } ISR(PCINT2_vect) { // cod întrerupere de tip pin change // TODO } void setup_interrupts() { // buton 1: PD2 / INT0 // buton 2: PD4 / PCINT20 cli(); // input DDRD &= ~(1 << PD2) & ~(1 << PD4); // input pullup PORTD |= (1 << PD2) | (1 << PD4); // configurare intreruperi // intreruperi externe // TODO // întreruperi de tip pin change (activare vector de întreruperi) // TODO // activare intreruperi // intrerupere externa // TODO // întrerupere de tip pin change // TODO sei(); } void setup() { setup_interrupts(); DDRD |= (1 << PD7); PORTD &= ~(1 << PD7); } void loop() { }
Pe baza aceluiași montaj, folosiți întreruperi externe (INT și/sau PCINT) pentru a detecta apăsarea unui buton conectat la PD2
(pin 2) și a unuia conectat la PD4
(pin 4). Modificați intervalul de semnalizare (blink) al unui LED conectat la PD7
(pin 7) în ISR astfel:
PD2
crește intervalul cu 100 msPD4
scade intervalul cu 100 msvolatile int dt = 500; ISR(INT0_vect) { // cod întrerupere externă // TODO } ISR(PCINT2_vect) { // cod întrerupere de tip pin change // TODO } // setup_interrupts() la fel ca la Task 1 void setup() { setup_interrupts(); DDRD |= (1 << PD7); PORTD &= ~(1 << PD7); } void loop() { PORTD |= (1 << PD7); delay(dt); PORTD &= ~(1 << PD7); delay(dt); }