Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pm:lab:lab2-2023 [2023/03/19 14:24]
alexandru.predescu [4. Exerciții]
pm:lab:lab2-2023 [2024/03/18 09:08] (current)
florin.stancu add #include <avr/interrupt.h> to timer ISR example
Line 1: Line 1:
-/** +====== Laboratorul 2Întreruperi,​ Timere ======
- * uncomment this to publish the solution: +
- * ~~SHOWSOLUTION~~ +
- */ +
-~~SHOWSOLUTION~~+
  
-====== Laboratorul 2Întreruperi hardwareÎntreruperi externe ======+**Capitole utile din {{:pm:​doc8272.pdf|Datasheet ATmega324}}** 
 +  * 1. Pin Configurations 
 +    * secțiunea 1.1 - pag. 2 
 +  * 7. AVR CPU Core 
 +    * secțiunea 7.3 - pag. 11 
 +    * secțiunea 7.7 - pag. 16 
 +  * 12. Interrupts 
 +    * tabelul 12-1 - pag. 61 
 +  * 13. External Interrupts 
 +    * secțiunea 13.1 - pag. 67 
 +    * secțiunile 13.2.4-13.2.9 - pag. 69 
 +  * 16. 16-bit Timer/​Counter1 and Timer/​Counter3 with PWM 
 +    * secțiunile 16.1-16.3 - pag. 111 
 +    * secțiunea 16.5 - pag. 117 
 +    * secțiunea 16.7 - pag. 120 
 +    * secțiunile 16.9.1-16.9.2 - pag. 123 
 +    * secțiunea 16.11 - pag132
  
-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 butonindependent de programul principal.+<note important>​Capitolele sunt din {{:​pm:​doc8272.pdf|Datasheet ATmega324}},​ document pe care îl aveți și pe desktop-ul calculatorului din laborator. Nu garantăm aceeași ordine a capitolelor în cazul utilizării altui document!</​note>​ 
 + 
 +Acest laborator are ca scop familiarizarea voastră cu lucrul cu întreruperile hardware și cu timer-ele prezente în microcontroller-ul Atmega324. Vom folosi ​timer-ele doar pentru a număranu și pentru a genera semnal PWM. Această funcționalitate va fi studiată și utilizată în laboratorul următor.
  
 ===== 1. Întreruperi ===== ===== 1. Întreruperi =====
Line 18: Line 32:
  
 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. 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.
 +
 +**Tabela de vectori de întrerupere pentru ATmega324**
  
 ^ Vector no. ^ Program address ^ Source ^ Interrupt definition ^ ^ Vector no. ^ Program address ^ Source ^ Interrupt definition ^
Line 23: Line 39:
 | 2 | 0002 | INT0 | External Interrupt Request 0 | | 2 | 0002 | INT0 | External Interrupt Request 0 |
 | 3 | 0004 | INT1 | External Interrupt Request 1 | | 3 | 0004 | INT1 | External Interrupt Request 1 |
