Někdy je třeba něco zpomalit, nebo je potřeba generování časových pulzů. V dnešním článku se podíváme na základní 16bit časovače TIM6 a TIM7 s jejichž pomocí budeme blikat diodami v nastavených intervalech.
STM32F4 disponuje hned čtrnácti časovači. S jejichž pomocí je možné realizovat spoustu funkcí a některé časovače již obsahují možnost realizace například PWM. My se ale podíváme na „basic timers“, které umožňují jen obyčejné časování. Na další časovače a pokročilejší hrátky se podíváme v některém z dalších článků.
Časovače TIM6 a TIM7 jsou naprosto shodné. Jedná se o 16bit časovače s 16bit předděličkou hodin a funkcí auto-reload. Při události přetečení nebo auto-reload může být generováno přerušení nebo signál pro DMA (o obojím se také v některém z dalších článků seznámíme).
Obr.1: Blokové schéma časovače TIM6 a TIM7
V podstatě se jedná o čítač čítající nahoru (upcounter). Čítač je 16. bitový, takže maximální rozsah je od 0 do 65535. Čítač čítá rychlostí systémových hodin pro APB1 dělenou 16bit předděličkou. Pokud vše v MCU taktujeme maximální taktem, tak pro APB1 je hodinový signál 84MHz. Při hodnotě předděličky 0x0000 je čítač časován také taktem 84MHz. Při nastavení předěličky na hodnotu 0x0001 je ale čítač časován už jen frekvencí 42MHz, při hodnotě 0x0002 frekvencí 28MHz atd.
Dále můžeme přečíst ale dokonce i zapsat hodnotu přímo do čítače. Využívá se toho, pokud chceme zkrátit dobu čítání do nějaké události. Ale ke zkrácení času čítání se dá použít auto-reload, což je nakonec praktičtější (ukážeme si na příkladu). Veškeré nastavené hodnoty jsou uplatněny až ve chvíli, kdy je generován signál UEV (update event – při přetečení nebo auto-reload).
TIMx_CR1 (control register)
Základní nastavení čítače. Jedná se o 16bit registr, ale použitelných je jen určitých 5 bitů.
TIMx_CR2 (control register)
Je určen k nastavení triggru (pro použití v DA převodníku). Opět se jedná o 16bit registr ale použity jsou pouze 3 bity jejichž kombinací určujeme co bude generovat trigger signál.
TIMx_DIER (DMA/Interrupt enable register)
16bit registr, kterým povolujeme generování signálů přerušení nebo žádosti o DMA. Ty jsou generovány vždy, pokud dojde k přetečení nebo auto-reload čítače.
TIMx_SR (status register)
16bit registr ve kterém je využit jen jediný bit :D (Bit 0 - UIF). Tento bit je vždy nastaven pokud dojde k přetečení nebo auto-reload. Je nutné jej ručně resetovat.
TIMx_EGR (event generation registr)
16bit registr kde je opět využit jen jeden bit (Bit 0 - UG). Jeho nastavením dojde ke generování UEV, při které je časovač aktualizován a resetován.
TIMx_CNT (counter)
16bit registr. Jedná se přímo o čítač a je možno z něj hodnotu číst i ji měnit a to i za běhu samotného čítače.
TIMx_PSC (prescaler)
16bit registr do kterého uložíme vždy hodnotu, kterou chceme dělit hodinový signál.
TIMx_ARR (auto-reload register)
16bit registr. Do toho registru uložíme hodnotu, při které se má čítač resetovat.
Teď si ukážeme jak jednu diodu rozblikáme. Dejme tomu že budeme chtít, aby oranžová dioda se vždy po jedné sekundě střídavě rozsvítila a zhasla. Jak nastavit GPIO pro rozsvícení diody už umíme. Takže povolíme časovač, například TIM6 a nastavíme hodnoty předděličky a auto-reload tak, aby jeden cyklus časovače trval přibližně jednu vteřinu.
/* Rreset casovace TIM6 */ RCC->APB1RSTR |= RCC_APB1RSTR_TIM6RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM6RST; /* Povolení hodin pro casovac TIM6 */ RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; /* Nastaveni */ /* Pozn.: casovac TIM6 je 16bit */ TIM6->CR1 |= TIM_CR1_ARPE; // Povolení prednacteni auto-reload TIM6->ARR = 65523; // Auto-reload hodnota, pri ktere se ma citac resetovat TIM6->PSC = 1281; // Delicka hodinoveho signalu TIM6->CR1 |= TIM_CR1_CEN; // Spusteni casovace TIM6 TIM6->EGR = TIM_EGR_UG; // Aktualizace (update event) TIM6->SR = 0x0000; // Vrácení status bitu na log 0
První řádky známe, resetujeme a povolujeme periferii TIM6. Dále v registru TIM6_CR1 povolíme přednačítání auto-reload.
A co že to vlastně je ten auto-reload? Čítač po resetu (po události UEV) čítá od nuly. Jakmile je jeho hodnota rovna hodnotě registru TIM6_ARR, dojde opět ke generování UEV a nastavení bitu UIF v registru TM6_SR. Čítač se resetuje a začíná opět od nuly. Pokud povolíme před načtení, a během běhu časovače změníme hodnotu auto-reload, čítač bude resetován ještě při staré hodnotě a až v dalším cyklu se použije nová hodnota auto-reload. Pokud přednačtení nepovolíme, je uplatněna změna této hodnoty okamžitě.
Takže dále zapíšeme hodnoty do registru auto-reload TIM6_ARR a do registru předděličky hodinového signálu TIM6_PSC.
Hodnoty jsou zvoleny tak, aby vzdálenost jednotlivých UEV byla přibližně jedna sekunda. Pokud víme, že základní hodinový signál má 84MHz, čítač se inkrementuje 84 milionkrát za sekundu. Takže snížíme frekvenci hodinového signálu a zjistíme, že se čítač za jednu sekundu inkrementuje přibližně 65523 krát. A právě při této hodnotě chceme, aby se časovač resetoval.
(1 / [CK_PSC / (TIMx_PSC + 1)]) * TIMx_ARR = Timer Cycle Time
(1 / [84e6 / (1281 + 1)]) * 65523 = 1,000005786 s
Příklad.1: Výpočet času mezi dvěma UEV, kde CK_PSC = 84000000, TIMx_PSC = 1281 a TIMx_ARR = 65523
Dále povolíme samotný běh časovače bitem CEN v registru TIM6_CR1 a celý jej resetujeme a aktualizujeme generováním UEV, a to tak, že zapíšeme logickou jedničku do registru TIM6_EGR. To nám ale vyvolá nejen událost UEV, ale i nastavení bitu UIF v registru TIM6_SR, proto jej nulujeme.
Nyní tedy můžeme napsat samotný kód pro blikání diody.
while(1) { //Pokud dojde k UEV (update event) zmeni stav LED diody if(TIM6->SR & TIM_SR_UIF) { if(GPIOD->ODR & GPIO_ODR_ODR_13) GPIOD->ODR &= ~GPIO_ODR_ODR_13; else GPIOD->ODR |= GPIO_ODR_ODR_13; TIM6->SR = 0x0000; // Vrácení status bitu na log 0 } }
Cyklus while zajistí neustálé opakování podmínky. Podmínka je splněna, jakmile je nastaven bit UIF v registru TIM6_SR, neboť ten je nastaven, jen pokud dojde k UEV (tedy čítač dosáhl hodnoty auto-reload). Dále se ptáme, zda je dioda již rozsvícena, pokud ano, zhasneme ji, v opačném případě ji rozsvítíme.
Nesmíme zapomenou ručně nulovat UIF, jinak by při opakování cyklu while byla podmínka opět splněna a dioda by sice blikala, ale velice vysokou rychlostí :D.
Ještě přidám příklad s využitím obou časovačů, TIM6 i TIM7, kdy každý je použit pro blikání jiné diody a časovač TIM7 má mírně zkrácenou dobu mezi dvěma UEV.
#include<stm32f4xx.h> void GPIOD_conf(void) { /* Reset portu GPID */ RCC->AHB1RSTR |= RCC_AHB1RSTR_GPIODRST; RCC->AHB1RSTR &= ~(RCC_AHB1RSTR_GPIODRST); /* Povoleni hodin pro GPIOD */ RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; /* Nastaveni modu */ GPIOD->MODER |= GPIO_MODER_MODER15_0 | GPIO_MODER_MODER13_0; //nastaveni pinu 15 a 13 jako výstupní /* Nastaveni max. frekvence */ GPIOD->OSPEEDR = GPIO_OSPEEDER_OSPEEDR15_1; //maximalni frekvence 100MHz return; } void TIM6_conf(void) { /* Rreset casovace TIM6 */ RCC->APB1RSTR |= RCC_APB1RSTR_TIM6RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM6RST; /* Povolení hodin pro casovac TIM6 */ RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; /* Nastaveni */ /* Pozn.: casovac TIM6 je 16bit */ TIM6->CR1 |= TIM_CR1_ARPE; // Povolení prednacteni auto-reload TIM6->ARR = 65523; // Auto-reload hodnota, pri ktere se ma citac resetovat TIM6->PSC = 1281; // Delicka hodinoveho signalu TIM6->CR1 |= TIM_CR1_CEN; // Spusteni casovace TIM6 TIM6->EGR = TIM_EGR_UG; // Aktualizace (update event) TIM6->SR = 0x0000; // Vrácení status bitu na log 0 return; } void TIM7_conf(void) { /* Rreset casovace TIM7 */ RCC->APB1RSTR |= RCC_APB1RSTR_TIM7RST; RCC->APB1RSTR &= ~RCC_APB1RSTR_TIM7RST; /* Povolení hodin pro casovac TIM7 */ RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; /* Nastaveni */ /* Pozn.: casovac TIM7 je 16bit */ TIM7->CR1 |= TIM_CR1_ARPE; // Povolení prednacteni auto-reload TIM7->ARR = 65523; // Auto-reload hodnota, pri ktere se ma citac resetovat TIM7->PSC = 500; // Delicka hodinoveho signalu TIM7->SR = 0x0000; // Vrácení status bitu na log 0 TIM7->CR1 |= TIM_CR1_CEN; // Spusteni casovace TIM7 TIM7->EGR = TIM_EGR_UG; // Aktualizace (update event) return; } int main(void) { GPIOD_conf(); TIM7_conf(); TIM6_conf(); while(1) { //Pokud dojde k UEV (update event) zmeni stav LED diody if(TIM6->SR & TIM_SR_UIF) { if(GPIOD->ODR & GPIO_ODR_ODR_13) GPIOD->ODR &= ~GPIO_ODR_ODR_13; else GPIOD->ODR |= GPIO_ODR_ODR_13; TIM6->SR = 0x0000; // Vrácení status bitu na log 0 } //Pokud dojde k UEV (update event) zmeni stav LED diody if(TIM7->SR & TIM_SR_UIF) { if(GPIOD->ODR & GPIO_ODR_ODR_15) GPIOD->ODR &= ~GPIO_ODR_ODR_15; else GPIOD->ODR |= GPIO_ODR_ODR_15; TIM7->SR = 0x0000; // Vrácení status bitu na log 0 } } }
Dnes jsme si ukázali jednoduchou práci s časovači TIM6 a TIM7. Někdy příště si diodami zablikáme znova, ale s využitím přerušení. Také si někdy příště povíme o ostatních časovačích, které nabízejí mnoho dalších vylepšení.
Projek ke stažení ZDE.
Komentáře