The "Holy Bible" for embedded engineers
Mastering Timer and Counter Operations for Embedded Systems
Input capture, output compare, frequency measurement, and timing applications
Timers and counters are essential peripherals in embedded systems for precise timing, frequency measurement, PWM generation, and event counting. Understanding timer programming is crucial for real-time applications.
Clock Source → Prescaler → Counter → Compare/Output → Interrupt/DMA
↓ ↓ ↓ ↓ ↓
System Clock Divide by Count Up/ Generate Trigger
(84 MHz) PSC+1 Down/ PWM/Events CPU Events
Center
Up-Counting: 0 → 1 → 2 → ... → ARR → 0 → 1 → ...
Down-Counting: ARR → ARR-1 → ... → 1 → 0 → ARR → ...
Center-Aligned: 0 → 1 → ... → ARR → ARR-1 → ... → 1 → 0 → ...
Input Capture: External Event → Capture Timer Value → Calculate Timing
↓ ↓ ↓
GPIO Edge Store Count Frequency/Pulse
in Register Width
Output Compare: Timer Count → Compare with CCR → Generate Output Event
↓ ↓ ↓
Increment Match Value PWM/Interrupt
Counter (CCR Register) Generation
Timers serve as the fundamental building blocks for all time-related operations in embedded systems. They provide:
Timer programming is critical because:
Designing timer systems involves balancing several competing concerns:
Why it matters: Proper timer configuration ensures accurate timing and prevents overflow errors. Understanding the relationship between clock frequency, prescaler, and period is essential for achieving desired timing resolution and range.
Minimal example
// Basic timer configuration structure
typedef struct {
uint32_t prescaler; // Timer prescaler value
uint32_t period; // Timer period (ARR value)
uint32_t clock_freq; // Timer clock frequency
uint32_t mode; // Timer mode (UP, DOWN, CENTER)
} timer_config_t;
// Calculate timer frequency
uint32_t calculate_timer_frequency(uint32_t clock_freq, uint32_t prescaler, uint32_t period) {
return clock_freq / ((prescaler + 1) * (period + 1));
}
// Calculate timer period for target frequency
uint32_t calculate_timer_period(uint32_t clock_freq, uint32_t prescaler, uint32_t target_freq) {
return (clock_freq / ((prescaler + 1) * target_freq)) - 1;
}
Try it: Calculate the prescaler and period values needed for a 1kHz timer using an 84MHz clock.
Takeaways
Why it matters: Input capture enables precise measurement of external events, making it essential for frequency measurement, pulse width analysis, and event timing in embedded systems.
Minimal example
// Input capture configuration
typedef struct {
uint32_t channel; // Timer channel (1-4)
uint32_t edge; // Rising/Falling edge
uint32_t filter; // Input filter value
bool interrupt_enable; // Enable capture interrupt
} input_capture_config_t;
// Configure input capture
void configure_input_capture(TIM_HandleTypeDef* htim, input_capture_config_t* config) {
// Configure channel as input
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = config->edge;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = config->filter;
HAL_TIM_IC_ConfigChannel(htim, &sConfigIC, config->channel);
// Enable interrupt if requested
if (config->interrupt_enable) {
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1 << (config->channel - 1));
}
}
Try it: Implement input capture to measure the frequency of an external signal.
Takeaways
Why it matters: Output compare enables precise generation of timing events, PWM signals, and periodic outputs. It’s fundamental for motor control, audio generation, and timing synchronization.
Minimal example
// Output compare configuration
typedef struct {
uint32_t channel; // Timer channel (1-4)
uint32_t compare_value; // Compare value (CCR)
uint32_t mode; // Output mode (Toggle, PWM, Force)
bool interrupt_enable; // Enable compare interrupt
} output_compare_config_t;
// Configure output compare
void configure_output_compare(TIM_HandleTypeDef* htim, output_compare_config_t* config) {
// Configure channel as output
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = config->mode;
sConfigOC.Pulse = config->compare_value;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_OC_ConfigChannel(htim, &sConfigOC, config->channel);
// Enable interrupt if requested
if (config->interrupt_enable) {
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1 << (config->channel - 1));
}
}
Try it: Generate a 1kHz square wave with 50% duty cycle using output compare.
Takeaways
Why it matters: Timer interrupts and DMA integration enable efficient handling of timing events and high-speed data transfer, reducing CPU overhead and improving system performance.
Minimal example
// Timer interrupt configuration
void configure_timer_interrupt(TIM_HandleTypeDef* htim, uint32_t priority) {
// Enable timer update interrupt
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
// Configure NVIC priority
HAL_NVIC_SetPriority(TIM2_IRQn, priority, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
// Timer interrupt handler
void TIM2_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET) {
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
// Handle timer event
handle_timer_event();
}
}
}
Try it: Implement a timer interrupt that toggles an LED every 500ms.
Takeaways
Objective: Configure a timer to generate periodic interrupts and measure timing accuracy.
Steps:
Expected Outcome: Understanding of timer configuration and interrupt timing.
Objective: Use input capture to measure the frequency of an external signal.
Steps:
Expected Outcome: Practical experience with input capture and frequency measurement.
Objective: Generate PWM signals with variable duty cycle using output compare.
Steps:
Expected Outcome: Understanding of PWM generation and output compare operation.
Next Topic: Watchdog Timers → Interrupts and Exceptions