Čítač/Časovač 0 nejen na Attiny

Abstrakt

Čítače / časovače jsou ústřední periferií mikrokontrolerů AVR. V tomto článku se vás pokusím pomocí praktických ukázek seznámit s některými možnostmi a uplatněním čítače / časovače 0. Začneme čítáním externího signálu, přejdeme přes obecné možnosti časování, dotkneme se generování frekvence a kapitolu uzavřeme použitím PWM signálu.

Úvod

Čítače / časovače jsou komplexní periferie a jejich teoretické vysvětlení by zabralo hodně prostoru. Pokud bych ho koncentroval do jedné kapitoly, mohli by jste se v něm ztratit. Proto se teorii pokusím "rozpustit" do jednotlivých příkladů a seznámím vás s ní postupně. Každý příklad se bude věnovat nějaké schopnosti čítače/časovače a bude obsahovat postup jak čítač nakonfigurovat. Ti kteří budou chtít získat celkový přehled o čítači/časovači by si měli projít (a vyzkoušet) všechny příklady. Mírně zkušenější programátoři mohou přeskočit rovnou k vlastnostem které ještě neznají. Úplným začátečníkům vřele doporučuji prohlédnout si chování čítače na simulátoru. Také bych vám ve většině příkladů příkladů doporučoval provozovat čip s clockem odvozeným od krystalu - budeme pracovat s časováním :) Ještě dodám trochu terminologie, jestliže bude v textu napsáno "nastavíme bit" myslí se tím, že do něj zapíšeme log.1, naopak frází "vynulujeme bit" myslím zápis log.0. Pojmy čítač a časovač budu různě mixovat a myslím tím pořád jednu a tu samou periferii - čítač/časovač 0.

Stručný přehled

K ukázkám jsem zvolil čip Attiny2313A, jeho výbava je v podstatě stejné jako Attiny24, ale má víc pinů. Čítače jsou velmi podobné i na čipech Atmega, takže návod můžete uplatnit na širokém spektru čipů. Většinu vysvětlení provedu na čítači/časovači 0, protože je 8bitový a jednodušší. Čítač/časovač 1 je 16bitový ale většinu postupů na něm bude možné použít stejně + několik dalších navíc. Řídit čítač budete pomocí registrů TCCR0A, TCCR0B, OCR0A, OCR0B a TCNT0. Dva registry TIMSK a TIFR jsou pro oba čítače společné. Jádro čítače/časovače 0 tvoří registr TCNT0. S každým tiknutím clocku (ať už se bere odkudkoli) se tento registr inkrementuje o jedna.

Seznam příkladů

A) - Čítání externího signálu

Čítače v AVR umí čítat nejen vnitřní signál (odvozený od clocku procesoru), ale také obecně libovolný signál z vnějšku. V registru TCCR0B si pomocí bitů CS02,CS01 a CS00 můžeme vybrat zdroj signálu pro čítač. Úplně první možnost v tabulce 1 slouží k tomu aby byl čítač odpojen - tedy aby nečítal. Další možnosti k čítači připojují nějak podělený clock čipu. My se ale budeme zabývat posledními dvěma kombinacemi. Ty totiž umožňují čítat signál z vnějšku. A ten si umíme připravit libovolně pomalý. V registru TCCR0B zapíšeme do všech tří bitů CS02,CS01 a CS00 log.1. Čítač by pak měl čítat na každou vzestupnou hranu vnějšího signálu. Čítání probíhá v registru TCNT0, kde se hodnota s každou příchozí vzestupnou hranou zvětší o jedna. Do tohoto registru přirozeně můžete libovolně zapisovat a libovolně z něj číst (a obě možnosti najdou uplatnění). Pro vnější signál má čítač svůj vstup, který se jmenuje T0 a je na pinu PD4 (u Attiny2313). Čítači je jedno zda máte pin nastaven jako vstup či výstup, čítá bez ohledu na to kdo tam signál přivádí. Abychom viděli co se v TCNT0 odehrává, necháme program obsah tohoto registru zobrazovat na PORTB (na který si připojíme LEDky). Vstupní signál si připravíme tlačítkem, které kvalitně zafiltrujeme. Zákmity by zrovna u tohoto příkladu byly velmi nežádoucí. Čítač totiž spolehlivě čítá už i pulzy které jsou jen dvakrát delší než perioda clocku (a nespolehlivě i kratší). Jinak řečeno, jestliže čip běží na 1MHz, tak čítač započítá i pulzy krátké 2us. Z toho také plyne, že pro čítání externího signálu je výhodnější taktovat Atmel na vyšší frekvenci. Zapojíme tedy obvod podle schématu č.1, jen pro efekt si k tlačítku dáme LEDku indikující jeho stav (není nutné). Při stisku tlačítka se obsah TCNT0 inkrementuje a vy se o tom ihned dozvíte rozsvícením příslušné kombinace LED. Přirozeně vidíte binární číslo, ale to by vám nemělo činit potíže. Kvůli jednoduchosti a snadné sestavitelnosti příkladu se záměrně vyhýbám číslicovému nebo znakovému displeji. Někdo by mohl namítnout, že takové počítání stisků se dá naprogramovat i bez čítače. To je naprosto správná námitka, ale není na místě - my jsme si totiž chtěli ukázat že to čítačem jde ;) Pokud ale budete počítat buď krátké pulzy nebo obecně signály o vysoké frekvenci, nezbude vám jiná možnost než tato. Klidně si zkuste příklad upravit aby počítal sestupné hrany. Když podržíte tlačítko déle, uvidíte rozdíl. Ti trpělivější z vás mohou vidět, že po 255 kliknutí čítač přeteče a v TCNT0 bude zase nula. Tomuto se říká přetečení čítače a v budoucnu toho budeme využívat.

Tabulka 1 - Zdroje signálu pro čítač / časovač 0
CS02CS01CS00popis
000čítač zastavený
001clock jádra - bez předděličky
010clock / 8
011clock / 64
100clock / 256
101clock / 1024
110externí signál z T0, sestupná hrana
111externí signál z T0, vzestupná hrana

Schéma č.1 - čítač stisků tlačítka s binárním výstupem Obrázek č.1 - foto zapojení


// A1) jednoduchý čítač externích pulzů
#include <avr/io.h>

int main(void){
	DDRB=0xff; // výstup na LEDky
	DDRD &=~(1<<DDD4); // vstup PD4 (T0) 
	TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00);	// zdroj signálu pro čítač 0 - vnější signál, vzestupná hrana
    while (1) {
		PORTB=TCNT0;	// neustále zobrazujeme stav TCNT0 registru
    }
}

