Reklama

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

Úvod

Čí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.

Čítače / časovače jsou komplexní periferie a jejich teoretické vysvětlení by zabralo hodně prostoru. Pokud bych ho koncentroval do jedné kapitoly, mohli byste 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 periférii 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ů doporučoval provozovat mikrokontrolér s taktovacím signálem (clockem) odvozeným od krystalu, protože 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 mikrokontrolér 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 osmibitový a jednodušší. Čítač / časovač 1 je šestnáctibitový ale většinu postupů na něm bude “možné použít stejně (bude se mu věnovat samostatný článek). Ří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 hodin (ať už se berou odkudkoli) se tento registr inkrementuje o jedna.

Seznam příkladů

V tomto článku si ukážeme následující příklady:

  • Čítání externího signálu (A)
  • Čítání interního signálu – předdělička (B)
  • Compare jednotky a přerušení (C)
Čítání externího signálu (A)

Čítače v AVR umí čítat nejen vnitřní signál (odvozený od hodin mikrokontroléru), 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 tab.1 slouží k tomu, aby byl čítač odpojen, tedy aby nečítal. Další možnosti k čítači připojují nějak podělené hodiny mikrokontroléru. My se ale budeme zabývat posledními dvěma kombinacemi, protože 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 jedničku. 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, protože čítá bez ohledu na to, kdo tam signál přivádí. Abychom viděli co se v TCNT0 odehrává, tak necháme program obsah tohoto registru zobrazovat na PORTB (na který si připojíme LED diody). 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 hodin mikrokontroléru (a nespolehlivě i kratší). Jinak řečeno, jestliže mikrokontrolér běží na 1 MHz, tak čítač započítá i pulzy krátké 2 us. Z toho také plyne, že pro čítání externího signálu je výhodnější taktovat mikrokontrolér na vyšší frekvenci. Zapojíme tedy obvod podle Obr. 1 a jen pro efekt si k tlačítku dáme LED diodu indikující jeho stav (není to ale 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 diod. 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ě, protože my jsme si chtěli ukázat, že to čítačem jde taky;) Pokud ale budete počítat buď krátké impulzy, nebo obecně signály o vysoké frekvenci, tak vám nezbude jiná možnost než tato. Klidně si zkuste příklad upravit tak, aby počítal sestupné hrany. Když podržíte tlačítko déle, tak uvidíte rozdíl. Ti trpělivější z vás mohou vidět, že po 255 kliknutí čítač přeteče a v registru TCNT0 bude zase nula. Tomuto se říká přetečení čítače a v budoucnu toho budeme využívat.

Tab. 1: Zdroje signálu pro čítač / časovač 0

CS02 CS01 CS00 Popis
0 0 0 Čítač zastavený
0 0 1 Hodiny jádra – bez předděličky
0 1 0 Hodiny/ 8
0 1 1 Hodiny / 64
1 0 0 Hodiny / 256
1 0 1 Hodiny / 1024
1 1 0 Externí signál z T0, sestupná hrana
1 1 1 Externí signál z T0, vzestupná hrana

 
Obr. 1: Čítač stisků tlačítka s binárním výstupem

Obr. 1: Čítač stisků tlačítka s binárním výstupem

 

 

001: // A1) jednoduchý čítač externích pulzů
002: #include <avr/io.h>
003:
004: int main(void){
005: DDRB = 0xff;// výstup na LEDky
006: DDRD &= ~(1<<DDD4);// vstup PD4 (T0)
007: // zdroj signálu pro čítač 0 – vnější signál,
008: // vzestupná hrana
009: TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00);
010: while (1) {
011: PORTB = TCNT0;// neustále zobrazujeme stav TCNT0 registru
012: }
013: }
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.
 

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 stavový bit TOV0. Ten slouží k tomu, aby vám indikovala přetečení čítače. Případně se pomocí něj dá ještě vyvolat přerušení, ale o tom až později. Ve většině režimů když čítač přeteče z 0xFF (tedy 255) do 0, tak se bit TOV0 nastaví do stavu 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 bit (nebo použít přerušení) a po každém přetečení čítače si inkrementovat nějakou proměnnou abyste neztratili informaci o celkovém počtu. Pokud nepoužijete přerušení, tak si nezapomeňte bit TOV0 vždy včas smazat, jinak se o dalším přetečení nedozvíte. Tento bit se maže, tak jako vždycky a to zápisem log. 1. Přidáme si tedy do našeho schématu (Obr. 1) ještě jednu LED diodu (na PD3), kterou budeme indikovat stav TOV0. A abychom nemuseli mačkat tlačítko 256krát, tak si předvyplníme čítač nějakou vyšší hodnotou :) Když to zkusíte, tak zjistíte, že se bit 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.

 

