Capitole utile din Datasheet ATmega324P
PWM (Pulse Width Modulation) este o tehnică folosită pentru a varia în mod controlat tensiunea dată unui dispozitiv electronic.
Această metodă schimbă foarte rapid tensiunea oferită dispozitivului respectiv din ON în OFF și invers (treceri rapide din HIGH în LOW, spre exemplu: 5V - 0V). Raportul dintre perioada de timp corespunzătoare valorii ON și perioada totală dintr-un ciclu ON-OFF se numește factor de umplere (duty cycle) și reprezintă, în medie, tensiunea pe care o va primi dispozitivul electronic.
Practic, se pot controla circuite analogice din domeniul digital. Spre exemplu, unui LED i se va putea controla intensitatea luminoasă, la o serie de LED-uri RGB se va putea controla precis chiar și culoarea, unui speaker – frecvența sunetului redat la un moment de timp, unui motor – viteza de rotație etc.
Factorul de umplere se exprimă în procente și reprezintă cât la sută din perioada unui semnal acesta va fi pe nivelul ON. În figura de mai jos, se pot observa semnale PWM cu factori de umplere diferiți. Astfel, se poate deduce foarte ușor formula pentru a obține valoarea factorului de umplere (D):
$D[\%] = \frac{t\_on}{t\_on + t\_off} \cdot 100 = \frac{pulse\_width}{period} \cdot 100$
Astfel, tensiunea medie care ajunge la dispozitiv este dată de relația: D * Vcc.
Modularea folosește variația factorului de umplere a unui semnal dreptunghiular pentru a genera la ieșire o tensiune analogică. Considerând o formă de undă dreptunghiulară f(t) cu o valoare minimă ymin=0 și o valoare maximă ymax și factorul de umplere D (ca în figură) valoarea medie a formei de undă e dată de relația:
$\bar{y} = D \cdot ymax$
Multe circuite digitale pot genera semnale PWM. Majoritatea microcontroller-elor oferă această facilitate, pe care o implementează folosind un numărător care este incrementat periodic (conectat direct sau indirect la o unitate de ceas) și care este resetat la sfârșitul fiecărei perioade a PWM-ului. Când valoarea numărătorului este mai mare decât valoarea de referință, ieșirea PWM (output-ul) trece din starea HIGH în starea LOW (sau invers).
În cadrul laboratorului anterior, am văzut că microcontroller-ul Atmega324 are 3 timere: Timer0 (pe 8 biți), Timer1 (pe 16 biți) și Timer2 (pe 8 biți).
Fiecare dintre aceste timere putea fi configurat în diferite moduri prin intermediul registrelor TCCRnA și TCCRnB, cu ajutorul biților WGMnx
. Printre aceste moduri se numărau:
La modurile de lucru cu PWM, timer-ul va folosi unul dintre comparatoarele atașate (cu valorile din registrele OCRnA
/ OCRnB
) pentru a stabili valoarea unui pin de output (fiecare timer are câte 2 canale de output denumite: OCnA
și OCnB
).
Pentru a vedea exact ce pini are asociați ca output unui timer, vedeți capitolul Pin Configurations din Datasheet-ul ATmega324P:
Ne vom ocupa în principal de modul Fast PWM care este utilizat pentru majoritatea aplicațiilor, mai puțin cele în care este nevoie de un control precis (de exemplu motoare BLDC, audio).
La modul Fast PWM, numărarea se face doar pe frontul crescător al semnalului de ceas. Modificarea factorului de umplere se realizează instant, în schimb semnalul nu este centrat (este defazat, practic apare un “glitch” la modificarea semnificativă a factorului de umplere).
Există mai multe moduri de Fast PWM oferite de către microcontroller. De exemplu, pentru Timer-ul 1 avem:
De exemplu, presupunem că avem Timer1 configurat pe modul de funcționare Fast PWM (modul de funcționare nu trebuie neapărat să conțină cuvântul 'PWM' ca să poată fi folosit pentru generarea unui semnal). Fast PWM este caracterizat de o frecvență fixă și un prag stabilit de programator, ce poate fi modificat în timpul execuției.
1 0
pentru biții COM1A1 COM1A0
va lăsa semnalul de pe pinul OC1A
pe 1 în timpul numărătorii (până la atingerea pragului) și va pune semnalul pe 0 de la atingerea pragului până la capătul unui ciclu (numărare completă până la TOP).OCR1A = x * TOP / 100
(pentru Fast PWM, TOP poate fi 0xFF, 0x1FF sau 0x3FF, în funcție de configurația aleasă)
Exemplu de inițializare a Timer1 in modul Fast PWM 8-bit non-inverting, cu Prescaler la valoarea 1024 (frecvența PWM-ului va fi de 12MHz / 1024 / 255 ~= 45 Hz
):
/* OC1A is PD5, must be set to OUTPUT */ DDRB |= (1 << PD5); /* We must choose Fast PWM 8-bit, WGMn[3:0] = 0b0101 */ /* Note: WGM11 and WGM10 are on TCCR1A, while WGM12 and WGM13 are on TCCR1B! */ TCCR1A = (1 << WGM10); TCCR1B = (1 << WGM12); /* set COM1A to non-inverting output: COM1A[1:0] = 0b10 */ TCCR1A |= (1 << COM1A1); /* Use 1024 prescaler: CS1[2:0] = 0b101 */ TCCR1B |= (1 << CS12) | (1 << CS10); /* set duty cycle to 50%; note that we're using 8-bit mode, so TOP = 255 */ OCR1A = 127;
Deși nu îl vom folosi la laborator, este util de știut diferențele între modul de PWM rapid și acesta care poate fi mai precis (necesar în unele aplicații, precum controlarea motoarelor).
Principala diferență între acest mod și cel de Fast PWM este faptul că timer-ul va porni de la BOTTOM (i.e., poziția zero), va urca până la TOP (valoarea maximă) apoi va număra, descrescător, înapoi la BOTTOM. Comparatorul va da semnalul de output după aceiași regulă ca la Fast PWM, în funcție de rezultatul comparării cu registrul OCRnx
. Însă cea mai importantă diferență este faptul că valoarea lui OCRnx
rămâne salvată pe toată durata unui ciclu complet, astfel se păstrează durata semnalului ON pe toată perioada T_on + T_off și rezultă un semnal “curat”, fără decalaje ale frecvenței de PWM (de exemplu, unele frecvențe nedorite ar putea avea pierderi de energie în motoare DC).
Comparație între Fast PWM și Phase Correct PWM:
Obiectivul exercițiilor este să controlăm culoarea unui LED RGB cu ajutorul PWM. Cu acest LED se poate obține orice culoare printr-o combinație de intensități pe fiecare diodă în parte!
Cele trei LED-uri sunt conectate la:
PD5
are funcția OC1A
(asociat Timer-ului 1);PD7
are funcția OC2A
(asociat Timer-ului 2);PB3
are funcția OC0A
(asociat Timer-ului 0);Reminder: LED-urile sunt conectate in modul anod comun / “active-low” (LED-ul este aprins atunci cand pinul aferent este LOW, si stins atunci cand pinul este HIGH).
Task 0:
Task 1:
Task 2:
PD7
are outputul asociat timerului 2, însă acesta este deja folosit pentru a număra milisecundele de la momentul pornirii MCU-ului (system ticks)…0
la valoarea 188
apoi face Clear (executând întreruperea COMPA
– care ne incrementează systicks
');COMPB
) pentru a genera o întrerupere (TIMER2_COMPB
) la o valoare prag între 0 – 188
(deci valoarea medie pentru 50% va fi 94
);A
(i.e., atunci când se resetează timerul) iar, la întreruperea comparatorului B
, va trebui oprit (i.e., GPIO setat pe 1), astfel factorul de umplere fiind dat de valoarea registrului OCR2B
;) A
va trebui să rămână tot OCR2A = 188
(dorim, în continuare, să avem numărătoarea de milisecunde)!Task 3:
convert_HSV_to_RGB()
!1
pentru intensitate maximă;Task 4 (bonus):
PD4
, așa cum am văzut în primul laborator. PD4
este și ieșirea B a timer-ului 1, deci am putea folosi un factor de umplere de 50% pentru a produce sunet pe speaker. Scopul acestui exercițiu este să redați melodia pusă în scheletul de laborator (vedeți modulul sound.c
).surprise_notes
sunt diferitele note folosite, cu valorile frecvențelor în Hertzi;durations
este un vector cu duratele asociate fiecărei note din surprise_notes
;update_notes
va trebui să redea notele muzicale în ordinea dorită, reconfigurând timer-ul mereu OCR1A
ce va da frecveța semnalului; pentru a obține un semnal de 50%, OCR1B
va trebui să fie la jumătate din valoarea primului registru;update_notes
va trebui apelată în bucla principală la fiecare 25ms (folosiți systicks
pentru a știi când a trecut acest interval de timp de la ultimul update).TCNT
la fiecare update al TOP
-ului! Altfel, în unele situații, va trebui să așteptați un overflow pentru ca TCNT
să se reseteze.