poniedziałek, 31 sierpnia 2015

[2] STM32F4 - Discovery - Timery, PWM

Tym razem przedstawię sposób inicjalizacji timerów w mikrokontrolerze znajdującym się na płytce ewaluacyjnej STM32F4 - Discovery.

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.

  1. void InitializeTimer4(void)
  2. {
  3.      TIM_TimeBaseInitTypeDef timerInitStructure;
  4.  
  5.      //Wlaczenie zegara dla timera 4
  6.      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
  7.  
  8.      //Prescaler ustawiony na 0 przez co bedzie
  9.      //maksymalna częstotliwość taktowania
  10.      timerInitStructure.TIM_Prescaler = 0;
  11.      //Zliczaj w górę
  12.      timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  13.      //Aby PWM miał 10kHz wtedy do obliczeń korzystamy z wzoru
  14.      //TIM_Period = taktowanieAPB / PWMFreq - 1
  15.      //TIM_Period = 84000000 / 10000 - 1 = 8399
  16.      timerInitStructure.TIM_Period = 8399;
  17.  
  18.      timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  19.      timerInitStructure.TIM_RepetitionCounter = 0;
  20.  
  21.      //Inicjalizacja timera4
  22.      TIM_TimeBaseInit(TIM4, &timerInitStructure);
  23.      //Zacznij zliczanie
  24.      TIM_Cmd(TIM4, ENABLE);  
  25. }

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

  1. void InitializeTimer(void)
  2. {
  3.     TIM_TimeBaseInitTypeDef timerInitStructure;        
  4.  
  5.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  6.  
  7.     timerInitStructure.TIM_Prescaler = 0;
  8.     timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
  9.     timerInitStructure.TIM_Period = 16000;
  10.     timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  11.     timerInitStructure.TIM_RepetitionCounter = 0;
  12.     TIM_TimeBaseInit(TIM1, &timerInitStructure); //Inicjalizacja Timera 1
  13.     //Wymagana komenda do TIM1, ustawia ona bit BDTR->MOE=1                                  
  14.     //Break and dead time register
  15.     TIM_CtrlPWMOutputs(TIM1, ENABLE);
  16.  
  17.     TIM_Cmd(TIM1, ENABLE);
  18. }

Inicjalizacja PWM


Poniżej zaprezentowałem ustawienie przykładowej wartości PWM dla timera 1.

  1. void InitializePWM(void)
  2. {  
  3.     TIM_OCInitTypeDef outputChannelInit;
  4.     outputChannelInit.TIM_OCMode = TIM_OCMode_PWM1;
  5.     outputChannelInit.TIM_Pulse = pul;
  6.     outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;  
  7.     outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_High;    
  8.     TIM_OC2Init(TIM1, &outputChannelInit);
  9.     TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable);
  10.     TIM_ARRPreloadConfig(TIM1, ENABLE);
  11.     TIM_Cmd(TIM1, ENABLE);
  12.  
  13.     GPIO_PinAFConfig(GPIOE, GPIO_PinSource11, GPIO_AF_TIM1);
  14. }

