V úvodním díle tutoriálu o timerech jsem zmiňoval, že jde o jednu z nejpropracovanějších periferií na STM. Tento příklad toho bude důkazem. Mělo by z něj být patrné jak přizpůsobit "hardwarovou" konfiguraci timeru vybranému úkolu. Demonstraci si předvedeme na měření PWM signálu. Přesněji řečeno na měření periody a střídy pulzně šířkové modulace (viz oscilogram níže). Jen pro zajímavost vyjmenuji pár přístupů jak to lze provádět.
Využijete Capture události k okamžitému uložení stavu timeru. Tím odpadnou všechny chyby způsobené opožděnou reakcí SW. Jedním kanálem detekujete okamžik příchodu sestupné hrany, druhým kanálem čas příchodu vzestupné hrany. Obě události necháte vyvolat přerušení. Timer necháte běžet neustále na pozadí. V rutinách přerušení pak vyčtete obsah Capture registrů (tedy časové značky příchodu vybraných hran v signálu). Protože ale timer běží stále, musíte s trochou matematiky ošetřit situaci kdy by během měření přetekl. Což samo o sobě není problém. Trochu více péče vyžaduje ošetření situace kdy timer přeteče vícekrát (což ale hrozí jen při měření opravdu divokých signálů s velkým rozsahem hodnot). Shrňme si to. Tato metoda je přesná, relativně dost používaná a má drobná ale řešitelná úskalí. Pořád ale není dokonalá :)
Teď se teprve dostáváme k samotnému příkladu. V naší ukázce bude timer dělat vše sám a vyvolá přerušení až v okamžiku kdy bude mít změřeny všechny potřebné údaje. Jak takovou konfiguraci na timerech STM32 vytvořit vám napoví následující obrázek.
Vstupem do timeru může být buď CH1 nebo CH2. Protože má Discovery modul na pinu PA0 (TIM2_CH1) tlačítko, využívám CH2 (na PA1) - na obrázku nese název TI2. Z TI2 je možné získat dva signály TI2FP1 a TI2FP2. Každému z nich je možné předřadit digitální filtr a vybrat hranu na kterou mají signály vzniknout. Oba signály (ještě spolu s TRC signálem) jsou vedeny ke Capture registrům 1 a 2. Každému capture registru je možné zdroj signálu volit. Capture registr 2 tak může mimo jiné reagovat i na dění na kanále 1 (TI1) a naopak. Podobná situace je pak mezi kanály 3 a 4. Signály TI1FP1 a TI2FP2 navíc vedou do Trigger Controlleru kde může jejich příchod vyvolat další akce jako například start timeru nebo jeho reset. Z obrázku je patrné že výčet možností není ani zdaleka vyčerpávající, ale nerad bych vás zahltil. Tohle je vše co pro konfiguraci potřebujeme. A teď k tomu jak to celé bude fungovat.
Signál TI2FP2 necháme detekovat vzestupnou hranu. Ta představuje začátek i konec signálu který měříme. TI2FP1 pak nastavíme na detekci sestupné hrany. Ta bude zaznamenávat střídu měřeného signálu (dobu trvání log.1). TI2FP2 navíc zavedeme do trigger controlleru, kterému nastavíme aby timer s příchodem signálu restartoval. Díky tomu začne timer počítat se začátkem periody vždy od nuly. Capture registr 1 necháme odbírat signál TI2FP1. Díky tomu se do něj bude ukládat čas příchodu sestupné hrany (informace o střídě měřeného signálu). Do Capture registru 2 zavedeme signál TI2FP2 (vzestupná hrana) a budeme v něm zaznamenávat periodu. Teď by jste měli zpozornět ! Jeden signál ovládá zároveň capture událost i reset timeru, takže co se stane dřív ? Nejprve proběhne uložení do Capture registru. Od této události si povolíme přerušení. To nás bude informovat o dokončení měření. Raději si to ještě zrekapitulujeme. S příchodem vzestupné hrany se restartuje timer a začíná počítat od nuly. S příchodem sestupné hrany zaznamená svůj čas do Capture registru 1 a s příchodem další vzestupné hrany, zaznamená čas příchodu, restartuje se a vyvolá přerušení. V reakci na přerušení vyzvedneme z obou Capture registrů data a budeme o signálu vědět vše co jsme chtěli.
Digitální filtry ani Prescalery před Capture registry nebudeme potřebovat. Měřit budeme pomocí TIM2, protože je 32bitový. Celé měření poběží na pozadí. Jádro bude obsluhovat pouze jednu rutinu přerušení. Pokud chcete můžete využít služeb DMA a nechat si oba výsledky měření přenášet někam do paměti (Kanál 2 a Kanál 5 DMA). My výsledky čas od času pošleme pomocí USART na terminál v PC. Typicky je ale budete zpracovávat nějak jinak.
Celý zdrojový kódvoid init_tim2(void){ LL_TIM_InitTypeDef tim; LL_TIM_StructInit(&tim); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); tim.Autoreload = 0xFFFFFFFF; // strop na maximum (nechceme si omezovat rozsah měření) tim.Prescaler = 0; // prescaler na minimum (nechceme si ničit přesnost měření - zničíme ji až pak výpočtem) LL_TIM_Init(TIM2,&tim); // do Capture registru 1 zavedeme signál TI2FP1 (z kanálu TIM2_CH2) s detekcí sestupné hrany, bez filtru, bez předděličky LL_TIM_IC_Config(TIM2,LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_INDIRECTTI | LL_TIM_ICPSC_DIV1 | LL_TIM_IC_FILTER_FDIV1 | LL_TIM_IC_POLARITY_FALLING); // do Capture registru 2 zavedeme signál TI2FP2 (z kanálu TIM2_CH2) s detekcí vzestupné hrany, bez filtru, bez předděličky LL_TIM_IC_Config(TIM2,LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_DIRECTTI | LL_TIM_ICPSC_DIV1 | LL_TIM_IC_FILTER_FDIV1 | LL_TIM_IC_POLARITY_RISING); // jako trigger slouží signál TI2FP2 (vzestupná hrana na TIM2_CH2 - začátek signálu) LL_TIM_SetTriggerInput(TIM2,LL_TIM_TS_TI2FP2); // specifikujeme jak se má s příchozím triggrem naložit - resetovat timer (od vzestupné hrany začínáme měřit čas) LL_TIM_SetSlaveMode(TIM2,LL_TIM_SLAVEMODE_RESET); // povolíme si přerušení od kanálu 2 (dokončení měření) NVIC_SetPriority(TIM2_IRQn,2); NVIC_EnableIRQ(TIM2_IRQn); LL_TIM_EnableIT_CC2(TIM2); // povolíme oba kanály (input capture 1 a 2) LL_TIM_CC_EnableChannel(TIM2,LL_TIM_CHANNEL_CH1 | LL_TIM_CHANNEL_CH2); LL_TIM_EnableCounter(TIM2); // spustíme timer } // přerušení od timeru 2 void TIM2_IRQHandler(void){ // nezkoumám zdroj přerušení (vím že to je CC2, jiný jsem nepovolil) LL_TIM_ClearFlag_CC2(TIM2); // mažu vlajku přerušení t1=LL_TIM_IC_GetCaptureCH1(TIM2); // čas příchodu sestupné hrany (trvání pulzu log.1) t2=LL_TIM_IC_GetCaptureCH2(TIM2); // čas příchodu vzestupné hrany (trvání celé periody) }
Kód si zaslouží další komentář. Strop Timeru volíme maximum (tedy 2^32) abychom si zbytečně neomezovali maximální měřenou délku. Prescaler volím 0, díky tomu je časové rozlišení timeru nejjemnější (1/48 us). Díky 32bit rozsahu je i tak nejdelší měřitelná perioda asi 90s. Konfiguraci vstupních kanálů je možné provádět strukturou a nebo funkcí LL_TIM_IC_Config(). Jejím prvním argumentem je kanál timeru. Druhý argument musí být složen ze čtyř maker.
Několik řádek můžeme věnovat ještě zpracování dat. V rutině přerušení získáme dvě časové informace (periodu t2 a dobu trvání pulzu t1). Informace o střídě (Duty Cycle) zjistíme z poměru t1/t2. Je proto nezbytně nutné abychom se na hodnoty t1 a t2 koukali jako na neoddělitelnou dvojici. Před výpočtem si je tedy zkopírujeme do pomocných proměnných t1_calc a t2_calc. U nich nám nehrozí že je během zpracování v přerušení přepíšeme. Kopii ale musíme provádět s vypnutým přerušením abychom se vyvarovali přepsání jedné z nich během kopírování. Viz následující kus zdrojového kódu.
while (1){ LL_mDelay(200); // ~5x za sekundu pošli výsledek měření na terminál __disable_irq(); // během kopírování t1 a t2 se hodnota žádné z nich nesmí změnit ! t1_calc=t1; // zkopírujeme si aktuální informaci o střídě a periodě t2_calc=t2; __enable_irq(); // trocha celočíselné matematiky (berte hodně s rezervou !) // když to má význam bývá lepší posílat surová data než je takhle kazit freq=100*(uint64_t)48000000/(uint64_t)t2_calc; // jednotkou frekvence je 0.01Hz dcl=(10000*(uint64_t)t1_calc)/(uint64_t)t2_calc; // jednotkou střídy je 0.01% // trocha formátování, zjišťování celé části, desetinné části čísla atd. snprintf(text,sizeof(text),"%01lu.%02lu Hz, %01u.%02u %%\n\r",freq/100,freq%100,(uint8_t)(dcl/100),(uint8_t)(dcl%100)); usart1_puts(text); // pošli zprávu do PC // celé to trvá cca 2ms }
Dovětek bych věnoval rozlišovací schopnosti. Rozlišení měření střídy je tím větší čím vyšší je frekvence timeru. S frekvencí 48MHz je časové rozlišení přibližně 0.02us. Než to obecně popisovat, stačí předvést dva konkrétní příklady ze kterých vám bude vše jasné. Jestliže chci měřit PWM signál jež má frekvenci 500kHz mohu počítat s tím, že rozlišení střídy bude přibližně 1% (500kHz => perioda 2us, 2us/0.02us = 100). Naopak v signálu s frekvencí 50Hz mohu bude rozlišení střídy rovno 20ms/0.02us = 1ppm (tedy jedna miliontina). Celé to úzce souvisí s tím jak pak informaci zobrazovat nebo prezentovat, ale to už je jiná kapitola.
Jednoduchou modifikací výše uvedené ukázky můžeme timer uzpůsobit k měření periody. Stačí vypustit detekci sestupné hrany. Zavedeme tedy signál například do vstupu 1, necháme ho projít digitálním filtrem a detektorem hran a výsledný impulz (TI1FP1) přivedeme jak do Capture registru tak do Trigger controlleru. Stejně jako v předchozím příkladě s příchodem vzestupné hrany dojde k zaznamenání času do capture registru a restartování čítače. Tentokrát ani nemusíme volat přerušení. Timer může měřit periodu neustále na pozadí a kdykoli bude aplikace potřebovat vyčte si aktuální hodnotu z CCR registru. Neuvěřitelně prosté :) Schema konfigurace si můžete prohlédnout níže.
void init_tim2(void){ LL_TIM_InitTypeDef tim; LL_TIM_IC_InitTypeDef ic; // konfigurace PA15 (TIM2_CH1) LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA); LL_GPIO_SetPinMode(GPIOA,LL_GPIO_PIN_15,LL_GPIO_MODE_ALTERNATE); LL_GPIO_SetAFPin_8_15(GPIOA,LL_GPIO_PIN_15,LL_GPIO_AF_2); LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2); // clock pro TIM2 LL_TIM_StructInit(&tim); tim.Autoreload = 0xFFFFFFFF; // maximum (nebudeme si zbytečně omezovat rozsah) tim.Prescaler = 0; // 48MHz do timeru pro plné rozlišení LL_TIM_Init(TIM2,&tim); ic.ICActiveInput = LL_TIM_ACTIVEINPUT_DIRECTTI; // signál do CC1 je z TIM2_CH1 ic.ICPrescaler = LL_TIM_ICPSC_DIV1; // prescaler před CC1 nepotřebujeme ic.ICPolarity = LL_TIM_IC_POLARITY_RISING; // detekujeme vzestupnou hranu ic.ICFilter = LL_TIM_IC_FILTER_FDIV1; // filtr nepoužíváme LL_TIM_IC_Init(TIM2,LL_TIM_CHANNEL_CH1,&ic); // aplikuj konfiguraci na CH1 LL_TIM_SetTriggerInput(TIM2,LL_TIM_TS_TI1FP1); // Trigger do timeru veden z TI1FP1 (CH1) LL_TIM_SetSlaveMode(TIM2,LL_TIM_SLAVEMODE_RESET); // Triggerem resetovat timer LL_TIM_CC_EnableChannel(TIM2,LL_TIM_CHANNEL_CH1); // povolit CH1 LL_TIM_EnableCounter(TIM2); // spustit timer }
Ti kterým zdrojový kód s konfigurací timeru nestačí si mohou stáhnout celou ukázku zde. V ukázce měřím periodu signálu přiváděného na PA15 a informaci čas od času odesílám usartem (PA9) do terminálu na PC. V ukázce využívám "ořezanou" implementaci funkce printf (ze souboru tiny_printf.c). Odesílací funkce je pak v souboru syscalls.c, proto je celá ukázka v rar archivu.
Doufám že jste z příkladů získali představu jak timer konfigurvat a že se setkáme u dalších dílů tutoriálu.
Home
V1.00 16.8.2017
By Michal Dudka (m.dudka@seznam.cz)