-| 4 | 0006 | PCINT0 | Pin Change Interrupt Request 0 | +| 4 | 0006 | INT2 | External Interrupt Request 2 | 
-0008 | PCINT1 | Pin Change Interrupt Request 1 | +| 5 | 0008 | PCINT0 | Pin Change Interrupt Request 0 | 
-000A | PCINT2 | Pin Change Interrupt Request 2 | +000A | PCINT1 | Pin Change Interrupt Request 1 | 
-000C | WDT | Watchdog Time-out Interrupt | +000C | PCINT2 | Pin Change Interrupt Request 2 | 
-000E | TIMER2_COMPA | Timer/​Counter2 Compare Match A | +000E | PCINT3 | Pin Change Interrupt Request 3 | 
-0010 | TIMER2_COMPB | Timer/​Counter2 Compare Match B | +| 9 | 0010 | WDT | Watchdog Time-out Interrupt | 
-10 0012 | TIMER2_OVF | Timer/​Counter 2 Overflow | +10 0012 | TIMER2_COMPA | Timer/​Counter2 Compare Match A | 
-11 0014 | TIMER1_CAPT | Timer/​Counter1 Capture Event | +11 0014 | TIMER2_COMPB | Timer/​Counter2 Compare Match B | 
-12 0016 | TIMER1_COMPA | Timer/​Counter1 Compare Match A | +12 0016 | TIMER2_OVF | Timer/​Counter 2 Overflow | 
-13 0018 | TIMER1_COMPB | Timer/​Counter1 Compare Match B | +13 0018 | TIMER1_CAPT | Timer/​Counter1 Capture Event | 
-14 001A | TIMER1_OVF | Timer/​Counter1 Overflow | +14 001A | TIMER1_COMPA | Timer/​Counter1 Compare Match A | 
-15 001C | TIMER0_COMPA | Timer/​Counter0 Compare Match A | +15 001C | TIMER1_COMPB | Timer/​Counter1 Compare Match B | 
-16 001E | TIMER0_COMPB | Timer/​Counter0 Compare Match B | +16 001E | TIMER1_OVF | Timer/​Counter1 Overflow | 
-17 0020 | TIMER0_OVF | Timer/​Counter0 Overflow | +17 0020 | TIMER0_COMPA | Timer/​Counter0 Compare Match A | 
-18 0022 | SPI_STC | SPI Serial Transfer Complete | +18 0022 | TIMER0_COMPB | Timer/​Counter0 Compare Match B | 
-19 0024 | USART0_RX | USART0 Rx Complete | +19 0024 | TIMER0_OVF | Timer/​Counter0 Overflow | 
-20 0026 | USART0_UDRE | USART0 Data Register Empty | +20 0026 | SPI_STC | SPI Serial Transfer Complete | 
-21 0028 | USART0_TX | USART0 Tx Complete ​+21 0028 | USART0_RX | USART0 Rx Complete | 
-| 22 | 002A | ADC | ADC Conversion Complete | +22 002A | USART0_UDRE | USART0 Data Register Empty | 
-| 23 | 002C | EE_READY | EEPROM Ready |+23 002C | USART0_TX | USART0 Tx Complete |
 | 24 | 002E | ANALOG_COMP | Analog Comparator | | 24 | 002E | ANALOG_COMP | Analog Comparator |
-| 25 | 0030 | TWI | Two-Wire Serial Interface | +| 25 | 0030 | ADC | ADC Conversion Complete | 
-26 0032 | SPM_READY | Store Program Memory Ready |+| 26 | 0032 | EE_READY | EEPROM Ready | 
 +| 27 | 0034 | TWI | Two-Wire Serial Interface | 
 +28 0036 | SPM_READY | Store Program Memory Ready 
 +| 29 | 0038 | USART1_RX | USART1 Rx Complete | 
 +| 30 | 003A | USART1_UDRE | USART1 Data Register Empty | 
 +| 31 | 003C | USART1_TX | USART1 Tx Complete | 
 +| 32 | 003E | TIMER3_CAPT | Timer/​Counter3 Capture Event | 
 +| 33 | 0040 | TIMER3_COMPA | Timer/​Counter3 Compare Match A |  
 +| 34 | 0042 | TIMER3_COMPB | Timer/​Counter3 Compare Match B | 
 +| 35 | 0044 | TIMER3_OVF ​ | Timer/​Counter3 Overflow ​|
  
-/*După cum se observă din tabelul de mai sus, pe lângă întreruperile componentelor interne uC-ului (timer-e, interfețe seriale, convertor analog-digital),​ 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 se declanșează la ambele tranziții (de unde și numele Pin Change INTerrupt) ș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. PCINT0 cu întreruperea efectivă ex. PCINT8 ​(PC0). 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> ​*/+După cum se observă din tabelul ​de mai sus, pe lângă întreruperile componentelor interne uC-ului ​(timer-e, interfețe seriale, convertor analog-digital), există ​și câteva linii pentru ​întreruperi ​de la periferice externe**INT0-INT2** și **PCINT0-PCINT3**. Diferența dintre aceste două tipuri de întreruperi externe este dată de capabilitățile suportate și de granularitatea lor. 
  
