Wstęp
STM32F4 zawiera 14 wbudowany timerów. Każdy z zamontowanych mikrokontolerów w różnych płytkach ewaluacyjnych zawiera inną ilość timerów. Z tego powodu przed rozpoczęciem pracy z innym układem należy dokładnie sprawdzić instrukcję (ang. Manual).
Timer
|
Typ
|
Rozdzielczość
|
Dzielnik
|
Kanał
|
Max Zegar Intrerf
|
Max Zegar Timera
|
APB
|
1, 8
|
Zaawansowany
|
16 bit
|
16 bit
|
4
|
SysClk/2
|
SysClk
|
2
|
2, 5
|
Ogólny
|
32 bit
|
16 bit
|
4
|
SysClk/4
|
SysClk, SysClk/2
|
1
|
3, 4
|
Ogólny
|
16 bit
|
16 bit
|
4
|
SysClk/4
|
1
|
|
9
|
Ogólny
|
16 bit
|
16 bit
|
2
|
SysClk/2
|
SysClk
|
2
|
10, 11
|
Ogólny
|
16 bit
|
16 bit
|
1
|
SysClk/2
|
SysClk
|
2
|
12
|
Ogólny
|
16 bit
|
16 bit
|
2
|
SysClk/4
|
SysClk, SysClk/2
|
1
|
13, 14
|
Ogólny
|
16 bit
|
16 bit
|
1
|
SysClk/4
|
1
|
|
6, 7
|
Podstawowy
|
16 bit
|
16 bit
|
0
|
SysClk/4
|
1
|
Timer 1,8,2,5,3 oraz 4 pozwalają na tryb pracy UP, DOWN oraz UP/DOWN. Pozostałe timery pozwalają tylko na pracę UP.
Dzielnik (ang. Prescaler) jest 16 bitowy. Oznacza to, że można przyjąć każdą wartość z zakresu od 1 do 65536.
Inizjalizacja TIM4
Timer 4 jest podłączony do linii APB1. Zegar całego mikrokontrolera ustawiony został na 168MHz. Czyli taktowany będzie z częstotliwością 42 MHz. Poprzez pętle PLL zostanie ta wartość zwiększona do 84 MHz. W przypadku timerów podłączonych do APB2 częstotliwość taktowania wyniesie odpowiednio 84 MHz oraz 168MHz.
- void InitializeTimer4(void)
- {
- TIM_TimeBaseInitTypeDef timerInitStructure;
- //Wlaczenie zegara dla timera 4
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
- //Prescaler ustawiony na 0 przez co bedzie
- //maksymalna częstotliwość taktowania
- timerInitStructure.TIM_Prescaler = 0;
- //Zliczaj w górę
- timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- //Aby PWM miał 10kHz wtedy do obliczeń korzystamy z wzoru
- //TIM_Period = taktowanieAPB / PWMFreq - 1
- //TIM_Period = 84000000 / 10000 - 1 = 8399
- timerInitStructure.TIM_Period = 8399;
- timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
- timerInitStructure.TIM_RepetitionCounter = 0;
- //Inicjalizacja timera4
- TIM_TimeBaseInit(TIM4, &timerInitStructure);
- //Zacznij zliczanie
- TIM_Cmd(TIM4, ENABLE);
- }
Jeśli TIM_Period zostanie ustawiony jako większy niż dopuszczlna maksymalna wartość, wtedy należy zmniejszyć częstotliwość i zwiększyć dzielnik
Inicjalizacja TIM1
W tym przykładzie przedstawię sposób inicjalizacji TIM1. Jedyną różnicą w tym procesie jest wymagane dodatkowe ustawienie rejestru Break and dead time register.
Dla minimalnej częstotliwości okres wynosi:
TIM_PeriodMin = 84000000 / 10000 - 1 = 8399
Dla maksymalnej częstotliwości okres wynosi:
TIM_PeriodMax = 168000000 / 10000 - 1 = 16799
- void InitializeTimer(void)
- {
- TIM_TimeBaseInitTypeDef timerInitStructure;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
- timerInitStructure.TIM_Prescaler = 0;
- timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
- timerInitStructure.TIM_Period = 16000;
- timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
- timerInitStructure.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM1, &timerInitStructure); //Inicjalizacja Timera 1
- //Wymagana komenda do TIM1, ustawia ona bit BDTR->MOE=1
- //Break and dead time register
- TIM_CtrlPWMOutputs(TIM1, ENABLE);
- TIM_Cmd(TIM1, ENABLE);
- }
Inicjalizacja PWM
Poniżej zaprezentowałem ustawienie przykładowej wartości PWM dla timera 1.
- void InitializePWM(void)
- {
- TIM_OCInitTypeDef outputChannelInit;
- outputChannelInit.TIM_OCMode = TIM_OCMode_PWM1;
- outputChannelInit.TIM_Pulse = pul;
- outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
- outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_High;
- TIM_OC2Init(TIM1, &outputChannelInit);
- TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
- TIM_ARRPreloadConfig(TIM1, ENABLE);
- TIM_Cmd(TIM1, ENABLE);
- GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_TIM1);
- }
Dla drugiego przedstawionego w przykładach timera inicjalizacja poprzez stałą wartość może wyglądać następująco.
- void InitializePWMTIM1(void)
- {
- TIM_OCInitTypeDef outputChannelInit;
- //Możliwy jest wybór dwóch trybów PWM1 lub PWM2
- outputChannelInit.TIM_OCMode = TIM_OCMode_PWM2;
- outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
- outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_Low;
- outputChannelInit.TIM_Pulse = 2545; //30%
- TIM_OC1Init(TIM4, &TIM_OCStruct);
- TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
- }
TIM_OCMode daje możliwość ustawienia dwóch trybów pracy PWM1 (ang. Set on compare match) lub PWM2 (ang. Clear on compare match).
Poniżej przedstawiam krótki program ustawiający wybraną wartość PWM na diodach zamontowanych na płytce Discovery. Piny PD12 do PD15 są podłączone do TIM2.
Poniżej przedstawiam krótki program ustawiający wybraną wartość PWM na diodach zamontowanych na płytce Discovery. Piny PD12 do PD15 są podłączone do TIM2.
- //Biblioteki
- #include "stm32f4xx.h"
- #include "stm32f4xx_rcc.h"
- #include "stm32f4xx_gpio.h"
- #include "stm32f4xx_tim.h"
- TIM_OCInitTypeDef TIM_OCStruct;
- void GPIO_DIODES_INIT(void)
- {
- GPIO_InitTypeDef GPIO_Struct;
- //Wlaczenie zegara dla GPIOD
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
- //Ustawienie pinow
- GPIO_Struct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
- GPIO_Struct.GPIO_OType = GPIO_OType_PP;
- GPIO_Struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_Struct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_Struct.GPIO_Speed = GPIO_Speed_100MHz;
- //Funckje alternatywne
- GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
- GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
- GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
- GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
- GPIO_Init(GPIOD, &GPIO_Struct);
- }
- void TIM4_INIT(void)
- {
- TIM_TimeBaseInitTypeDef TIM_Struct;
- //Zegar dla TIM4
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
- //Prescaler na 0 czyli maksymalne taktowanie dla tim4 to 84MHz
- TIM_Struct.TIM_Prescaler = 0;
- //Zliczanie w gore, mozna wybrac zliczanie w dol
- TIM_Struct.TIM_CounterMode = TIM_CounterMode_Up;
- //TIM4 jest 16bitowy czyli 2^16 = 65535
- //Wzor jest nastepujacy
- //Okres = czestotliwosc / czestotliwosc_pwm - 1
- //Czyli dla zegara 84MHz, PWM na 20kHz mozna wyliczyc
- //Okres = 84000000 / 20000 - 1 =
- //Dla wartosci ponad 65535 nalezy zwiekszyc prescaler dla timera
- TIM_Struct.TIM_Period = 4199;
- TIM_Struct.TIM_ClockDivision = TIM_CKD_DIV1;
- TIM_Struct.TIM_RepetitionCounter = 0;
- //Wlaczenie TIM4
- TIM_TimeBaseInit(TIM4, &TIM_Struct);
- //Rozpoczecie odliczania
- TIM_Cmd(TIM4, ENABLE);
- }
- void TIM4_PWM_INIT(void)
- {
- TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
- TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
- TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
- }
- void PWM_SetValuePD12(int value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC1Init(TIM4, &TIM_OCStruct);
- TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
- }
- void PWM_SetValuePD13(int value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC2Init(TIM4, &TIM_OCStruct);
- TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
- }
- void PWM_SetValuePD14(int value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC3Init(TIM4, &TIM_OCStruct);
- TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
- }
- void PWM_SetValuePD15(int value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC4Init(TIM4, &TIM_OCStruct);
- TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
- }
- int main(void)
- {
- SystemInit();
- GPIO_DIODES_INIT();
- TIM4_INIT();
- TIM4_PWM_INIT();
- PWM_SetValuePD12(1000);
- PWM_SetValuePD13(2000);
- PWM_SetValuePD14(3000);
- PWM_SetValuePD15(4000);
- while (1){ }
- }
Poniżej przedstawię program przetestowany na na mikrokontrolerze STM32F446RE, umieszczonego na płytce Nucleo. Zegar mikrokontrolera taktowanego z wewnętrznego oscylatora osiąga wartość 180MHz, przy maksymalnych ustawieniach. Aby osiągnąć 1kHz dla timera, należy prescaler ustawić na 0. Po czym podzielić zegar systemowy przez 1000. Co daje wynik 17999, Jest on możliwy do ustawienia ponieważ TIM2 jest 32 bitowy. W przypadku STM32F4, timer 32 bitowy wystarczy włączyć. W STM32F1 czy STM32L1 timery 32 bitowe muszą zostać włączone w parze jeden jako slave drugi jako master.
Włączony zostanie kanał 1 TIM2, który jest podłączony do pinu PB0.
- TIM_OCInitTypeDef TIM_OCStruct;
- void InitPWM()
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_TimeBaseInitTypeDef TIM_BaseStruct;
- RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
- GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3);
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
- GPIO_Init(GPIOB, &GPIO_InitStruct);
- TIM_BaseStruct.TIM_Prescaler = 0;
- TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
- TIM_BaseStruct.TIM_Period = ((SystemCoreClock / 1000)) - 1;
- TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
- TIM_BaseStruct.TIM_RepetitionCounter = 0;
- TIM_TimeBaseInit(TIM3, &TIM_BaseStruct);
- TIM_Cmd(TIM3, ENABLE);
- TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
- TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
- TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
- }
- void PWMWlaczDzwiek(int value)
- {
- TIM_OCStruct.TIM_Pulse = value;
- TIM_OC3Init(TIM3, &TIM_OCStruct);
- TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
- }