Library 48- Measure PWM input signal with STM32F4
First library in 2015 is here. With it, you will be able to measure PWM input signal from “other world”. STM32F4’s timers have capability to make an interrupt on edge, when signal is active on input pin for specific timer. This allows us, to measure signal in input. With a simple calculations, we can detect frequency of signal and duty cycle.
Of course, this is not veery accurate, but with 168MHz F407 device, using 32bit TIM2 (high resolution) I got pretty good results. Input PWM was set to random value 2545Hz with 23% duty cycle. Results from TIM2 were:
- Frequency: 2545.13Hz
- Duty cycle: 22.97%
Result’s quality depends on frequency you measure and also on timer’s performance (prescaler, max period value).
In library below, everything you have to set are:
- Timer you will use for measuring signals
- Timer’s IRQ for NVIC interrupt request
- Create timer’s IRQ for interrupt handler
- Call defined function in this handler
- Call function to get results
- More in example..
Library
Features
- Measure PWM input signal on all available timers with input pin possibility
- Measure frequency and duty cycle
Dependencies
- CMSIS
- STM32F4xx
- STM32F4xx RCC
- STM32F4xx GPIO
- STM32F4xx TIM
- TM
- TM TIMER PROPERTIES
- 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 input channel.
Timer | Channel 1 | Channel 2 | ||||
---|---|---|---|---|---|---|
PP1 | PP2 | PP3 | PP1 | PP2 | PP3 | |
TIM 1 | PA8 | PE9 | PA9 | PE10 | ||
TIM 2 | PA0 | PA5 | PA15 | PA1 | PB3 | |
TIM 3 | PA6 | PB4 | PC6 | PA7 | PB5 | PC7 |
TIM 4 | PB6 | PD12 | PB7 | PD13 | ||
TIM 5 | PA0 | PH10 | PA1 | PH11 | ||
TIM 8 | PC6 | PI5 | PC7 | PI6 | ||
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
- TIM10, TIM11, TIM13 and TIM14 have only one PWM channel
- Only 1 channel can be selected on 1 timer
Other notes
- If you don’t know which frequency you can expect on your input, then set initialization function to 1, which means that you want to measure minimal frequency of 1Hz. If you want to go even lower, you can basically go to a value of
- TIM_CLOCK / (TIM_MAXPRESCALER * MAXTIM_PERIOD)
- This can be very low value, but if you have then signal in 10kHz spectrum, your result can veery very fail from real. I suggest you that you set this value to 1 if you don’t know what you can expect, if you know, then set it to a little bit smaller value.
- Example: if you expect signal of 1000Hz, then set value to 950 in initialization function or something like that
- I suggest you to use 32-bit timer (TIM2, TIM5) to measure input PWM, you got better result and better accuracy
- Expected frequency is just some kind of reference, how to set timer with prescaler, so you will be able to measure entire PWM period with one timer’s period value for valid result!
Default NVIC priority for all timers is set to 0x01. If you need to set another value, then use define in defines.h file:
1 2 |
/* PWM IN timer global priority */ #define PWMIN_PRIORITY 0x01 |
Sub priority for timers is generated in a way that which timer you initialize first, this timer has higher sub priority.
Functions and enumerations
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 |
/** * PWM Input result enumeration * * Parameters: * - TM_PWMIN_Result_Ok: * Everything OK * - TM_PWMIN_Result_TimerNotValid: * Invalid timer selected for PWM input capture * - TM_PWMIN_Result_ChannelNotValid: * Invalid input channel selected for timer * - TM_PWMIN_Result_PinNotValid: * Invalid pin selected for timer */ typedef enum { TM_PWMIN_Result_Ok = 0, TM_PWMIN_Result_TimerNotValid, TM_PWMIN_Result_ChannelNotValid, TM_PWMIN_Result_PinNotValid } TM_PWMIN_Result_t; /** * PWM Input working struct * * Parameters: * - float Frequency: * Public, Measured frequency on input pin * - float DutyCycle: * Public, Measured duty cycle on input pin * - TIM_TypeDef* __TIM: * Private, Pointer to timer used for measure * - TM_TIMER_PROPERTIES_t __TIM_Data: * Settings about timer * - float __Freq: * Private, temporary frequency value * - float __Duty: * Private, temporary duty cycle value * - uint8_t __SubPriority: * Private, NVIC subpriority */ typedef struct { /* Public */ float Frequency; float DutyCycle; /* Private */ TIM_TypeDef* __TIM; TM_TIMER_PROPERTIES_t __TIM_Data; float __Freq; float __Duty; uint8_t __SubPriority; } TM_PWMIN_t; /** * Select input channel for PWM input on specific timer * */ typedef enum { TM_PWMIN_Channel_1 = 0, TM_PWMIN_Channel_2 } TM_PWMIN_Channel_t; /** * Select pin for input channel on specific timer * */ typedef enum { TM_PWMIN_PinsPack_1 = 0, TM_PWMIN_PinsPack_2, TM_PWMIN_PinsPack_3 } TM_PWMIN_PinsPack_t; /** * Initialize and prepare timer for input PWM capture * * Parameters: * - TIM_TypeDef* TIMx: * Timer you will use for input PWM capture * - TM_PWMIN_t* PWMIN_Data: * Pointer to an empty TM_PWMIN_t struct * - TM_PWMIN_Channel_t PWMIN_Channel: * Channel you will use on timer. * - TM_PWMIN_PinsPack_t PinsPack: * Pinspack you will use for your channel * - float MinExpectedFrequency: * Type minimal input frequency you expect in input. If you don't know what to expect, set to 1 * - IRQn_Type TIMx_IRQn: * IRQ for NVIC settings. For TIM2 it can be TIM2_IRQn, for TIM3,4,5,... it can be TIMx_IRQn, where x is a number of timer * * Member of TM_PWMIN_Result_t is returned */ extern TM_PWMIN_Result_t TM_PWMIN_InitTimer(TIM_TypeDef* TIMx, TM_PWMIN_t* PWMIN_Data, TM_PWMIN_Channel_t PWMIN_Channel, TM_PWMIN_PinsPack_t PinsPack, float MinExpectedFrequency, IRQn_Type TIMx_IRQn); /** * Read data from measured input PWM signal * * Parameters: * - TM_PWMIN_t* PWMIN_Data: * Pointer to TM_PWMIN_t struct * Frequency and Dutycycle will be stored in this structure * * Data are valid if frequency is greater than 0 * * Member of TM_PWMIN_Result_t is returned */ extern TM_PWMIN_Result_t TM_PWMIN_Get(TM_PWMIN_t* PWMIN_Data); /** * Most important function * This function handles TIMx interrupts to calculate PWM input data. * You need to call it in your TIMx handler. * * For TIM2, you will do: * void TIM2_IRQHandler(void) { * TM_PWMIN_InterruptHandler(&PWMIN_TIM2_Data); * } * * Parameters: * - TM_PWMIN_t* PWMIN_Data: * Pointer to TM_PWMIN_t struct * * Member of TM_PWMIN_Result_t is returned */ extern TM_PWMIN_Result_t TM_PWMIN_InterruptHandler(TM_PWMIN_t* PWMIN_Data); |
Example
- Example and results are based on STM32F4-Discovery board.
- TIM4 makes PWM on pin PD12 with 3251Hz.
- TIM2 and TIM3 are measuring timers
- TIM2 is 32-bit, TIM3 is 16-bit.
- Results in pictures below
- First has capture timers set to 1000Hz expected frequency, second with 1Hz
- TIM3 starts failing with result
- First has capture timers set to 1000Hz expected frequency, second with 1Hz
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 |
/** * Keil project example for PWM input * * In example, "fake" PWM signal of 3251Hz is generated * and connected with wire to another for measuring. * * Results I got were: * - Results on F407 168MHz device * - Real input frequency was 3251Hz * - 1000Hz expected frequency: * - TIM2 (32-bit, 84MHz clock): frequency: 3251.4Hz, duty: 49.99 * - TIM3 (16-bit, 84MHz/prescaler clock): frequency: 3251.53; duty: 49.99 * - 1Hz expected frequency: * - TIM2 (32-bit, 84MHz clock): frequency: 3251.4Hz, duty: 49.99 * - TIM3 (16-bit, 84MHz/prescaler clock): frequency: 3276.13; duty: 50.00 * - TIM3 has "only" 16bit and clock is quite faster for 1Hz, so it has prescaler, * which gives you less accurate value. * * 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_delay.h" #include "tm_stm32f4_disco.h" #include "tm_stm32f4_pwmin.h" #include "tm_stm32f4_pwm.h" #include "tm_stm32f4_swo.h" TM_PWMIN_t PWMIN_TIM1; TM_PWMIN_t PWMIN_TIM2; TM_PWMIN_t PWMIN_TIM3; TM_PWMIN_t PWMIN_TIM5; TM_PWM_TIM_t PWM_Data; int main(void) { /* Initialize system */ SystemInit(); /* Initialize delay */ TM_DELAY_Init(); /* Initialize leds on board */ TM_DISCO_LedInit(); /* PWM output for testing */ /* Init TIM4 for PWM output, 10kHz PWM */ TM_PWM_InitTimer(TIM4, &PWM_Data, 3251); /* TIM4, pin PD12 */ TM_PWM_InitChannel(&PWM_Data, TM_PWM_Channel_1, TM_PWM_PinsPack_2); /* 50 percent duty cycle on PD12 output */ TM_PWM_SetChannelPercent(&PWM_Data, TM_PWM_Channel_1, 50); /* PWM output for testing */ /* PWM input */ /* Initialize timer for PWM Input capture = */ /* We expect signal greater or equal to 1000Hz */ /* Uncomment "1" and comment "1000" to change expected frequency */ TM_PWMIN_InitTimer(TIM2, &PWMIN_TIM2, TM_PWMIN_Channel_1, TM_PWMIN_PinsPack_2, 1000 /* 1 */, TIM2_IRQn); /* We expect signal greater or equal to 1000Hz */ /* Uncomment "1" and comment "1000" to change expected frequency */ TM_PWMIN_InitTimer(TIM3, &PWMIN_TIM3, TM_PWMIN_Channel_2, TM_PWMIN_PinsPack_2, 1000 /* 1 */, TIM3_IRQn); /* PWM input */ /* Initialize SWO debug */ TM_SWO_Init(); while (1) { /* Every 100ms look for new signal */ if (TM_DELAY_Time() >= 100) { TM_DELAY_SetTime(0); /* Get new data for both input signals */ TM_PWMIN_Get(&PWMIN_TIM2); TM_PWMIN_Get(&PWMIN_TIM3); /* Valid PWM input signal */ if (PWMIN_TIM2.Frequency > 0) { /* Print via SWo debug */ TM_SWO_Printf("TIM2: F: %7.2f, D: %5.2f\n", PWMIN_TIM2.Frequency, PWMIN_TIM2.DutyCycle); } /* Valid PWM input signal */ if (PWMIN_TIM3.Frequency > 0) { /* Print via SWo debug */ TM_SWO_Printf("TIM3: F: %7.2f, D: %5.2f\n", PWMIN_TIM3.Frequency, PWMIN_TIM3.DutyCycle); } /* Print separator to debug window */ TM_SWO_Printf("----\n"); } } } /* TIM2 IRQ handler */ void TIM2_IRQHandler(void) { /* Interrupt request, don't forget! */ TM_PWMIN_InterruptHandler(&PWMIN_TIM2); } /* TIM3 IRQ handler */ void TIM3_IRQHandler(void) { /* Interrupt request, don't forget! */ TM_PWMIN_InterruptHandler(&PWMIN_TIM3); } |
Project available at my Github account, download library below.
Recent comments