This shows you the differences between two versions of the page.
pm:lab:lab2-2022 [2023/03/18 21:20] alexandru.predescu [Configurare] |
pm:lab:lab2-2022 [2024/03/17 09:54] (current) florin.stancu |
||
---|---|---|---|
Line 5: | Line 5: | ||
~~SHOWSOLUTION~~ | ~~SHOWSOLUTION~~ | ||
- | ====== Laboratorul 2: Întreruperi. Întreruperi externe. ====== | + | ====== Laboratorul 2: Întreruperi hardware. Întreruperi externe ====== |
- | 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. | + | 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 ===== | ===== 1. Întreruperi ===== | ||
Line 54: | Line 54: | ||
- | ==== 1.1. Utilizarea întreruperilor ==== | + | ==== Utilizarea întreruperilor ==== |
În general, pașii parcurși pentru a activa o întrerupere sunt următorii: | În general, pașii parcurși pentru a activa o întrerupere sunt următorii: | ||
Line 69: | Line 69: | ||
</file> | </file> | ||
+ | În general, pașii parcurși pentru a activa o întrerupere externă INT sau PCINT sunt următorii: | ||
- | ==== 1.2. Lucrul cu întreruperi ==== | + | === 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 {{:pm:atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|datasheet}}, în timp ce pentru întreruperi de tip pin change se folosește registrul ''PCICR''. | ||
+ | |||
+ | <file c> | ||
+ | // î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 | ||
+ | </file> | ||
+ | |||
+ | <note tip> | ||
+ | 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 {{:pm:atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|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**. | ||
+ | </note> | ||
+ | |||
+ | === Scriem rutina de tratare a întreruperii === | ||
+ | |||
+ | De exemplu, pentru întreruperile de tip ''INT0'', respectiv ''PCINT2'', codul va arăta în felul următor: | ||
+ | |||
+ | <file c> | ||
+ | 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 | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | <note important> | ||
+ | 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. | ||
+ | </note> | ||
+ | |||
+ | === 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) | ||
+ | |||
+ | <file c> | ||
+ | // întrerupere externă | ||
+ | EIMSK |= (1 << INT0); // Turns on INT0 | ||
+ | // întrerupere de tip pin change | ||
+ | PCMSK2 |= (1 << PCINT20); // Turns on PCINT20 (PD4) | ||
+ | // activare întreruperi globale | ||
+ | sei(); | ||
+ | </file> | ||
+ | |||
+ | |||
+ | |||
+ | ==== Lucrul cu întreruperi ==== | ||
Reguli de programare în context întrerupere: | 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 | * 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. | + | * 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). | + | * 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 | * Variabilele comune trebuie marcate ca ''volatile'' pentru ca accesele la acestea să nu fie optimizate de către compilator | ||
Line 86: | Line 146: | ||
* **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. | * **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. | ||
- | <note important>Întreruperile întâlnite care nu au o rutină de tratare vor cauza o întrerupere de reset!</note> | + | <note important>Întreruperile active care nu au specificată o rutină de tratare **vor cauza o întrerupere de reset**!</note> |
- | ===== 1. Întreruperi externe. INT vs. PCINT ===== | + | ===== 2. Întreruperi externe. INT vs. PCINT ===== |
Întreruperile se pot împărți în două categorii: | Întreruperile se pot împărți în două categorii: | ||
* întreruperile componentelor //interne//: timere, interfețe seriale (USART), convertor analog-digital | * întreruperile componentelor //interne//: timere, interfețe seriale (USART), convertor analog-digital | ||
* întreruperile componentelor //externe//: activate de pinii externi ai uC-ului | * întreruperile componentelor //externe//: activate de pinii externi ai uC-ului | ||
- | În [[pm:lab:lab2-2022|Laboratorul 2]] ați studiat mecanismul de întreruperi de pe Atmega328p și ați configurat întreruperi în contextul timer-elor. | ||
- | 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. | + | 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. | 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. | ||
Line 101: | Line 160: | ||
<note important>Î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 {{:pm:atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|datasheet}}</note> | <note important>Î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 {{:pm:atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|datasheet}}</note> | ||
+ | ===== 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()** | ||
+ | |||
+ | <file c> | ||
+ | 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); | ||
+ | } | ||
+ | </file> | ||
+ | |||
+ | |||
+ | <hidden> | ||
+ | AAB: add more info about debouncing, some photos from oscilloscope maybe... | ||
+ | mention some simple debouncing methods, like using delays | ||
+ | </hidden> | ||
===== 4. Exerciții ===== | ===== 4. Exerciții ===== | ||
- | === Task 1 (întreruperi / butoane) === | + | === 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. | 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: | **Tinkercad** - Testați programul folosind următorul montaj: | ||
Line 112: | Line 210: | ||
{{:pm:lab:lab3_2021:button_led_interrupts.png?600|}} | {{:pm:lab:lab3_2021:button_led_interrupts.png?600|}} | ||
- | <file> | + | |
+ | **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 [[https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce|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) | ||
+ | |||
+ | |||
+ | <file c> | ||
ISR(INT0_vect) | ISR(INT0_vect) | ||
{ | { | ||
// cod întrerupere externă | // cod întrerupere externă | ||
- | PORTD ^= (1 << PD7); | + | PORTD ^= (1 << PD7); |
} | } | ||
- | </file> | ||
- | * Configurați întreruperea externă (INT) pe front descrescător (falling edge) sau PCINT-ul corespunzător pinului folosit | + | ISR(PCINT2_vect) { |
- | * Modificați starea LED-ului la apăsarea oricărui buton (PD2, PD4) | + | // cod întrerupere de tip pin change |
+ | // TODO | ||
+ | } | ||
- | **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 [[https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce|debouncing]] pentru a detecta o singură apăsare pe buton: | + | void setup_interrupts() { |
- | * NU este recomandat să folosiți delay în ISR | + | // buton 1: PD2 / INT0 |
- | * 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) | + | // 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() { | ||
+ | |||
+ | } | ||
+ | </file> | ||
<hidden> | <hidden> | ||
**Soluția** se găsește pe [[https://www.tinkercad.com/things/cVPb9x8NUbX|Tinkercad Button Led Interrupts]] | **Soluția** se găsește pe [[https://www.tinkercad.com/things/cVPb9x8NUbX|Tinkercad Button Led Interrupts]] | ||
+ | </hidden> | ||
+ | |||
+ | === Task 2 (blink control) - 4p === | ||
+ | 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 | ||
+ | |||
+ | <file c> | ||
+ | |||
+ | 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); | ||
+ | } | ||
+ | |||
+ | </file> | ||
+ | |||
+ | <hidden> | ||
+ | **Soluția** se găsește pe [[https://www.tinkercad.com/things/knmCTWcDM6Q|Tinkercad Button Led Blink Interrupts]] | ||
</hidden> | </hidden> | ||
Line 140: | Line 321: | ||
* Responsabili: [[alexandru.predescu@upb.ro | Alexandru Predescu]] | * Responsabili: [[alexandru.predescu@upb.ro | Alexandru Predescu]] | ||
- | <solution> | ||
- | <hidden>Arhiva cu soluțiile o puteți descărca de aici: {{:pm:lab:lab2_2022:lab2-solved.zip}}</hidden> | ||
- | </solution> | ||
===== 5. Linkuri utile ===== | ===== 5. Linkuri utile ===== |