001: // A2) jednoduchý čítač – sledujeme vlajku přetečení
002: #include <avr/io.h>
003:
004: int main(void){
005: DDRB = 0xff;// výstup na LEDky
006: DDRD &= ~(1<<DDD4);// vstup PD4 (T0)
007: DDRD |= (1<<DDD3);// výtup na LEDku (indikace TOV0)
008: // zdroj signálu pro čítač 0 – vnější signál,
009: // vzestupná hrana
010: TCCR0B = (1<<CS02) | (1<<CS01) | (1<<CS00);
011: TCNT0 = 250;
012: while (1) {
013: PORTB = TCNT0;// neustále zobrazujeme stav TCNT0 registru
014: if(TIFR & (1<<TOV0)){PORTD |= (1<<PORTD3);}else{PORTD &= ~(1<<PORTD3);}
015: }
016: }
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.
 
Čítání interního signálu – předdělička (B)

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í hodiny mikrokontroléru, tak začne plnit úlohu časovače. Protože hodiny mikrokontroléru bývají typicky v řádu MHz, tak máme k dispozici předděličku (prescaler), pomocí níž můžeme frekvenci hodin přicházejících do čítače snižovat. Podíváte-li se zpět do Tab. 1, tak 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 hodiny mikrokontroléru. Nechme opět stejné zapojení jako v předchozím příkladě (Obr. 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 diody. Přetečení si ohlídáme bitem TOV0 a přirozeně jej nezapomeneme smazat. Abychom získali jakousi sebedůvěru v ovládání čítače, tak zkusíme nejprve předpovědět, s jakou frekvencí se bude LED dioda přepínat. Hodiny mikrokontroléru máme 1 MHz, proto jeden “tick” trvá 1 us. Předdělička je nastavena na hodnotu 1024. Aby se čítač jednou inkrementoval, tak musí do předděličky přijít 1024 “ticků”. Jeden “tick” čítače tedy trvá 1024 us. Čítač se musí inkrementovat 256krát než přeteče, takže by mělo trvat celkem: T = 1024 x 256 x 1 us = 262144 us, než dojde k nastavení bitu TOV0. V tom okamžiku přepneme LED diodu z jednoho stavu do druhého a stejnou dobu musíme počkat než LED diodu vrátíme do původního stavu. Celý cyklus tedy bude trvat dvě přetečení časovače, tedy přibližně: 2 x 262 ms = 524 ms. LED dioda by tedy měla blikat přibližně s frekvencí 2 Hz. Slovo přibližně je tu na místě, protože hodiny mikrokontroléru odvozujeme od interního RC oscilátoru. RC oscilátor s frekvencí 1 MHz má odchylku +/- pár procent. Kdo chce pracovat přesněji (a že většina z vás bude chtít), tak bude muset začít používat krystal (a obětovat tak dva piny). O tom ale zase asi jindy. Na Obr. 2 vidíte výstup našeho programu. Očekávaná délka pulzu by měla činit 262.144 ms a ve skutečnosti je 259 ms (Obr. 2), z toho můžeme usuzovat, že hodiny mikrokontroléru se mi od 1 MHz odchylují přibližně o 1 %. Na Obr. 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 255 us. 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 bitu. Za jak dlouho zareaguje, záleží na tom, u které instrukce se zrovna nachází v okamžiku, kdy se bit 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. 2: Výstup programu s předděličkou 1024

Obr. 2: Výstup programu s předděličkou 1024

 
Obr. 3: Výstup programu bez předděličky

Obr. 3: Výstup programu bez předděličky

 

 

001: //B) jednoduchý čítač – blikáme ledkou 1
002: #include <avr/io.h>
003:
004: int main(void){
005: DDRB = 0xff;// výstup na LEDky
006: DDRD &= ~(1<<DDD4);// vstup PD4 (T0)
007: DDRD |= (1<<DDD3);// výtup na LEDku (indikace TOV0)
008: // zdroj signálu pro čítač 0 – vnitřní clock / 1024
009: TCCR0B = (1<<CS02) | (1<<CS00);
010: while (1) {
011: PORTB = TCNT0;// neustále zobrazujeme stav TCNT0 registru
012: if(TIFR & (1<<TOV0)){
013: PIND |= (1<<PIND3);// přepínám hodnotu na PD3 – použití registru PIND
014: // je “finta” ale v souladu s datasheetem (sekce 10.1.2 – doporučuji)
015: TIFR |= (1<<TOV0);// mažu vlajku TOV0
016: }
017: }
018: }
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.
 
Compare jednotky a přerušení (C)

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ů, se nastaví bit OCF0A nebo OCF0B do stavu log. 1 (podle toho, který registr se s hodnotou v čítači shodoval). Obě compare jednotky pracují nezávisle. Typicky chcete, aby program provedl nějakou akci v okamžiku, kdy čítač dočítá do nějaké hodnoty. Nastavení některého z OCF bitů 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á mikrokontrolér 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 – OC0A a OC0B výstupy v režimech CTC a Normal, který bude uveden v posledním díle seriálu).

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 1 ms rozsvítit LED diodu a po 0.5 ms ji zase zhasnout. Tedy vytvořit 1 ms po startu pulz o délce 500 us. Nejprve budeme muset vhodně nastavit předděličku časovači. Máme k dispozici 1 MHz, to jest časovou základnu 1 us. S tou bychom dokázali realizovat nejdelší čas 255 us. Musíme tedy vstupní signál podělit. Pokud ho podělíme osmi, dostaneme frekvenci 125 kHz (periodu 8 us). Takhle jsme schopni realizovat čas až 255 x 8 = 2040 us. To nám bude stačit. Do kolika tedy musí časovač napočítat, aby mu to trvalo 1 ms ? 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, tak se nastaví bit OCF0A a zavolá se přerušení. Pro realizaci času 1.5 ms potřebujeme nechat čítač napočítat do 1500 / 8 = 188. Do registru OCR0B tedy uložíme hodnotu 187. Přerušení se povolují a zakazují v registru TIMSK, který je společný pro oba čítače / časovače. 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 diodu, 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. 4. Na něm je patrné, že opravdu 1 ms po stisku tlačítka se LED dioda rozsvítí a 0.5 ms 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. 4: Generování zpožděného pulzu pomocí přerušení od compare události

