În acest laborator vom învăța ce este și cum funcționează un timer/counter și vom adăuga un astfel de modul implementării noastre a microcontroller-ului ATtiny20.
În esență, un timer este un simplu numărător implementat în hardware:
reg [7:0] counter; always @(posedge clk) if (reset) counter <= 0; else counter <= counter + 1;
Datorită faptului că un timer e implementat în hardware, incrementarea lui este complet transparentă pentru programator, permițându-i acestuia să execute alte task-uri.
Un bun exemplu de utilizare a unui timer este păstrarea evidenței timpului într-o aplicație. Putem configura un “stopwatch” care să declanșeze un eveniment ce va trebui tratat de software. Astfel, putem deduce că este foarte util ca un timer să poată genera o întrerupere, adică o funcție care se execută atunci când un anumit eveniment hardware are loc (exemplu: counter-ul a ajuns la o valoare prag stabilita sau la punctul de overflow).
În laboratorul de astăzi vom aprofunda conceptul de timere (fără întreruperi).
În principiu, orice timer ar trebui să aibă următoarele caracteristici:
Pulse-width modulation este o tehnică de generare a unui semnal analogic pornind de la unul digital (adică de a înlocui un DAC). Fiindcă logica digitală nu poate genera la ieșire decât tensiuni de Vcc sau de GND (ideal vorbind), PWM își propune să sintetizeze tensiuni cuprinse între aceste valori prin comutarea foarte rapidă între cele două.
Spre exemplu, presupunând că avem o tensiune Vcc de 5V și GND de 0V, dacă am comuta foarte repede între 0V și 5V ieșirea, menținând procentele egale de timp între cele două nivele logice (adică la fel de mult pe 1 ca și pe 0), atunci am putea spune că, în medie, tensiunea noastră de ieșire este de 2.5V. Asemănător, dacă am păstra aceeași frecvență de comutație, însă am tine ieșirea 75% din timp pe 1 și 25% din timp pe 0, atunci am putea spune că avem o ieșire cu o tensiune medie de 0.75 * 5V + 0.25 * 0V = 3.75V.
Putem spune, așadar, că PWM se traduce prin modulare în factor de umplere (factor de umplere înseamnă raportul dintre perioada cât stă semnalul pe 1 și perioada totală). Prin urmare tensiunea rezultată dintr-un semnal PWM este direct proporționala cu factorul de umplere. Astfel putem, plecând de la semnale digitale, să generăm semnale analogice. In cazul unui LED, spre exemplu, actionandu-l prin PWM putem sa-l aprindem/stingem gradual, dand impresia ca isi modifica, de fapt, intensitatea.
Un timer poate continua să numere după ce generează un eveniment. Grafic, este această imagine (în desen, valoarea la care se generează evenimentul este denumită “Compare”):
Asociat numărătorului se afla un output:
assign out = (counter >= compare);
Putem observa că factorul de umplere al acestui semnal este invers proporțional cu valoarea lui Compare
. Ca atare, putem genera un semnal PWM. Cand valoarea counter-ului este mai mica decat valoarea de prag 'Compare', consideram ca semnalul PWM este pe LOW; cand este mai mare decat valoarea de prag, semnalul este pe HIGH; crescand aceasta valoare de prag, semnalul va sta mai putin in starea HIGH, ceea ce inseamna ca duty cycle va scadea.
Informațiile particulare despre timerele mictrocontrollerului ATtiny20 le putem găsi în datasheet, capitolele 11 și 12. Noi vom implementa doar Timer/Counter0
.
Timerele ATtiny20 au 3 registre foarte importante:
Este registrul cu rolul de contor.
Aceste două registre conțin fiecare câte o valoare de comparație între BOTTOM și MAX. Atunci când TCNT0 == OCR0A
sau TCNT0 == OCR0B
se pot întâmpla mai multe lucruri, în funcție de configurare:
Evenimentul generat atunci când TCNT0 == OCR0A
sau TCNT0 == OCR0B
poate fi:
Registrele OCR0A/OCR0B pot fi folosite și pentru a genera un semnal PWM dacă timer-ul este configurat să genereze un eveniment de comparație și să continue să numere, și acel eveniment este schimbarea stării pinului OC0A/OC0B. În acest caz putem varia factorul de umplere modificând valoarea registrului OCR0A/OCR0B (în imaginea din capitolul precedent, valoarea “Compare”), iar semnalul PWM va fi dispoibil pe pinul OC0A/OC0B (în imaginea din capitolul precedent, valoarea “Output”).
Fiindcă sunt două la număr înseamnă că putem genera două semnale PWM folosind un singur timer.
Acestea sunt registrele de control pentru modul de funcționare al timer-ului. În realitate, cele două alcătuiesc un singur registru de control, TCCR0, pe 16 biți. Registrul respectiv conține multe informații de configurare codificate În biții săi. O lista completă a acestora o puteți găsi în datasheet, capitolul 11.9.1 și 11.9.2 paginile 69-73, însă un rezumat scurt vom face aici.
Remarcăm faptul că registrul TCCR0A conține 3 sub-valori:
Remarcăm faptul că registrul TCCR0B conține 2 sub-valori:
Prescalerul are rolul de a diviza frecventa ceasului. Astfel, in loc sa incrementam contorul unui timer la fiecare oscilatie, putem alege sa o facem la fiecare 2/4/8/16/32/64/etc oscilatii.
După cum spuneam, timer-ul poate avea comportament diferit în funcție de configurare. Modurile de funcționare controlează direcția de numărare (crescătoare, descrescătoare sau ambele), valoarea TOP și parțial comportamentul pinilor OC0A/OC0B.
Modul de funcționare poate fi setat modificând biții WGM0 din registrele TCCR0A și TCCR0B. Ele pot fi:
WGM0 == 0
)BOTTOM == 0
) crescător până la 255 (deci până la TOP == MAX == 255
), apoi se resetează înapoi la 0WGM0 == 2
)BOTTOM == 0
) crescător până la OCR0A (deci până la TOP == OCR0A
), apoi se resetează înapoi la 0WGM0 == 3 sau WGM0 == 7
)BOTTOM == 0
) crescător până la 255, sau până la OCR0A (deci până la TOP == MAX == 255
, dacă WGM0 == 3
, sau TOP == OCR0A
, dacă WGM0 == 7
), apoi se resetează înapoi la 0Timer-ul 0 al unui microcontroller ATtiny20 se poate configura în modul normal astfel:
ldi R16, 0b00000000 ; COM0A = 0 (normal port operation, OC0A disconnected) ; COM0B = 0 (normal port operation, OC0B disconnected) ; WGM0[1:0] = 0 (normal mode operation) out TCCR0A, R16 ; Pornim timer-ul scriind o valoare diferită de 0 pe biții CS0 ldi R16, 0b00000101 ; WGM0[2] = 0 (normal mode operation) ; CS0 = 5 (clkT = clkIO/1024 from prescaler) out TCCR0B, R16
Din punctul în care am scris ceva nenul pe biții 2:0 din TCCR0B (biții CS0), timer-ul a pornit. Este important de realizat că timer-ul nu poate fi cu adevărat pornit sau oprit ci, pur și simplu, dacă CS0 este 0, atunci modulul nu primește semnal de ceas, deci contorul nu va fi incrementat. Așadar, din momentul ultimei instrucțiuni din exemplul de cod, timer-ul începe să își incrementeze contorul cu câte o unitate la fiecare 1024 de cicli de ceas.
timer_unit.v
Task 1 (2p) Construiți logica de prescaling pentru ceasul timer-ului 0.
Task 2 (3p) Decodificați biții din registrele de control TCCR0A
și TCCR0B
. Implementați logica de selecție a modului de operare și a valorii maxime de numărare (TOP). Implementați logica de incrementare a contorului.
Task 3 (3p) Implementați logica ieșirilor OC0A
și OC0B
.
Task 4 (2p) Scrieți și simulați un program în avrasm care:
fast PWM, TOP == OCR0A
clkI/O / 8 (From prescaler)
Toggle OC0A on Compare Match
Clear OC0B on Compare Match, set OC0B at BOTTOM
(non-inverting mode)
TCCR0A
, TCCR0B
, TCNT0
, OCR0A
, OCR0B
) au fost deja trecute În defines.vh
, însa pot fi găsite și în datasheet la pagina 203 dacă este nevoie de ele.