Naveo si 16 Mhz MCU clock, želiš 20 khz PWM i želiš to napraviti korištenjem TIMERA 2.
1. Najprije izračunaš period PWM-a na 20 Khz formulom T=1/f = 1/20000 Hz = 0.00005 S ili 50 us. Dakle, 8 Bitni TIMER 2 ne smije napraviti overflow prije nego li prođe 50 us vremena.
2. Pronađeš tablicu prescalera TIMER 2 i izračunaš prescaler na kojemu TIMER neće napraviti overflow prije nego li prođe 50 us vremena. Tablica prescalera nalazi se na stranici 186 datasheeta. Kako znaš da ti je ulazna frekvencija 16 MHz računaš overflow TIMER-a od najmanjeg prema većim prescelerima:
Prescaler 1 (clk/1):
Dijeliš 16 Mhz sa prescalerom clk/1 i dobiješ 16 Mhz frekvenciju koja ulazi u TIMER 2. Izračunaš period te frekvencije T=1/f = 1/16000000 = 62.5 nS i pomnožiš jedan period sa 256 stanja 8 bitnog TIMER-a da bi on napravio overflow. 62.5 ns * 256 = 16us (Overflow je nakon 16 us -> Timer mora brojati sporije!)
Prescaler 2 (clk/8):
Dijeliš 16 Mhz sa prescalerom clk/8 i dobiješ 2Mhz frekvenciju koja ulazi u TIMER 2. Izračunaš period te frekvencije T=1/f = 1/2000000 = 500 ns i pomnožiš jedan period sa 256 stanja 8 bitnog TIMER-a da bi on napravio overflow. 500 ns * 256 = 128 us (Overflow je nakon 128 us -> Ovaj prescaler koristiš!)
Odabir prescalera vrši se putem CS bitova (Clock Select) tako da pronađeš tablicu koja opisuje ove bitove za TIMER 2. To ti se nalazi na stranici 191 datasheeta. Iz tablice možeš vidjeti da je za clk/8 potrebno podesiti CS bitove na sljedeće postavke:
CS22 = 0
CS21 = 1
CS20 = 0
Na stranici 190 imaš registar koji sadrži sve CS bitove za TIMER 2 i zove se TCCR2B. U ovom trenutku možeš napisati liniju programa koja podešava CS bitove, a kako je potrebno samo CS21 postaviti u log 1 u C-u to napraviš ovako:
Code: Select all
#include <avr\io.h>
int main(void){
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8 = 2 Mhz */
while(1){
}
}
Generirati PWM 20 kHz možeš u svim načinima rada TIMER-a osim u NORMAL. To nas vodi do tablice
"Waveform Generation Mode Bit Description" koja se nalazi na stranici 189 datasheet-a. Svi načini rada "CTC, Phase Correct i Fast PWM" mogu osim hardware TOP vrijednosti brojača (255) postaviti novu TOP vrijednost korištenjem OCR2A registra. U ovom slučaju imaš 3 načina na koja možeš podesiti TIMER:
Mode 2: CTC sa gornjom vrijednosti TIMER-a OCR2A
Mode 5: PWM s, Phase Correct sa gornjom vrijednosti TIMER-a OCR2A
Mode 7: Fast PWM sa gornjom vrijednosti TIMER-a OCR2A
WGM bitovima TIMER-a određuješ način rada u kojemu će raditi TIMER. Da sad ne pišem sva 3 načina kako ovo možeš napraviti opisati ću ti kako to napraviti u Fast PWM načinu rada TIMER-a. Na stranici 189 datasheeta možeš pronaći konfiguraciju WGM bitova za MODE 7 (Fast PWM sa top vrijednosti OCR2A). Po tablici je vidljivo da WGM bitove moraš postaviti na sljedeće postavke:
WGM2 = 1
WGM1 = 1
WGM0 = 1
Ono što te može zbuniti je razlika između WGM2 i WGM22. To je mali propust tehničke dokumentacije. Kako su CS bitove nazvali CS22, CS21 i CS20 trebali su i WGM bitove nazvati WGM22, WGM21 i WGM20, ali to su malo čudno zapisali u tablici WGM bitova... Broj 2 govori da se radi o TIMER-u 2, pa ti onda ostaju samo bitovi imena WGM2, WGM1 i WGM0, a zapravo se radi u WGM22, WGM21 i WGM20.
Na stranici 187 datasheeta imaš registar TCCR2A u kojemu se nalaze 2 WGM bita za konfiguraciju TIMER-a WGM20 i WGM21 i njih trebaš podići u log 1. Osim toga na stranici 190 imaš TCCR2B registar u kojemu se nalazi WGM22 bit i njega moraš podići u log1. To bi u software-u izgledalo ovako:
Code: Select all
#include <avr\io.h>
int main(void){
TCCR2A |= (1<<WGM20)|(1<<WGM21); /* Fast PWM, TOP OCR2A */
TCCR2B |= (1<<WGM22);
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8, 2 Mhz */
while(1){
}
}
Ono što sada moraš znati je činjenica da je TIMER u modu Fast PWM sa TOP vrijednosti OCR2A registra. Kako će TIMER 2 uvijek brojati samo do OCR2A registra njime podešavaš frekvenciju PWM-a koju želiš. Potrebno je izračunati koju vrijednost upisati u OCR2A registar da bi dobio točno 20 kHz PWM. To računaš ovako:
Ako je jedna clock TIMER-a 0.5 us (freq 2Mhz, clk/64), onda podjeliš svoj željeni period 50 us sa jednim clockom TIMER-a 0,5 us. 50us/0,5us = 100. U tartufima sam već objašnjavao zašto se u OCR2A registar ne upisuje vrijednost 100 nego umanjena za 1, dakle 99. Timer broji od 0, ali i kada nabroji do vrijedosti 100 on će na vrijednosti 100 biti još jedan clock. Zato u OCR2A registar upisuješ 99 kako bi dobio točno 20 khz izlazni PWM. Sljedećom naredbom podešavaš gornju vrijednost TIMER-a i nakon toga TIMER će napraviti overflow nakon točno 50us što odgovara frekvenciji 20 khz.
Code: Select all
#include <avr\io.h>
int main(void){
OCR2A = 99; /* new TOP: (99+1)*0,5us = 50us -> 20kHz */
TCCR2A |= (1<<WGM20)|(1<<WGM21); /* Fast PWM, TOP OCR2A */
TCCR2B |= (1<<WGM22);
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8, 2 Mhz */
while(1){
}
}
Ono što mislim da si najviše fulao u ovoj priči je činjenica da pokušavaš promjenom OCR2A registra mjenjati duty PWM-a, to se radi na sasvim drugi način. OCR2A registar je gornja vrijednost tvog TIMER-a i kada jedamput podesiš TIMER na 20 kHz ovaj registar više ne diraš. Ako promjeniš gornju vrijedost TIMER-a u OCR2A registru odmah promjeniš i izlaznu frekvenciju PWM-a, a to ne želiš.
Tvoj TIMER uvijek broji od 0 do vrijednosti upisane u OCR2A registar i to više nikada ne mjenjaš. Kod AVR-a imaš i OCR2B registar kojeg trebaš iskoristiti za Duty PWM-a. Prvenstveno trebaš podesiti TIMER da ti OCR2B registar upravlja izlaznim pinom mikrokontrolera. Za to ti služe COM bitovi TIMER-a. U svakom modu TIMER-a COM bitovi znače drugačiju konfiguraciju, stoga moraš naći tablicu COM bitova za TIMER 2 u Fast PWM načinu rada. Ova tablica nalazi se na stranici 188 datasheeta imena "Compare Output Mode, Fast PWM mode". U toj tablici izabereš kakav PWM želiš, inverting ili non-inverting. Tebi je zapravo svejedno jeli inverting ili non inverting-mod. Jedina razlika ovih modova je što će PWM na resetu TIMER-a biti u 0 ili 1, a na compare match sa registrom OCR2B promjeniti stanje.
Za ovaj primjer odabrati ćemo mod u kojemu TIMER postavlja izlaz PWM-a na 1 kada se resetira na 0. Ovaj način rada po datasheetu se zove: "Clear OC2B on Compare Match, set OC2B at BOTTOM", a bitovi koji ga opisuju su:
COM2B1 = 1
COM2B0 = 0
Oba bita nazale se u registru TCCR2A opisani na stranici 187 datasheet-a. U ovom slučaju moraš samo COM2B1 podići u log 1 i to u software-u izgleda ovako:
Code: Select all
#include <avr\io.h>
int main(void){
OCR2A = 99; /* new TOP: (99+1)*0,5us = 50us -> 20kHz */
TCCR2A |= (1<<WGM20)|(1<<WGM21); /* Fast PWM, TOP OCR2A */
TCCR2B |= (1<<WGM22);
TCCR2A |= (1<<COM2B1); /* Clear OC2B on Compare Match, set at BOT */
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8, 2 Mhz */
while(1){
}
}
Kada si podesio COM bitove na ovu konfiguraciju TIMER će u slučaju kada dođe na vrijednost 0 uvijek postaviti izlaz PWM-a na logičku jedinicu, a spustiti će ga na nulu kada TIMER nabroji do OCR2B registra. Upozoravam "OCR2B" registra, a ne "OCR2A" registra. OCR2A registar je onaj koji ti osigurava 20 khz PWM.
OCR2B registar je onaj kojim upravljaš duty PWM-a, njega postavljaš u interval [0-OCR2A] registra. Sve što još moraš napraviti da bi dobio PWM je konfigurirati DUTY u OCR2B registru i postaviti OC2B pin kao izlaz.
Kako vidim po pinovima MCU-a (OC2B) je PH6 i na njemu ćeš generirati PWM, stoga PH6 moraš postaviti kao izlaz:
Code: Select all
#include <avr\io.h>
int main(void){
DDRH |= (1<<PH6); /* set output -> PWM 20 Khz */
OCR2A = 99; /* new TOP: (99+1)*0,5us = 50us -> 20kHz */
TCCR2A |= (1<<WGM20)|(1<<WGM21); /* Fast PWM, TOP OCR2A */
TCCR2B |= (1<<WGM22);
TCCR2A |= (1<<COM2B1); /* Clear OC2B on Compare Match, set at BOT */
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8, 2 Mhz */
while(1){
}
}
Stvari bi sada trebale biti jednostavne. Duty podešavaš u OCR2B registu jer će TIMER promijeniti stanje OC2B pina svaki puta kada dođe do vrijedosti OCR2B registra. Timer ti uvijek broji do TOP vrijednosti zadane OCR2A registrom i ona je 20 kHz. Tvoj duty PWM-a ide od 0 do 99, jer je 99 i maksimalna vrijednost TIMER-a pošto čitav cirkus nakon toga počinje iz nule.
Ja ću čisto reda radi staviti duty na vrijedost 50% što bi trebalo biti upis 49 u OCR2B registar. To bi bilo PWM 20 khz duty 50%. SW izgleda ovako:
Code: Select all
#include <avr\io.h>
int main(void){
DDRH |= (1<<PH6); /* set output -> PWM 20 Khz */
OCR2A = 99; /* new TOP: (99+1)*0,5us = 50us -> 20kHz */
OCR2B = 49; /* set duty 50% (freq 20 khz) */
TCCR2A |= (1<<WGM20)|(1<<WGM21); /* Fast PWM, TOP OCR2A */
TCCR2B |= (1<<WGM22);
TCCR2A |= (1<<COM2B1); /* Clear OC2B on Compare Match, set at BOT */
TCCR2B |= (1<<CS21); /* Run TIMER 2 from prescaler clk/8, 2 Mhz */
while(1){
}
}
Ovo nisam testirao jer nemam hardware, ali vrlo je mala mogućnost da sam se negdje zeznuo u proračunima. Nadam se da ćeš iz ovog teksta shvatiti u čemu je trik tvog problema. Ima tu još sitnica za objasniti, ali danas mi se više neda pisati... Mislio sam da ću i ovo natipkati brzo, ali izgubih više od sat vremena...
Pozdrav,
Josip Štivić