LABORATORUL 04

Modulul de măsurare a timpului (Timer)

Durată: 2 ore Suport: Capitolul 4 Platformă: Arduino Uno / ATmega328P PDF îndrumar EN English version

Modulele timer sunt numărătoare hardware care funcționează independent de procesor. Ele permit generarea de intervale precise, măsurarea duratelor și producerea semnalelor PWM - fără a bloca programul cu delay().

1Obiectivele lucrării

  • Înțelegerea rolului prescalerului și calculul frecvenței de numărare
  • Configurarea modului CTC pentru generarea unei baze de timp precise
  • Utilizarea întreruperii de comparare pentru execuție periodică
  • Generarea semnalelor PWM și controlul factorului de umplere
  • Înlocuirea funcției delay() cu temporizare neblocantă

2Materiale necesare

  • 1 Arduino Uno
  • 1 Breadboard
  • 2 LED-uri + rezistoare 220 Ω
  • 1 Potențiometru 10 kΩ
  • 1 Buzzer pasiv (opțional)
  • 6 Fire jumper

3Noțiuni teoretice

Principiul de funcționare

Un timer este un numărător care crește cu 1 la fiecare impuls de ceas primit. Când atinge valoarea maximă (255 pentru 8 biți, 65 535 pentru 16 biți), se resetează și setează un fanion de depășire. Fiind implementat în hardware, numără în paralel cu execuția programului.

Timerele disponibile pe ATmega328P

TimerRezoluțiePini PWMFolosit intern de
Timer08 bițiD5, D6millis(), delay() - evitați modificarea
Timer116 bițiD9, D10biblioteca Servo
Timer28 bițiD3, D11tone()

Prescalerul

Ceasul de 16 MHz este prea rapid pentru intervale lungi: un timer pe 16 biți s-ar umple în doar 4,1 ms. Prescalerul împarte frecvența înainte de a ajunge la numărător:

Prescaler NFrecvență numărareDurată 1 incrementTimp până la overflow (16 biți)
116 MHz62,5 ns4,10 ms
82 MHz0,5 µs32,8 ms
64250 kHz4 µs262 ms
25662,5 kHz16 µs1,05 s
102415,625 kHz64 µs4,19 s

Moduri de funcționare

Mod NORMAL - numără până la MAX, apoi overflow MAX Mod CTC - se resetează la OCR și comută ieșirea OCR ieșire OC1A (toggle la fiecare compare match)
Fig. 1 - Comparație între modul Normal (numărare până la MAX) și modul CTC (resetare la valoarea din registrul de comparare, cu comutarea ieșirii).
Formula de bază pentru modul CTC
OCRn = ( F_CPU / (2 · N · f_dorită) ) − 1
Pentru o întrerupere periodică (nu comutare la pin), factorul 2 dispare:
OCRn = ( F_CPU / (N · f_dorită) ) − 1

4Calculator interactiv de timer

Modificați parametrii și urmăriți cum se schimbă perioada și frecvența. Graficul arată numărătorul crescând până la valoarea de comparare și semnalul rezultat la pin.

Prescaler, OCR și frecvența rezultată
VerificarePentru F_CPU = 16 MHz, prescaler 256 și OCR = 62 499 se obține exact 1 secundă: (62 499 + 1) · 256 / 16 000 000 = 1,000 s.

5Generarea semnalului PWM

PWM (Pulse Width Modulation) produce o tensiune medie variabilă prin comutarea rapidă între 0 și 5 V. Raportul dintre timpul „pornit" și perioada totală se numește factor de umplere. Un LED comandat astfel pare că își schimbă intensitatea, iar un motor își schimbă turația.