Ještě chvíli zůstaneme u čítání externího signálu. Zkusíme si vysledovat chování čítače když přetéká. V registru TIFR je vlajka TOV0. Ta slouží k tomu aby vám indikovala přetečení čítače. Případně se pomocí ní dá ještě vyvolat přerušení, ale o tom později. Ve většině režimů když čítač přeteče z 0xFF (tedy 255) do 0, tak se vlajka TOV0 nastaví do log.1. Čímž vás informuje o tom, že tato událost nastala. V případě čítání externího signálu je to nesmírně důležité. Ať už čítáte auta na silnici nebo pulzy z detektoru radioaktivního záření, skoro vždycky budete chtít počítat víc jak 255 událostí. Musíte proto sledovat vlajku (nebo použít přerušení) a po každém přetečení čítače si inkrementovat nějakou proměnnou aby jste neztratili informaci o celkovém počtu. Pokud nepoužijete přerušení, nezapomeňte si vlajku TOV0 vždy včas smazat, jinak se o dalším přetečení nedozvíte. Vlajka se maže tak jako vždycky - zápisem log.1. Přidáme si tedy do našeho schématu (č.1) ještě jednu LEDku (na PD3), kterou budeme indikovat stav TOV0. A abychom nemuseli mačkat tlačítko 256x ... předplníme si čítač nějakou vyšší hodnotou :) Když to zkusíte, zjistíte, že se vlajka nastaví přesně v okamžiku kdy dojde k přetečení z 0xFF do 0 a zůstane nastavená - což je přirozeně v souladu s datasheetem.

// A2) jednoduchý čítač - sledujeme vlajku přetečení
#include <avr/io.h>

int main(void){
	DDRB=0xff; // výstup na LEDky
	DDRD &=~(1<<DDD4); // vstup PD4 (T0)
	DDRD |= (1<<DDD3);	// výtup na LEDku (indikace TOV0)
	TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00);	// zdroj signálu pro čítač 0 - vnější signál, vzestupná hrana
	TCNT0 = 250;
    while (1) {
		PORTB=TCNT0;	// neustále zobrazujeme stav TCNT0 registru
		if(TIFR & (1<<TOV0)){PORTD |=(1<<PORTD3);}else{PORTD &=~(1<<PORTD3);}
    }
}

B) - Čítání interního signálu - předdělička

Do teď jsme čítali vnější signál a mohli jsme užívat názvu čítač. Když ale připojíme čítač na vnitřní clock Atmelu začne plnit úlohu časovače. Protože clock čipu bývá typicky v řádu MHz dává nám Atmel k dispozici i předděličku (prescaler), pomocí níž můžeme clock přicházející do čítače snižovat. Podíváte-li se zpět do tabulky 1, uvidíte že můžete použít dělení 8,64,256 a 1024. Kromě toho také nemusíte dělit vůbec a použít přímo clock čipu. Nechme opět stejné zapojení jako v předchozím příkladě (schéma č.1), na portu B budeme stále indikovat stav čítače (i když to není nutné) a na pinu PD3 budeme při každém přetečení měnit stav LED. Přetečení si ohlídáme vlajkou TOV0 a přirozeně ji nezapomeneme smazat. Abychom získali jakousi sebedůvěru v ovládání čítače, zkusíme nejprve předpovědět s jakou frekvencí se bude LED přepínat. Clock Atmelu je 1MHz, jeden tick tedy trvá 1us, předdělička je nastavena na 1024. Aby se čítač jednou inkrementoval musí do předděličky přijít 1024 ticků, jeden tick čítače tedy trvá 1024us. Čítač se musí inkrementovat 256krát než přeteče, takže by mělo trvat celkem T=1024*256*1us = 262144us než dojde k nastavení vlajky TOV0. V tom okamžiku přepneme LED z jednoho stavu do druhého a stejnou dobu musíme počkat než LED vrátíme do původního stavu. Celý cyklus tedy bude trvat dvě přetečení časovače, tedy přibližně 2*262ms = 524ms. LED by tedy měla blikat přibližně s frekvencí 2Hz. Slovo přibližně je tu na místě, clock čipu totiž odvozujeme od interního RC oscilátoru, takže 1MHz je s přesností +- pár procent. Kdo chce pracovat přesněji (a že většina z vás bude chtít) bude muset začít používat krystal (a obětovat tak dva piny) - o tom ale asi jindy. Na obrázku č.2 vidíte výstup našeho programu. Očekávaná délka pulzu by měla činit 262.144ms a ve skutečnosti je 259ms (obrázek č.2), z toho můžeme usuzovat, že clock Atmelu se mi od 1MHz odchyluje přibližně o 1%. Na obrázku č.3 vidíte výsledek když do čítače pustíme hodinový signál bez předděličky. Očekávaná délka pulzu je okolo 255us. Všimněte si ale "rozmazaných" hran signálu (tzv. jitter). Ty jsou dány tím, že program má v hlavní smyčce víc věcí na práci a nějakou dobu mu trvá než dojde k instrukci kde kontroluje stav vlajky. Za jak dlouho zareaguje záleží na tom u které instrukce se zrovna nachází v okamžiku kdy se vlajka nastaví. Takovouhle nejistotu v rychlosti reakce můžete očekávat u každého úkolu, který budete řešit pollingem. Přirozeně si později předvedeme jak to udělat elegantněji.

obrázek č.2 obrázek č.3

//B) jednoduchý čítač - blikáme ledkou 1
#include <avr/io.h>

int main(void){
DDRB=0xff; // výstup na LEDky
DDRD &=~(1<<DDD4); // vstup PD4 (T0)
DDRD |= (1<<DDD3);	// výtup na LEDku (indikace TOV0)
TCCR0B = (1<<CS02) | (1<<CS00);	// zdroj signálu pro čítač 0 - vnitřní clock / 1024
while (1) {
 PORTB=TCNT0;	// neustále zobrazujeme stav TCNT0 registru
 if(TIFR & (1<<TOV0)){
  PIND |= (1<<PIND3);	// přepínám hodnotu na PD3 - použití registru PIND je "finta" ale v souladu s datasheetem (sekce 10.1.2 - doporučuji)
  TIFR |= (1<<TOV0);	// mažu vlajku TOV0
  }
 }
}

C) - Compare jednotky a přerušení

Od teď to začne být zajímavější :) Mrkneme se na tzv Compare jednotky (Compare unit). Čítač obsahuje dvě a jejich činnost nám zpřístupňuje hromadu funkcí z nichž nejzajímavější je generování PWM a možnost volit si strop čítače, ale nepředbíhejme. Do dvou registrů OCR0A a OCR0B můžeme zapsat libovolné hodnoty v rozsahu 0-255. V okamžiku kdy se bude hodnota čítače (TCNT0) shodovat s jedním z OCR registrů nastaví se vlajka OCF0A nebo OCF0B do log.1 (podle toho který registr se s hodnotou v čítači shodoval). Obě vlajky a oba procesy porovnávání jsou nezávislé, klidně se tedy mohou nastavit obě vlajky ve stejný okamžik, pokud jsou hodnoty OCR0A a OCR0B stejné. Typicky chcete aby program provedl nějakou akci v okamžiku kdy čítač dočítá do nějaké hodnoty. Nastavení některé z OCF vlajek může vyvolat přerušení. A nejen to čítač může v závislosti na některé z těchto událostí přímo ovládat výstupní piny ! Tady je potřeba zpozornět, protože dle mého názoru tu má Atmel dosti nejasnou dokumentaci. Tabulky 34. a 37. v datasheetu musíte brát s rezervou, protože akce závisí na stavu vnitřního OC0x bitu, který nemáte možnost přímo sledovat. A ovládat ho musíte nepěknou oklikou (tu předvedu v příkladu I).

Tyto funkce si proto vyzkoušíme pomocí přerušení. V reakci na vnější signál (stisknutí tlačítka) chceme se zpožděním 1ms rozsvítit LED a po 0.5ms ji zase zhasnout. Tedy vytvořit 1ms po startu pulz o délce 500us. Nejprve budeme muset vhodně nastavit předděličku časovači. Máme k dispozici 1MHz, to jest časovou základnu 1us. S tou bychom dokázali realizovat nejdelší čas 255us. Musíme tedy vstupní signál podělit. Pokud ho podělíme osmi, dostaneme frekvenci 125kHz (periodu 8us). Takhle jsme schopni realizovat čas až 255*8=2040us. To nám bude stačit. Do kolika tedy musí časovač napočítat aby mu to trvalo 1ms ? Do 1000/8 = 125. Ale protože čítač čítá od nuly tak do registru OCR0A uložíme 124. Jakmile časovač napočítá do této hodnoty, nastaví se vlajka OCF0A a zavolá se přerušení. Pro realizaci času 1.5ms potřebujeme nechat čítač napočítat do 1500/8=188. Do OCR0B tedy uložíme 187. Přerušení se povolují a zakazují v registru TIMSK, který je společný pro oba čítače/časovače na čipu. Bitem OCIE0A povolujete přerušení od OCR0A a bitem OCIE0B od OCR0B. Bitem TOIE0 bychom pak mohli povolit přerušení od přetečení časovače, ale to teď nebudeme potřebovat. V rutině přerušení od kanálu A rozsvítíme LED, v přerušení od kanálu B ji zhasneme a rovnou i vypneme časovač (dál už nebude potřeba). Výsledek pokusu je pak na obrázku č.4. Na něm je patrné, že opravdu 1ms po stisku tlačítka se LED rozvítí a 0.5ms na to zase zhasne. Tyto možnosti najdou uplatnění tam kde budete potřebovat časování. Já jich využíval třeba při spouštění optické závěrky, kterou bylo nutné otevírat s předstihem aby byla v potřebný okamžik již plně otevřená.


Obrázek č.4 - generování zpožděného pulzu pomocí přerušení od compare události

// C) Přerušení od Compare A a Compare B
#include <avr/io.h>
#include <avr/interrupt.h>

// přerušení od Compare A události
ISR(TIMER0_COMPA_vect){
PORTB |= (1<<PORTB0); // rozsviť LED
}

// přerušení od Compare B události
ISR(TIMER0_COMPB_vect){
PORTB &=~(1<<PORTB0); // zhasni LED
TCCR0B = 0; // vypni časovač
}

int main(void){
DDRD &=~(1<<DDD4); // vstup pro tlačítko
DDRB |= (1<<DDB0); // LEDka
OCR0A=124;	// první čas (rozsvícení LED)
OCR0B=187;	// druhý čas (zhasnutí LED)
TIMSK = (1<<OCIE0B) | (1<<OCIE0A);	// povolujeme přerušení od OCR0A a OCR0B
TIFR |= (1<<OCF0B) | (1<<OCF0A);	// pro jistotu mažeme vlajky, co kdyby vám zůstali z "minula"
sei();	// globální povolení přerušení

while(1){
	while(!(PIND & (1<<PIND4))){} // čekej dokud není stisknuto tlačítko
	TCNT0 = 0; // vynuluj časovač (kdo ví co tam zbylo z "minule")
	TCCR0B = (1<<CS01); // spouštíme časovač
	while(PIND & (1<<PIND4)){}	// čekej dokud není uvolněno tlačítko
	}
}

D) CTC mód - 1 sekunda pomocí přerušení

Časovač může pracovat v různých módech. Jeden z módů je CTC. Ten umožňuje měnit strop časovače. V něm můžete ovládat do kolika časovač napočítá než "přeteče" - tedy než začne počítat od nuly. Pomocí bitů WGM02,WGM01 a WGM00 v registru TCCR0A volíte mód časovače (tabulka 2). Ve všech předchozích případech jsme používali mód "Normal". V něm časovač počítá od nuly do 0xFF (strop), pak přeteče (a nastaví vlajku TOV0) a zase počítá od nuly. V CTC módu je strop časovače registr OCR0A. Časovač počítá jen do hodnoty v registru OCR0A a pak začne počítat zase od nuly. Může tedy počítat do jakékoli hodnoty mezi 1-255. Vlajky TOV0,OCF0B i OCF0A se chovají stejně jako v režimu "Normal". Jestliže tedy časovač napočítá do svého stropu (OCR0A), nastaví se vlajka OCF0A. Podle ní také poznáte že "přetekl". Přirozeně si můžete zapnout i přerušení od této události. Předvedeme si jak s touto funkcí realizovat čas 1s. Na tuto úlohu by byl sice vhodnější 16bitový čítač/časovač 1, ale pro jednoduchost zůstaneme u časovače 0. Zvolíme předděličku 8 a časovač necháme počítat do 250. Při 1MHz clocku bude jedno přetečení časovače trvat 8*250 = 2000us. Od přetečení si necháme volat přerušení a v něm budeme inkrementovat proměnnou. Ta musí dosáhnout 500. V tom okamžiku uplynul čas 2000us * 500 = 1s. Zde je na místě připomenout, že časovač počítá od nuly! Pokud mu zvolíte strop 1 bude muset napočítat dva cykly než přeteče. Pokud má tedy počítat do 250 je potřeba mu zvolit strop 249. Komu to stále není jasné, spočítejte si prsty na ruce a začněte nulou. Za poznámku stojí deklarace proměnné num. Deklarujeme ji s třídou static. Tím překladači říkáme že si proměnná má uchovat svoji hodnotu i po skončení bloku (rutiny přerušení). Pokud bychom to neudělali, se skončením přerušení by skončila platnost proměnné num a ztratili bychom její hodnotu. Přirozeně bychom mohli proměnnou definovat jako globální, ale pak bychom si zase zabrali název num i přes to že tato proměnná nikde jinde potřeba nebude. Pro přehlednost kódu tedy doporučuji raději řešení pomocí static. Další finta, která by mohla čtenáře zmást je přepínání LED v rutině přerušení pomocí zápisu do registru PIND. Ten obecně slouží ke čtení vstupů, ale novější čipy rodiny AVR umožňují zápisem log.1 na příslušnou pozici přepínat (toggle) hodnotu v registru PORTD. Analogicky lze postup použít i pro další piny.

