Čítač / časovač TCB je přítomen na všech MCU rodiny "moderních" AVR. Seznam "features" v datasheetu dává naznačuje, že jeho dominantním (ne však jediným) posláním je měření času vnějších signálů. V tomto krátkém tutoriálu chci ukázat jak jej lze využít k měření šířky pulzu. Což je role ve které ho využívám například pro čtení signálu z RC přijímačů (typicky 1-2ms pulzy s frekvencí 50Hz). Ukázky si dovolím napsat pro čip Atmega4808, ale budou přenositelné i na čipy řad Attiny nebo AVRxx.
Když se podíváte v datasheetu do tabulky I/O Multiplexing, kde jsou funkce pinů, zjistíte že tam nic jako "vstup" pro TCB není. Jak se tedy signál k TCB dostane ? Skrze event systém. Díky němu je výběr vstupního signálu pro TCB velmi flexibilní. Dá se říct, že lze jako vstup zvolit libovolný pin. Neplatí to ale obecně a omezení vyplývají ze samotného event sytému. Abych byl konkrétní, tak třeba není možné do event sytému (na Atmega4808) zavést signály ze tří různých pinů na stejném portu. Podívejte se na tabulku v kapitole 14.5.2 Channel n Generator Selection
Samotná orientace v tabulce není náročná, ale je náročné to srozumitelně vysvětlit. Hlavně proto že si autoři usnadnili práci a tabulku zkrátili. Event systém má až 8 kanálů (CH0 až CH7). Každý kanál má svůj registr (CHANNEL0 až CHANNEL7) a svůj sloupeček, označený CH0 až CH7. V něm jsou uvedeny všechny možné signály, které do tohoto kanálu lze zavést. Každý signál pak má ve sloupci HEX svůj kód, kterým se volí. Některé signály jako třeba výstup komparátoru (AC0 OUT), nebo konec převodu (ADC RESRDY) můžete zavést do libovolného kanálu a proto jejich buňka přesahuje do sloupců všech kanálů. Jiné signály, jako třeba PIT_clock/1024 můžete zavést jen do kanálů CH0,CH2,CH4,CH6. Potud je vše srozumitelné.
Komplikace nastavá u řádků CCL_LUTn, PORT0_PINn, PORT1_PINn, USARTn a TCBn. Těchto pět řádků totiž zastupuje 4+8+8+8+16 řádků. Neboť ve vašem MCU je více CCL_LUT, více PORTů, více USARTů a více TCB. Do toho se přidá ještě nevhodné použití "n" (kdy to jednou znamená číslo pinu a jindy binární reprezentaci čísla pinu a ve sloupci HEX kde by "n" mělo být tak není) a je zmatení kompletní. Aby tabulka nebyla "moc dlouhá" (550 stránkovému dokumentu by jedna strana navíc jistě strašně ublížila) nutí vás autoři aby jste tabulku pochopili empiricky. Takže přistoupíme na jejich hru a vysvětlíme to na příkladech.
Už umíme "poslat" do kanálů event systému různé signály a nyní potřebujeme vědět jak signály přiřadit "spotřebičům" (USER). USERem mohou být různé periferie a jsou shrnuty v tabulce 14.5.3 User Channel Mux. Každý user má svůj vlastní registr USER0 až USER23 do kterého je možné zapsat hodnotu 0 až 8. Hodnota 0 znamená že USERovi není přiřazen žádný kanál event systému a žádné signály do něj neproudí. Hodnota 1 až 8 pak určuje ze kterého kanálu signál do useru vede. Jeden event kanál může mít přiřazený klidně několik USERů. V hlavičkovém souboru ale žádné registry USER0 až USER23 nenajdete. Tam jsou totiž pojmenované "specificky". Z datasheetu se například dočteme že USER20 je TCB0, v hlavičkovém souboru je ale registr USER0 pojmenován jako USERTCB0. Což jistě zvětšuje čitelnost a Microchip by měl upravit názvy v datasheetu. Attiny ale takto popisně pojmenované USER registry v hlavičkovém souboru nemá.
Pro účely naší ukázky budeme signál přivádět na pin PA6 a pak srkze kanál 0 event systému k časovači TCB0. Ten jak už bylo řečeno umí pracovat ve vícero režimech. TCB v Režimu "PW" (Pulse Width / Šířka pulzu) neustále čítá a čeká na jednu hranu signálu (řekněme vzestupnou). S jejím příchodem se čítač vynuluje a čítá dál (od nuly). S příchodem opačné hrany (řekněme sestupné) se provede capture událost - tedy obsah čítače se uloží do CCMP registru. A v něm bude číselná hodnota která odpovídá době trvání pulzu. Tato capture událost nastaví vlajku CAPT v INTFLAGS registru a ta může vyvolat přerušení (pokud je povoleno bitem CAPT v registru INTCTRL). Aby TCB takto pracoval je nutné mu bitem CAPTEI povolit detekci capture události. Hranu na kterou se má restartovat a na kterou má uložit čas (tedy polaritu měřeného pulzu) lze měnit bitem EDGE. Pokud je to potřeba, lze si bitem FILTER povolit digitální filtr. Ten vzorkuje vstupní signál (s frekvencí jádra) a teprve až po čtyřech po sobě jdoucích stejných úrovních pošle úroveň vstupního signálu dál. To znamená že pulzy kratší jak čtyři takty odfiltruje a neprojdou. Ale také to znamená že všechny signály vstupující do TCB jsou o čtyři takty zpozděné (což nemá na měření vliv, neboť se zpozdí obě hrany stejně).
Krátkou zmínku si zaslouží i bit DBGRUN, který povoluje chod TCB i v okamžiku kdy je jádro zastavené debuggerem. To je velmi žádoucí, neboť v mnoha aplikacích vnější signál nepočká a přichází neustále. Vůbec nevadí, že případně spoustu příchozích pulzů TCB změří a program je nevyčte (neboť je zastaven debuggerem). Po příchodu do rutiny přerušení jsme zvyklí mazat vlajku která ho vyvolala (jinak bychom se z něj nikdy nedostali). To ale v tomto případě dělat nemusíme, stačí přečíst obsah CCMP registru a zároveň s tím dojde i ke smazání vlajky.
// Měří šířku pulzu přivedenou na PA6 s rozlišením 1us #include <avr/io.h> #include <avr/interrupt.h> volatile uint8_t flag=0; volatile uint16_t pulse_width; int main(void) { _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB,CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm); // 1MHz // volí zdroj signálu pro kanál 0 - zde PA6 (viz tabulka) EVSYS.CHANNEL0 = EVSYS_GENERATOR_PORT0_PIN6_gc; // volí ze kterého kanálu event systému si TCB0 bude brát signál EVSYS.USERTCB0 = EVSYS_CHANNEL_CHANNEL0_gc; TCB0.DBGCTRL = TCB_DBGRUN_bm; // povolí chod TCB i během "debugu" TCB0.INTCTRL = TCB_CAPT_bm; // povolí přerušení od "capture" události TCB0.CTRLB = TCB_CNTMODE_PW_gc; // volí mód "Pulse width" TCB0.CTRLA = TCB_ENABLE_bm; // spouští TCB TCB0.EVCTRL = TCB_FILTER_bm | TCB_CAPTEI_bm; // povoluje "capture" událost a aktivuje filtr sei(); // globální povolení přerušení while (1){ if(flag){ // pokud je změřen nový pulz // nějak zpracuj pulse_width flag = 0; } // prostor pro jinou užitečnou činnost } } ISR(TCB0_INT_vect){ pulse_width = TCB0.CCMP; // přečíst čas flag = 1; // dát vědět hlavní smyčce }
Pro ověření jsem přivedl na vstup pulz šířky 1.869ms a od našeho programu jsem obdržel hodnotu 1876us. Odchylku 1869/1876 = -0.4% můžeme připsat na vrub přesnosti vnitřního oscilátoru MCU. Abych si byl naprosto jist, zkusil jsme ještě změřit pulz šířky 3.7384ms s výsledkem 3752us. I v tomto případě je procentuální odchylka stejná což dokládá že je způsobena odlišnou frekvencí interního oscilátoru, od (výrazně přesnější) frekvence osciloskopu jímž jsem šířku měřil. Rozsah měření je dán stropem timeru. V našem případě je maximální měřitelný pulz délky něco málo přes 65ms. Pokud bychom chtěli měřit delší pulzy, museli bychom snížit clock pro TCB. A naopak, pokud bychom chtěli měřit kratší pulzy, můžeme frekvenci TCB zvětšit a získat tím lepší časové rozlišení.
Obdobným způsobem pracuje i další užitečný mód Frequency and Pulse-Width Measurement, který měří šířku pulzu i frekvenci. Ale o tom zase někdy jindy.
Home
V1.00 14.6.2024
By Michal Dudka (m.dudka@seznam.cz)