Simulator PWM - factor de umplere și tensiune medie
Configurarea Timer1 - registrul de control
Configurația de mai susWGM12 = 1 → mod CTC; CS12 = 1, CS11 = 0, CS10 = 0 → prescaler 256; OCIE1A = 1 → întreruperea de comparare A este activată.
Modul de funcționare al unui timer: prescaler, TCNT, comparare cu OCR
Fig. 2 - Modul de funcționare al unui timer: sursa de ceas trece prin prescaler, alimentează numărătorul TCNT, iar comparatorul îl verifică permanent față de OCR. Rezultă două surse de întrerupere: overflow și egalitate. Fig. 4.1 din îndrumar
Diagrama bloc a modulului timer pe 8 biți
Fig. 3 - Diagrama bloc completă a modulului timer pe 8 biți, din datasheet-ul ATmega328P. Fig. 4.2 din îndrumar
Registrul TCCR0A
Fig. 4 - Registrul TCCR0A: biții COM controlează comportamentul pinilor de ieșire, iar WGM01:WGM00 fac parte din selecția modului de generare a undei. Fig. 4.3 din îndrumar
Registrul TCCR0B
Fig. 5 - Registrul TCCR0B: biții CS02:CS00 selectează prescalerul, iar WGM02 completează configurarea modului. Fig. 4.4 din îndrumar
Observație despre numerotareFigurile din îndrumar prezintă Timer0 (8 biți), iar exemplele din această pagină folosesc Timer1 (16 biți). Structura este identică - se schimbă doar cifra din numele regiștrilor (TCCR0A → TCCR1A) și domeniul numărătorului. Timer1 este preferat pentru că nu interferează cu millis().

PWM la nivel de registru - ce face analogWrite()

Funcția analogWrite() ascunde configurarea a trei regiștri. Scrisă explicit, aceeași operație arată astfel - și devine complet controlabilă: puteți alege frecvența, rezoluția și polaritatea, lucruri imposibile prin bibliotecă.

pwm_registri.ino - PWM rapid pe 8 biți, pinul 9
void pwmInit() {
  DDRB |= (1 << PB1);              // pinul 9 (OC1A) ca iesire

  // COM1A1 = 1, COM1A0 = 0  -> iesire necinversata: HIGH la start, LOW la compare
  TCCR1A |= (1 << COM1A1);
  TCCR1A &= ~(1 << COM1A0);

  // WGM13:WGM10 = 0101  -> Fast PWM pe 8 biti (TOP = 255)
  TCCR1A |=  (1 << WGM10);
  TCCR1A &= ~(1 << WGM11);
  TCCR1B |=  (1 << WGM12);
  TCCR1B &= ~(1 << WGM13);

  // prescaler 64 -> f_PWM = 16 MHz / (64 * 256) = 976 Hz
  TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));   // curata campul
  TCCR1B |=   (1 << CS11) | (1 << CS10);                   // scrie 011
}

void pwmSet(uint8_t duty) {
  OCR1A = duty;                    // 0 = mereu stins, 255 = aproape mereu aprins
}

void setup() {
  pwmInit();
  pwmSet(128);                     // 50% - echivalent cu analogWrite(9, 128)
}

void loop() { }
Observați tiparul „curăță apoi scrie"Prescalerul ocupă trei biți, deci nu se poate seta cu un simplu |= - ar rămâne activi și biții anteriori. Se șterge întâi tot câmpul cu &= ~(...), apoi se scrie valoarea dorită. Este idiomul standard pentru orice câmp de mai mulți biți, explicat pe larg în Laboratorul 1B.
De ce contează controlul frecvențeianalogWrite() impune 490 Hz pe pinii 9 și 10. Pentru comanda unui motor, această frecvență cade în banda audibilă și produce un fluierat supărător. Prin configurare directă puteți urca la peste 20 kHz, deasupra pragului de auz - o cerință frecventă în practică, imposibil de îndeplinit cu biblioteca.
Montaj practic pentru un ceas digital cu afișare pe LCD
Fig. 6 - Montajul aplicației propuse în îndrumar: un ceas digital, în care baza de timp de o secundă este generată de întreruperea de comparare a timerului, iar ora se afișează pe LCD. Fig. 4.10 din îndrumar

Portarea configurării unui timer

EtapăAVR / ATmega328PSTM32ESP32
Activarea ceasuluiimplicităRCC->APB1ENR |= TIM2ENgestionată de SDK
PrescalerTCCR1B, biții CS12:CS10TIMx->PSC (valoare directă)divizor în configurare LEDC
Valoare de comparareOCR1ATIMx->CCR1ledc_set_duty()
Perioadă / TOPICR1 sau fixTIMx->ARRrezoluție în biți
Activare întrerupereTIMSK1 |= (1<<OCIE1A)TIMx->DIER |= CC1IEcallback înregistrat
Conceptele nu se schimbăPrescaler, valoare de comparare, mod de numărare, întrerupere la egalitate - toate există pe orice microcontroler. Se schimbă doar numele regiștrilor și faptul că, pe controlerele moderne, prescalerul este o valoare numerică directă în loc de o combinație de trei biți.

6Cod sursă

Bază de timp de 1 secundă în mod CTC

timer_ctc_1s.ino
volatile bool secundaTrecuta = false;

ISR(TIMER1_COMPA_vect) {      // se execută exact la fiecare secundă
  secundaTrecuta = true;
  PORTB ^= (1 << PB5);        // comută LED-ul de pe pinul 13
}

void setup() {
  DDRB |= (1 << PB5);

  cli();                      // oprește întreruperile pe durata configurării
  TCCR1A = 0;                 // curăță registrele
  TCCR1B = 0;
  TCNT1  = 0;                 // resetează numărătorul

  OCR1A = 62499;              // (16 000 000 / 256) - 1 = 1 secundă
  TCCR1B |= (1 << WGM12);     // mod CTC
  TCCR1B |= (1 << CS12);      // prescaler 256
  TIMSK1 |= (1 << OCIE1A);    // activează întreruperea de comparare
  sei();                      // reactivează întreruperile

  DDRB |= (1 << PB4);         // pinul 12: martor pentru bucla principală
}

void loop() {
  if (secundaTrecuta) {
    secundaTrecuta = false;
    PORTB ^= (1 << PB4);      // dovada că bucla a primit evenimentul
  }
}

Temporizare neblocantă - alternativa la delay()

delay() oprește complet programul. Cu millis() se pot rula mai multe activități cu perioade diferite, în paralel:

multitasking_millis.ino
const uint8_t LED_1 = 8, LED_2 = 9;
unsigned long ultimul1 = 0, ultimul2 = 0;
const unsigned long PERIOADA_1 = 250;   // ms
const unsigned long PERIOADA_2 = 1000;  // ms

void setup() {
  pinMode(LED_1, OUTPUT);
  pinMode(LED_2, OUTPUT);
}

void loop() {
  unsigned long acum = millis();

  if (acum - ultimul1 >= PERIOADA_1) {
    ultimul1 = acum;
    digitalWrite(LED_1, !digitalRead(LED_1));
  }

  if (acum - ultimul2 >= PERIOADA_2) {
    ultimul2 = acum;
    digitalWrite(LED_2, !digitalRead(LED_2));
  }

  // bucla rămâne liberă pentru alte sarcini
}

Control PWM al intensității

Factorul de umplere este comandat aici direct din program, printr-o rampă. La lucrarea a cincea îl veți înlocui cu valoarea citită de la un potențiometru, prin convertorul analog-numeric.

pwm_rampa.ino
const uint8_t PIN_LED = 9;    // pin PWM (Timer1)

void setup() {
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
  // creste treptat: LED-ul se aprinde lin
  for (uint16_t duty = 0; duty <= 255; duty += 5) {
    analogWrite(PIN_LED, duty);
    delay(25);
  }

  // scade treptat: LED-ul se stinge lin
  for (uint16_t duty = 255; duty > 0; duty -= 5) {
    analogWrite(PIN_LED, duty);
    delay(25);
  }
}
De reținutPinul nu produce niciodată o tensiune intermediară: el comută mereu între 0 și 5 V. Ochiul este cel care mediază, pentru că frecvența este mult peste pragul la care poate distinge clipirile. Un voltmetru legat la pin ar arăta însă exact media, adică duty/255 × 5 V.

7Montaj interactiv

Un timer este un numărător care merge singur. Montajele de mai jos vă arată amândouă utilizările lui: generarea unui semnal PWM (unde vedeți unda și factorul de umplere) și măsurarea unui interval exact în modul CTC. Schimbați prescalerul din regiștri și priviți cum se modifică frecvența.

