The Embedded New Testament

The "Holy Bible" for embedded engineers


Project maintained by theEmbeddedGeorge Hosted on GitHub Pages — Theme by mattgraham

⏱️ Timer/Counter Programming

Mastering Timer and Counter Operations for Embedded Systems
Input capture, output compare, frequency measurement, and timing applications


📋 Table of Contents


🎯 Overview

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.


🚀 Quick Reference: Key Facts


🔍 Visual Understanding

Timer Block Diagram

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

Timer Modes Visualization

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 vs Output Compare

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

🧠 Conceptual Foundation

The Timer as a Time Foundation

Timers serve as the fundamental building blocks for all time-related operations in embedded systems. They provide:

Why Timer Programming Matters

Timer programming is critical because:

The Timer Design Challenge

Designing timer systems involves balancing several competing concerns:


🎯 Core Concepts

Concept: Timer Configuration and Frequency Calculation

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

Concept: Input Capture for Precise Timing Measurement

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

Concept: Output Compare for Precise Event Generation

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

Concept: Timer Interrupts and DMA Integration

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


🧪 Guided Labs

Lab 1: Basic Timer Configuration and Interrupts

Objective: Configure a timer to generate periodic interrupts and measure timing accuracy.

Steps:

  1. Configure a timer for 1kHz operation
  2. Enable timer interrupts
  3. Toggle GPIO pin in interrupt handler
  4. Measure timing accuracy with oscilloscope

Expected Outcome: Understanding of timer configuration and interrupt timing.

Lab 2: Input Capture for Frequency Measurement

Objective: Use input capture to measure the frequency of an external signal.

Steps:

  1. Configure timer channel for input capture
  2. Generate test signal with function generator
  3. Implement frequency calculation algorithm
  4. Compare measured vs. actual frequency

Expected Outcome: Practical experience with input capture and frequency measurement.

Lab 3: Output Compare for PWM Generation

Objective: Generate PWM signals with variable duty cycle using output compare.

Steps:

  1. Configure timer for PWM mode
  2. Implement duty cycle control
  3. Generate different PWM frequencies
  4. Measure PWM characteristics with oscilloscope

Expected Outcome: Understanding of PWM generation and output compare operation.


Check Yourself

Basic Understanding

Practical Application

Advanced Concepts



🎯 Practical Considerations

System-Level Design Decisions

Performance and Optimization

Debugging and Testing


📚 Additional Resources

Documentation

Tools

Books


Next Topic: Watchdog TimersInterrupts and Exceptions