-<note tip>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 exemplusuntem ​în handler-ul pt recepția unui frame pe interfața seriala, și dorim să activăm întreruperile unui timer).</​note>​+  * Semnalele pentru ​întreruperile ​**INTn** vin pe portul D pinii 23 respectiv portul B pinul 2 și 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** se declanșează la ambele tranziții (de unde și numele **Pin Change INTerrupt**) ș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 e.g. **PCINT0** cu întreruperea efectivă e.g. **PCINT0 (PA0)**. Maparea dintre vector și întreruperi se poate găsi în secțiunea 30 pag. 555 din {{:​pm:​doc8272.pdf|datasheet}}</​note>​
 +
 +**Pini întreruperi externe pe capsula ATmega324**
 +
 +{{.:​lab2:​irq_pins.png?​500|Pini întreruperi externe pe capsula ATmega324}}
 +
 +<note tip>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 serială, și dorim să activăm întreruperile unui timer).</​note>​
  
 ==== 1.1. Utilizarea întreruperilor ==== ==== 1.1. 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 102:
 cli(); cli();
 </​file>​ </​file>​
- 
-Î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 === === 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''​. ​+În exemplu, pentru ''​INT0''​''​INT1''​ și ''​INT2'',​ configurarea se face din registrul ''​EICRA''​ (External Interrupt Control Register A, secțiunea ​13.2.1, pagina ​67 din {{:pm:doc8272.pdf|datasheet}}), în timp ce pentru întreruperi de tip pin change se folosește registrul ''​PCICR''​. ​ 
 + 
 +**Registrul EICRA** 
 + 
 +{{.:​lab2:​eicra.png?​700|eicra_reg}} 
 + 
 +**Modurile în care pot fi configurate întreruperile** 
 + 
 +{{.:​lab2:​eicra2.png?​700|eicra_modes}}
  
 <file c> <file c>
Line 79: Line 119:
 EICRA |= (1 << ISC00); ​   // set INT0 to trigger on ANY logic change EICRA |= (1 << ISC00); ​   // set INT0 to trigger on ANY logic change
 // întreruperi de tip pin change (activare vector de întreruperi) // întreruperi de tip pin change (activare vector de întreruperi)
-PCICR |= (1 << ​PCIE2); // enable the pin change interrupt, set PCIE2 to enable ​PCMSK2 ​scan+PCICR |= (1 << ​PCIE1); // enable the pin change interrupt, set PCIE1 to enable ​PCMSK1 ​scan
 // alte întreruperi // alte întreruperi
 </​file>​ </​file>​
-  ​+    ​
 <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:​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.
 </​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:+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 de tip ''​INT0''​ și respectiv ''​PCINT1'',​ codul va arăta în felul următor:
  
 <file c> <file c>
 ISR(INT0_vect) ISR(INT0_vect)
 { {
-  // cod întrerupere externă ​PD2 /INT0 +  // cod întrerupere externă
-  // verificare tranziție pe front crescător, descrescător sau oricare +
-  // (după cum este configurat INT0)+
 } }
  
-ISR(PCINT2_vect){+ISR(PCINT1_vect){
   // cod întrerupere de tip pin change   // cod întrerupere de tip pin change
-  if ((PIND & (1 << ​PD4)) == 0){ +  ​ 
-     // ​întreruperea a fost generată de pinul PD4 / PCINT20 +  ​if ((PINB & (1 << ​PB1)) == 0){ 
-     // verificăm nivelul logic al pinului+     // ​întrerupere ​generată de pinul PB1
   }   }
-  // întreruperea a fost generată de alt pin 
 } }
 </​file>​ </​file>​
- 
-<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. 
-</​note>​ 
  
 === Activăm întreruperea și testăm programul === === Activăm întreruperea și testăm programul ===
-  ​+
 În cazul de față, pentru activarea întreruperilor externe, se configurează registrul External Interrupt Mask Register (''​EIMSK''​) astfel: Î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 +   * biții ''​INT2:​0''​ controlează dacă întreruperile externe (INT0-INT2) 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)+Pentru celelalte întreruperi,​ se configurează registrul corespunzător (e.g. ''​PCMSK1''​ pentru activarea întreruperilor de tip pin change pe fiecare pin)
   ​   ​
 <file c> <file c>
Line 123: Line 158:
 EIMSK |= (1 << INT0); ​    // Turns on INT0 EIMSK |= (1 << INT0); ​    // Turns on INT0
 // întrerupere de tip pin change // întrerupere de tip pin change
