Laboratorul 2: Întreruperi hardware. Întreruperi externe

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.

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

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

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

Configurăm perifericul care va trimite întreruperile

De 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

O întrerupere externă poate fi configurată astfel încât să detecteze tranziția semnalului logic pe front crescător, descrescător sau oricare (vezi tabelul 13-1 din datasheet). În comparație, o întrerupere de tip pin change este generată la modificarea nivelului logic al semnalului, iar valoarea semnalului trebuie verificată explicit în codul întreruperii.

Scriem rutina de tratare a întreruperii

De exemplu, pentru întreruperile de tip INT0, 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
}

Dacă avem mai multe întreruperi pe același vector (de ex. mai multe butoane), nu se poate determina cu exactitate care dintre butoane a generat întreruperea de tip PCINT. În plus, este posibil să apară mai multe întreruperi în timp ce starea unui pin verificat să rămână neschimbată, caz în care va fi detectată o nouă apăsare în mod eronat.

Activăm întreruperea și testăm programul

În cazul de față, pentru activarea întreruperilor externe, se configurează registrul External Interrupt Mask Register (EIMSK) astfel:

  • biții 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();

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 active care nu au specificată o rutină de tratare vor cauza o întrerupere de reset!

2. Întreruperi externe. INT vs. PCINT

Întreruperile se pot împărți în două categorii:

  • întreruperile componentelor interne: timere, interfețe seriale (USART), convertor analog-digital
  • întreruperile componentelor externe: activate de pinii externi ai uC-ului

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.

În cazul întreruperilor de tip pin change interrupt, a nu se confunda vectorul de tratare a întreruperilor ex. PCINT2 cu întreruperea efectivă ex. PCINT20 (PD4). Maparea dintre vector și întreruperi se poate găsi în secțiunea 30 pag. 278 din datasheet

3. Întreruperi externe în Arduino

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:

  • LOW: nivel logic 0
  • CHANGE: tranziție de nivel logic
  • RISING: front crescător
  • FALLING: front descrescător

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

4. Exerciții

Task 1 (întreruperi / butoane) - 6p

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.

  • Configurați întreruperea externă (INT) pe front descrescător (falling edge) sau PCINT-ul corespunzător pinului folosit
  • Modificați starea LED-ului la apăsarea oricărui buton (PD2, PD4)

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:

  • NU este recomandat să folosiți delay în ISR
  • Se poate folosi funcția millis() pentru a verifica intervalul de timp de la ultima apăsare (100 ms ar trebui să fie suficient pentru un pushbutton/microswitch, dar ajustați după caz)
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:

  • butonul conectat la PD2 crește intervalul cu 100 ms
  • butonul conectat la PD4 scade intervalul cu 100 ms
volatile 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);
}

5. Resurse

pinout Arduino UNO

5. Linkuri utile

pm/lab/lab2-2022.txt · Last modified: 2024/03/17 09:54 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