Dla drugiego przedstawionego w przykładach timera inicjalizacja poprzez stałą wartość może wyglądać następująco.

  1. void InitializePWMTIM1(void)
  2. {
  3.     TIM_OCInitTypeDef outputChannelInit;
  4.  
  5.     //Możliwy jest wybór dwóch trybów PWM1 lub PWM2
  6.     outputChannelInit.TIM_OCMode = TIM_OCMode_PWM2;
  7.     outputChannelInit.TIM_OutputState = TIM_OutputState_Enable;
  8.     outputChannelInit.TIM_OCPolarity = TIM_OCPolarity_Low;
  9.  
  10.     outputChannelInit.TIM_Pulse = 2545;      //30%
  11.     TIM_OC1Init(TIM4, &TIM_OCStruct);
  12.     TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
  13. }

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.

  1. //Biblioteki
  2. #include "stm32f4xx.h"
  3. #include "stm32f4xx_rcc.h"
  4. #include "stm32f4xx_gpio.h"
  5. #include "stm32f4xx_tim.h"
  6. TIM_OCInitTypeDef TIM_OCStruct;
  7. void GPIO_DIODES_INIT(void)
  8. {
  9.     GPIO_InitTypeDef GPIO_Struct;
  10.     //Wlaczenie zegara dla GPIOD
  11.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
  12.     //Ustawienie pinow
  13.     GPIO_Struct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
  14.     GPIO_Struct.GPIO_OType = GPIO_OType_PP;
  15.     GPIO_Struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  16.     GPIO_Struct.GPIO_Mode = GPIO_Mode_AF;
  17.     GPIO_Struct.GPIO_Speed = GPIO_Speed_100MHz;
  18.     //Funckje alternatywne
  19.     GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
  20.     GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
  21.     GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
  22.     GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
  23.     GPIO_Init(GPIOD, &GPIO_Struct);
  24. }
  25. void TIM4_INIT(void)
  26. {
  27.     TIM_TimeBaseInitTypeDef TIM_Struct;
  28.     //Zegar dla TIM4
  29.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
  30.     //Prescaler na 0 czyli maksymalne taktowanie dla tim4 to 84MHz
  31.     TIM_Struct.TIM_Prescaler = 0;
  32.     //Zliczanie w gore, mozna wybrac zliczanie w dol
  33.     TIM_Struct.TIM_CounterMode = TIM_CounterMode_Up;
  34.     //TIM4 jest 16bitowy czyli 2^16 = 65535
  35.     //Wzor jest nastepujacy
  36.     //Okres = czestotliwosc / czestotliwosc_pwm - 1
  37.     //Czyli dla zegara 84MHz, PWM na 20kHz mozna wyliczyc
  38.     //Okres = 84000000 / 20000 - 1 =
  39.     //Dla wartosci ponad 65535 nalezy zwiekszyc prescaler dla timera
  40.     TIM_Struct.TIM_Period = 4199;
  41.     TIM_Struct.TIM_ClockDivision = TIM_CKD_DIV1;
  42.     TIM_Struct.TIM_RepetitionCounter = 0;
  43.     //Wlaczenie TIM4
  44.     TIM_TimeBaseInit(TIM4, &TIM_Struct);
  45.     //Rozpoczecie odliczania
  46.     TIM_Cmd(TIM4, ENABLE);
  47. }
  48. void TIM4_PWM_INIT(void)
  49. {
  50.     TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
  51.     TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
  52.     TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
  53. }
  54. void PWM_SetValuePD12(int value)
  55. {
  56.     TIM_OCStruct.TIM_Pulse = value;
  57.     TIM_OC1Init(TIM4, &TIM_OCStruct);
  58.     TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
  59. }
  60. void PWM_SetValuePD13(int value)
  61. {
  62.     TIM_OCStruct.TIM_Pulse = value;
  63.     TIM_OC2Init(TIM4, &TIM_OCStruct);
  64.     TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
  65. }
  66. void PWM_SetValuePD14(int value)
  67. {
  68.     TIM_OCStruct.TIM_Pulse = value;
  69.     TIM_OC3Init(TIM4, &TIM_OCStruct);
  70.     TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
  71. }
  72. void PWM_SetValuePD15(int value)
  73. {
  74.     TIM_OCStruct.TIM_Pulse = value;
  75.     TIM_OC4Init(TIM4, &TIM_OCStruct);
  76.     TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
  77. }
  78. int main(void)
  79. {
  80.     SystemInit();
  81.     GPIO_DIODES_INIT();
  82.     TIM4_INIT();
  83.     TIM4_PWM_INIT();
  84.     PWM_SetValuePD12(1000);
  85.     PWM_SetValuePD13(2000);
  86.     PWM_SetValuePD14(3000);
  87.     PWM_SetValuePD15(4000);
  88.     while (1){ }
  89. }

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.

  1. TIM_OCInitTypeDef TIM_OCStruct;
  2.  
  3. void InitPWM()
  4. {
  5.     GPIO_InitTypeDef GPIO_InitStruct;
  6.     TIM_TimeBaseInitTypeDef TIM_BaseStruct;
  7.  
  8.     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  9.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  10.  
  11.     GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3);
  12.  
  13.     GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
  14.     GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  15.     GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
  16.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
  17.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
  18.     GPIO_Init(GPIOB, &GPIO_InitStruct);
  19.  
  20.     TIM_BaseStruct.TIM_Prescaler = 0;
  21.     TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
  22.     TIM_BaseStruct.TIM_Period = ((SystemCoreClock / 1000)) - 1;
  23.     TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
  24.     TIM_BaseStruct.TIM_RepetitionCounter = 0;
  25.     TIM_TimeBaseInit(TIM3, &TIM_BaseStruct);
  26.  
  27.     TIM_Cmd(TIM3, ENABLE);
  28.  
  29.     TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
  30.     TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
  31.     TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
  32. }
  33.  
  34. void PWMWlaczDzwiek(int value)
  35. {
  36.     TIM_OCStruct.TIM_Pulse = value;
  37.     TIM_OC3Init(TIM3, &TIM_OCStruct);
  38.     TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
  39. }