Wstęp
Mikrokontroler F103RB pozwala na obsługę czterech wbudowanych timerów. Poniżej przedstawiam dokładne informacje zaczerpnięte z dokumentacji:
Timer
|
Rozdzielczość
|
Zliczanie
|
Dzielnik
|
Kanały
|
Tim1
|
16 bit
|
Góra, dół, góra/dół
|
1 do 65536
|
4
|
Tim2..4
|
16 bit
|
Góra, dół, góra/dół
|
1 do 65536
|
4
|
- porównywanie stanu licznika z zadaną wartością;
- generacja sygnału PWM;
- pomiar parametrów PWM;
- zliczanie impulsów zewnętrznych;
- kaskadowe połączenie liczników;
- współpraca z takimi układami jak czujnik Halla, DMA, enkoder;
Taktowanie liczników odbywa się poprzez szynę APB1 dla TIM2, TIM3 oraz TIM4. Natomiast TIM1 taktowany poprzez szynę APB2. W obu przypadkach przy wykorzystywaniu ustawień 64MHz z wewnętrznego oscylatora otrzymuje się taką samą częstotliwość na obu szynach, oczywiście dla timerów.
Wartość dzielnika znajduje się w zakresie od 1 do 65536. Pozwala to na osiągnięcie częstotliwości taktowania. Co przy wykorzystywaniu maksymalnej częstotliwości pozwala na osiągnięcie taktowania od około 1000Hz (976Hz) do 64MHz.
Każdy z dostępnych Timerów posiada 4 kanały służące do porównywania wartości. Dodatkowo Tim1 jako najbardziej zaawansowany ma funkcję multipleksacji PWM na 6 kanałów. Sam PWM zaprogramowany na 16-bit pozwoli na pełną regulację w zakresie od 0 do 100%.
Programowanie
Inicjalizacja timerów opiera się o następujące ustawienia:
- TIM_Prescaler - określa wartości dzielnika częstotliwości sygnału taktującego;
- TIM_Period - okres licznika;
- TIM_ClockDivision - wybór dzielnika zegara dla układu generującego czas martwy oraz dla filtra;
- TIM_RepetitionCounter - tryb pracy licznika;
Dla timerów TIM2 do TIM3 można ustawić standardowe funkcje obsługi przerwania tzn. TIMx_IRQHandler. Natomiast dla TIM1 do wyboru sa następujące możliwości:
- TIM1_BRK_IRQn
- TIM1_UP_IRQn
- TIM1_TRG_COM_IRQn
- TIM1_CC_IRQn
Program - przerwania od licznika
Ten program będzie przedstawiał inicjalizację przerwania poprzez TIM2. W przerwaniu będzie wykonywane cykliczne zapalenie i zgaszenie diody.
#include "stm32f10x.h" #define Pin GPIO_Pin_5 #define Line GPIOA #define Clock RCC_APB2Periph_GPIOA void TIM2_IRQHandler(void); void GPIOInit(void); void TIM2Init(void); void NVICInit(void); int main(void) { GPIOInit(); TIM2Init(); NVICInit(); while (1) { } } void TIM2Init(void) { TIM_TimeBaseInitTypeDef TIMInit; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Taktowanie licznika 64MHz/64000 = 1kHz (1ms) TIMInit.TIM_Prescaler = 64000 - 1; //przepełnienie licznika 2000 taktów - 2s TIMInit.TIM_Period = 2000 - 2; //dzielnik dla zegara czasu martwego i filtru TIMInit.TIM_ClockDivision = TIM_CKD_DIV1; //licznik powtórzeń TIMInit.TIM_RepetitionCounter = 0; //tryb pracy licznika TIMInit.TIM_CounterMode = TIM_CounterMode_Up; //Inicjalizacja licznika TIM_TimeBaseInit(TIM2, &TIMInit); //Wlaczenie przerwan od licznikow, wlaczenie //przerwania kiedy nastąpi przepełnienie licznika TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //Wlaczenie timerow TIM_Cmd(TIM2, ENABLE); } void NVICInit() { NVIC_InitTypeDef NVICInit; NVICInit.NVIC_IRQChannel = TIM2_IRQn; NVICInit.NVIC_IRQChannelPreemptionPriority = 0; NVICInit.NVIC_IRQChannelSubPriority = 0; NVICInit.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVICInit); } //Obsługa przerwania dla timera void TIM2_IRQHandler() { //Wywolanie przerwania po przepełnieniu licznika if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)) { GPIO_ResetBits(GPIOA, GPIO_Pin_5); } else { GPIO_SetBits(GPIOA, GPIO_Pin_5); } } } //Deklaracja pinu z podłączoną diodą PA5 void GPIOInit(void) { GPIO_InitTypeDef GpioInit; //Włączenie zegara RCC_APB2PeriphClockCmd(Clock, ENABLE); GpioInit.GPIO_Pin = Pin; GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(Line, &GpioInit); }
Program dla timera 1 należy delikatnie zmodyfikować. Przede wszystkim będzie on taktowany z innej linii APB2 zamiast APB1. Inna będzie także deklaracja przerwania, TIM1_UP_IRQn oraz TIM1_UP_IRQHandler().
Program z ustawieniami dla timera pierwszego zamieszczam poniżej.
Program z ustawieniami dla timera pierwszego zamieszczam poniżej.
#include "stm32f10x.h" #define Pin GPIO_Pin_5 #define Line GPIOA #define Clock RCC_APB2Periph_GPIOA void TIM1_UP_IRQHandler(void); void GPIOInit(void); void TIM1Init(void); void NVICInit(void); int main(void) { GPIOInit(); TIM1Init(); NVICInit(); while (1) { } } void TIM1Init(void) { TIM_TimeBaseInitTypeDef TIMInit; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //Taktowanie licznika 64MHz/64000 = 1kHz (1ms) TIMInit.TIM_Prescaler = 64000 - 1; //przepełnienie licznika 2000 taktów - 2s TIMInit.TIM_Period = 2000 - 2; //dzielnik dla zegara czasu martwego i filtru TIMInit.TIM_ClockDivision = TIM_CKD_DIV1; //licznik powtórzeń TIMInit.TIM_RepetitionCounter = 0; //tryb pracy licznika TIMInit.TIM_CounterMode = TIM_CounterMode_Up; //Inicjalizacja licznika TIM_TimeBaseInit(TIM1, &TIMInit); //Wlaczenie przerwan od licznikow, wlaczenie //przerwania kiedy nastąpi przepełnienie licznika TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //Wlaczenie timerow TIM_Cmd(TIM1, ENABLE); } void NVICInit() { NVIC_InitTypeDef NVICInit; NVICInit.NVIC_IRQChannel = TIM1_UP_IRQn; NVICInit.NVIC_IRQChannelPreemptionPriority = 0; NVICInit.NVIC_IRQChannelSubPriority = 0; NVICInit.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVICInit); } //Obsługa przerwania dla timera void TIM1_UP_IRQHandler() { //Wywolanie przerwania po przepełnieniu licznika if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)) { GPIO_ResetBits(GPIOA, GPIO_Pin_5); } else { GPIO_SetBits(GPIOA, GPIO_Pin_5); } } } //Deklaracja pinu z podłączoną diodą PA5 void GPIOInit(void) { GPIO_InitTypeDef GpioInit; //Włączenie zegara RCC_APB2PeriphClockCmd(Clock, ENABLE); GpioInit.GPIO_Pin = Pin; GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(Line, &GpioInit); }
Program 2
Jak już wcześniej wspomniałem do dyspozycji są 4 kanały, wobec tego istnieje możliwość wysterowania większej liczby diod wykorzystując tylko jeden timer. Aby była możliwość takiego sterowania należy skorzystać z kanału Capture Compare. Pozwala on na porównywanie stanu licznika z zaprogramowaną wartością.
W programie zaprezentuje możliwość wysterowania dwóch diod zewnętrznych podlączonych do pinów PB15 i PB14.
#include "stm32f10x.h" void GPIOInit(void); void NVICInit(void); void TIM2Init(void); void TIM2_IRQHandler(void); int main(void) { GPIOInit(); TIM2Init(); NVICInit(); while (1) { } } void TIM2_IRQHandler() { //Obsługa przerwania od przepełnienia licznika if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)) GPIO_ResetBits(GPIOA, GPIO_Pin_5); else GPIO_SetBits(GPIOA, GPIO_Pin_5); GPIO_SetBits(GPIOB, GPIO_Pin_15|GPIO_Pin_14); } //Porównywanie wartości if (TIM_GetITStatus(TIM2, TIM_IT_CC1) == SET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); GPIO_ResetBits(GPIOB, GPIO_Pin_15); } if (TIM_GetITStatus(TIM2, TIM_IT_CC2) == SET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); GPIO_ResetBits(GPIOB, GPIO_Pin_14); } } void TIM2Init(void) { TIM_TimeBaseInitTypeDef TIMInit; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Taktowanie licznika 64MHz/64000 = 1kHz (1ms) TIMInit.TIM_Prescaler = 64000 - 1; //przepełnienie licznika 2000 taktów - 2s TIMInit.TIM_Period = 2000 - 2; //dzielnik dla zegara czasu martwego i filtru TIMInit.TIM_ClockDivision = TIM_CKD_DIV1; //licznik powtórzeń TIMInit.TIM_RepetitionCounter = 0; //tryb pracy licznika TIMInit.TIM_CounterMode = TIM_CounterMode_Up; //Inicjalizacja licznika TIM_TimeBaseInit(TIM2, &TIMInit); //Wlaczenie przerwan od licznikow, wlaczenie //przerwania kiedy nastąpi przepełnienie licznika oraz dla porównywania kanałów TIM_ITConfig(TIM2, TIM_IT_Update|TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4, ENABLE); //Wlaczenie timerow TIM_Cmd(TIM2, ENABLE); TIM_SetCompare1(TIM2, 1100); TIM_SetCompare2(TIM2, 1350); } void NVICInit() { NVIC_InitTypeDef NVICInit; NVICInit.NVIC_IRQChannel = TIM2_IRQn; NVICInit.NVIC_IRQChannelPreemptionPriority = 0; NVICInit.NVIC_IRQChannelSubPriority = 0; NVICInit.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVICInit); } void GPIOInit(void) { GPIO_InitTypeDef GpioInit; //Włączenie zegara RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GpioInit.GPIO_Pin = GPIO_Pin_5; GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GpioInit); GpioInit.GPIO_Pin = GPIO_Pin_15|GPIO_Pin_14; GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GpioInit); }
W kolejnym poście opiszę sposób obsługi PWM za pomocą liczników.