V tomto tutoriálu se vám pokusím představit jednoduchou ale vcelku užitečnou periferii - PIT. Jak už název napovídá PIT slouží k periodickému volání přerušení, což se jistě shodneme, je užitečná funkce. Jen namátkou se hodí ke sledování stavu tlačítek a jiných uživatelských vstupů (potenciometrů, rotačních enkodérů a pod.) nebo k pravidelnému obnovení dat na displeji. Tyhle úkoly většinou nepotřebujete provádět s přesně danou frekvencí. Jinak řečeno je vám vcelku jedno jestli se provádí 20 nebo 40krát za sekundu. A přesně pro tenhle typ úloh tu je PIT. Ale nejen pro ně. Díky tomu že PIT sdílí stejný clock jako RTC, může běžet i v režimech spánku, takže ho užijete i na pravidelné probouzení. A do třetice umí PIT generovat pravidelné eventy, takže jeho služeb mohou využívat i další periferie.
Jak už bylo řečeno je PIT přidružený k RTC a mají společný clock. Pokud tedy používáte RTC, musíte se u PIT smířit se stejným clockem. Tím může být buď vnější 32.768kHz krystal, vnitřní 32k ULP oscilátor a nebo obecný vnější clock přivedený skrze pin TOSC1. Krystal a vnější clock si necháme na nějaké budoucí hrátky s RTC a PIT budeme používat jen s vnitřním ULP oscilátorem. Jen připomenu, že jeho frekvence je někde okolo 32kHz a rozptyl je značný. Při pokojové teplotě dosahuje až +-3% a v širším rozsahu teplot (0-105°C) až +-10%. To nám ale nevadí. Z UPL může vést do PIT krom plné frekvence 32kHz, také redukovaný clock 1kHz a jak brzy uvidíte je možné díky tomu nastavit PIT na časy delší jak sekunda.
Při konfiguraci tedy nejprve zvolte zdroj clocku pro RTC/PIT a to v registru RTC.CLKSEL. V registru RTC.PITCTRLA si skupinou bitů PERIOD vyberte "strop" PITu. Vybrat můžete jednu ze 14ti hodnot v rozsahu od 4 do 32768. S 32kHz clockem lze tedy vybírat periodu v rozsahu 1/(32k/4) = 125us až 1/(32k/32768) =~1s. S 1kHz clockem se pak časy posouvají do pásma 4ms až 32s. Timer spustíte bitem PITEN. Jak už bylo zmíněno, PIT běží asynchronně, takže zápis do jeho registrů podléhá synchronizaci. Zapíšte-li něco do PITCTRLA, bude trvat několik period 32kHz clocku než ke změně opravdu dojde. O stavu synchronizace vás informuje bit CTRLBUSY v registru PITSTATUS. Pokud je nastaven, synchronizace ještě probíhá. Pokud chcete obsah tohoto registru číst (a nebo ho modifikovat - tedy přečíst, upravit a zpět zapsat), zkontrolujte si zda právě neprobíhá synchronizace. Pokud ano, počkejte si až skončí. V registru PITINTCTRL je jediný bit PI jehož nastavením povolujete přerušení. V registru PITINTFLAGS má tentýž bit roli vlajky, která vás informuje o tom, že PIT přetekl (což vyvolá přerušení pokud je povoleno). Tuhle vlajku si musíte ručně smazat jinak se bude přerušení volat neustále (což by už pro vás měla být známá věc). No a to je asi vše. Jen tak na okraj poznamenám, že v registru PITDBGCTRL si můžete povolit aby PIT běžel i během debugu (tedy když je zastavené jádro) - ale to asi často chtít nebudete.
V úvodu jsem zmínil, že PIT umí vytvářet eventy, které potom mohou sloužit k rozličným účelům. Letmý pohled do kapitoly o eventech v datasheetu nám prozradí, že je může posílat na asynchronní kanál 3 (ASYNCCH3) a že to mohou být různé podíly clocku PITu v rozsahu od 64 do 8192. Tedy pro naši modelovou situaci s 32kHz clockem je možné generovat eventy s frekvencemi v pásmu od 4 do 500Hz.
V demonstračním příkladu zkusíme PIT krmit 32kHz clockem z ULP oscilátoru. Do event systému si vypustíme frekvenci přibližně 31Hz (32k/1024). Periodu PITu si nastavíme na 0.5s (32k/16384). V rutině přerušení budeme přepínat stav LEDky (PB5) a blikat s ní s frekvencí okolo 1Hz. Eventy si jen pro informaci vypustíme na EVOUT2 (PC2). To by mělo k předvedení funkce bohatě stačit.
/* tutorial TinyAVR 1-Series * PIT (Periodic Interrup Timer) * periodicky blikáme s LEDkou na PB5 pomocí přerušení od PIT * vypouštíme signál z PIT na Asynchronní event kanál 3 * stav tohoto event kanálu vypouštíme skrze EVOUT2 (PC2) ven */ /* Ve fuses zvolen 20MHz oscilátor */ #define F_CPU 20000000UL #include <util/delay.h> #include <avr/io.h> #include <avr/interrupt.h> #include <util/delay.h> void clock_20MHz(void); void init_pit(void); int main(void){ clock_20MHz(); // jedeme na plný výkon 20MHz PORTB.DIRSET = PIN5_bm; // ovládáme LED na PB5 PORTC.DIRSET = PIN2_bm; // na PC2 vyvedeme eventy (EVOUT2) PORTMUX.CTRLA |= PORTMUX_EVOUT2_bm; // přidělíme PC2 funkci EVOUT2 init_pit(); // inicializujeme PIT // Na event kanál ASYNCHH 3 přivedeme jeden ze signálů PIT (32k/1024 =~31Hz) EVSYS.ASYNCCH3 = EVSYS_ASYNCCH3_PIT_DIV1024_gc; // Na EVOUT2 (alias Asyncuser10) přivedeme signál z kanálu ASYNCHH 3 EVSYS.ASYNCUSER10 = EVSYS_ASYNCUSER10_ASYNCCH3_gc; sei(); // globální povolení přerušení while (1){ // není co dělat... } } // Přerušení od PIT ISR(RTC_PIT_vect){ RTC.PITINTFLAGS = RTC_PI_bm; // mažeme vlajku přerušení // uděláme nějakou "zajímavou" akci PORTB.OUTTGL = PIN5_bm; // přepneme stav LED } void init_pit(void){ // nastavím zdroj clocku pro RTC (a tedy i PIT) - vnitřní 32k ULP oscilátor RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; RTC.PITINTCTRL = RTC_PI_bm; // povolím přerušení od PIT // registr PITCTRLA se musí synchronizovat... // takže pro jistotu zkontrolujeme a případně počkáme na dokončení předchozího zápisu while(RTC.PITSTATUS & RTC_CTRLABUSY_bm){} // čekej dokud probíhá předchozí zápis do PITCTRLA RTC.PITCTRLA = RTC_PERIOD_CYC16384_gc | RTC_PITEN_bm; // spustí PIT s periodou ~2Hz // k dokončení zápisu do tohoto registru dojde až za určitý (nemalý) čas } // Nastaví clock 20MHz (z interního 20MHz bez děličky) void clock_20MHz(void){ // v případě potřeby zde dočasně vypněte přerušení CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLA = CLKCTRL_CLKSEL_OSC20M_gc; // vybírá 20MHz oscilátor CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku) }
Na následujícím oscilogramu se můžete přesvědčit, že příklad pracuje tak jak očekáváme. Dokonce i frekvence docela slušně odpovídají (když uvážíme jak mizernou přesnost by ULP oscilátor měl mít). Ve vcelku nudném příkladu zůstává ale několik zajímavých otázek. Když je zápis do kontrolního registru PITu asynchronní, jak dlouho trvá než se tam hodnota opravdu zapíše ? V našem případě na to mají vliv dva faktory. Prvním z nich je doba rozběhu ULP oscilátoru, která je přibližně 250us. Oscilátor se totiž rozbíhá až v okamžiku kdy ho nějaká periferie vyžaduje. V CLKCTRL by snad mělo jít zapnout, že má běžet trvale. Druhým faktorem je doba synchronizace při zápisu registru (o níž už byla řeč). V datasheetu jsem našel jen formulaci "will take a number of cycles" ("cycles" se myslí period našeho 32kHz clocku). Jednoduchým sledováním BUSY vlajky jsem měřil jak dlouho synchronizace probíhá a vycházelo mi 280us. Kolik z toho padá na vrub rozběhu ULP a kolik samotné synchronizaci jsem už ale nezjišťoval. Na druhém oscilogramu si můžete prohlédnout za jak dlouho po spuštění PITu přijde první přerušení. Možná jste tak jako já čekali, že se PIT rozbíhá "od nuly" a že první přerušení můžeme očekávat po 500ms. Realita je ale jiná a první přerušení přichází přesně po polovině nastaveného času. Zkoušel jsem periodu zmenšit a stále to byla jedna její polovina. Těmito detaily si ale nemusíte plést hlavu, protože vás většinou nebudou zajímat.
Doufám, že jste se v tomto krátkém tutoriálku seznámili s další užitečnou periferií a že se setkáme u dalších dílů.
Home
| V1.02 31.1.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /