Reklama

Čítač / Časovač 0 nejen na ATtiny – 3. Díl

Úvod

Toto je poslední díl, kterým uzavíráme popis práce s čítačem / časovačem 0 nejen u ATtiny. Podíváme se na práci s PWM režimy a navíc si probereme práci s OC0A a OC0B výstupy v režimech CTC a Normal. Seznam kapitol, které naleznete v tomto článku:

  • PWM módy (G)
  • Fast PWM – DA převodník s trojúhelníkovým průběhem (H)
  • OC0A a OC0B výstupy v režimech CTC a Normal (I)
PWM módy (G)

Čítač / časovač 0 má k dispozici čtyři režimy, které jsou určené ke 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 režimy najdete v Tab. 1 v předchozím díle. Rozdělují se na dva typy – Fast PWM a Phase Correct PWM. U Fast PWM čítač nejprve nastaví výstup do stavu log. 1 a začne čítat, jakmile dosáhne hodnoty v registru OCR0 (A nebo B), tak nastaví příslušný výstup do stavu 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 pin ve stavu log. 1 a celou periodou). U Phase Correct PWM čítač nejprve vynuluje výstup a čítá směrem nahoru. Jakmile dopočítá do hodnoty registru OCR0, tak nastaví výstup do stavu log. 1 a čítá až do stropu a potom změní směr čítání a čítá dolů. Jakmile opět dojde do hodnoty registru 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ž řídíte motor a potřebujete zároveň měřit jeho odběr, nebo potřebujete snížit rušení). Já s ním ale zkušenosti nemám. Je potřeba si uvědomit, že v režimu Phase Correct PWM, trvá celá perioda dvojnásobek než u režimu Fast PWM. V režimech 1 a 3 lze používat oba výstupy OC0A i OC0B k generování signálu. Stropem čítače je hodnota 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 jednoho kanálu (OC0A). PWM tedy může vznikat jen na výstupu OC0B. Pin OC0A může v nejlepším případě přepínat svoji hodnotu jako v příkladech (E) nebo (F), které lze nalézt v předchozím díle. Se snižujícím se stropem čítače (a tím pádem zvyšující se frekvencí) přirozeně klesá rozlišovací schopnost PWM. Polarita PWM signálů jde řídit pomocí bitů COM0B1, COM0B0, COM0A1 a COM0A0 v registru TCCR0A – viz Tab. 1,2,3 a 4. Ve všech PWM režimech je zápis do registrů OCR0A a OCR0B ošetřen vyrovnávací pamětí (tzv. double buffer). Pokud v PWM režimu něco zapíšete do registru OCR0, tak se to uloží nejprve do stínového registru a teprve s přetečením čítače se hodnota OCR0 nastaví na vámi požadovanou hodnotu. Slouží to k tomu, aby přepisem registru 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 čítač / č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 anebo opačně, obětovat 0 % střídu a začínat od nenulové ale umožnit výstupu setrvávat ve stavu log. 1 při 100 % střídě. Tyto varianty je pak potřeba 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).

Tab. 1: Chování OC0B ve Fast PWM režimech

COM0B1 COM0B0 Popis
0 0 OC0B není připojen k čítači
0 1 -
1 0 Nulování OC0B při “Compare” události
1 1 Nastavení OC0B při “Compare” události

 

Tab. 2: Chování OC0B v Phase Correct PWM režimech

COM0B1 COM0B0 Popis
0 0 OC0B není připojen k čítači
0 1 -
1 0 Nulování OC0B při “Compare” události při čítání nahoru. Nastavení OC0B při “Compare” události při čítání dolů.
1 1 Nastavení OC0B při “Compare” události při čítání nahoru. Nulování OC0B při “Compare” události při čítání dolů.

 

Tab. 3: Chování OC0A ve Fast PWM režimech

COM0A1 COM0A0 Popis
0 0 OC0A není připojen k čítači
0 1 V režimu 3 není OC0A připojen k čítači. V režimu 7 se při “Compare” události hodnota OC0A přepne.
1 0 Nulování OC0A při “Compare” události.
1 1 Nastavení OC0A při “Compare” události.

 

Tab. 4: Chování OC0A v Phase Correct PWM režimech

COM0A1 COM0A0 Popis
0 0 OC0A není připojen k čítači
0 1 V režimu 1 není OC0A připojen k čítači. V režimu 5 se při “Compare” události hodnota OC0A přepne.
1 0 Nulování OC0A při “Compare” události při čítání nahoru. Nastavení OC0A při “Compare” události při čítání dolů.
1 1 Nastavení OC0A při “Compare” události při čítání nahoru. Nulování OC0A při “Compare” události při čítání dolů.

 

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