-PCMSK2 ​|= (1 << ​PCINT20); // Turns on PCINT20 ​(PD4+PCMSK1 ​|= (1 << ​PCINT9); // Turns on PCINT9 ​(PB1) 
-// activare întreruperi ​globale+// alte întreruperi 
 +// ... 
 +// activare întreruperi ​la nivel global
 sei(); sei();
 </​file>​ </​file>​
  
 +<spoiler **Pe lângă cele amintite, mai există o serie de registre care controlează funcționarea întreruperilor**>​
 +Descrierea completă a acestor registre o găsiți în {{:​pm:​doc8272.pdf|datasheet}} în capitolele //​Interrupts//,//​ External Interrupts//​.
  
 +  * Status Register (SREG)
 +    * conține flag-uri setate în urma operațiilor unității aritmetice logice
 +    * conține flag-ul ''​I''​ de activare/​dezactivare întreruperi
 +    * NU este salvat la apariția unei întreruperi
 +    * descris în datasheet în capitolul //AVR CPU Core//
  
-==== 1.2. Lucrul cu întreruperi ====+Registrul SREG
  
-Reguli de programare în context întrerupere:​+{{.:​lab2:​sreg.png|Registrul SREG}} 
 + 
 +  * MCU Control Register (MCUCR) 
 +    * bitul ''​IVSEL''​ controlează unde se plasează vectorii de întreruperi (0 - începutul memoriei Flash, 1 - începutul secțiunii de Boot Loader din Flash) 
 +    * bitul ''​IVCE''​ activează scrierea bitului ''​IVSEL''​ 
 + 
 +Registrul MCUCR 
 + 
 +{{.:​lab2:​mcucr.png?​700}} 
 + 
 +  * External Interrupt Mask Register (EIMSK) 
 +    * biții ''​INT2:​0''​ controlează dacă întreruperile externe sunt activate 
 +    * pt oricare bit ''​INT2:​0'',​ dacă este 1 și bitul ''​I''​ din ''​SREG''​ este 1 atunci sunt activate întreruperile externe pe pinul corespunzător. 
 + 
 +</​spoiler>​ 
 + 
 +==== 1.2. Lucrul cu întreruperi în avr-gcc ==== 
 +avr-libc oferă interfața din **avr/​interrupt.h** pentru definirea rutinelor de tratare a întreruperilor. Tabela de vectori specifică fiecărui microcontroller (în cazul nostru pentru ATMega324) este declarată în header-ul de IO specific (ex: iom324.h) 
 + 
 +**Exemple de întreruperi** 
 +^ Întrerupere ^ Vector ^ Descriere | 
 +| TIMER1_COMPA | TIMER1_COMPA_vect | Compare Match pragul A pe timer-ul 1 | 
 +| TIMER1_COMPB | TIMER1_COMPB_vect | Compare Match pragul B pe timer-ul 1 | 
 +| TIMER1_OVF | TIMER1_OVF_vect | Overflow pe contorul timer-ului 1 | 
 +| PCINT1 | PCINT1_vect | Eveniment de "pin change"​ pe portul B | 
 +| ... | ... | ... | 
 + 
 +Definirea rutinei de tratare se face cu macro-ul ISR: 
 + 
 +<file c> 
 +#include <​avr/​interrupt.h>​ 
 + 
 +ISR(INT0_vect) 
 +
 +    // ... 
 +
 +</​file>​ 
 + 
 +**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 ​trebuiesc ​marcate ca ''​volatile''​ pentru ca accesele la acestea să nu fie optimizate de către compilator
  
 +<spoiler **Întreruperile pot fi declarate cu anumite flag-uri -  Click AICI pentru mai multe detalii**>​
 +
 +
 +<file c>
 +#include <​avr/​interrupt.h>​
 +
 +ISR(vector, flag)
 +{
 +    // cod întrerupere
 +}
 +</​file>​
 +
 +Macro-ul ISR definește rutina de tratare pentru perifericul respectiv, salvează SREG și apelează instrucțiunea **reti** la ieșirea din rutină. Rutinele pot fi definite cu următoarele flag-uri:
 +  * ISR_BLOCK: comportamentul default, în care întreruperile globale sunt dezactivate atunci când se intră într-o întrerupere
 +  * ISR_NOBLOCK:​ facilitează existența întreruperilor imbricate (poate fi util dacă se dorește ca o altă întrerupere să fie întârziată)
 +  * ISR_NAKED: omite prologul și epilogul rutinei de tratare, adică salvarea SREG și apelul reti()
 +  * ISR_ALIASOF:​ rutina este identică cu cea a altui vector de întrerupere
 +
 +<file c>
 +#include <​avr/​interrupt.h>​
 +
 +ISR(INT0_vect)
 +{
 +    // cod întrerupere
 +}
 +
 +ISR(INT1_vect,​ ISR_ALIASOF(INT0_vect))
 +
 +</​file>​
 +</​spoiler>​
  
 Alte wrapper-e (în jurul unor instrucțiuni ale core-ului AVR) oferite de interfață sunt: Alte wrapper-e (în jurul unor instrucțiuni ale core-ului AVR) oferite de interfață sunt:
Line 146: Line 258:
   * **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 o rutină de tratare ​specificată în cod **vor cauza o întrerupere de Reset**!</​note>​
  
 +===== 2. Timer =====
  
-===== 2. Întreruperi externeINT vs. PCINT ====+==== 2.1Principiul de funcționare al unui Timer ====
-Î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-PCINT2Diferența dintre aceste două tipuri ​de întreruperi externe este dată de capabilitățile suportate și de granularitatea lor.+Timer-ul/​Counter-ul oferă facilitatea de a măsura intervale fixe de timp și 
 +de a genera ​întreruperi la expirarea intervalului măsuratUn timer, odată inițializat va funcționa independent ​de unitatea centrală (UCP). Acest lucru permite eliminarea buclelor ​de delay din programul principal.
  
-Semnalele pentru întreruperile ​**INTn** pot genera ​î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 singură întrerupereSemnalele 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.+Principiul de funcționare a unui Timer poate fi descris în linii mari de cele trei unități din <imgref timer>:​ 
 +  - **Registrul numărător** (Timer Counter, TCNT) - măsoară efectiv intervalele de timp și este incrementat automat cu frecvență dată
 +  - **Prescaler-ul** - are rolul de a diviza ​în funcție de necesitățile aplicației frecvența de ceas. 
 +  - 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ă.
  
-<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>​+**Componentele ​și funcționarea unui timer pentru ATmega324**
  
-===== 3Întreruperi externe în Arduino =====+{{.:​lab2:​timer_block.png?​600|Componentele și funcționarea unui timer pentru ATmega324}}
  
-Deși noi vom lucra în principal ​cu registre, în Arduino, întreruperile se mai pot configura folosind funcția **attachInterrupt()**. +Timer-ele sunt prevăzute ​cu mai multe canale astfel încât se pot desfășura diferite ​număratori în paralel. ATmega324 este prevăzut cu 3 unități de timer: două de pe 8 biți și una de 16 biți.
-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 +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 laboratorul viitor.
-  * CHANGE: tranziție de nivel logic +
-  * RISING: front crescător +
-  * FALLING: front descrescător+
  
-Dezactivarea întreruperilor externe se poate face cu funcția **detachInterrupt()** +==== 2.2. Moduri de funcționare ====
-Activarea sau dezactivarea temporară a tuturor întreruperilor se poate face cu funcțiile **noInterrupts()** și **interrupts()**+
  
-<file c> +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: 
-int counter = 0;+  * 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
  
-void myCallback() { +În tabelul următor sunt prezentate cele două moduri pe care le vom folosi în acest laborator.
-  counter += 1; +
-}+
  
-void setup() { +**Descrierea modurilor de funcționare**
-  pinMode(2, INPUT_PULLUP);​ +
-  attachInterrupt(0,​ myCallback, FALLING); +
-}+
  