Tabulka 2 - Módy čítače / časovače 0
MódWGM02WGM01WGM00název módustrop
0000Normal0xFF
1001Phase Correct PWM0xFF
2010CTCOCR0A
3011Fast PWM0xFF
4100--
5101Phase Correct PWMOCR0A
6110--
7111Fast PWMOCR0A

// D) CTC mód - realizace 1s pomocí přerušení
#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER0_COMPA_vect){
static int num=0;	
num++;	// počítáme kolikrát přišlo přerušení
if(num>=500){	// pokud 500x, uplynula 1s
	PIND |= (1<<PIND0); // přepni LED - "finta" nemusí fungovat u všech AVR !
	num=0;	// a začneme počítat znovu
	}
}

int main(void){

DDRD |= (1<<DDD0); // výstup (LED)
TCCR0A = (1<<WGM01);	// CTC mód
OCR0A=249;	// strop časovače (počítá 250 pulzů)
TCCR0B = (1<<CS01);	// clock časovače 1MHz/8 
TIFR = (1<<OCF0A);	// mažeme vlajku (kdo ví v jakém stavu byla)
TIMSK = (1<<OCIE0A);	// zapínáme přerušení od OCR0A (tedy od přetečení časovače)
sei(); // povolujeme globálně přerušení
while(1){
	asm("nop");
	}
}

E) CTC mód - generování frekvence

Časovač 0 může sám ovládat piny OC0A (PB2) a OC0B (PD5). V CTC módu jde této vlastnosti využít ke generování frekvence (třeba "tónů"). Generování pak probíhá nezávisle na programu a frekvence je navázaná na clock čipu (takže je signál bez jitteru). Je-li čítač v režimu Normal nebo CTC je možné přidělit mu ovládání pinů OC0A a OC0B. A to pomocí bitů COM0A0, COM0A1, COM0B0 a COM0B1 v registru TCCR0A. Každá dvojice bitů ovládá jeden z výstupů. Pokud jsou oba bity nulové, je pin OC0A (respektive OC0B) od čítače odpojen. Pokud COM0A0 nastavíte do log.1 bude čítač pin OC0A (PB2) přepínat s každou compare událostí. Analogicky to platí pro výstup OC0B (PD5), který se ovládá pomocí bitu COM0B0. Další možnosti (nastavitelné zbylými bity COM0A1 a COM0B1) si předvedeme v dalších příkladech. Nicméně možnost přepnutí bitu na compare událost vám umožňuje autonomně generovat obdélníkový průběh o volitelné frekvenci. V CTC módu můžete čítači nastavovat strop a měnit tak frekvenci jeho přetékání. Přetečení v tomto režimu přijde vždy s compare událostí. Čítač počítá do hodnoty OCR0A, jakmile jí dosáhne vyvolá se compare událost, nastaví se vlajka (OCF0A) a pokud je nastaveno (bitem COM0A0 v log.1) tak se změní hodnota na pinu OC0A. Spolu s tím čítač přeteče a začíná počítat znovu. Hodnotou v OCR0A tedy nastavujete frekvenci přepínání pinu OC0A. Jednoduchým příkladem uplatnění je "pískátko", které vidíte na schématu č.2. Výstup čipu jsme zde posílili tranzistorem. Reproduktor může být v podstatě jakéhokoli typu. Pokud jde o to udělat co největší rachot je dobré volit frekvenci pískání blízko k rezonanční frekvenci reproduktoru. V našem příkladu použijeme běžný "buzzer", což není nic jiného než miniaturní reproduktor (viz obrázek č.6). Jeho rezonanční frekvence je přibližně 2000 Hz. My ho budeme budit frekvencí 1000Hz. Požadujeme tedy periodu 1ms. Protože čítač při přetečení přepíná pin OC0A, potřebujeme aby to provedl během jedné periody dvakrát. Musíme realizovat čas 500us. Zvolíme proto předěličku 8 a časovač necháme počítat do 63 (strop bude tedy 62). Zvolíme mód CTC (WGM01=1) a bitem COM0A0 přidělíme časovači pin OC0A na kterém je připojen reproduktor. Pak už jen stačí pustit čítači clock (s předděličkou 8). Program čeká na stisk tlačítka, pak spouští časovač a nechává ho generovat tón. Dále čeká na uvolnění tlačítka a pak vypíná reproduktor. Je potřeba si uvědomit, že pokud časovač vypnete (odpojíte mu clock) stále ještě bude ovládat pin OC0A a vy nemáte záruku jestli na pinu skončila log.1 nebo log.0. Vzhledem k tomu, že má reproduktor v sepnutém stavu celkem velký odběr, je vhodné čítači také odebrat ovládání pinu OC0A (vynulováním bitů COM0A0 a COM0A1) a na PB2 (OC0A) zapsat log.0.

Schéma č.2 Obrázek č.6 - generování tónu v režimu CTC s OCR0A

