This shows you the differences between two versions of the page.
pm:lab:lab2-2022 [2023/03/18 21:16] alexandru.predescu [4. Exerciții] |
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 68: | Line 68: | ||
cli(); | cli(); | ||
</file> | </file> | ||
- | |||
- | |||
- | ==== 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. | ||
- | |||
- | <note important>Întreruperile întâlnite care nu au o rutină de tratare vor cauza o întrerupere de reset!</note> | ||
- | |||
- | |||
- | ===== 1. Î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 | ||
- | Î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. | ||
- | |||
- | 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. | ||
- | |||
- | <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> | ||
- | |||
- | |||
- | ==== Configurare ==== | ||
În general, pașii parcurși pentru a activa o întrerupere externă INT sau PCINT sunt următorii: | În general, pașii parcurși pentru a activa o întrerupere externă INT sau PCINT sunt următorii: | ||
Line 108: | Line 73: | ||
=== Configurăm perifericul care va trimite întreruperile === | === Configurăm perifericul care va trimite întreruperile === | ||
- | Î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 {{:pm:atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|datasheet}}, în timp ce pentru întreruperi de tip pin change se folosește registrul ''PCICR''. | + | 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> | <file c> | ||
Line 119: | Line 84: | ||
| | ||
<note tip> | <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:doc8272.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**. | + | 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> | </note> | ||
| | ||
=== Scriem rutina de tratare a întreruperii === | === Scriem rutina de tratare a întreruperii === | ||
- | De exemplu, pentru întreruperile de tip ''INT0'' și respectiv ''PCINT2'', codul va arăta în felul următor: | + | De exemplu, pentru întreruperile de tip ''INT0'', respectiv ''PCINT2'', codul va arăta în felul următor: |
<file c> | <file c> | ||
Line 145: | Line 110: | ||
<note important> | <note important> | ||
- | Dacă avem mai multe întreruperi pe același vector și 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. | + | 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> | </note> | ||
Line 163: | Line 128: | ||
</file> | </file> | ||
+ | |||
+ | |||
+ | ==== 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. | ||
+ | |||
+ | <note important>Întreruperile active care nu au specificată o rutină de tratare **vor cauza o întrerupere de reset**!</note> | ||
+ | |||
+ | |||
+ | ===== 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. | ||
+ | |||
+ | <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 0 (î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 173: | 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 |
- | * [expert] Se poate folosi un timer pentru a înlocui apelul funcției //millis()// | + | 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 200: | Line 319: | ||
* Arduino UNO pinout | * Arduino UNO pinout | ||
{{:pm:lab:uno.jpg?600|pinout Arduino UNO}} | {{:pm:lab:uno.jpg?600|pinout Arduino UNO}} | ||
- | * Responsabili: [[St3fandascalu@gmail.com | Stefan Dascalu]], [[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 ===== |