Obr. 4: Generování zpožděného pulzu pomocí přerušení od compare události

 
 

001: // C) Přerušení od Compare A a Compare B
002: #include <avr/io.h>
003: #include <avr/interrupt.h>
004:
005: // přerušení od Compare A události
006: ISR(TIMER0_COMPA_vect){
007: PORTB |= (1<<PORTB0);// rozsviť LED
008: }
009:
010: // přerušení od Compare B události
011: ISR(TIMER0_COMPB_vect){
012: PORTB &= ~(1<<PORTB0);// zhasni LED
013: TCCR0B = 0;// vypni časovač
014: }
015:
016: int main(void){
017: DDRD &= ~(1<<DDD4);// vstup pro tlačítko
018: DDRB |= (1<<DDB0);// LEDka
019: OCR0A = 124;// první čas (rozsvícení LED)
020: OCR0B = 187;// druhý čas (zhasnutí LED)
021: // povolujeme přerušení od OCR0A a OCR0B
022: TIMSK = (1<<OCIE0B) | (1<<OCIE0A);
023: // pro jistotu mažeme vlajky, co kdyby vám
024: // zůstaly z “minula”
025: TIFR |= (1<<OCF0B) | (1<<OCF0A);
026: sei();// globální povolení přerušení
027:
028: while(1){
029: while(!(PIND & (1<<PIND4))){}// čekej dokud není stisknuto tlačítko
030: TCNT0 = 0;// vynuluj časovač (kdo ví co tam zbylo z "minule")
031: TCCR0B = (1<<CS01);// spouštíme časovač
032: while(PIND & (1<<PIND4)){}// čekej dokud není uvolněno tlačítko
033: }
034: }
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.
 

V následujících dvou dílech si ukážeme další příklady, takže se máte určitě na co těšit!

 

 
 
Autor: Michal Dudka
 

[1] ATMEL. 8-bit Microcontroller with 2/4K Bytes In-System Programmable Flash ATtiny2313A ATtiny4313. [online] citováno 12. února 2017. Dostupné na www: http://www.atmel.com/images/doc8246.pdf
[2] ATMEL. AVR130: Setup and Use of AVR Timers. [online] citováno 12. ú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 12. února 2017. Dostupné na www: http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers?page=all

 

Jiné příspěvek v kategorii:

 
Čítač / Časovač 0 nejen na ATtiny – 1. Díl
Čítač / Časovač 0 nejen na ATtiny – 3. 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.