Library 33- PWM for STM32F4
I had enough of each time configuring PWM for some reason, so I decided to make a library for it. This library allows you to use PWM signal on all possible timers on all possible pins for timer. I made it for controling servos, but it can be used for anything else. PWM works in FAST PWM mode.
You just set your PWM frequency, timer you will use, channel on timer and you are ready to go.
If you need any specific and exotic settings, you should look at my PWM tutorial.
Library
Features
- Drive PWM signal on all possible PWM timers
- Only frequency is required for timer initialization
- Supports all possible output pins for each timer
- Fast PWM mode
- Version 1.1 – September 15, 2014
- PWM library splitted into a 2 libraries. One for PWM and one for timer properties. This allows me to use one library with timer’s settings on more other libraries.
- Version 2 – January 03, 2015
- Library has been rewritten, new examples below
Dependencies
- CMSIS
- STM32F4xx
- STM32F4xx RCC
- STM32F4xx GPIO
- STM32F4xx TIM
- TM
- TM TIMER PROPERTIES
- TM GPIO
- defines.h
- TM TIMER PROPERTIES
Table below shows all possible pins for each timer and channel. You can select any of max 3 pins for each output channel.
Timer | Channel 1 | Channel 2 | Channel 3 | Channel 4 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
PP1 | PP2 | PP3 | PP1 | PP2 | PP3 | PP1 | PP2 | PP3 | PP1 | PP2 | PP3 | |
TIM 1 | PA8 | PE9 | PA9 | PE10 | PA10 | PE13 | PA11 | PE14 | ||||
TIM 2 | PA0 | PA5 | PA15 | PA1 | PB3 | PA2 | PB10 | PA3 | PB11 | |||
TIM 3 | PA6 | PB4 | PC6 | PA7 | PB5 | PC7 | PB0 | PC8 | PB1 | PC9 | ||
TIM 4 | PB6 | PD12 | PB7 | PD13 | PB8 | PD14 | PB9 | PD15 | ||||
TIM 5 | PA0 | PH10 | PA1 | PH11 | PA2 | PH12 | PA3 | PI0 | ||||
TIM 8 | PC6 | PI5 | PC7 | PI6 | PC8 | PI7 | PC9 | PI2 | ||||
TIM 9 | PA2 | PE5 | PA3 | PE6 | ||||||||
TIM 10 | PB8 | PF6 | ||||||||||
TIM 11 | PB9 | PF7 | ||||||||||
TIM 12 | PB14 | PH6 | PB15 | PH9 | ||||||||
TIM 13 | PA6 | PF8 | ||||||||||
TIM 14 | PA7 | PF9 |
- PPx: Pins Pack 1 to 3, for 3 possible channel outputs on timer.
Notes on table above
- Not all timers are available on all STM32F4xx devices
- Not all timers works with same tick frequency
- All timers have 16bit prescaler
- TIM6 and TIM7 don’t have PWM feature, they are only basic timers
- TIM2 and TIM5 are 32bit timers
- TIM9 and TIM12 have two PWM channels
- TIM10, TIM11, TIM13 and TIM14 have only one PWM channel
- All channels at one timer have the same PWM frequency!
This library do everything by it’s self. Everything you need to set is a PWM frequency you want to use (let’s say 50Hz for servo motor) for specific timer you will use. Library automatically sets period and prescaler for timer. This value is set, that allows you maximum possible resolution for PWM duty cycle at given frequency.
Everything is explained in example at the bottom. To work with PWM, you will need some typedefs and functions. They are below:
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 146 147 148 149 150 151 152 153 154 155 156 157 158 |
/** * Result enumeration * * Parameters * - TM_PWM_Result_Ok: * Everything OK * - TM_PWM_Result_FrequencyTooHigh: * You select too high frequency for timer for PWM * - TM_PWM_Result_FrequencyTooLow: * Prescaler value is too big for selected frequency * - TM_PWM_Result_PulseTooHigh: * Pulse for Output compare is larger than timer period * - TM_PWM_Result_TimerNotValid: * Selected timer is not valid. This happens when you select TIM6 or TIM7, * because they don't have PWM capability. * - TM_PWM_Result_ChannelNotValid: * Channel is not valid. Some timers don't have all 4 timers available for PWM * - TM_PWM_Result_PinNotValid: * Selected pin is not valid. Most channels have only 2 possible pins for PWM, * but some 3. If you select pin 3 on channel that don't have 3rd pin available * for PWM, this will be returned */ typedef enum { TM_PWM_Result_Ok = 0, TM_PWM_Result_FrequencyTooHigh, TM_PWM_Result_FrequencyTooLow, TM_PWM_Result_PulseTooHigh, TM_PWM_Result_TimerNotValid, TM_PWM_Result_ChannelNotValid, TM_PWM_Result_PinNotValid } TM_PWM_Result_t; /** * Timer data * * Parameters: * - TIM_TypeDef* TIM: * Pointer to timer used * - uint32_t Period: * Period used, set on initialization for PWM * - uint32_t Prescaler: * Prescaler used for PWM frequency * - uint32_t Frequency: * PWM frequency used * - uint32_t Micros: * Microseconds used for one period. * This is not useful in large pwm frequency, but good for controlling servos or similar, * Where you need exact time of pulse high */ typedef struct { TIM_TypeDef* TIM; uint32_t Period; uint32_t Prescaler; uint32_t Frequency; uint32_t Micros; } TM_PWM_TIM_t; /** * Channel selected for PWM on specific timer * */ typedef enum { TM_PWM_Channel_1 = 0, TM_PWM_Channel_2, TM_PWM_Channel_3, TM_PWM_Channel_4 } TM_PWM_Channel_t; /** * Pin selected for corresponding channel on specific channel * */ typedef enum { TM_PWM_PinsPack_1 = 0, TM_PWM_PinsPack_2, TM_PWM_PinsPack_3 } TM_PWM_PinsPack_t; /** * Initialize specific timer for PWM capability * * Parameters: * - TIM_TypeDef* TIMx: * Pointer to selected timer, you want to use for PWM * - TM_PWM_TIM_t* TIM_Data: * Pointer to blank TM_PWM_TIM_t structure. * Here will init function save all data for specific timer * - uint32_t PWMFrequency: * Select custom frequency for PWM * * Member of TM_PWM_Result_t is returned */ extern TM_PWM_Result_t TM_PWM_InitTimer(TIM_TypeDef* TIMx, TM_PWM_TIM_t* TIM_Data, double PWMFrequency); /** * Initialize channel used for specific timer * * Parameters: * - TM_PWM_TIM_t* TIM_Data: * Pointer to struct with already initialized timer for PWM * - TM_PWM_Channel_t Channel: * Select channel you will use on specific timer * - TM_PWM_PinsPack_t PinsPack: * Select which pinspack you will use for pin * * Member of TM_PWM_Result_t is returned */ extern TM_PWM_Result_t TM_PWM_InitChannel(TM_PWM_TIM_t* TIM_Data, TM_PWM_Channel_t Channel, TM_PWM_PinsPack_t PinsPack); /** * Set PWM value for specific timer and channel * * Parameters: * - TM_PWM_TIM_t* TIM_Data: * Pointer to initialized timer data * - TM_PWM_Channel_t Channel: * Channel for which you set value * - uint32_t Pulse: * Pulse, to be set for compare match * * Member of TM_PWM_Result_t is returned */ extern TM_PWM_Result_t TM_PWM_SetChannel(TM_PWM_TIM_t* TIM_Data, TM_PWM_Channel_t Channel, uint32_t Pulse); /** * Set PWM value for specific timer and channel with percentage feature * * Parameters: * - TM_PWM_TIM_t* TIM_Data: * Pointer to initialized timer data * - TM_PWM_Channel_t Channel: * Channel for which you set value * - float percent: * Percentage from 0 to 100, to set PWM value * * Member of TM_PWM_Result_t is returned */ extern TM_PWM_Result_t TM_PWM_SetChannelPercent(TM_PWM_TIM_t* TIM_Data, TM_PWM_Channel_t Channel, float percent); /** * Set PWM value for specific timer and channel with pulse high time feature. * You have to specify amount of micro seconds pulse will be high for specific timer and channel. * This method is not so good on PWM large than 100k, because you cannot set nice and correct settings, with microseconds * accuracy. It is perfect, and was designed for low frequencies, eg. use with servos, where you have exact amount of time * for servo's rotation. * * Parameters: * - TM_PWM_TIM_t* TIM_Data: * Pointer to initialized timer data * - TM_PWM_Channel_t Channel: * Channel for which you set value * - uint32_t micros: * Microseconds for pulse high on PWM. Cannot be large than timer period in micros. * PWM 1kHz = Timer period = 1000000 / 1000 = 1000us. This parameter can not be greater than 1000us in this case. * * Member of TM_PWM_Result_t is returned */ extern TM_PWM_Result_t TM_PWM_SetChannelMicros(TM_PWM_TIM_t* TIM_Data, TM_PWM_Channel_t Channel, uint32_t micros); |
Example
For example, leds on STM32F4-Discovery and Nucleo F401-RE boards are used. STM32F429-Discovery doesn’t have leds connected to PWM pins.
I tested some other pins, if they are actually working with oscilloscope, but there is a lot of pins and timers, so I didn’t check for all possibility. They should work.
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 |
/** * Keil project for PWM example * * Before you start, select your target, on the right of the "Load" button * * STM32F429-Discovery doesn't have leds connected to PWM pins, * so you can test this with STM32F4-Discovery or Nucleo-F4(0/1)1RE boards with leds. * * Below is code for both boards in main.c * You have to set correct target above first, but code will be always compiled, * if you select STM32F4-Discovery or Nucleo F401RE boards * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 * @packs STM32F4xx Keil packs version 2.2.0 or greater required * @stdperiph STM32F4xx Standard peripheral drivers version 1.4.0 or greater required */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_pwm.h" int main(void) { TM_PWM_TIM_t TIM4_Data, TIM2_Data; /* Initialize system */ SystemInit(); /* STM32F4-Discovery LEDS start */ /* Leds on PD12, PD13, PD14, PD15 */ /* Set PWM to 1kHz frequency on timer TIM4 */ /* 1 kHz = 1ms = 1000us */ TM_PWM_InitTimer(TIM4, &TIM4_Data, 1000); /* Initialize PWM on TIM4, Channel 1 and PinsPack 2 = PD12 */ TM_PWM_InitChannel(&TIM4_Data, TM_PWM_Channel_1, TM_PWM_PinsPack_2); /* Initialize PWM on TIM4, Channel 2 and PinsPack 2 = PD13 */ TM_PWM_InitChannel(&TIM4_Data, TM_PWM_Channel_2, TM_PWM_PinsPack_2); /* Initialize PWM on TIM4, Channel 3 and PinsPack 2 = PD14 */ TM_PWM_InitChannel(&TIM4_Data, TM_PWM_Channel_3, TM_PWM_PinsPack_2); /* Initialize PWM on TIM4, Channel 4 and PinsPack 2 = PD15 */ TM_PWM_InitChannel(&TIM4_Data, TM_PWM_Channel_4, TM_PWM_PinsPack_2); /* Set channel 1 value, 50% duty cycle */ TM_PWM_SetChannel(&TIM4_Data, TM_PWM_Channel_1, TIM4_Data.Period / 2); /* Set channel 2 value, 33% duty cycle */ TM_PWM_SetChannel(&TIM4_Data, TM_PWM_Channel_2, TIM4_Data.Period / 3); /* Set channel 3 value, 25% duty cycle */ TM_PWM_SetChannel(&TIM4_Data, TM_PWM_Channel_3, TIM4_Data.Period / 4); /* Set channel 4 value, 5% duty cycle*/ TM_PWM_SetChannelPercent(&TIM4_Data, TM_PWM_Channel_4, 5); /* STM32F4-Discovery LEDS stop */ /* Nucleo F4(0/1)1-RE LED start */ /* Led connected to PA5 */ /* Set PWM to 1kHz frequency on timer TIM2 */ /* 1 kHz = 1ms = 1000us */ TM_PWM_InitTimer(TIM2, &TIM2_Data, 1000); /* Initialize PWM on TIM2, Channel 1 and PinsPack 2 = PA5 */ TM_PWM_InitChannel(&TIM2_Data, TM_PWM_Channel_1, TM_PWM_PinsPack_2); /* Set channel 1 value, 500us pulse high = 500 / 1000 = 0.5 = 50% duty cycle */ TM_PWM_SetChannelMicros(&TIM2_Data, TM_PWM_Channel_1, 500); /* Nucleo F401-RE LED stop */ while (1) { } } |
Simple Servo controller 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 |
/** * Keil project for PWM SERVO example * * Servo works with 50Hz (20ms) PWM frequency input. * High pulse is between 1 and 2 ms, but some are working from 0.8 to 2.2ms * My at home is going from 0.9 to 2.1 ms * * Before you start, select your target, on the right of the "Load" button * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 * @packs STM32F4xx Keil packs version 2.2.0 or greater required * @stdperiph STM32F4xx Standard peripheral drivers version 1.4.0 or greater required */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_pwm.h" #include "tm_stm32f4_disco.h" #include "tm_stm32f4_delay.h" int main(void) { TM_PWM_TIM_t TIM2_Data; uint16_t position = 1000; /* Initialize system */ SystemInit(); /* Initialize delay functions */ TM_DELAY_Init(); /* Set PWM to 50Hz frequency on timer TIM2 */ /* 50Hz = 20ms = 20000us */ TM_PWM_InitTimer(TIM2, &TIM2_Data, 50); /* Initialize PWM on TIM2, Channel 1 and PinsPack 2 = PA5 */ TM_PWM_InitChannel(&TIM2_Data, TM_PWM_Channel_1, TM_PWM_PinsPack_2); /* Set channel 1 value, 1500us = servo at center position */ TM_PWM_SetChannelMicros(&TIM2_Data, TM_PWM_Channel_1, 1500); /* Servo in position */ Delayms(1000); while (1) { if (position == 1000) { /* Change direction */ position = 2000; } else { /* Change direction */ position = 1000; } /* Set servo position */ TM_PWM_SetChannelMicros(&TIM2_Data, TM_PWM_Channel_1, position); /* Set some delay for servo to set to correct position */ Delayms(1500); } } |
Both projects are available at my Github account, download library below.
Recent comments