Până acum, timpul a existat doar indirect, ca durata pe care o consumă instrucțiunile executate. Aproape orice aplicație reală are însă nevoie de timp ca de o mărime pe care să o poată măsura și impune: un motor cere o putere medie reglabilă, un senzor cu ultrasunete cere lățimea unui impuls cu precizie de microsecunde, un protocol de comunicație cere eșantionarea liniei exact la mijlocul fiecărui bit. Pentru toate acestea, microcontrolerul are un bloc hardware dedicat, la bază un simplu numărător care avansează singur, în paralel cu programul - modulul de tip timer.
1Obiectul și structura cursului6 min
Un timer numără impulsuri primite pe intrarea sa de tact, fără ca procesorul să execute vreo instrucțiune pentru asta. Odată pornit, avansează indiferent dacă unitatea centrală calculează, așteaptă sau e într-un regim de consum redus - una din puținele periferice care oferă o măsură a timpului independentă de ceea ce face software-ul. Pe o placă Arduino Uno, ceasul e de 16 MHz, deci un impuls la fiecare 62,5 ns: numărul acumulat în registru se traduce direct în durată.
- Rutina de tratare trebuie scurtă, fără întârzieri, comunică prin variabile
volatile(Cursul 05) - O variabilă mai mare de un octet cere secțiune critică pentru citire atomică (Cursul 05)
- Să explicați diferența dintre regimul timer și regimul counter al aceluiași modul hardware
- Să distingeți modurile normal, CTC, PWM rapid și PWM cu fază corectă
- Să calculați, din frecvența ceasului, factorul de prescalare și valoarea de comparare, perioada sau frecvența unei întreruperi sau a unui semnal PWM
- Să explicați legătura dintre factorul de umplere și valoarea medie a unui semnal PWM
- Să explicați de ce unitatea de captură dă o precizie pe care o rutină de întrerupere obișnuită nu o poate atinge
2Timer sau counter, și de ce delay() e o soluție proastă11 min
Timer sau counter
Vorbim despre timer când semnalul de tact are o distribuție uniformă în timp (un ceas cu perioadă cunoscută) - numărul acumulat e proporțional cu timpul scurs. Vorbim despre counter (numărător de evenimente) când semnalul vine din exterior, cu distribuție aleatoare - impulsuri de la un encoder, o barieră optică. Hardware-ul e identic: pe ATmega328P, biții de selecție a sursei de tact comută același modul între cele două regimuri - de aici numele oficial, Timer/Counter.
De ce delay() este cea mai proastă soluție
delay(1000) execută o buclă care citește repetat contorul de milisecunde, ieșind
când acesta a crescut cu o mie - milioane de instrucțiuni cheltuite pentru a nu face nimic. Mai grav:
cât timp programul așteaptă, nu poate răspunde la nimic altceva - un buton apăsat și eliberat în
acest interval nu va fi observat, un caracter serial poate fi pierdut.
/* blocanta: procesorul e imobilizat 500 ms */
void loop() {
digitalWrite(LED, HIGH); delay(500);
digitalWrite(LED, LOW); delay(500);
}
/* neblocanta: bucla se parcurge de mii de ori pe secunda */
unsigned long ultimaComutare = 0;
const unsigned long PERIOADA = 500;
void loop() {
unsigned long acum = millis();
if (acum - ultimaComutare >= PERIOADA) {
ultimaComutare = acum; /* se retine momentul, nu se aduna */
stareLed = !stareLed;
digitalWrite(LED, stareLed);
}
/* aici pot fi tratate butoane, senzori, comunicatie seriala */
}
millis() returnează un contor incrementat de rutina de întrerupere a unui timer
hardware. Diferența e că programul consultă timpul în loc să îl aștepte. Etapa următoare,
tratată în acest curs, e să lăsăm timerul să anunțe singur programul, prin întrerupere, sau chiar să
genereze semnalul dorit direct la pin, fără ca procesorul să fie implicat deloc.3Clasificarea modulelor de tip timer11 min
O clasificare orientată spre funcție distinge cinci structuri, cu hardware adesea comun.
Timerul simplu numără ceasul intern și se resetează la depășire (overflow), semnalând
prin indicator și/sau întrerupere. Domeniul de numărare e [0, 2ⁿ-1] - un numărător de 8
biți la 16 MHz se umple în 16 µs, unul de 16 biți în 4,096 ms, insuficient pentru a cronometra o
secundă fără prescalare.
Numărătorul de evenimente e același modul, cu tactul comutat pe un pin extern - nu mai măsoară timp, acumulează evenimente. Combinația celor două regimuri (un timer fixează o fereastră de o secundă, un counter acumulează impulsuri în ea) dă direct frecvența unui semnal, în hertzi.
Generatorul de PWM produce, integral în hardware, un semnal cu frecvență constantă și factor de umplere reglabil - discutat pe larg mai jos.
Watchdog timer-ul numără continuu, independent de program; programul are obligația să-l reinițializeze periodic. Dacă se blochează într-o buclă infinită sau într-o întrerupere din care nu mai iese, reinițializarea nu mai are loc, iar temporizatorul generează un reset care repornește sistemul într-o stare cunoscută.
RTC-ul (Real-Time Clock) păstrează ora și data pe termen lung, la un cuarț de 32.768 Hz (=2¹⁵, astfel încât o divizare succesivă cu doi, prin 15 bistabile, dă exact un impuls pe secundă, fără eroare de rotunjire). ATmega328P nu are un RTC complet, dar poate apropia funcția cu Timer2 în regim asincron.
| Modul | Rezoluție | Factori de prescalare | Funcții de bibliotecă dependente |
|---|---|---|---|
| Timer0 | 8 biți | 1, 8, 64, 256, 1024 | millis(), micros(), delay(), analogWrite() pe pinii 5, 6 |
| Timer1 | 16 biți | 1, 8, 64, 256, 1024 | biblioteca Servo, analogWrite() pe pinii 9, 10 |
| Timer2 | 8 biți | 1, 8, 32, 64, 128, 256, 1024 | tone(), analogWrite() pe pinii 3, 11 |
Nucleul Arduino configurează Timer0 cu prescalare 64 și îi activează întreruperea de depășire,
care alimentează millis(), micros() și delay(). Dacă
programul schimbă prescalarea lui Timer0 pentru un PWM de altă frecvență pe pinul 5, obține
într-adevăr acea frecvență, dar accelerează sau încetinește simultan contorul de milisecunde - toate
temporizările bazate pe millis() devin false, fără niciun mesaj de eroare. Regula: pe
o placă Arduino, Timer0 se lasă în pace; experimentele de configurare manuală se fac pe Timer1 sau
Timer2. Doar Timer1 are unitate de captură și registre de comparare pe 16 biți - singurul potrivit
pentru măsurători fine de timp.
4Modurile de lucru: normal, CTC, PWM rapid, PWM fază corectă11 min
Comportamentul numărătorului între două reveniri la zero definește modul de lucru, ales prin biții WGM din TCCRnA/TCCRnB. Diferența privește valoarea maximă (TOP), sensul numărării și momentul comutării ieșirii.
2ⁿ-1, revine la zero și marchează
depășirea. Perioada e fixă, determinată doar de tact și rezoluție - schimbarea ei cere
preîncărcarea numărătorului în fiecare rutină, operație care introduce o eroare dependentă de
latența întreruperii.PWM rapid: numărătorul parcurge continuu 0 → TOP; ieșirea e activă la începutul ciclului, inactivă la egalitatea cu registrul de comparare. Numărătorul parcurge domeniul o singură dată pe perioadă, deci frecvența e cea mai mare posibilă pentru o rezoluție dată.
PWM cu fază corectă: numărătorul urcă la TOP, apoi coboară la zero; ieșirea comută la fiecare trecere prin valoarea de comparare, o dată pe fiecare rampă. Impulsul rezultat e centrat pe mijlocul perioadei, indiferent de factorul de umplere - de aici numele: faza nu se deplasează la modificarea lățimii impulsului. Prețul: perioada devine aproximativ dublă, frecvența se înjumătățește față de PWM rapid.
5Modalități de accesare a datelor din timer9 min
Există trei căi prin care software-ul intră în contact cu modulul de timp.
Citirea directă a registrului de contor
uint8_t valoare8 = TCNT0; /* numaratorul de 8 biti al Timer0 */
uint16_t valoare16 = TCNT1; /* numaratorul de 16 biti al Timer1 */
Nu perturbă numărătorul - o simplă fotografie. Utilă pentru cronometrarea unei secvențe de cod (citire înainte și după) sau cronometrări scurte, fără depășire.
TCNT1 se citește prin două instrucțiuni succesive (octet inferior, apoi superior). Hardware-ul rezolvă parțial problema printr-un registru tampon: la citirea octetului inferior, octetul superior e copiat automat într-un tampon, citit imediat după - dar acest mecanism funcționează doar dacă cele două citiri nu sunt separate de o întrerupere care accesează la rândul ei un alt registru de 16 biți al aceluiași timer, pentru că tamponul e unic. Accesul din programul principal la un registru de 16 biți al lui Timer1, dacă o întrerupere accesează același timer, trebuie protejat prin dezactivarea temporară a întreruperilor.
Interogarea indicatorilor de stare
while (!(TIFR1 & (1<<TOV1))) { ; } /* bucla goala: se pierd cicluri */
TIFR1 = (1 << TOV1); /* stergerea se face scriind 1 */
Precizie maximă (reacție în câteva cicluri, fără latența unei întreruperi), dar cât timp bucla se
învârte, procesorul nu poate face nimic altceva - exact defectul lui delay(), mai vizibil.
Utilă în secvențe scurte și critice care nu pot accepta latența unei întreruperi.
Folosirea întreruperilor - modalitatea preferată
Trei condiții, toate obligatorii: activarea individuală a sursei (TIMSK0/1/2), activarea globală
(sei()), și o rutină scrisă pentru vectorul corect - un vector fără rutină duce la
resetarea microcontrolerului. Trei categorii de întreruperi de timer: Overflow (la depășire),
Compare Match (la egalitatea cu registrul de comparare, echidistante în modul CTC) și
Input Capture (la un front pe pinul de captură, cu memorare automată a numărătorului - secțiunea
finală a cursului).
6Circuitul de prescalare și formulele de bază10 min
Un numărător de 8 biți alimentat direct cu ceasul de 16 MHz se depășește la fiecare
256×62,5 ns = 16 µs, adică de 62 500 de ori pe secundă - practic inutilizabil pentru
temporizări, iar la fiecare depășire procesorul consumă cicluri pentru context. Soluția: circuitul
de prescalare.
Prescalerul e un lanț de bistabile care împart succesiv frecvența la doi; un multiplexor alege
etajul. Valorile disponibile sunt de forma 2^P: pentru Timer0 și Timer1,
1, 8, 64, 256, 1024; pentru Timer2, suplimentar 32, 128.
T_tick = N/f_clk.Mod CTC, perioada evenimentelor:
T = N·(OCRnA+1)/f_clk, deci
f = f_clk / (N·(OCRnA+1)).Valoarea de comparare necesară unei perioade impuse:
OCRnA = f_clk/(N·f) - 1.Un factor mare prelungește durata până la depășire (temporizări lungi cu un numărător mic), dar înrăutățește rezoluția: dacă un impuls durează 64 de perioade de ceas, momentele nu se pot distinge decât cu o precizie de 64 de perioade (4 µs la 16 MHz). Regula practică: cel mai mic factor de prescalare care mai acoperă intervalul dorit.
ATmega328P, 16 MHz. Configurați un timer pentru o întrerupere la fiecare milisecundă.
Vezi rezolvarea
Impulsuri de ceas într-o milisecundă: f_clk/f = 16×10⁶/1000 = 16000.
Fără prescalare (N=1): OCRnA = 16000-1 = 15999 - nu încape în 8 biți, dar încape
confortabil în cei 16 biți ai lui Timer1 (rezoluție maximă, 62,5 ns).
Cu un timer de 8 biți, OCRnA ≤ 255: se caută cel mai mic N cu 16000/N - 1 ≤ 255, adică
N ≥ 62,5. Din listă, primul care satisface: N = 64.
OCRnA = 16×10⁶/(64×1000) - 1 = 250 - 1 = 249 - întreg, încape în 8 biți, perioadă exactă,
fără eroare. Verificare: 250 × 4 µs = 1000 µs. ✓
7Aplicație: o bază de timp de o milisecundă6 min
volatile uint32_t milisecunde = 0;
void configurareBazaDeTimp(void) {
cli();
TCCR1A = 0x00;
TCCR1B = (1 << WGM12); /* mod CTC, TOP dat de OCR1A */
OCR1A = 249; /* 16 MHz / (64*250) = 1000 Hz */
TCCR1B |= (1<<CS11)|(1<<CS10); /* prescaler 64: porneste numaratorul */
TIMSK1 = (1 << OCIE1A);
TCNT1 = 0;
sei();
}
ISR(TIMER1_COMPA_vect) { milisecunde++; }
8Caracteristicile semnalelor periodice8 min
Un semnal x(t) e periodic, de perioadă T, dacă x(t) = x(t+T) pentru
orice t. Frecvența e numărul de repetări pe secundă: f = 1/T.
Pentru un semnal dreptunghiular, factorul de umplere (duty cycle) e raportul dintre durata
stării active T_on și perioadă: FU = (T_on/T)×100 [%]. 0% = permanent inactiv, 100% =
permanent activ, 50% = simetric.
V_med = Vcc × FU/100. La un motor CC, inerția
mecanică și inductanța înfășurării netezesc curentul - turația se reglează continuu prin factorul de
umplere. La un LED, ochiul integrează lumina pe ~20 ms - peste ~100 Hz, comutările devin invizibile
(sub acest prag apare pâlpâirea; de aceea frecvențele PWM implicite Arduino, ~490 și ~980 Hz, sunt
peste prag).Aceeași reducere de putere s-ar putea obține cu un rezistor serie sau un tranzistor în regim liniar, dar diferența de putere s-ar transforma în căldură. Comutatorul PWM e fie complet deschis (curent nul), fie complet închis (cădere de tensiune aproape nulă) - puterea disipată pe el e mică în ambele stări. De aceea PWM reglează puteri de zeci sau sute de wați cu un tranzistor care abia se încălzește. Important: modularea în durată nu schimbă amplitudinea, doar valoarea medie - sarcina trebuie să aibă inerție suficientă pentru a o media; aplicat unei sarcini rapide, fără filtrare, un semnal PWM rămâne o succesiune de impulsuri complete.
9PWM rapid și PWM cu fază corectă9 min
În PWM rapid, numărătorul crește continuu 0→TOP; pinul devine activ la revenirea la zero, inactiv la egalitatea cu registrul de comparare.
f_PWM = f_clk / (N·(TOP+1)).Factor de umplere:
FU = (OCRnx+1)/(TOP+1) × 100%.Rezoluție (trepte):
R = log₂(TOP+1) biți.Cu TOP=255, N=64, f_clk=16 MHz:
f_PWM ≈ 976,6 Hz - exact frecvența
analogWrite() pe pinii 5 și 6 ai Unoului, cu 8 biți de rezoluție (256 de trepte).Produsul dintre frecvența PWM și numărul de trepte e constant, egal cu f_clk/N - nu
se poate obține simultan frecvență mare și rezoluție mare fără a mări ceasul sau a reduce
prescalarea.
În PWM cu fază corectă, numărătorul parcurge domeniul de două ori pe perioadă (urcă,
coboară): f_PWM = f_clk / (2·N·TOP). Pe modulele AVR de 8 biți, numărul de impulsuri pe
perioadă e 510 (=2×255) - cu N=64, rezultă ≈490,2 Hz, frecvența
analogWrite() pe pinii 3, 9, 10, 11: aproximativ jumătate din PWM rapid, la aceeași
rezoluție - prețul plătit pentru fixarea fazei.
10Implementarea PWM și legătura cu analogWrite()9 min
void configurarePWM2(void) {
DDRB |= (1 << PB3); /* OC2A = pinul 11 pe Uno */
/* WGM21:WGM20=11 -> Fast PWM, TOP=0xFF; COM2A1:COM2A0=10 -> neinversat */
TCCR2A = (1<<WGM21)|(1<<WGM20)|(1<<COM2A1);
TCCR2B = (1 << CS22); /* prescaler 64: 16MHz/(64*256) = 976,6 Hz */
OCR2A = 25; /* 26/256 ~ 10% umplere */
}
void setFactorUmplere(uint8_t procent) {
OCR2A = (uint16_t)procent * 255 / 100;
}
Biții COM2A1:COM2A0 aleg ieșire neinversată (activ de la începutul perioadei până la egalitate), inversată (pereche complementară, sarcină comandată pe nivel jos), sau deconectată (pinul rămâne GPIO obișnuit, dar modulul continuă să numere și poate genera întreruperi).
Ce face analogWrite()
Identifică timerul asociat pinului cerut, activează blocul de comparare (biții COM) și scrie argumentul în registrul de comparare - numele e nefericit ales: ieșirea nu e analogică, ci digitală, comutând între 0 V și 5 V, doar valoarea medie variază continuu. Argumentul are 8 biți pentru că registrele de comparare ale timerelor de 8 biți au 8 biți - o limitare a hardware-ului, nu a funcției. Frecvența nu poate fi schimbată prin analogWrite() (nu atinge biții de prescalare). Doar șase pini ai Unoului o suportă, pentru că doar șase ieșiri de comparare există în hardware.
Dacă valoarea de comparare ar fi modificată exact când numărătorul tocmai a depășit-o, impulsul curent ar fi deformat, ar putea lipsi sau s-ar putea prelungi peste o perioadă întreagă. Modulele AVR folosesc registre-tampon: valoarea scrisă e reținută și transferată în registrul activ doar la începutul unei noi perioade - mecanismul elimină cele mai multe erori de sincronizare, dar nu toate; în modurile cu TOP variabil, o modificare la moment nepotrivit poate încă produce un ciclu de durată greșită.
11Capturarea evenimentelor externe13 min
Problema inversă generării: determinarea momentului exact la care s-a produs un eveniment extern, rezolvată de unitatea de captură (input capture).
Pinul ICP1 (pinul digital 8 pe Uno) e supravegheat permanent de hardware. La un front de sensul configurat, valoarea instantanee a numărătorului e copiată automat în ICR1, se activează indicatorul ICF1 și, dacă validată, se generează o cerere de întrerupere.
Regula pentru alegerea prescalerului: eroarea de datare crește direct cu factorul de prescalare -
pentru capturi se alege cel mai mic prescaler posibil care nu depășește domeniul de măsurare
(Δt_max = (2^r - 1)·N/f_clk).
Senzorul emite un tren de ultrasunete și activează o linie de ieșire exact atât timp cât durează
călătoria dus-întors a undei. Distanța: d = v_sunet·Δt/2, cu v_sunet ≈ 343 m/s.
ISR(TIMER1_CAPT_vect) {
if (TCCR1B & (1<<ICES1)) { /* front crescator */
captura1 = ICR1;
TCCR1B &= ~(1<<ICES1); /* urmatorul: descrescator */
} else { /* front descrescator */
latime = ICR1 - captura1;
TCCR1B |= (1<<ICES1);
gata = 1;
}
TIFR1 = (1 << ICF1);
}
Soluția uzuală de început, pulseIn(), blochează procesorul zeci de milisecunde și e
afectată de orice întrerupere survenită în interval; cu unitatea de captură, măsurătoarea se face în
hardware, programul pierde doar câteva microsecunde ale celor două rutine.
Vezi calculul de rezoluție și domeniu (prescaler 8)
T_tick = 8/16×10⁶ = 0,5 µs. Un impuls corespunde Δd = 343×0,5×10⁻⁶/2 ≈ 0,086
mm - mult sub precizia proprie a senzorului, deci rezoluția temporală nu e factorul
limitativ.
Domeniul maxim: Δt_max = 65535×0,5 µs ≈ 32,8 ms, adică d_max ≈ 5,6 m -
peste domeniul util al senzorului (câțiva metri). Prescalerul 8 e potrivit: rezoluție inutil de bună,
domeniu suficient. Cu prescaler 1, domeniul ar scădea la 4,1 ms (~70 cm) - insuficient.
ICR1 - captura1 pe aritmetică fără semn rămâne corectă chiar dacă numărătorul a
trecut prin zero între cele două capturi. ICF1 se șterge explicit după comutarea sensului
frontului, pentru că operația de comutare poate ea însăși activa indicatorul, producând o
întrerupere falsă imediat după ieșirea din rutină.
12Erori frecvente4 min
- „Pe o placă Arduino pot reconfigura orice timer, inclusiv Timer0, fără efecte secundare." Fals - Timer0 alimentează millis(), micros() și delay(). Reconfigurarea prescalerului lui schimbă toate temporizările bazate pe millis(), fără niciun mesaj de eroare vizibil. Experimentele de configurare manuală a timerelor se fac pe Timer1 sau Timer2, nu pe Timer0.
- „analogWrite() dă o ieșire analogică reală." Fals - ieșirea comută digital între 0 V și 5 V; doar valoarea ei medie, percepută de o sarcină cu inerție, variază continuu odată cu factorul de umplere. Tratați PWM ca pe un semnal digital cu factor de umplere variabil, nu ca pe o tensiune analogică reală.
- „Pentru a măsura lățimea unui impuls, citesc numărătorul chiar în rutina întreruperii externe declanșate de front." Fals - valoarea citită e afectată de latența întreruperii (terminarea instrucțiunii curente, salvarea contextului), care variază cu zeci de cicluri și nu poate fi compensată. Folosiți unitatea de captură (Timer1 + ICP1), care memorează numărătorul în hardware, exact la momentul frontului.
13Rezumat și glosar5 min
Un timer e, la bază, un numărător hardware alimentat printr-un prescaler dintr-o sursă de tact stabilă. Trei mecanisme derivă din el: comparând numărătorul cu un prag și ștergându-l automat (mod CTC), se obțin întreruperi periodice exacte, care înlocuiesc delay(); comandând un pin în funcție de aceeași comparație, se obține un semnal PWM cu factor de umplere reglabil, cu randament mult mai bun decât reducerea tensiunii cu un rezistor serie; memorând numărătorul la un front extern (input capture), se obține datarea exactă a evenimentelor, cu o precizie pe care software-ul nu o poate atinge. Perioada sau frecvența obținută rezultă întotdeauna din trei numere - frecvența ceasului, factorul de prescalare, valoarea de comparare - și alegerea lor e un compromis între rezoluție și domeniu. Modulele de timp sunt resurse partajate cu bibliotecile Arduino: orice reconfigurare trebuie făcută știind ce funcții încetează, în acel moment, să mai spună adevărul.
14Întrebări de verificare7 min
- Explicați deosebirea dintre regimul de timer și cel de counter al aceluiași modul hardware. Ce se modifică în configurare la trecerea de la unul la celălalt?
- De ce e delay() o risipă de resurse? Descrieți concret ce se pierde în timpul așteptării.
- Un numărător de 8 biți e alimentat direct cu ceasul de 16 MHz. După cât timp se produce depășirea, și de câte ori pe secundă ar fi apelată rutina de întrerupere?
- De ce factorii de prescalare disponibili sunt puteri ale lui doi, nu valori arbitrare? Ce se pierde la un factor de prescalare mai mare decât e necesar?
- Se cere o întrerupere la fiecare 10 ms, cu Timer1 la 16 MHz, în mod CTC. Calculați OCR1A pentru un factor de prescalare de 64 și verificați dacă rezultatul e realizabil.
- Comparați modul normal cu modul CTC din punctul de vedere al preciziei intervalelor generate.
- Un semnal PWM are perioada 2 ms și starea activă 0,4 ms. Calculați frecvența, factorul de umplere, și tensiunea medie la o amplitudine de 5 V.
15Direcții de aprofundare2 min
Cursul următor folosește exact același mecanism de comparare al unui timer, aplicat problemei inverse celei de generare - transformarea unei tensiuni analogice continue într-un număr: convertorul analog-numeric.
Formulele de prescalare, generarea de PWM și captura evenimentelor din acest curs devin montaj real în Laboratorul 04.