TIM3 is a general purpose timer found on all the STM32 family processors. Among other features, it has four capture compare channels that can be used to generate regular interrupts. In this article I will show you how to set up simple interrupt events based on these features.
This is part of a series of articles about the TIM3 general purpose timer. In the preceding parts I introduced the TIM3 timer features and showed you how to identify the timer clock and set up the prescaler and reload register.You can see all the STM32 TIM3 posts here. Code is written using the Standard Peripheral Library for the STM32F4Discovery board. Timer related portions should run directly on other STM32 family members since they all have a TIM3
In this part, I will explain the basic configuration of the capture compare registers, describe how they can be used to generate regular interrupts and give you sample code to demonstrate the facility.
Capture Compare Registers
Here is a modified view of the TIM3 timer taken from the reference manual RM0090.
You can see that there are four registers associated with the timer TIM3. These are referred to as CCR1, CCR2, CCR3 and CCR4 or CCRx in general. Each channel is actually a pretty complex affair I am only going to concern myself with its behaviour in output compare modes. The reference manual RM0090 has this diagram of the output configuration:
A look at the reference manual reveals that in basic Output Compare mode, I can program the output mode controller to one of four different states on a match. It can be:
- frozen – that is, nothing happens
- set active (forced high)
- set inactive (forced low)
- toggled
Other results are possible in other modes such as PWM.
In this article though, I just want to look at triggering an interrupt event. Although there are four Output Compare channels, and each can generate its own interrupt, they all get handled by the same Interrupt Service Routine (ISR). That means a series of tests will be needed in the ISR to determine exactly which channel caused the interrupt. I will just use two channels to illustrate how all this works.
Example Problem Statement
For this example, I want to set up two Output Compare channels on TIM3. Each channel will simply generate an interrupt. The ISR for that channel will toggle an LED on the target board. Each channel gets its own LED. Channel 1 will look after the blue LED and flash it 4 times per second. Channel 2 will look after the orange LED and flash it 2 times a second. These are simple numbers and the interrupt rate is quite low so that it is easy to see what is happening. Adapt the code for your needs.
Set up the Timer Prescale and Auto Reload
As with most timer problems, the first task is to set up the basic counter activity. This problem needs counts that are some handy but fairly small fraction of a second. Since the problem statement mentions numbers like 4 times per second and 8 times per second, it seems reasonable to try and arrange the counting frequency to be a convenient power of 2. Something like 256Hz or 128kHz of 3200Hz. Whatever, some number that lets me easily get binary fractions.
I have already established that the input frequency to the prescaler is 72MHz. This is an inconvenient value since there are no divisors that give me the kind of counter frequency that I was looking for. After a bit of fiddling with some of the factors of 72,000,000 I discovered that if I divide it by 28,125 I get 2560. Now that is a handy value for the counter since I can easily get many multiples of 2 and 5 out of it. Multiples of 3 would be more tricky but that is not part of my problem. The actual frequency for the counter is not critical in this case so long as it is high enough to give me the resolution I require.
Note that it is not sufficient to just pick a frequency. You must choose an value that divides exactly into the clock frequency if the results are to be accurate. the TIM3 counter frequency gets stored in a variable so that we can use it when calculating the intervals for the CCRx registers.
1 |
uint32_t TIM3COUNTER_Frequency = 2560; |
Now I need to set the prescaler to (28125-1) = 28124 so that my counter will increment at 2560Hz. The prescaler value is calculated from the timer input clock frequency (TIM3CLK_Frequency) and the desired TIM3COUNTER_Frequency.
When using the Output Compare channels, I need the CNT register to wrap around completely for reasons that should be clear shortly. To do this, I need to load the ARR register with 65535.
The update event interrupt is not needed so basic timer configuration looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void timer_init (void) { uint32_t TIM3CLK_Frequency = get_timer_clock_frequency(); uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1; /* allow the timer to wrap around naturally */ uint16_t reload = 65535; /* make sure the peripheral is clocked */ RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* set everything back to default values */ TIM_TimeBaseStructInit (&TIM_TimeBaseStructure); /* only changes from the defaults are needed */ TIM_TimeBaseStructure.TIM_Period = reload; TIM_TimeBaseStructure.TIM_Prescaler = prescaler; TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure); } |
Once the timer is started, it will free run at the specified frequency and then I have to think about how to configure the Output Compare registers.
Configure the Output Compare Function
Along with all the other configuration, I have to tell the timer to use the CCRx registers in output compare mode. The Standard Peripheral Library makes all this super easy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void timer_ccr_init (void) { TIM_OCInitTypeDef TIM_OCInitStructure; /* always initialise local variables before use */ TIM_OCStructInit (&TIM_OCInitStructure); /* just use basic Output Compare Mode*/ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active; /* set the initial match interval for CC1 */ TIM_OCInitStructure.TIM_Pulse = CCR1_Interval; TIM_OC1Init (TIM3, &TIM_OCInitStructure); /* and then for CC2 */ TIM_OCInitStructure.TIM_Pulse = CCR2_Interval; TIM_OC2Init (TIM3, &TIM_OCInitStructure); } |
Each channel is configured separately and I need only configure the channels I need. They are completely independent. The initial interval must be loaded into the register when using the library code but that value is only good for the first match and assumes that the CNT register starts at zero. When a match between CNT and the CCRx register is made, the CNT register carries on counting. That leaves me with the problem of how to update the CCRx registers after each match. The value used for the interval will be the number of CNT counts between interrupts. That gets calculated depending on the problem requirements.
Updating the Output Compare Registers
There is nothing in the Output Compare that will reset the CNT register. Even if there was, it would be no good to me because I want to use the CNT register contents to match against up to four CCRx registers. So, how can I get recurring intervals?
The CNT register, by default, will just count upwards at a fixed rate, wrapping around to zero again when it gets to the end. I just made sure of that in the timer setup. Each CCRx register is just compared with CNT for equality – it does not care what the actual value is. Every time the ISR runs then, I just need to add a fixed interval to the CCRx register. When the CNT value catches up with the CCRx again another interrupt will be generated. Overflows will not matter since both registers overflow in the same way and a match must eventually occur. Updating the CCR is now very simple. This code fragment deals with channel 1. The other channels are similar:
1 2 |
uint16_t CCR1_Current = TIM_GetCapture1 (TIM3); TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval); |
In the problem statement, I wanted the blue LED to flash 4 times a second using channel 1 and the orange LED to flash 2 times a second using channel 2. To get two flashes per second, I set the interval to a quarter second since, at each interrupt, I will toggle the LED pin. Similarly, I use 8 ticks per second for the blue LED
1 2 |
uint16_t CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED </code><code>uint16_t CCR2_Interval = TIM3COUNTER_Frequency / 4; // orange LED |
Variable Update Intervals
There is no reason why the same interval has to be used each time the interrupt fires. If I want to drive stepper motors, I can arrange to have a steadily diminishing interval as the motor accelerates. The values could be calculated or looked up in a table. Because I can have four channels and each is independent, I could easily drive four stepper motors from the same timer and have different motion profiles on each one.
Servicing the Output Compare Interrupt
Recall that several channels are all sharing the same ISR so in there I must poll each of the possible interrupt sources, act on any that are active and reset their flags ready for the next time. The entire ISR for my two channel problem looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void TIM3_IRQHandler (void) { /* run through the interrupt sources looking for a hit */ if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) { GPIO_ToggleBits (BLUE_LED); uint16_t CCR1_Current = TIM_GetCapture1 (TIM3); TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval); TIM_ClearITPendingBit (TIM3, TIM_IT_CC1); } if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) { GPIO_ToggleBits (ORANGE_LED); uint16_t CCR2_Current = TIM_GetCapture2 (TIM3); TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval); TIM_ClearITPendingBit (TIM3, TIM_IT_CC2); } } |
The action associated with each channel here is just toggling an LED on and off. It could just as easily be setting the pattern of bits needed to drive the phases of a stepper motor. Just try to keep the ISR code as short and simple as you can. All that remains is to enable the interrupt and let it loose.
Enabling the Output Compare Interrupt
Although all the Output Compare channels share an ISR, each has its own interrupt enable flag so they can be turned on and off individually. Again, the Standard Peripheral Library makes this easy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void timer_interrupt_enable (void) { /* * It is important to clear any pending interrupt flags since the timer * has been free-running since we last used it and that may generate * interrupts on match even though the associated interrupt event has * not been enabled. */ TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2); /* put the counter into a known state */ TIM_SetCounter (TIM3, 0); /* enable the interrupt for CC1 and CC2 only */ TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); } |
Once the timer is started, my ISR should run and the LEDs will flash in the required way.
Putting it Together
Here is a complete code listing demonstrating how to do all these things with the STM32F4 Discovery
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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
#include "stm32f4xx.h" #include "systick.h" #define MHz 1000000L #define KHz 1000L // The LED indicators on the STM32F4Discovery board #define LED_PORT GPIOD #define LED_PORT_CLOCK RCC_AHB1Periph_GPIOD #define GREEN_PIN GPIO_Pin_12 #define ORANGE_PIN GPIO_Pin_13 #define RED_PIN GPIO_Pin_14 #define BLUE_PIN GPIO_Pin_15 #define ALL_LED_PINS GREEN_PIN | ORANGE_PIN | RED_PIN | BLUE_PIN #define GREEN_LED LED_PORT,GREEN_PIN #define ORANGE_LED LED_PORT,ORANGE_PIN #define RED_LED LED_PORT,RED_PIN #define BLUE_LED LED_PORT,BLUE_PIN #define ALL_LEDS LED_PORT,ALL_LED_PINS /* unfortunate globals because they get used in the ISR */ const uint32_t TIM3COUNTER_Frequency = 2560; /* determine the correct counter intervals */ uint16_t CCR1_Interval; // blue LED uint16_t CCR2_Interval; // orange LED void timer_interrupt_init (void) { NVIC_InitTypeDef NVIC_InitStructure; RCC_ClocksTypeDef RCC_Clocks; /* Enable the timer global Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init (&NVIC_InitStructure); } uint32_t get_timer_clock_frequency (void) { RCC_ClocksTypeDef RCC_Clocks; RCC_GetClocksFreq (&RCC_Clocks); uint32_t multiplier; if (RCC_Clocks.PCLK1_Frequency == RCC_Clocks.SYSCLK_Frequency) { multiplier = 1; } else { multiplier = 2; } return multiplier * RCC_Clocks.PCLK1_Frequency; } void timer_clock_init (void) { uint32_t TIM3CLK_Frequency = get_timer_clock_frequency(); uint16_t prescaler = (TIM3CLK_Frequency / TIM3COUNTER_Frequency) - 1; /* allow the timer to wrap around naturally */ uint16_t reload = 65535; /* make sure the peripheral is clocked */ RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; /* set everything back to default values */ TIM_TimeBaseStructInit (&TIM_TimeBaseStructure); /* only changes from the defaults are needed */ TIM_TimeBaseStructure.TIM_Period = reload; TIM_TimeBaseStructure.TIM_Prescaler = prescaler; TIM_TimeBaseInit (TIM3, &TIM_TimeBaseStructure); } void timer_start (void) { TIM_Cmd (TIM3, ENABLE); } void timer_stop (void) { TIM_Cmd (TIM3, DISABLE); } void timer_interrupt_enable (void) { /* * It is important to clear any pending interrupt flags since the timer * has been free-running since we last used it and that may generate * interrups on overflow even though the associated interrupt event has * not been enabled. */ TIM_ClearITPendingBit (TIM3, TIM_IT_CC1 | TIM_IT_CC2); /* put the counter into a known state */ //TIM_SetCounter (TIM3, 0); /* enable the interrupt for CC1 and CC2 only */ TIM_ITConfig (TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE); } // for C++ ensure the interrupt handler is linked as a C function #ifdef __cplusplus extern "C" { #endif void TIM3_IRQHandler (void) { /* run through the interrupt sources looking for a hit */ if (TIM_GetITStatus (TIM3, TIM_IT_CC1) != RESET) { GPIO_ToggleBits (BLUE_LED); uint16_t CCR1_Current = TIM_GetCapture1 (TIM3); TIM_SetCompare1 (TIM3, CCR1_Current + CCR1_Interval); TIM_ClearITPendingBit (TIM3, TIM_IT_CC1); } if (TIM_GetITStatus (TIM3, TIM_IT_CC2) != RESET) { GPIO_ToggleBits (ORANGE_LED); uint16_t CCR2_Current = TIM_GetCapture2 (TIM3); TIM_SetCompare2 (TIM3, CCR2_Current + CCR2_Interval); TIM_ClearITPendingBit (TIM3, TIM_IT_CC2); } } #ifdef __cplusplus } #endif void timer_ccr_init (void) { TIM_OCInitTypeDef TIM_OCInitStructure; /* always initialise local variables before use */ TIM_OCStructInit (&TIM_OCInitStructure); /* just use basic Output Compare Mode*/ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Active; /* set the initial match interval for CC1 */ TIM_OCInitStructure.TIM_Pulse = CCR1_Interval; TIM_OC1Init (TIM3, &TIM_OCInitStructure); /* and then for CC2 */ TIM_OCInitStructure.TIM_Pulse = CCR2_Interval; TIM_OC2Init (TIM3, &TIM_OCInitStructure); } // these are the LEDs on the STM32F4Discovery void board_leds_init (void) { GPIO_InitTypeDef GPIO_InitStructure; // always do this with an auto structure as it is undefined GPIO_StructInit (&GPIO_InitStructure); RCC_AHB1PeriphClockCmd (LED_PORT_CLOCK, ENABLE); GPIO_StructInit (&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init (LED_PORT, &GPIO_InitStructure); GPIO_ResetBits (LED_PORT, RED_PIN + GREEN_PIN + BLUE_PIN + ORANGE_PIN); } void flash_green_led_forever (void) { while (1) { GPIO_ToggleBits (GREEN_LED); delay_ms (500); } } int main (void) { CCR1_Interval = TIM3COUNTER_Frequency / 8; // blue LED CCR2_Interval = TIM3COUNTER_Frequency /1; // orange LED systickInit (1000); board_leds_init(); timer_clock_init(); timer_ccr_init(); timer_interrupt_init(); timer_interrupt_enable(); timer_start(); flash_green_led_forever(); return 0; // there is no going back but keep the compiler happy } |
Happy Coding
Pingback: TIM3 on the STM32 - an introduction - Micromouse Online
I want my timer start from 0 to 2^32-1(free run timer) and generate an interrupt on every 16ms and the timer shoudnt reset to 0. is it possible to generate an free run timer interrupt?
That is exactly what the example here does. The capture compare register just needs updating in each ISR.
Hi, How does the math work out for 4 toggles every second for the blue led? I tried calculating it by hand I got 4 toggles every half second
2560 Hertz = .3906 milliseconds
2560 Hertz / 8 = 320(CCR1_interval)
.3906 milliseconds * (320 *4) = roughly .5 second
You divide by 8 to get the 320 microseconds but then only multiply by four which will give you 0.5 seconds. Remember also that the LED is toggled each tick to the flash frequency will be half the toggle frequency.
Hello, I think to have some problem in undersanding the example. I was imagining that the LED toggle was handled directly by the comparison between the CNT and the CCRx registers configuring the CCMRx register in this way. Why do I nees an ISR that execute the GPIO_ToggleBits? Are the ports connected to the LED configured as simple GPIOs? Thank you very much in advance for your answer.
Thanks for looking. The idea was only to illustrate how code can be called in response to the output compare. If you want to connect an output pin directly to the OC event, that is a slightly different issue.
Hello, I am very confused why when I set the CCR1_Interval = TIM3COUNTER_Frequency, the led is on for 2 seconds and not 1 second. I thought when the CNT = CCR1, the led is toggled via the interrupt. So when CCR1_Interval = TIM3COUNTER_Frequency, the interrupt should be called every second to toggle the led right?
Does it have to do with this statement in the previous comment: “Remember also that the LED is toggled each tick to the flash frequency will be half the toggle frequency.” ? Could you elaborate this with an simple example?
The code given, on the STM32F4Discovery flashes the orange LED at 0.5Hz. If you are getting half that then I don’t really understand. Are you running on a different target? Perhaps I missed something about the way the clocks are set up. As for the toggle rate vs the flash rate. A full cycle has the LED on for half the time and off for half the time. So, if it changes state twice per second, the frequency is just 1Hz. That is exactly what the green LED should be doing.
Hi Peter,
I see now. I figured out the issue, it was that I wasn’t setting the prescaler variable correctly, I was using the System Clock which I have it set to 168MHz but if forgot the timers are located on a different bus, I think in mine case,since i have the System Clock max out to 168MHz, the TIM clock should run at 84MHz, once i used the correct value, everything worked as expected. thanks