Na příkladě si předvedeme jednoduchý způsob jak pomocí PWM vytvořit improvizovaný D/A 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 pin OC0B (PD5), tak abychom měli možnost použít kterýkoli ze čtyř PWM režimů. Časovač nakonfigurujeme do režimu Fast PWM se stropem OCR0A (režim 7). Zůstane nám možnost zvyšovat frekvenci PWM, ale 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 Obr. 1. Parametry RC filtru a metodika generování by postačila hned na několik článků, ale my se teď 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ě 20 µs. Záměrně volím časování pomocí _delay funkcí, protože poté zůstává příklad čitelný. V praxi určitě využijete ke změně hodnoty PWM druhý čítač / č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 až v dalším příkladu. Výsledek příkladu je na Obr. 2. 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ý” , ale je krásně vidět průběh PWM a princip na kterém generování signálu pracuje.

 
Obr. 1: Jednoduchý D/A převod pomocí PWM, kondenzátor má záměrně nízkou hodnotu

Obr. 1: Jednoduchý D/A převod pomocí PWM, kondenzátor má záměrně nízkou hodnotu

 

Obr. 2: Jednoduchý DA převod pomocí PWM, červený průběh za RC filtrem, modrý průběh výstup OC0B

Obr. 2: Jednoduchý DA převod pomocí PWM, červený průběh za RC filtrem, modrý průběh výstup OC0B

 

001: // H1) – generování trojúhelníkového průběhu –
002: // demonstrace Fast PWM se stropem OCR0A
003: #include <avr/io.h>
004: #define F_CPU 1000000
005: #include <util/delay.h>
006:
007: char i = 0;
008: char prubeh[18] = {0,1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1};
009:
010: int main(void){
011: DDRD = (1<<DDD5);// OC0B výstup
012: // Fast PWM mód se stropem OC0A, PWM na kanálu OC0B
013: TCCR0A = (1<<COM0B1) | (1<<WGM00) | (1<<WGM01) ;
014: OCR0A = 9;// 10 kroků PWM regulace, perioda 10us
015: OCR0B = 0;// OC0B určuje střídu
016: // spouštíme čítač s clockem čipu (1MHz)
017: TCCR0B = (1<<CS00) | (1<<WGM02);
018:
019: while(1){
020: _delay_us(20);
021: OCR0B = prubeh[i];// nová hodnota střídy
022: i++;// index pole prubeh
023: if(i >= 18){i = 0;}// začínáme generovat znovu
024: }
025: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

Ve druhém příkladu budeme generovat pilovitý průběh s největším rozlišením jaké nám čítač / č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 (Obr. 3). Kdo nemá osciloskop, nebo si rád hraje se světly, může si na výstup připojit LED diody a sledovat kolísání jasu. Vzhledem k frekvenci PWM (3,9 kHz) ani nebude potřebovat RC filtry. Program je opět přímočarý a jednoduchý. Na začátku nezapomeneme nakonfigurovat piny OC0A a OC0B jako výstupy (až vám někdy nepojede PWM, vzpomeňte si na to). 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ě posunout. Výsledný průběh je relativně čistý, nezapomeňte, ale že generovaná frekvence je velice nízká (přibližně 1 Hz). Průběhy s vyšší frekvencí budou ztrácet na kvalitě, kvůli nízké frekvenci PWM (3,9 kHz). Pro generování audio frekvencí bychom museli buď mikrokontrolér taktovat na vyšší frekvenci (nejlépe na 20 MHz) anebo přejít na ATtiny25/45/85, která obsahuje 64 MHz č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 :)

 
Obr. 3: Generátor dvou nezávislých průběhů pomocí PWM

Obr. 3: Generátor dvou nezávislých průběhů pomocí PWM

 
Obr. 4: Dvoukanálový DA převod pomocí PWM. Signál je

Obr. 4: Dvoukanálový DA převod pomocí PWM. Signál je “krásný” jen díky nízké frekvenci generovaného signálu.

 

 