// E) - generování tónu v režimu CTC s OCR0A
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void){
DDRD &=~(1<<DDD4); // vstup pro tlačítko
DDRB |= (1<<DDB2); // bzučák
PORTB &=~(1<<PORTB2); // vypínáme bzučák - nechceme aby do něj tekl proud dokud nepracuje

while(1){
	while(!(PIND & (1<<PIND4))){} // čekej dokud není stisknuto tlačítko
	TCNT0 = 0; // vynuluj časovač (kdo ví co tam zbylo z "minule")
	TCCR0A = (1<<COM0A0) | (1<<WGM01);
	OCR0A = 62;
	TCCR0B = (1<<CS01); // spouštíme časovač
	while(PIND & (1<<PIND4)){}	// čekej dokud není uvolněno tlačítko
	TCCR0B = 0; // vypínáme časovač
	TCCR0A &=~((1<<COM0A0) | (1<<COM0A1)); // odebíráme mu možnost ovládat port
	PORTB &=~(1<<PORTB2); // vypínáme bzučák - ať nemá odběr
	}
}

F) CTC mód - generování fázově posunutých signálů

Zajímavou aplikací časovače v CTC módu je možnost generovat fázově posunuté signály s volitelnou frekvencí. Rozsah je bohužel omezen pouze na fázový rozdíl 180°. Stačí nastavit oba piny OC0A a OC0B jako výstupy a přidělit je čítači (nastavením bitů COM0A0 a COM0B0). Spustit časovač v režimu CTC (nastavením bitu WGM01), zvolit si předděličku a nastavit strop časovače (v registru OCR0A). Registrem OCR0B se pak nastavuje fázový předstih signálu na pinu OC0B. Myšlenka je prostá, čítač nejprve projde úrovní nastavenou v OCR0B a přepne výstup na pinu OC0B, pak dopočítá do stropu (OCR0A) a přepne výstup OC0A a tak stále dokola. Rozdíl mezi OCR0B a OCR0A je časový posun mezi oběma signály. Hodnota v OCR0B musí být menší nebo rovna OCR0A, jinak nebude docházet k přepínání signálu OC0B (čítač do hodnoty OCR0B nikdy nedopočítá). Rozlišení takového generátoru závisí na stropu čítače. Čím je hodnota v OCR0A větší, tím více kroků fázového posunu (hodnot v OCR0B) máte k dispozici. Opět se pro tento účel může lépe hodit 16bitový čítač / časovač 1. Bohužel fázi nejde jednoduše ladit v rozsahu 360°. Je-li některý z pinů OC0A nebo OC0B přidělen čítači před startem, je příslušný výstup čítačem vynulován. Nemůžete tedy bez peripetií jako v příkladě I) nastartovat čítač s OC0A v log.1 a OC0B v log.0 nebo naopak. A i když tyto problémy překonáte, pořád budete moct ladit fázový posun jen o 180° a to buď v intervalu 0-180° nebo 180-360°. V následujícím příkladu (zdrojový kód F1) necháme signál na OC0B zpožděný o 20us za signálem na OC0A. Při periodě 400us to tvoří přibližně 18°. V tomto nastavené lze zpozdit signál na OC0B maximálně o 199us, tedy o 179°. Fázový posuv tedy můžete měnit v rozsahu 0-179°. Zpoždění OC0B za OC0A můžete spočítat jako rozdíl OCR0A - OCR0B. Obecně pak fázový posuv můžete určit vztahem
Posun = 360°/perioda*zpoždění
a když do něj dosadíte "registry" a zohledníte že perioda je 2*OCR0A dostanete:
Posun = 180°/OCR0A*(OCR0A-OCR0B)

Obrázek F1 - Generování fázově posunutých signálů (zdrojový kód F1) Obrázek F2 - Generování fázově posunutých signálů s obrácenou polaritou OC0B (zdrojový kód F2)

// F1) - generování fázově posunutých signálů v CTC režimu
#include <avr/io.h>

int main(void){
DDRB = (1<<DDB2); // OC0A výstup
DDRD = (1<<DDD5); // OC0B výstup
TCCR0A = (1<<COM0A0) | (1<<COM0B0) | (1<<WGM01); // CTC mód + přidělení OC0A a OC0B čítači
OCR0A = 200;	// perioda čítače
OCR0B = 180;	// fázové posunutí OC0B vůči OC0A
TCCR0B = (1<<CS00); // spouštíme čítač s clockem čipu (1MHz)

while(1){
 asm("nop");
 }
}

Potřebujete-li pracovat s fázovým posuvem v intervalu 180°-360° musíte si pomoct menší fintou a před začátkem generování signál na OC0B invertovat. O tom jak se něco takového dělá se dozvíte v příkladu I). Detailnější vysvětlení najdete tam. Já jen ve zkratce řeknu, že čítač nejprve přepnete do módu "Set on compare match", pak vynutíte compare událost. Tím se interní signál pro OC0B přepne do log.1. Pak si vrátíte mód čítače na CTC a když ho teď spustíte, tak bude OC0A startovat z log.0 a OC0B z log.1. Tím docílíte posunutí OC0B signálu o dalších 180°. Změnou hodnoty registru OCR0B pak ladíte fázový posuv v rozsahu 180-359°. Fázový posun pak lze spočítat stejně jako v předchozím příkladě, jen stačí přičíst 180°
Posun = 180° + 180°/OCR0A*(OCR0A-OCR0B)

// F2) - generování fázově posunutých signálů v CTC režimu s opačnou fází na OC0B
#include <avr/io.h>

int main(void){
DDRB = (1<<DDB2); // OC0A výstup
DDRD = (1<<DDD5); // OC0B výstup
TCCR0A = (1<<COM0B1) | (1<<COM0B0); // čítač do režimu "Set on compare match" na kanále B
TCCR0B |= (1<<FOC0B); // vynucení compare události - nastavuje interní hodnotu OC0B na log.1
TCCR0A = (1<<COM0A0) | (1<<COM0B0) | (1<<WGM01); // CTC mód + přidělení OC0A čítači
OCR0A = 200;	// perioda čítače
OCR0B = 180;	// fázové posunutí OC0B vůči OC0A
TCCR0B = (1<<CS00); // spouštíme čítač

while(1){
 asm("nop");
 }
}

G) PWM módy

