http://stm32f4-discovery.net/2014/05/stm32f4-stm32f429-discovery-pwm-tutorial/
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.com
* @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.