STM32F4 PWM tutorial with TIMERs
In this tutorial, I will show you, how to implement PWM outputs on STM32F4xx devices. This is for a lot of people pretty hard work, but believe me, it’s quite quick and easy. I will go step by step on how to make a PWM output on specific timer.
Update: I made a library for PWM, available here.
STM32F4 timers
They have up to 14 timers inside. Table below shows their description. You have to know, that let’s say F401 doesn’t have so many timers like F407 or F429. You have to always check manual for it’s chip if it has available timer.
Timer | Type | Resolution | Prescaler | Channels | MAX INTERFACE CLOCK | MAX TIMER CLOCK* | APB |
---|---|---|---|---|---|---|---|
TIM1, TIM8 | Advanced | 16bit | 16bit | 4 | SysClk/2 | SysClk | 2 |
TIM2, TIM5 | General purpose | 32bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM3, TIM4 | General purpose | 16bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM9 | General purpose | 16bit | 16bit | 2 | SysClk/2 | SysClk | 2 |
TIM10, TIM11 | General purpose | 16bit | 16bit | 1 | SysClk/2 | SysClk | 2 |
TIM12 | General purpose | 16bit | 16bit | 2 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM13, TIM14 | General purpose | 16bit | 16bit | 1 | SysClk/4 | SysClk, SysClk/2 | 1 |
TIM6, TIM7 | Basic | 16bit | 16bit | 0 | SysClk/4 | SysClk, SysClk/2 | 1 |
* Clock speed depends on which STM32F4xx device is used
Timer initialization
Before we can use PWM, we have to initialize timer. Because STM32F429 Discovery board does not have leds on PWM pins, I will use STM32F4 Discovery with 168MHz core clock for this example. Leds are connected to pins PD12 to PD15, what give us timer TIM4 with Output channels 1 to 4.
Initialize TIM4
Here we will initialize timer, for our PWM frequency. We have to set prescaler and period for how fast will it count and how much it will count for specific PWM frequency.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
void TM_TIMER_Init(void) { TIM_TimeBaseInitTypeDef TIM_BaseStruct; /* Enable clock for TIM4 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); /* TIM4 is connected to APB1 bus, which has on F407 device 42MHz clock But, timer has internal PLL, which double this frequency for timer, up to 84MHz Remember: Not each timer is connected to APB1, there are also timers connected on APB2, which works at 84MHz by default, and internal PLL increase this to up to 168MHz Set timer prescaller Timer count frequency is set with timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1) In our case, we want a max frequency for timer, so we set prescaller to 0 And our timer will have tick frequency timer_tick_frequency = 84000000 / (0 + 1) = 84000000 */ TIM_BaseStruct.TIM_Prescaler = 0; /* Count up */ TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up; /* Set timer period when it have reset First you have to know max value for timer In our case it is 16bit = 65535 To get your frequency for PWM, equation is simple PWM_frequency = timer_tick_frequency / (TIM_Period + 1) If you know your PWM frequency you want to have timer period set correct TIM_Period = timer_tick_frequency / PWM_frequency - 1 In our case, for 10Khz PWM_frequency, set Period to TIM_Period = 84000000 / 10000 - 1 = 8399 If you get TIM_Period larger than max timer value (in our case 65535), you have to choose larger prescaler and slow down timer tick frequency */ TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */ TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_BaseStruct.TIM_RepetitionCounter = 0; /* Initialize TIM4 */ TIM_TimeBaseInit(TIM4, &TIM_BaseStruct); /* Start count on TIM4 */ TIM_Cmd(TIM4, ENABLE); } |
Initialize PWM 4 channels
Set PWM output channels to PWM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
void TM_PWM_Init(void) { TIM_OCInitTypeDef TIM_OCStruct; /* Common settings */ /* PWM mode 2 = Clear on compare match */ /* PWM mode 1 = Set on compare match */ TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low; /* To get proper duty cycle, you have simple equation pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1 where DutyCycle is in percent, between 0 and 100% 25% duty cycle: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099 50% duty cycle: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199 75% duty cycle: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299 100% duty cycle: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399 Remember: if pulse_length is larger than TIM_Period, you will have output HIGH all the time */ TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */ TIM_OC1Init(TIM4, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */ TIM_OC2Init(TIM4, &TIM_OCStruct); TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */ TIM_OC3Init(TIM4, &TIM_OCStruct); TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */ TIM_OC4Init(TIM4, &TIM_OCStruct); TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); } |
Initialize outputs
Initialize and connect pins for specific TIM. Notice, that GPIOs have AF (Alternating Function) mode, because timer has full control on them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void TM_LEDS_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* Clock for GPIOD */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); /* Alternating functions for pins */ 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); /* Set pins */ GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; 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(GPIOD, &GPIO_InitStruct); } |
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
/** * PWM example for STM32F4 Discovery * It should work on STM32F429 Discovery too and all other STM32F4xx devices * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 */ #include "defines.h" #include "stm32f4xx.h" #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_tim.h" void TM_LEDS_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; /* Clock for GPIOD */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); /* Alternating functions for pins */ 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); /* Set pins */ GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; 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(GPIOD, &GPIO_InitStruct); } void TM_TIMER_Init(void) { TIM_TimeBaseInitTypeDef TIM_BaseStruct; /* Enable clock for TIM4 */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); /* TIM4 is connected to APB1 bus, which has on F407 device 42MHz clock But, timer has internal PLL, which double this frequency for timer, up to 84MHz Remember: Not each timer is connected to APB1, there are also timers connected on APB2, which works at 84MHz by default, and internal PLL increase this to up to 168MHz Set timer prescaller Timer count frequency is set with timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1) In our case, we want a max frequency for timer, so we set prescaller to 0 And our timer will have tick frequency timer_tick_frequency = 84000000 / (0 + 1) = 84000000 */ TIM_BaseStruct.TIM_Prescaler = 0; /* Count up */ TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up; /* Set timer period when it have reset First you have to know max value for timer In our case it is 16bit = 65535 To get your frequency for PWM, equation is simple PWM_frequency = timer_tick_frequency / (TIM_Period + 1) If you know your PWM frequency you want to have timer period set correct TIM_Period = timer_tick_frequency / PWM_frequency - 1 In our case, for 10Khz PWM_frequency, set Period to TIM_Period = 84000000 / 10000 - 1 = 8399 If you get TIM_Period larger than max timer value (in our case 65535), you have to choose larger prescaler and slow down timer tick frequency */ TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */ TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_BaseStruct.TIM_RepetitionCounter = 0; /* Initialize TIM4 */ TIM_TimeBaseInit(TIM4, &TIM_BaseStruct); /* Start count on TIM4 */ TIM_Cmd(TIM4, ENABLE); } void TM_PWM_Init(void) { TIM_OCInitTypeDef TIM_OCStruct; /* Common settings */ /* PWM mode 2 = Clear on compare match */ /* PWM mode 1 = Set on compare match */ TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low; /* To get proper duty cycle, you have simple equation pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1 where DutyCycle is in percent, between 0 and 100% 25% duty cycle: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099 50% duty cycle: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199 75% duty cycle: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299 100% duty cycle: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399 Remember: if pulse_length is larger than TIM_Period, you will have output HIGH all the time */ TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */ TIM_OC1Init(TIM4, &TIM_OCStruct); TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */ TIM_OC2Init(TIM4, &TIM_OCStruct); TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */ TIM_OC3Init(TIM4, &TIM_OCStruct); TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */ TIM_OC4Init(TIM4, &TIM_OCStruct); TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable); } int main(void) { /* Initialize system */ SystemInit(); /* Init leds */ TM_LEDS_Init(); /* Init timer */ TM_TIMER_Init(); /* Init PWM */ TM_PWM_Init(); while (1) { } } |
That’s it. You have not set your timer to work in PWM mode.
Hope it helps.
Recent comments