These STM32F4 Basics posts aren't really tutorials so much as they are slightly-organized notes with short and contrived examples. Many aspects and nuances are ignored. Look through the documentation and headers referenced throughout this post to fill in the gaps. The Timer chapters in RM0090 would be a good place to start.
A timer can be used in several different ways:
- As a timebase: a counter that can periodically run a block of code.
- As a counter that tracks how often a pin has transitioned high or low.
- For pulse-width modulation (output or input.)
- ...
There are several clock sources, multiplexers, prescalers and clocks that come into play. The clock tree (RM0090, Section 5.2, p.85) should be kept in mind, and double-checked when things don't work as expected. The system_stm32f4xx.c file is used to setup this stuff.
Remember that IRQs will not go into effect until they are enabled in the NVIC. ISR names must match the names defined in startup_stm32f4xx.s.
There are 14 timers with varying abilities. (DM00037051, Table 3, p.29)
Timebases
The simplest timer starts at zero, counts up to some chosen number, then generates an update event. That event can be used to call an ISR. After that event the counter can start over again or stop. The rate at which the counter counts depends on its clock source. All timers can use the internal timer clock shown in the clock tree, and most timers can use other sources covered later on.
If the MCU is setup with a 168MHz system clock, an AHB prescaler of 1, and an APB prescaler of 4, the internal timer clock will run at 84MHz. A 16-bit prescaler is often used to slow down the timer. With a prescaler of 42000 there will be 2000 "ticks" per second -- two ticks per millisecond. The timer will count up to a 16-bit or 32-bit number called the "auto-reload" value, then generate an update event and reset the counter. With an auto-reload value of 2, and a prescaler of 42000, an update event will occur once per millisecond. The formula is:
Example:
// Illuminate the blue LED (D15) three seconds after power-on.
#include "stm32f4xx.h"
#include "core_cm4.h"
int main() {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; // Enable GPIOD clock
GPIOD->MODER |= GPIO_MODER_MODER15_0; // Enable output mode for D15
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // Enable TIM6 clock
TIM6->PSC = 41999; // Set prescaler to 41999
TIM6->ARR = 5999; // Set auto-reload to 5999
TIM6->CR1 |= TIM_CR1_OPM; // One pulse mode
TIM6->EGR |= TIM_EGR_UG; // Force update
TIM6->SR &= ~TIM_SR_UIF; // Clear the update flag
TIM6->DIER |= TIM_DIER_UIE; // Enable interrupt on update event
NVIC_EnableIRQ(TIM6_DAC_IRQn); // Enable TIM6 IRQ
TIM6->CR1 |= TIM_CR1_CEN; // Enable TIM6 counter
while(1) {
}
}
void TIM6_DAC_IRQHandler() {
if(TIM6->SR & TIM_SR_UIF != 0) // If update flag is set
GPIOD->BSRRL = GPIO_BSRR_BS_15; // Set D15 high
TIM6->SR &= ~TIM_SR_UIF; // Interrupt has been handled
}
Externally-Clocked Counters
You can use a GPIO pin as the clock source instead of clocking the counter with some fraction of the system clock. The signal on that pin does not even need to be periodic. A clock pulse can be interpreted from each rising edge on that pin. The timer functions that a GPIO pin can accommodate vary from pin to pin. (DM00037051, Table 6, p.44) There are two external clock modes: external input and external trigger. The main differences are that the external trigger mode can have a prescaler, and there are fewer pins that can be used in that mode.
External Input Example:
// Count how often the blue pushbutton is pressed (how often A0 transitions high.)
// The count is stored in TIM5->TIM_CNT, examine it in GDB with "print *(unsigned long*) 0x40000C24"
// Unfortunately the pushbutton is not debounced in hardware and the software filters are not enough,
// so the count can increment by more than one each time you press the button.
#include "stm32f4xx.h"
int main() {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock
GPIOA->MODER |= GPIO_MODER_MODER0_1; // Enable AF mode for A0
GPIOA->AFR[0] |= 0b0010; // Select AF2 for A0 (TIM3/4/5)
RCC->APB1ENR |= RCC_APB1ENR_TIM5EN; // Enable TIM5 clock
TIM5->CCMR1 |= TIM_CCMR1_CC1S_0; // Input mode, map TI1 to IC1
TIM5->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); // CC1P and CC1NP = 0 for rising edge
TIM5->SMCR |= TIM_SMCR_SMS; // External clock mode 1
TIM5->SMCR |= TIM_SMCR_TS_0 | TIM_SMCR_TS_2; // Trigger selection: TI1
TIM5->CR1 |= TIM_CR1_CEN; // Enable TIM5 counter
while(1) {
}
}
External Trigger Example:
// Count every fourth press of the blue pushbutton (pin A0 transitioning high.)
// The count is stored in TIM2->TIM_CNT, examine it in GDB with "print *(unsigned long*) 0x40000024"
// Unfortunately the pushbutton is not debounced in hardware and the software filters are not enough,
// so the count will not be exact.
#include "stm32f4xx.h"
int main() {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock
GPIOA->MODER |= GPIO_MODER_MODER0_1; // Enable AF mode for A0
GPIOA->AFR[0] |= 0b0001; // Select AF1 for A0 (TIM1/2)
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Enable TIM2 clock
TIM2->SMCR |= TIM_SMCR_ETPS_1; // Prescaler = 4
TIM2->SMCR &= ~TIM_SMCR_ETP; // Trigger on rising edge
TIM2->SMCR |= TIM_SMCR_ECE; // External clock mode 2
TIM2->CR1 |= TIM_CR1_CEN; // Enable TIM2 counter
while(1) {
}
}
The rest will be covered in Part 2.