PWM: factorul de umplere comandă intensitatea LED-ului
#include <avr/io.h>
#include <util/delay.h>

/* OCR1A hotaraste cat timp din perioada iesirea sta in 1.
   OCR1A = 0   -> LED stins;  OCR1A = 255 -> LED la maxim. */

int main(void)
{
    DDRB |= (1 << PB1);        // pinul 9 = OC1A, iesire

    /* PWM rapid pe 8 biti, TOP = 255, iesire neinversata pe OC1A */
    TCCR1A = (1 << COM1A1) | (1 << WGM10);
    TCCR1B = (1 << WGM12)  | (1 << CS11);   // prescaler 8

    unsigned int d;

    while (1) {
        for (d = 0; d <= 255; d += 5) {     // creste treptat
            OCR1A = d;
            _delay_ms(25);
        }
        for (d = 255; d > 0; d -= 5) {      // scade treptat
            OCR1A = d;
            _delay_ms(25);
        }
    }
}
De încercatPriviți unda din panoul PWM în timp ce OCR1A urcă și coboară: perioada rămâne aceeași, se schimbă doar lățimea impulsului - iar LED-ul urmează, deși pinul comută mereu doar între 0 și 5 V. Opriți execuția și scrieți direct o valoare în OCR1A din panoul de regiștri. Apoi schimbați CS11 în CS12 (prescaler 256) și priviți frecvența afișată sub undă: scade de 32 de ori, iar LED-ul începe să pâlpâie vizibil.
Exercițiu - o secundă exactă în mod CTC
#include <avr/io.h>
#include <avr/interrupt.h>

/* Formula:  OCR1A = F_CPU / (prescaler * frecventa_dorita) - 1
   La 16 MHz, prescaler 1024 si o comutare pe secunda:
       OCR1A = 16000000 / (1024 * 1) - 1 = 15624              */

ISR(TIMER1_COMPA_vect)
{
    PORTB ^= (1 << PB5);
}

int main(void)
{
    DDRB |= (1 << PB5);

    TCCR1A = 0;
    TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10);   // CTC, prescaler 1024
    OCR1A  = 15624;
    TIMSK1 = (1 << OCIE1A);
    sei();

    /* Exercitiu: modificati OCR1A astfel incat LED-ul sa comute
       la fiecare 250 ms. Calculati valoarea, nu o ghiciti.
       Verificati pe osciloscop: la viteza 4x, o perioada completa
       trebuie sa dureze 500 ms.                                  */

    while (1) { }
}
De încercatAm pus viteza pe 4× ca să nu așteptați. Osciloscopul afișează timpul simulat real, deci măsurătoarea rămâne corectă. Dacă puneți în OCR1A o valoare mai mare de 65535, ea nu încape în registrul pe 16 biți - încercați și vedeți ce iese.

8Sarcini de lucru

  • Configurați Timer1 în mod CTC pentru a genera o întrerupere la fiecare secundă și verificați precizia cu un cronometru pe 60 de secunde.
  • Calculați și verificați valorile OCR1A pentru perioade de 0,5 s, 2 s și 5 s. Ce se întâmplă la 5 s cu prescaler 256?
  • Realizați un program care aprinde două LED-uri cu perioade diferite (250 ms și 1 s), fără a folosi delay().
  • Comandați intensitatea unui LED prin PWM, cu factorul de umplere dat de un potențiometru.
  • Măsurați frecvența semnalului PWM pe pinul 9 și comparați cu valoarea teoretică de 490 Hz.
  • Generați un efect de „respirație”: intensitatea LED-ului crește și scade lin, continuu.

9Aplicație de aprofundare

ExtindereRealizați un cronometru digital cu afișare pe serial, care numără minute, secunde și zecimi de secundă, folosind exclusiv întreruperea de timer (fără millis()). Adăugați un buton pe INT0 pentru start / stop și unul pe INT1 pentru resetare.

10Întrebări de verificare

11Resurse