Časovač 0 má k dispozici 4 módy určené k generování PWM (pulzně šířkové modulace). Ta přirozeně najde uplatnění v široké paletě úloh, od regulace jasu až po řízení motorů. Všechny čtyři módy najdete v tabulce 2. Jsou dva druhy, Fast PWM a Phase Correct PWM. U Fast PWM čítač nejprve nastaví výstup do log.1, jakmile dosáhne hodnoty v OCR0 (A nebo B) nastaví příslušný výstup do log.0 a tam ho nechá než dopočítá do stropu. Pak se celá situace opakuje. Hodnotou v OCR0 tedy nastavujete střídu signálu (poměr mezi dobou kdy je v log.1 a celou periodou). U Phase Correct PWM čítač nejprve vynuluje výstup a čítá směrem nahoru. Jakmile dopočítá do hodnoty OCR0, nastaví výstup do log.1 a čítá až do stropu, pak změní směr čítání a čítá dolů. Jakmile opět dojde do hodnoty OCR0, tak výstup vynuluje a pokračuje v čítání až do nuly. Pak se celá akce opakuje. Tenhle režim je pro některé aplikace vhodnější (například když měříte proud motorem, nebo potřebujete snížit rušení). Já s ním ale zkušenosti nemám. Je potřeba si uvědomit, že v Phase Correct PWM módu, trvá celá perioda dvojnásobek než u Fast PWM. V módech 1 a 3 lze používat oba výstupy OC0A i OC0B k generování signálu. Stropem čítače je 0xFF a nelze tak příliš měnit frekvenci PWM (jedině předděličkou). V režimech 5 a 7 lze obětovat OCR0A registr jako strop časovače a měnit frekvenci PWM, ale za cenu ztráty OC0A výstupu. PWM tedy může vznikat jen na výstupu OC0B. OC0A může v nejlepším případě přepínat svoji hodnotu jako v příkladě E) nebo F). V těchto režimech ale přirozeně se snižujícím se stropem čítače (a tím pádem zvyšující se frekvencí) klesá rozlišovací schopnost PWM. Polarita PWM signálů jde řídit pomocí bitů COM0B1,COM0B0,COM0A1 a COM0A0 v registru TCCR0A - viz tabulky 3,4,5 a 6. Ve všech PWM módech je zápis do OCR0A a OCR0B ošetřen vyrovnávací pamětí (tzv double buffer). Pokud v PWM režimu něco zapíšete do OCR0, uloží se to nejprve do stínového registru a teprve s přetečením časovače se hodnota OCR0 nastaví na vámi požadovanou. Slouží to k tomu aby přepisem OCR0 nevznikaly chyby (glitch) ve výstupním PWM signálu. Zápisem v nevhodný okamžik by se mohlo totiž stát, že by časovač Compare událost minul. Dále je potřeba poznamenat, že regulace může dosáhnout jen jednoho z krajů. Tedy nelze vytvořit regulaci od nuly (kdy není výstup nikdy nastaven) až do 100% střídy (kdy je výstup nastaven stále). Vhodnou volbou polarity a režimu lze docílit buď možnosti regulace od nuly ale bez možnosti dosáhnout 100% střídy a nebo opačně, obětovat 0% střídu a začínat od nenulové ale umožnit výstupu setrvávat v log.1 při 100% střídě. Tento problém řeší Phase correct PWM mód, který umožňuje regulovat PWM v plném rozsahu 0-100%. Případně je možné problém obcházet tím že čítač / časovač vypnete, odeberete mu ovládání portů a nastavíte stálou hodnotu portu “ručně” (pomocí registru PORTx). Pak ale nezapomeňte po každém vypnutí čítače vynulovat registr TCNT0

Tabulka č.3 - Chování OC0B ve Fast PWM módech
COM0B1COM0B0popis
00OC0B není připojen k čítači
01-
10Nulování OC0B při compare události
11Nastavení OC0B při compare události

Tabulka č.4 - Chování OC0B ve Phase Correct PWM módech
COM0B1COM0B0popis
00OC0B není připojen k čítači
01-
10Nulování OC0B při compare události při čítání nahoru.
Nastavení OC0B při compare události při čítání dolů
11Nastavení OC0B při compare události při čítání nahoru.
Nulování OC0B při compare události při čítání dolů.

Tabulka č.5 - Chování OC0A ve Fast PWM módech
COM0A1COM0A0popis
00OC0A není připojen k čítači
01v módu 3 OC0A není připojen k čítači
v módu 7 se při Compare události hodnota OC0A přepne.
10Nulování OC0A při compare události.
11Nastavení OC0A při compare události.

Tabulka č.6 - Chování OC0A ve Phase Correct PWM módech
COM0A1COM0A0popis
00OC0A není připojen k čítači
01v módu 1 OC0A není připojn k čítači
v módu 5 se při Compare události hodnota OC0A přepne.
10Nulování OC0A při compare události při čítání nahoru.
Nastavení OC0A při compare události při čítání dolů
11Nastavení OC0A při compare události při čítání nahoru.
Nulování OC0A při compare události při čítání dolů.

H) Fast PWM - DA převodník s trojúhelníkovým průběhem

Na příkladě si předvedeme jednoduchý způsob jak pomocí PWM vytvořit improvizovaný DA převodník. Což je mimochodem to samé co se v Arduinu skrývá pod funkcí AnalogWrite(). My ale na rozdíl od Arduina budeme mít možnost ovládat mnohem více parametrů PWM. Jako výstup použijeme OC0B (PD5), abychom tak měli možnost použít kterýkoli ze čtyř PWM režimů. Časovač nakonfigurujeme v módu Fast PWM se stropem OCR0A (mód 7). Zůstane nám možnost zvyšovat frekvenci PWM na úkor rozlišení. První ukázku provedeme záměrně s malým množstvím kroků aby bylo chování patrné na osciloskopu. Zapojíme obvod podle schématu č.3. Parametry RC filtru a metodika generování by postačila hned na několik článků, my se teď ale věnujeme ovládání čítače, takže diskuzi jaké hodnoty RC článku volit necháme stranou. Program mění PWM každých přibližně 20us. Záměrně volím časování pomocí _delay funkcí, protože pak příklad zůstává čitelný. V praxi určitě využijete ke změně hodnoty PWM druhý časovač. Hodnoty PWM jsou uloženy v poli prubeh. Jejich změnou můžete průběh signálu měnit naprosto obecně. Takto jednoduché uspořádání by šlo přirozeně řešit algoritmem, který by následující vzorek dopočítával. To si ale předvedeme v dalším příkladu. Výsledek příkladu je na obrázku H1. Vzhledem k nízkému rozlišení PWM (jen deset kroků) a nevhodné volbě RC článku není výsledný signál zrovna "pěkný" nicméně je ale krásně vidět průběh PWM a princip na kterém generování signálu pracuje.


Schéma č.3 - jednoduchý DA převod pomocí PWM, kondenzátor má záměrně nízkou hodnotu