001:
002: // H2) – generování trojúhelníkového průběhu –
003: // demonstrace Fast PWM se stropem 0xFF
004: #include <avr/io.h>
005: #define F_CPU 1000000
006: #include <util/delay.h>
007:
008: #define NAHORU 1
009: #define DOLU 0
010: char i = 0, smer = NAHORU;
011:
012: int main(void){
013: DDRD = (1<<DDD5);// OC0B výstup
014: DDRB = (1<<DDB2);// OC0A výstup
015: // Fast PWM mód se stropem 0xFF, PWM na kanálu OC0B i OC0A
016: TCCR0A = (1<<COM0B1) | (1<<COM0A1) | (1<<WGM00) | (1<<WGM01);
017: OCR0A = 255;// OC0A necháme začínat ze stropu
018: OCR0B = 0;// OC0B necháme začínat od nuly
019: TCCR0B = (1<<CS00);// spouštíme čítač s clockem čipu (1MHz)
020:
021: while(1){
022: _delay_ms(2);// časuje výsledný průběh
023: OCR0A = 255–i;// nastavujeme novou hodnotu střídy
024: OCR0B = i;
025: // vypočítáváme novohou nodnotu střídy pro
026: // trojúhelníkový průběh
027: if(smer == NAHORU){
028: i++;
029: if(i == 255){smer = DOLU;}
030: }
031: else{
032: i––;
033: if(i == 0){smer = NAHORU;}
034: }
035: }
036: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 
OC0A a OC0B výstupy v režimech CTC a Normal (I)

Po malé konzultaci se světem (fórum 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, tak už nad nimi nemáte 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č / časovač 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 / časovači a chcete ho nastavit do stavu log. 1. Musíte nejprve čítač / časovač 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áš výstup) do stavu log. 1. Vy ale nechcete čekat až nastane “Compare” událost a chcete nastavit bit hned! Proto musíte zapsat stav 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 ve stavu log. 1. Před tím mohl být v libovolném stavu. Když ho chcete vynulovat, tak musíte podstoupit stejné martyrium. Přepnout čítač do režimu “Clear on compare match” (COM0B1=1 a COM0B0=0) a opět zapsat stav 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á, tak vám nezbude nic jiného, než se o to pokusit takhle. Předvedu to na jednoduchém příkladu. Budu chtít vygenerovat pulz přesné délky 100 µs. Č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 stavu log. 1. Pak jej přepnu do režimu “Clear on compare match” a spustím. Jakmile “Compare” událost nastane, tak čítač svůj výstup vynuluje nezávisle na mém programu. Díky tomu vygeneruje vždy přesně 100 µs 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 pomocí sei(). 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, tak 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ý 100 µs pulz (Obr. 5). Přirozeně je v takovém případě nutné používat kvalitní zdroj hodin pro mikrokontrolér. S interním RC oscilátorem se přesnosti na jednu mikrosekundu dá dosáhnout jen těžko (museli byste jej pomocí OSSCAL kalibrovat). Já použil externí krystalový oscilátor.

 
Obr. 5: Přesný 100 µs pulz pomocí

Obr. 5: Přesný 100 µs pulz pomocí “set / clear on compare match” v režimu Normal nebo CTC

 

 

001: // I) – přesný jednorázový pulz v CTC a Normal
002: // I)režimech, pomocí OC0B výstupu
003: #include <avr/io.h>
004: #define F_CPU 1000000
005: #include <util/delay.h>
006: #include <avr/interrupt.h>
007:
008: int main(void){
09:
010: DDRD = (1<<DDD5);// OC0B výstup
011: OCR0B = 97;// nastavuji čas
012:
013: while(1){
014: _delay_ms(1);// opakuj celou akci pořád dokola
015: // zastavení čítače
016: TCCR0B &= ~((1<<CS00) | (1<<CS01) | (1<<CS02));
017: TCNT0 = 0;// vynulování čítače
018: cli();// odtud nesmím být přerušen !
019: // čítač do režimu “Set on compare match”
020: TCCR0A = (1<<COM0B1) | (1<<COM0B0);
021: TCCR0B |= (1<<FOC0B);// vynucení compare události – výstup je nastaven do log.1
022: TCCR0A = (1<<COM0B1);// přepnutí do režimu “clear on compare match”
023: TCCR0B = (1<<CS00);// spuštění čítače
024: sei();// už se můžeme věnovat čemukoli
025: // dalšímu, smím být přerušen…
026: }
027: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 

 
 
Autor: Michal Dudka
 

 

[1] ATMEL. 8-bit Microcontroller with 2/4K Bytes In-System Programmable Flash ATtiny2313A ATtiny4313. [online] citováno 17. února 2017. Dostupné na www: http://www.atmel.com/images/doc8246.pdf
[2] ATMEL. AVR130: Setup and Use of AVR Timers. [online] citováno 17. února 2017. Dostupné na www: http://www.atmel.com/Images/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf
[3] AVRFREAKS. Newbie’s Guide to AVR Timers. [online] citováno 17. února 2017. Dostupné na www: http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers?page=all
Jiné příspěvky v kategorii:

 
Čítač / Časovač 0 nejen na ATtiny – 1. Díl
Čítač / Časovač 0 nejen na ATtiny – 2. Díl

 
Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.