-void loop() { 
-  Serial.print("​numar apasari: "); 
-  Serial.println(counter);​ 
-  delay(1000);​ 
-} 
-</​file>​ 
  
 +^ Mod ^ Descriere ^ Imagine contor ^ Proprietăți ^
 +|  **Normal **  | * pornește de la 0 \\ * numără până 0xFFFF | {{.:​lab2:​timer_normal_v2.png?​350|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-ele 0 și 2\\ ''​OCR1A''​ sau ''​ICR1''​ pentru timer-ul 1 | {{.:​lab2:​timer_ctc_v2.png?​350|Modul CTC}}| * frecvența este variabilă, determinată de valoarea pragului, frecvența ceasului și de prescaler |
  
-===== 4Exerciții ​=====+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 (//aici veți activa 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 (//aici activați întreruperile//​) | 
 +|  **Timer1** ​ \\ \\   **16 biți** ​ | ''​TCNT1H/​L'' ​\\  ''​TCNT1H''​ + ''​TCNT1L'' ​ | 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//) | 
 +| ::: | ''​OCR1AH/​L''​ , ''​OCR1BH/​L''​ | Registre prag pe 16 biți ale timer-ului 1 (//la fel ca la timer0//) | 
 +| ::: | ''​TIMSK1'',​ ''​TIFR1''​| (//la fel ca la timer0//) | 
 +| ::: | ''​ICR1H/​L''​ | 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 ​ | 
 + 
 + 
 +<note tip>​Întreruperile sunt activate doar dacă bitul ''​I''​ din ''​SREG''​ este setat (întreruperile sunt activate global) 
 +</​note>​
  
-=== Task 1 (întreruperi / butoane) ​6p ===  +==== 2.4. Lucrul cu Timer-ul ==== 
-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.+=== Setarea modului de funcționare ===
  
-**Tinkercad** ​Testați programul folosind următorul montaj:+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
  
-{{:​pm:​lab:​lab3_2021:​button_led_interrupts.png?600|}}+De exemplu, ca să setăm timer-ul 0 să numere până la 5, ne vom uita în datasheet la capitolul 15 (//8-bit Timer/​Counter0 with PWM//) - secțiunea //Register Description//​ -> //​TCCR0A//​ 
 +  - găsim modul ''​CTC''​ ca având biții ''​0 1 0''​ pe biții ''​WGM2..0''​ 
 +  - 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:
 <file c> <file c>
-ISR(INT0_vect) 
-{ 
-  // cod întrerupere externă 
-  PORTD ^= (1 << PD7);  ​ 
-} 
-</​file>​ 
  
-  * Configurați întreruperea externă ​(INTpe front descrescător (falling edge) sau PCINT-ul corespunzător pinului folosit +TCCR0A |= (1 << WGM01); 
-  ​* Modificați starea LED-ului la apăsarea oricărui buton (PD2, PD4) +OCR0A  = 5;
-  * Modificați starea LED-ului de pe placă (pin 13) la interval de o secundă.+
  
-**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: +</file>
-  * 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)+
  
-<​hidden>​ 
-**Soluția** se găsește pe [[https://​www.tinkercad.com/​things/​cVPb9x8NUbX|Tinkercad Button Led Interrupts]] 
-</​hidden>​ 
  
-=== Task 2 (blink control) ​4p ===  +=== Setarea prescaler-ului ===
-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 +Pentru setarea prescaler-ului se vor modifica biții tip ''​CS..'' ​din registrul ​''​TCCRnB'' ​al timer-ului respectiv.
-  * butonul conectat la ''​PD4'' ​scade intervalul cu 100 ms+
  
 +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 Description//​ -> //TCCR2B//
 +  - găsim tabelul pentru valorile ''​CS..''​
 +  - 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:
 <file c> <file c>
 +TCCR2B |= (1 << CS22) | (1 << CS21);
 +</​file>​
  
-int dt = 500; 
  
-ISR(INT0_vect)+=== Setarea întreruperilor === 
 + 
 +Pentru un timer deja configurat, pentru a seta întreruperile trebuie doar să activăm bitul corespunzător din ''​TIMSKx''​ 
 + 
 +De exemplu, pentru pragul A al timer-ului 1 vom scrie: 
 + 
 + 
 +<file c > 
 +#include <​avr/​interrupt.h>​ 
 + 
 +ISR(TIMER1_COMPA_vect)
 { {
-  // cod întrerupere ​externă +  // cod întrerupere ​
- +
 } }
 +void init_timer1()
 +{
 +  TIMSK1 |= (1 << OCIE1A);
 +}
 +int main()
 +{
 +  sei(); ​         // activăm primirea întreruperilor
 +  init_timer1(); ​ // apelăm funcția de inițializare
 +  // ...
 +}
 +</​file>​
  
-ISR(PCINT2_vect) { +==== 2.5. Accesarea registrelor pe 16 biți ====
-  // cod întrerupere de tip pin change+
  
-}+**TCNT1, OCR1A/B si ICR1** sunt registre de 16 biți care pot fi accesate de către AVR CPU cu ajutorul bus-ului de date de 8 biți. Un registru de 16 biți poate să fie accesat numai folosind două operatii, fie de scriere, fie de 
 +citire pe 8 biți. Pentru a executa o scriere de 16 biți, byte-ul HIGH trebuie să fie scris înaintea byte-ului LOW. Intern, scrierea byte-ului HIGH nu actualizează imediat registrul, el fiind salvat într-o locație temporară. Scrierea byte-ului LOW determină actualizarea întregului registru într-un singur ciclu, folosind valoarea HIGH salvată în zona temporară. 
 +<note tip> 
 +La folosirea compilatorului de C secvența de scriere/​citire a unui registru pe 16 biți este implementată automat de către acesta. Pentru a folosi această facilitate utilizați macro-ul aferent registrului fără terminația L sau H (pentru byte-ul LOW, respectiv HIGH). 
 +</​note>​
  
-void setup_interrupts() { +==== 2.6. Calculator ====
-  // buton 1: PD2 / INT0 +
-  // buton 2: PD4 / PCINT20 +
-  cli();+
  
-  // input +Î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. 12 MHz pentru placa de laboratorși de modul de funcționare ales. Un exemplu de calcul este prezentat mai jos:
-  DDRD &= ~(1 << PD2& ~(1 << PD4); +
-  // input pullup +
-  PORTD |= (1 << PD2) | (1 << PD4);+
  
-  ​// configurare intreruperi +<code C> 
-  // intreruperi externe +// calculate the frequency of the interrupt (f) from the timer configuration and clock speed (f_clk) 
-  // TODO+f_int = f_clk (prescaler * (tc + 1)) 
 +// calculate the target timer count (tc) for the required interrupt frequency 
 +tc = f_clk (prescaler * f_int) - 1 
 +</code>
  
-  // întreruperi de tip pin change ​(activare vector ​de întreruperi) +Alegerea prescaler-ului se face astfel încât să se poată obține frecvența ​(exactă) dorită a întreruperilor. De exemplu, pentru o frecvență ​de 1Hz (f_clk = 12MHzse obțin următoarele variante pentru determinarea prescaler-ului și a limitei de numărare a timer-ului:
-  // TODO+
  
-  // activare intreruperi +^ Prescaler ^ Limită counter ^ Timer 1 (16 bit) ^ Timer 0,2 (8 bit) ^ Obs. ^ 
-  // intrerupere externa +| 1 | 11999999 | Invalid (16 bit) | Invalid (8 bit) | overflow | 
-  /TODO+| 8 | 1499999 | Invalid (16 bit) | Invalid (8 bit) | overflow | 
 +| 64 | ‭187499‬ | Invalid (16 bit) | Invalid (8 bit) | overflow | 
 +| 256 | ‭46874‬ | Valid | Invalid (8 bit) | Se poate folosi doar Timer 1 | 
 +| 1024 | 11717.75 | Invalid | Invalid | Nu este exact/divizibil |
  
-  // întrerupere ​de tip pin change +<note tip>​Timer2 poate fi configurat și cu prescaler ​de 32 sau 128 (secțiunea 17.11.2, pag. 160 din {{:​pm:​doc8272.pdf|datasheet}}) 
-  /TODO+</note>
  
-  sei(); +Există însă calculatoare care pot fi utile pentru determinarea rapidă a valorilor pentru registrele de configurare ale unui Timer precum:
-}+
  
 +{{url>​https://​web.archive.org/​web/​20180312235028/​https://​et06.dunweber.com/​atmega_timers/​|ATmega Timer/​Counter/​Prescaler calculator}}
  
-void setup() ​{ +/*{{url>​https://​www.arduinoslovakia.eu/​application/​timer-calculator|ATmega Timer/​Counter/​AVR Timer Interrupts Calculator}}*/
-  setup_interrupts();​ +
-  DDRD |= (1 << PD7); +
-  PORTD &= ~(1 << PD7); +
-}+
  
-void loop() {   +===== 3. Exerciții =====
-  PORTD |(1 << PD7); +
-  delay(dt);​ +
-  PORTD &~(1 << PD7); +
-  delay(dt);​ +
-}+
  
-</​file>​+**Task 1.1** 
 + 
 +  * Implementați o funcție asemănătoare cu ''​millis()''​ din biblioteca Arduino. Aceasta ar trebui să returneze intervalul de timp trecut de la pornirea (sau ultimul reset) al uC-ului. Configurați interfața USART0 cu aceeași parametri folosiți în laboratorul trecut și transmiteți către PC un mesaj ales de voi la intervale de 1 secundă. 
 + 
 +HINTS: 
 +  * Frecvența de ceas a uC-ului este de 12MHz. Folosiți formulele prezentate anterior pentru a obține valorile potrivite pentru prescaler și registrul de comparație ale timer-ului ales astfel încât întreruperile generate de acesta să se declanșeze la intervalul de timp dorit. 
 +  * Timer2 poate fi utilizat cu un număr mai mare de valori ale prescaler-ului. 
 + 
 +**Task 1.2** 
 +  * Pentru a putea citi în mod corect apăsări scurte ale butoanelor este necesară o metodă de debouncing. Cum ar putea fi rezolvată problema citirilor eronate cu ajutorul funcției ''​millis()''?​ Concepeți o funcție (în pseudocod) care să ilustreze soluția găsită. 
 + 
 +**Task 2.**
  
-<​hidden>​ +  ​Reluați exercițiul 3 din primul laborator ​și implementați-l folosind timer-e și întreruperi pentru citirea butoanelor și efectul de clipire al LED-urilor.
-**Soluția** se găsește pe [[https://​www.tinkercad.com/​things/​knmCTWcDM6Q|Tinkercad Button Led Blink Interrupts]] +
-</​hidden>​+
  
 +<spoiler **Reminder Ex. 3, lab 0**>
 +  * La apăsarea succesivă a BTN1, LED-ul RGB trebuie să își schimbe culoarea (Red -> Green -> Blue -> Red)
 +  * BTN2 trebuie să controleze efectul de clipire al LED-ului aprins
 +</​spoiler>​
  
 +**Task 3.**
 +  * Folosiți butoanele ''​PD6''​ și ''​PB2''​ pentru a controla buzzer-ul:
 +    * Unul dintre butoane selectează frecvența sunetului generat de buzzer (dintre 3 valori, de ex. 100Hz, 200Hz, 300Hz)
 +    * Buzzer-ul funcționează doar când mențineți al doilea buton apăsat
 +    * Puteți folosi LED-ul RGB pentru a indica frecvența aleasă
  
-===== 5Resurse =====+**Task 4(BONUS)**
  
-  * {{:​pm:​atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|Datasheet Atmega 328p}} +  * Funcția implementată la primul exercițiu va acumula o anumită eroare din cauza faptului că frecvența de ceas nu poate fi divizată perfect
-  Arduino UNO pinout +    Încercați să calculați după câte cicluri de funcționare a timer-ului eroarea devine semnificativă (peste 1 ms)
-{{:​pm:​lab:​uno.jpg?​600|pinout Arduino UNO}} +    Propuneți o metodă prin care se poate reduce această eroare 
-  Responsabili:​ [[alexandru.predescu@upb.ro | Alexandru Predescu]]+    * Încercați implementarea metodei propuse
  
-<​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 ===== 
  
-  * [[http://​www.nongnu.org/​avr-libc/​user-manual/​group__avr__interrupts.html|AVR Libc - interrupt.h]] 
-  * [[http://​ww1.microchip.com/​downloads/​en/​devicedoc/​atmel-0856-avr-instruction-set-manual.pdf|Setul de instrucțiuni AVR]] 
-  * [[https://​www.arduino.cc/​reference/​en/​libraries/​timerone/​|TimerOne]] 
-  * [[https://​www.arduino.cc/​reference/​en/​language/​functions/​advanced-io/​tone/​|tone()]] 
-  * [[https://​www.renesas.com/​us/​en/​support/​engineer-school/​mcu-programming-peripherals-04-interrupts|Interrupts]] 
  
pm/lab/lab2-2023.1679228678.txt.gz · Last modified: 2023/03/19 14:24 by alexandru.predescu
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