obrázek H1 - jednoduchý DA převod pomocí PWM, červený průběh za RC filtrem, modrý průběh výstup OC0B

// H1) - generování trojúhelníkového průběhu - demonstrace Fast PWM se stropem OCR0A
#include <avr/io.h>
#define F_CPU 1000000
#include <util/delay.h>

char i=0;
char prubeh[18]={0,1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1};

int main(void){
DDRD = (1<<DDD5); // OC0B výstup
// Fast PWM mód se stropem OC0A, PWM na kanálu OC0B
TCCR0A = (1<<COM0B1) | (1<<WGM00) | (1<<WGM01) ; 
OCR0A = 9;	// 10 kroků PWM regulace, perioda 10us
OCR0B = 0;	// OC0B určuje střídu
TCCR0B = (1<<CS00) | (1<<WGM02); // spouštíme čítač s clockem čipu (1MHz)

while(1){
 _delay_us(20);
 OCR0B=prubeh[i]; // nová hodnota střídy
 i++;	// index pole prubeh
 if(i>=18){i=0;} // začínáme generovat znovu
 }
}

Ve druhém příkladě budeme generovat pilovitý průběh s největším rozlišením jaké nám časovač 0 dovoluje. Signál tedy budeme skládat z 256 úrovní. Na to bychom mohli použít oba režimy Fast PWM. Vhodnější ale bude Fast PWM se stropem 0xFF, protože budeme mít na hraní k dispozici dva kanály. V našem pokusu si připojíme na oba výstupy opět RC článek abychom demonstrovali možnosti generování signálu (schéma č.4). Kdo nemá osciloskop nebo si rád hraje se světly, může si na výstup připojit LEDky a sledovat kolísání jasu. Vzhledem k frekvenci PWM (3.9kHz) ani nebude potřebovat RC filtry. Program je opět přímočarý a jednoduchý. Na začátku nezapomeneme nakonfigurovat OC0A i OC0B jako výstupy (až vám někdy nepojede PWM, vzpomeňte si na to :D ). V hlavní smyčce časujeme generovaný průběh kvůli čitelnosti programu zase _delay funkcemi. Drobnou modifikací programu by bylo možné průběhy libovolně roz posunout. Výsledný průběh je relativně čistý, nezapomeňte, ale že generovaná frekvence je velice nízká (přibližně 1Hz). Průběhy s vyšší frekvencí budou ztrácet na kvalitě, kvůli nízké frekvenci PWM (3.9kHz). Pro generování audio frekvencí bychom museli buď čip taktovat na vyšší frekvenci (nejlépe na 20MHz) a nebo přejít na Attiny25/45/85, která obsahuje 64MHz časovač. Ten by se pro tyto účely hodil lépe. Vzhledem k tomu, že mě jeho vyzkoušení láká, určitě se o něm dočtete :)

Schéma č.4 - generátor dvou nezávislých průběhů pomocí PWMDokumentační fotografie "generátoru"


obrázek H2 - dvoukanálový DA převod pomocí PWM. Signál je "krásný" jen díky nízké frekvenci generovaného signálu.

// H2) - generování trojúhelníkového průběhu - demonstrace Fast PWM se stropem 0xFF
#include <avr/io.h>
#define F_CPU 1000000
#include <util/delay.h>

#define NAHORU 1
#define DOLU 0
char i=0, smer=NAHORU;

int main(void){
DDRD = (1<<DDD5); // OC0B výstup
DDRB = (1<<DDB2); // OC0A výstup
// Fast PWM mód se stropem 0xFF, PWM na kanálu OC0B i OC0A
TCCR0A = (1<<COM0B1) | (1<<COM0A1) | (1<<WGM00) | (1<<WGM01) ; 
OCR0A = 255;	// OC0A necháme začínat ze stropu
OCR0B = 0;	// OC0B necháme začínat od nuly
TCCR0B = (1<<CS00); // spouštíme čítač s clockem čipu (1MHz)

while(1){
 _delay_ms(2);	// časuje výsledný průběh
 OCR0A = 255-i; // nastavujeme novou hodnotu střídy
 OCR0B = i;
 // vypočítáváme novohou nodnotu střídy pro trojúhelníkový průběh
 if(smer==NAHORU){
  i++;
  if(i==255){smer=DOLU;}
  }
 else{
  i--;
  if(i==0){smer=NAHORU;}
  }
 }
}

I) OC0A a OC0B výstupy v režimech CTC a Normal

