Čítač / Časovač 2 je takovou zastrčenou periferií na kterou se v návodech zapomíná, přitom ale zvládá některé unikátní úlohy. Není proto od věci, podívat se mu na zoubek.
Zajisté jste se už seznámili s funkcemi čítačů 0 a 1 a říkáte si, čím nás může čítač 2 překvapit ? Ovládá jen jeden compare kanál (OC2 na PD7) a ve většině funkcí se shoduje s čítačem 0. Má jednu odlišnost, která však má zásadní důsledky. Dá se totiž taktovat asynchronně, tedy signálem nezávislým na clocku čipu. To znamená, že clock čipu ani nemusí běžet aby čítač 2 mohl pracovat. Díky tomu může běžet i v režimu spánku a probouzet Atmel. Kromě toho je čítač 2 vybaven obvodem, který umožňuje připojení hodinového krystalu 32.768kHz na piny TOSC1 a TOSC2. Je tedy dokonale vybavený aby mohl realizovat obvod reálného času (RTC). I když se to zdá jako veliký benefit, najdou tyto funkce nejspíš jen omezené použití. Externí RTC obvody totiž typicky dosahují vyšších přesností, nižší spotřeby a s jejich využitím vám odpadne práce s algoritmizací kalendáře. Přece jen tu ale zůstává jistá skulinka, kdy možnosti čítače 2 s hodinovým krystalem využijete. Stopky nebo různá měření časů v řádu vteřin se vyplatí realizovat s pomocí čítače 2 a neobtěžovat se s osazováním a programováním externího RTC obvodu. Samozřejmě čítač 2 zastane i běžné úkoly jako generování PWM, nebo obecné časovaní a není nutné ho používat v režimech, které si budeme ukazovat v příkladech.
Tuto kapitolu prosím berte s rezervou, protože čítač 2 používám zřídka a důvody proč se ovládá tak jak se ovládá nemám příliš rozmyšlené. V synchronním režimu pracuje úplně stejně jako jakýkoli jiný časovač, jen má svou vlastní nezávislou předděličku (čítač 0 a 1 mají společnou). V asynchronním režimu se práce se všemi registry čítače řídí jeho vlastním clockem (typicky 32.768kHz). Protože clock čipu běží na jiné frekvenci musí u všech zápisů a čtení těchto registrů proběhnout synchronizace. Při čtení to není až takový problém, protože takto procesoru musí být (minimálně 4x) vyšší než takt čítače. Problém je při zápisu, zapíšete-li cokoli třeba do TCNT2 registru, dojde k zápisu do dočasného registru a z něj s obsah přenese teprve až s vzestupnou hranou asynchronního clocku. Vy bohužel přesně nevíte kdy to nastane. Aby jste se mohli orientovat v tom zda už k zápisu došlo, musíte sledovat vlajky TCN2UB, OCR2UB a TCR2UB v registru ASSR. Vlajky svým nastaveném signalizují že zápis probíhá a zapsáno je až v okamžiku, kdy se vlajky vynulují. Toto přirozeně platí jen v asynchronním režimu, pokud používáte čítač 2 v synchronním režimu (tedy pouštíte do něj clock Atmelu), nemusí vás tato problematika zajímat. Registry TCNT2, OCR2, TCCR2 se používají stejně jako u čítače 0. Zda bude čítač taktován synchronním nebo asynchronním signálem vybíráte bitem AS2 v registru ASSR. Jeho nastavením vyberete asynchronní režim a pokud je na pinech TOSC1 a TOSC2 připojen krystal, začne se rozbíhat. Rozběh může trvat relativně dlouho (klidně víc jak sekundu). Od spuštění asynchronního režimu musí veškerý přístup k čítači probíhat podle výše uvedených pravidel. Krom nich existují ještě další pravidla jak čítač rozbíhat, protože při přechodu do asynchronního režimu se může stav všech registrů pozměnit. Ale to jsou detaily, které za vás řeší datasheet v němž je popsán postup jak časovač spustit. A aby jste ho nemuseli louskat, tak to předvedeme v příkladech. Většinou časovač jen spustíte a už jeho obsah měnit nebudete. Pokud by jste s ním i přes to chtěli provádět nějaké složitosti, budete si muset postupy prostudovat. Naštěstí to ale nejsou ani dvě stránky. Přerušení se vybírají jako obvykle v registru TIMSK pomocí bitů OCIE2 a TOIE2. Časovač 2 může vzbudit čip z režimu "Power save", nikoli však z režimu "power down" nebo "standby".
Existují kvanta teorie o tom jak používat krystalový oscilátor tak aby dosahoval nejvyšších přesností. Jaké mu připojit kapacity, jaký sériový odpor atd. Já tomu popravdě příliš nerozumím, ale z datasheetu vím že interní obvod hodinového oscilátoru obsahuje kapacity 36pF, takže můžete s klidem připojit krystal k TOSC1 a TOSC2 a kapacity neřešit.
V příkladech se nebudeme zbytečně zdržovat s použitím časovače v synchronním režimu, protože ten je shodný s čítačem 0. Všechny příklady se tedy budou zabývat použitím asynchronního režimu. Pokud je chcete zkoušet, sežeňte si krystal.
V prvním příkladě si ukážeme jak časovač v asynchronním režimu spustit a vygenerujeme si přerušení jak od přetečení tak od compare události. Tento režim činnosti najde asi největší uplatnění protože časovač nastavíme tak aby volal přerušení každou sekundu. Obě rutiny přerušení necháme indikovat svoje volání na piny PA0 a PA1. Jestliže taktujeme čítač frekvenci 32768Hz, je velmi vhodné využít jeho předděličky a podělit signál 128. Tím z něj získáme frekvenci 256Hz. No a jestliže čítač musí pro přetečení napočítat 256 impulzů, bude k přetečení docházet každou sekundu. Předdělička se jako obvykle řídí bity CS20,CS21 a CS22 v registru TCCR2. Postup zapnutí časovače probíhá následovně. Vypneme přerušení, protože při přechodu do asynchronního režimu není jisté co se stane s obsahem řídících registrů časovače a mohlo by se tedy omylem spusti. Přirozeně pokud víte že není zapnuté, nemá smysl ho vypínat. Poté spustíme časovač v asynchronním režimu nastavením bitu AS2. Následně zapíšeme do registrů časovače co potřebujeme. V našem případě v TCCR2 volím předděličku a v OCR2 volím hodnotu pro compare událost (polovinu celkového času). Pak musím počkat než dojde k přepisu těchto dvou registrů. Čekám proto na vynulování vlajek OCR2UB a TCR2UB. Pak mám jistotu, že časovač pracuje s hodnotami, které jsem zapsal. Pokud tu jistotu mít nepotřebuji, nemusím na nic čekat, ale bůh s vámi, pokud se pokusíte zapsat do některého z registrů dřív než skončí předchozí zápis. Takovou chybu už budete těžko hledat, takže je celkem slušné počkat si. Pak je potřeba smazat obě vlajky OCF2 a TOV2, protože během přepisu mohlo dojít k jejich nastavení. A jak jistě víte, pokud je vlajka nastavena nepřijde přerušení ! Teprve pak povolím přerušení nastavením bitů OCIE2 a TOIE2. Nakonec stačí povolit přerušení globálně a celá aplikace pracuje. V rutinách přerušení už pak je přepínáme hodnoty na pomocných výstupech PA0 a PA1.
// A) Časovač 2 v asynchronním režimu s krystalem 32.768kHz #include <avr/io.h> #include <avr/interrupt.h> int main(void){ DDRA = (1<<DDA0) | (1<<DDA1); // PA0 a PA1 indikace činnosti TIMSK &=~((1<<OCIE2) |(1<<TOIE2)); // vypnu přerušení od časovače 2 ASSR = (1<<AS2); // přepínám časovač 2 do asynchronního režimu OCR2 = 128; // v polovině času chceme vyvolat další přerušení TCCR2 = (1<<CS22) | (1<<CS20); // počkej než se data z TCCR2 a OCR2 přenesou do čítače while((ASSR & (1<<OCR2UB)) || (ASSR & (1<<TCR2UB))){}; TIFR |= (1<<OCF2) | (1<<TOV2); // smaž vlajky - při přepisu mohlo dojít k jejich nastavení TIMSK |= (1<<OCIE2) | (1<<TOIE2); // povol obě přerušení sei(); // globální povolení přerušení while(1){ // nic nedělej } } ISR(TIMER2_OVF_vect){ PORTA = PORTA ^ (1<<DDA1); // přepni PA1 } ISR(TIMER2_COMP_vect){ PORTA = PORTA ^ (1<<DDA0); // přepni PA0 }
Výstup programu můžete vidět na obrázku a1. Protože rutina přerušení přichází každou sekundu a dochází v ní k přepínání výstupu, má frekvence kterou takto generujeme hodnotu 0.5Hz. Pin se přirozeně musí přepnout tam i zpět a to mu trvá celkem 2 sekundy. Na modrém průběhu vidíte signál z capture události. K přerušení dojde vždy v polovině času realizovaného časovačem. Na červeném signálu je pak signál z pinu PA1, který je ovládán přerušením od přetečení. Z měření na osciloskopu je patrné, že časy krásně sedí. Aby taky ne, když je krystal o několik řádů přesnější než měření osciloskopem. Žlutý průběh je z TOSC1 - tedy signál přímo z krystalu. Detail jeho průběhu můžete vidět na obrázku a2. Všimněte si že to není čistý sinusový průběh a jeho amplituda je celkem nízká (méně jak 0.5V). Opět je vidět, že měřicí schopnosti osciloskopu nejsou dostatečné abychom stanovili frekvenci přesně. K těmto účelům by byl vhodnější čítač.
Příklad B) bude taková odpočinková práce. Pouze si ukážeme, že čítač v asynchronním režimu ovládá výstupní pin OC2 tak jak jsme na to zvyklí. Zkusíme si tedy vygenerovat jednoduché PWM. Aby bylo rozumně patrné vypneme na čítači předděličku a budeme jej taktovat přímo frekvencí 32.768kH. Hodnotu PWM zvolíme schválně malou, aby bylo možné ověřit na osciloskopu zda se generuje správně. Při nastavování nám odpadá starost o vlajky a přerušení. Stačí tedy nastavit OC2 (PD7) jako výstup, spustit časovač v asynchronním režimu (nastavením AS2) a nastavit hodnotu PWM. Zápis do registrů časovače je opět ošetřen čekáním než je zápis dokončen.
// B) Časovač 2 PWM v asynchronním režimu #include <avr/io.h> int main(void){ DDRD = (1<<DDA7) ; // PD7 (OC2) je výstup TIMSK &=~((1<<OCIE2) |(1<<TOIE2)); // vypnu přerušení od časovače 2 ASSR = (1<<AS2); // přepínám časovač 2 do asynchronního režimu // krmíme čítač hodinovým kmitočtem bez předděličky, mód fast PWM, přidělujeme ovládání OC2 (PD7) čítači TCCR2 = (1<<CS20) | (1<<WGM21) | (1<<WGM20) | (1<<COM21); OCR2 = 8; // 8 taktů oscilátoru drž na PD7 log.1 // počkej než se data z TCCR2 a OCR2 přenesou do čítače while((ASSR & (1<<OCR2UB)) || (ASSR & (1<<TCR2UB))){}; // nemusím čekat, když nic dalšího nedělám // od teď jsou oba registry nastaveny while(1){ // nic nedělej } }
Na obrázku b1 vidíte, že je perioda 32768/256 = 128Hz. Na obrázku b2 pak můžete napočítat že doba trvání kladného pulzu je 9 taktů hodinového krystalu. Možná vás překvapí proč 9, když jsme zapsali do OCR2 hodnotu 8. Nezapomeňte že čítač počítá od nuly a než napočítá do 8, trvá mu to devět taktů (zkuste si na prstech).
V tomto příkladě si ukážeme jak může časovač 2 posloužit k probouzení z režimu spánku. Uplatnění najde v aplikacích kde potřebujete jednak snížit odběr čipu a k tomu mít ještě k dispozici přesnou časovou základu. Co se týče spotřeby ale v tomto příkladě neporazíme použití externího obvodu RTC. Přes to se pojďme podívat jak na to. V prvé řadě si vypustíme na OC2 1Hz signál aby mohl sloužit jiným obvodům vyžadujícím 1Hz časovou základnu. Generování bude probíhat i v režimu spánku. Proces spouštění je mix předchozích dvou příkladů. S tím rozdílem, že nepoužijeme přerušení od compare události. Po startu časovače v asynchronním režimu je dobré počkat než se oscilátor rozběhne. Jak dlouho mu to trvá můžete vidět na obrázku c1. Kdyby se totiž nerozběhl a my bychom čip uspali, už by nás neměl jak probudit. Dále jsem přidal jen přechod do režimu spánku (Power save). Jakmile dojde k přetečení časovače, vyvolá se rutina přerušení a čip se probere ze spánku. Tady je potřeba dát pozor na jednu věc. Pokud by jste se rozhodli čip ihned uspat mohl by nastat problém. Protože vlajku přetečení (TOV2) ovládá časovač s taktem pouhých 32768Hz, mohlo by se stát, že čip uspíte ještě dřív než se vlajka smaže. K jejímu smazání je totiž nutné aby čítač alespoň jednou inkrementoval a zároveň Atmelu běžel jeho clock. A pokud čip uspíte s nastavenou vlajkou, nedojde k jejímu smazání nikdy. Nesmazaná vlajka nedovolí vzniknout přerušení (to vzniká spolu s nastavením vlajky) a čip se pak už nikdy neprobudí. Tento problém proto obcházím podle návodu v datasheetu. Do některého z registrů časovače zapíšu hodnotu a počkám až dojde k jejímu zapsání. Pak mám jistotu že došlo k inkrementaci časovače a že je tedy vlajka smazaná. Přirozeně si nechci časovač přenastavit a tak zapisuji do OCR2 to co tam už je. Teprve pak rutinu přerušení ukončím. Program pak přejde do hlavní smyčky, kde na něj čeká instrukce uspání.
// C) Časovač 2 v režimu spánku #define F_CPU 8000000 #include <avr/io.h> #include <avr/interrupt.h> #include <avr/sleep.h> #include <util/delay.h> int main(void){ DDRD = (1<<DDA7) | (1<<DDA6); // OC2 (PD7) a PD6 je výstup TIMSK &=~((1<<OCIE2) |(1<<TOIE2)); // vypnu přerušení od časovače 2 ASSR = (1<<AS2); // přepínám časovač 2 do asynchronního režimu OCR2 = 128; // budeme generovat 1Hz na OC2 // předdělička 128, fast PWM mód s PWM na OC2 TCCR2 = (1<<CS20) | (1<<CS22) | (1<<WGM21) | (1<<WGM20) | (1<<COM21); // počkej než se data z TCCR2 a OCR2 přenesou do čítače while((ASSR & (1<<OCR2UB)) || (ASSR & (1<<TCR2UB))){}; _delay_ms(1000); // počkej ať máš jistotu že se krystal rozběhl TIFR |= (1<<OCF2) | (1<<TOV2); // smaž vlajky - při přepisu mohlo dojít k jejich nastavení TIMSK |= (1<<TOIE2); // povol přerušení od přetečení set_sleep_mode(SLEEP_MODE_PWR_SAVE); // vybíráme režim spánku sleep_enable(); // pololíme přechod do režimu spánku sei(); // globální povolení přerušení while(1){ sleep_cpu(); // hezké sny } } ISR(TIMER2_OVF_vect){ PORTD = PORTD ^ (1<<DDD6); // přepni PD6 OCR2 = 128; // zapisuji do OCR aniž bych v něm chtěl něco měnit !!! while(ASSR & (1<<OCR2UB)){}; // počkám až budu vědět že čítač inkrementoval // pak mám jistotu, že smazal i vlajku přerušení a můžu si dovolit čip zase uspat }
Na obrázku c1 vidíte že na OC2 (PD7) dochází ke generování 1Hz signálu. Změny v červeném signálu pak představují okamžiky kdy došlo k volání rutiny přerušení a tedy i probuzení čipu. Žlutý průběh pak názorně předvádí jak dlouho trvá oscilátoru než se spustí. Je patrné pozvolné narůstání amplitudy. Snímek znázorňuje situaci ihned po restartu čipu. Vzhledem k tomu že jsem pokusy prováděl na vývojové desce se spoustou jiných spotřebičů, nemůžu vám předložit odběr čipu v tomto režimu. Ze zkušeností s Atmega8A ale můžu říct že podle provozního napětí se odběr v režimu spánku s běžícím hodinovým krystalem pohybuje od 100uA do 500uA. Kdežto v aktivním režimu se odběr pohybuje v jednotkách mA.
Home
V1.00 2015
By Michal Dudka (m.dudka@seznam.cz)