Po malé konzultaci se světem (forum AVRFreaks), jsem si vyjasnil ovládání výstupů čítače v režimech CTC a Normal. Jakmile totiž jednou přidělíte čítači ovládání výstupů OC0A nebo OC0B, nemáte už nad nimi přímou kontrolu skrze registry PORTx. Výstup čítače je řízen interním OC0x bitem, který není nijak přímo přístupný. Takže nejen, že ho nemůžete přímo ovlivnit, nemůžete si ani přečíst jeho stav. Jinak řečeno, když přidělíte čítači výstupy, nemůžete si skrze čítač zjistit v jakém jsou stavu a ani jejich stav přímo ovlivňovat. Musíte to vždy dělat oklikou. Řekněme, že jste pin OC0B přidělili čítači a chcete ho nastavit do log.1. Musíte nejprve čítač nastavit do režimu "set on compare match" (nastavením bitů COM0B1 a COM0B0). Pokud nastane compare událost tak čítač v tomto režimu nastaví interní OC0B bit (a tedy i vás výstup) do log.1. Vy ale nechcete čekat až nastane compare událost a chcete nastavit bit hned ! Proto musíte zapsat log.1 do bitu FOC0B v registru TCCR0B. Tím "vynutíte" compare událost a interní OC0B bit se nastaví jako by ke compare události došlo. Teprve teď máte jistotu, že je výstup v log.1. Před tím mohl být v libovolném stavu. Když ho chcete vynulovat musíte podstoupit stejné martyrium. Přepnout čítač do režimu "clear on compare match" (COM0B1=1 a COM0B0=0) a opět zapsat log.1 do FOC0B. Teprve pak si můžete být jisti, že je výstup v příslušném stavu. Pomocí těchto funkcí můžete vygenerovat jednorázový pulz pevně dané délky. Tuto úlohu umíte vyřešit i pomocí přerušení, ale přece jen reakce na přerušení není časově přesně ohraničená a vykazuje jistý jitter (zvlášť pokud přerušení přijde v okamžiku kdy vykonáváte jinou rutinu přerušení). Takže pokud chcete mít jistotu že délka pulzu bude rozumně přesná, nezbude vám nic jiného než se o to pokusit takhle. Předvedu to na jednoduchém příkladě. Budu chtít vygenerovat pulz přesné délky 100us. Časovač nechám pracovat v režimu normal, takže bity WGMx nechám nulové. Do OCR0B registru si uložím necelých 100 (což je doba kterou chci vygenerovat). Komplikace kolem nastavení bitu OC0B vyžadují aby byl výstup čítače nastaven dříve než ho spustím, proto je nutné s tímto časem navíc počítat a hodnotu v OCR0B drobně snížit. K nastavení výstupu dojde tři instrukce před spuštěním časovače, měl bych tedy do OCR0B nahrát 97. Tento předstih se ale těžko předpovídá, protože musíte počítat asemblerovské instrukce. Je proto vhodnější odladit si ho empiricky. Pak potřebuji výše zmíněnou oklikou nastavit výstup čítače do log.1. Pak jej přepnu do režimu "clear on compare match" a spustím. Jakmile compare událost nastane, čítač svůj výstup vynuluje nezávisle na mém programu. Díky tomu vygeneruje vždy přesně 100us pulz. Je ale nutné aby během celé spouštěcí sekvence nebyl program přerušen. Proto před spuštěním vypnu všechna přerušení pomocí funkce cli() a po spuštění zase přerušení povolím. A pro jistotu před spuštěním čítač vypnu a vynuluji. Protože pokud tuto rutinu budete chtít někde použít potřebujete mít jistotu, že čítač počítá od nuly a od okamžiku kdy chceme. Knihovnu avr/interrupt.h vkládám jen kvůli instrukcím cli() a sei(), jinak nemá žádný význam. Výsledkem je krásný 100us pulz (obrázek I1). Přirozeně je v takovém případě nutné používat kvalitní zdroj clocku pro čip. S interním RC oscilátorem se přesnosti na jednu mikrosekundu dá dosáhnout jen těžko (museli by jste jej pomocí OSSCAL kalibrovat). Já použil externí krystalový oscilátor.


Obrázek I1 - přesný 100us pulz pomocí "set / clear on compare match" v režimu Normal nebo CTC

// I) - přesný jednorázový pulz v CTC a Normal módech, pomocí OC0B výstupu
#include <avr/io.h> 
#define F_CPU 1000000
#include <util/delay.h>
#include <avr/interrupt.h>

int main(void){ 

DDRD = (1<<DDD5); // OC0B výstup
OCR0B = 97; // nastavuji čas 

while(1){
 _delay_ms(1); // opakuj celou akci pořád dokola 
 TCCR0B &=~((1<<CS00) | (1<<CS01) | (1<<CS02)); // zastavení čítače
 TCNT0=0; // vynulování čítače
 cli(); // odtud nesmím být přerušen !
 TCCR0A = (1<<COM0B1) | (1<<COM0B0); // čítač do režimu "Set on compare match"
 TCCR0B |= (1<<FOC0B); // vynucení compare události - výstup je nastaven do log.1
 TCCR0A = (1<<COM0B1); // přepnutí do režimu "clear on compare match"
 TCCR0B = (1<<CS00); // spuštění čítače
 sei(); // už se můžeme věnovat čemukoli dalšímu, smím být přerušen...
 } 
}

J) Čítač/časovač 0 v režimu Phase correct PWM

V tomto krátkém příkladku si ukážeme že Phase Correct PWM mód umí regulovat PWM v rozsahu 0-100%. Z hlediska programátora k němu můžete přistupovat stejně jako k režimu Fast PWM. Stačí jej zvolit a pomocí bitů COM0Bx a COM0Ax vybrat pro výstupy OC0A a OC0B použití přímého nebo invertovaného PWM. Frekvenci signálu spočtete podle jednoduchého vztahu F=F_CPU/(510*N) kde N představuje předděličku časovače (1, 8, 64, 256 nebo 1024). Raději uvedu příklad. Běží-li vám procesor na frekvenci 1MHz a spustíte-li časovač s předděličkou 1, dostáváte frekvenci PWM rovnu 1000000/(510*1) = 1.96kHz. Hodnotu PWM na každém kanálu pak řídíte pouze zápisem do registru OCR0A a OCR0B. Pokud do některého z nich zapíšete nulu, bude výstup trvale v log.0 (případně log.1 pokud používáté invertovaný mód). Po zápisu 0xFF bude výstup trvale v log.1 (případně v log.0 pokud na daném kanále používáté invertovaný mód). Konfiguraci shrnuje následující zdrojový kód. Oba kanály (OC0A na PB2 a OC0B na PA7) používám v neinvertovaném módu, clock časovače je s předděličkou 1, tedy 1MHz. Na obrázku pod zdrojovým kódem si můžete prohlédnout výsledky. Červený průběh znázorňuje neměnnou hodnotu na výstupu OC0B, zelené průběhy znázorňují signál při různě volených hodnotách OC0A volených postupně od 0 do 0xFF a je ne nich vidět že výstup může dosáhnout 0% i 100% hodnoty PWM. V dolní části obrazovky pak vidíte že frekvence je 1.98kHz. Možná vás zarazí že to nekoresponduje s 1.96kHz zmíněnými v textu, odchylka je způsobena nepřesností interního RC oscilátoru, ukázku jsem totiž prováděl na čipu bez krystalového oscilátoru. Přirozeně není od věci všimnout si že průběhy jsou zarovnané odlišně jak u Fast PWM režimu. U něj by se dalo říct že jsou výstupy zarovnané "doleva", tedy všechny začínají log.1 ve stejném okamžiku. Kdežto u phase correct PWM modu je jejich zarovnání "centrované".

// J konfigurace Phase Correct PWM módu
#include <avr/io.h>

int main(void)
{
	DDRA |= (1<<PORTA7); // OC0B výstup
	DDRB |= (1<<PORTB2); // OC0A výstup
	TCCR0A = (1<<COM0B1) | (1<<COM0A1) | (1<<WGM00); // Phase correct PWM neinvertovaný na OC0A a OC0B
	OCR0A = 0x00;
	OCR0B = 128;
	TCCR0B = (1<<CS00); // clock 1MHz, perioda 1MHz/510 ~= 1.96khz
    while (1){
		// neni co dělat
    }
}


Obrázek x1 - červený průběh je OC0B, zelené průběhy jsou OC0A s různým nastavením OCR0A.

Odkazy