top of page

Understanding the Output Compare Mode of STM32F407

Updated: Aug 31, 2024




What is Output Compare Mode?


Output Compare mode is a feature of the STM32F407’s timers that allows you to control a pin based on the timer's counter value. Here’s the basic idea: as the timer counts up (or down), it compares the current count with a value you set. When the timer’s counter matches your value, it triggers an action on the associated GPIO pin—like toggling it, setting it high, or clearing it low.


 

How Output Compare Mode Works


  1. Timer Counter (CNT): The core of Output Compare mode is the timer counter, which increments (or decrements) with each clock pulse, based on the timer's configuration.


  2. Capture/Compare Register (CCR): You set a specific value in the Capture/Compare Register. This value is compared with the current timer counter value.


  3. Comparison Event: When the counter reaches the value stored in the CCR, an event is triggered. This event can be configured to perform actions like toggling a GPIO pin, setting it high, or clearing it low.


  4. Repetition: The process repeats as the counter continues counting, allowing for periodic actions at precise intervals.


 

Block Diagram




 

Objective of this Lesson


In this lesson, we aim to demonstrate how to use the Output Compare mode of Timer 4 on the STM32F407 microcontroller to toggle two LEDs connected to PD12 and PD13. The LED on PD12 will blink every 100 milliseconds, while the LED on PD13 will blink every 1000 milliseconds. We’ll configure Timer 4 in Output Compare Mode to toggle the LED.


 

Setting Up Timer 4 and GPIO Pins for Output Compare Mode


In this section, we'll walk through the process of configuring Timer 4 and the GPIO pins on the STM32F407 to achieve the desired LED toggling. We’ll cover the steps to enable the necessary system clocks, configure the GPIO pins (PD12 and PD13) for alternate function mode, and set up Timer 4 to use Output Compare mode. This will include adjusting the timer’s prescaler and auto-reload values, setting the compare register values, and enabling the timer interrupts to manage the LED toggling at precise intervals.

Detailed Steps:


i. Enable System Clocks


Before configuring Timer 4 and the GPIO pins, we need to ensure that their corresponding clocks are enabled.


  • GPIO Port Clock: Enable the clock for GPIO port D, which controls PD12 and PD13.

// Enable clock access to GPIOD peripheral
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
  • This line of code sets the corresponding bit (bit 3 ) in the RCC_AHB1ENR register to turn on the clock for the GPIO port D (GPIOD).


  • Timer Clock: Enable the clock for Timer 4 to allow it to generate the necessary timing signals.

// Enable clock access to Timer 4 peripheral
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;

This line of code sets the corresponding bit (bit 2) in the RCC_APB1ENR register to turn on the clock for Timer 4.


ii. Configure GPIO Pins for Timer 4


To use PD12 and PD13 with Timer 4, we need to configure them for Alternate Function mode. Here's how this works:


Alternate Function Mode


Alternate Function Mode allows GPIO pins to be connected to various peripheral functions instead of their default I/O functions. For Timer 4, we need to set PD12 and PD13 to connect to Timer 4’s Output Compare channels.


  • Alternate Function 2 (AF2): Timer 4’s channels are mapped to Alternate Function 2. We need to configure PD12 and PD13 to use this function so that they can be controlled by Timer 4.


  • Set GPIO Pins to Alternate Function Mode

    To configure PD12 and PD13 for Timer 4, we first need to set these pins to Alternate Function mode in the GPIO port configuration:

// Configure PD12 and PD13 as Alternate Function mode 
GPIOD->MODER &= ~(GPIO_MODER_MODE12 | GPIO_MODER_MODE13);
GPIOD->MODER |= (GPIO_MODER_MODE12_1 | GPIO_MODER_MODE13_1);

MODE12 and MODE13 (bits 24-25 and 26-27 in GPIOD->MODER register) control the mode of PD12 and PD13. Setting these to Alternate Function mode involves clearing the existing bits and setting them to the mode value 01 (Alternate Function mode).


Assign Timer 4 to the Pins


  1. Next, we need to assign Timer 4 to PD12 and PD13 by configuring the Alternate Function registers:

  // Set PD12 and PD13 to AF2 (Alternate Function 2) for TIM4
  GPIOD->AFR[1] &= ~(GPIO_AFRH_AFSEL12 | GPIO_AFRH_AFSEL13);
  GPIOD->AFR[1] |= GPIO_AFRH_AFSEL12_1 | GPIO_AFRH_AFSEL13_1;

AFR[1]: The Alternate Function Register for high-numbered pins (PD12 and PD13).

AFSEL12 and AFSEL13: These bits select the alternate function for PD12 and PD13.


By setting PD12 and PD13 to Alternate Function mode and assigning Timer 4 to them, the pins are now ready to work with Timer 4’s Output Compare channels.


iii. Configure Timer 4


With PD12 and PD13 properly configured, the next step is to set up Timer 4. This involves adjusting various settings to achieve the desired timing intervals for toggling the LEDs.


Set Timer Prescaler

Configure the timer’s prescaler to divide the system clock frequency. This adjustment determines the timer’s base clock frequency.

// Set Timer 4 prescaler to 15999 to achieve 1 kHz timer clock with a 16 MHz system clock 
TIM4->PSC = 16000 - 1;

The prescaler divides the system clock (16 MHz) by 16000, resulting in a timer clock of 1 kHz. Subtracting 1 from the prescaler value is required as the prescaler register adds 1 to the input value.


Configure Auto-Reload Register

Set the auto-reload register to define the timer period. This determines how long the timer counts before resetting.

// Set Timer 4 auto-reload register to 999 for a 1-second interval at 1 kHz 
TIM4->ARR = 1000 - 1;

The auto-reload register value of 999 means the timer will count from 0 to 999, creating a period of 1 second (at 1 kHz frequency). Subtracting 1 is necessary as the ARR register counts from 0.


Set Compare Register Values

Configure the compare register values to determine when the timer should toggle the LEDs.

// Configure Channel 1 (PD12) to toggle mode

//set toggle mode for Channel 1
TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0;

// Enable output for Channel 1
TIM4->CCER |= TIM_CCER_CC1E;

// Initial compare value for Channel 1 
TIM4->CCR1 = 0; 

// Configure Channel 2 (PD13) to toggle mode

// Set toggle mode for Channel 2
TIM4->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_0;

// Enable output for Channel 2
TIM4->CCER |= TIM_CCER_CC2E;

// Initial compare value for Channel 2
TIM4->CCR2 = 0;

Channel 1 (PD12): Set to toggle mode by configuring the OC1M bits in TIM4->CCMR1. Enable the output by setting CC1E in TIM4->CCER. The initial compare value is set to 0, meaning the output toggles immediately.

Channel 2 (PD13): Similarly configured for toggle mode and output enabled. The compare value is also set to 0.


Enable Timer Outputs

Activate the output channels of Timer 4 to control PD12 and PD13 based on the compare register values.

// Enable Timer 4 outputs 
TIM4->CCER |= TIM_CCER_CC1E | TIM_CCER_CC2E;

This line ensures that the outputs for both channels are enabled, allowing Timer 4 to control PD12 and PD13.


Enable Timer Interrupts

Set up interrupts to handle Timer 4 events. This allows you to manage the LED toggling dynamically.

// Enable interrupts for both channels 
TIM4->DIER |= TIM_DIER_CC1IE | TIM_DIER_CC2IE; 

// Enable Timer 4 interrupt in NVIC 
NVIC_EnableIRQ(TIM4_IRQn);

DIER Register: Enables interrupts for both compare channels (CC1IE and CC2IE).

NVIC: Configures the Nested Vectored Interrupt Controller to handle Timer 4 interrupts.


Start Timer 4

Finally, start Timer 4 by enabling its counter.

// Clear Timer 4 counter register
 TIM4->CNT = 0;

// Start Timer 4 by enabling the counter 
TIM4->CR1 |= TIM_CR1_CEN;

Reset the timer counter and start counting by setting the CEN bit in the TIM4->CR1 register.

This setup ensures Timer 4 is correctly configured to toggle LEDs connected to PD12 and PD13 at the specified intervals.


iv. Handle Timer Interrupts


To ensure Timer 4 performs the desired operations, we need to handle its interrupts using an Interrupt Service Routine (ISR). This routine will be executed automatically whenever Timer 4 reaches the compare values specified in its registers.


In the ISR, we will:

  1. Determine the Source of the Interrupt: Check if the interrupt was triggered by Channel 1 or Channel 2.


  2. Clear the Interrupt Flag: Reset the flag to indicate that the interrupt has been handled.


  3. Update Compare Register Values: Adjust the compare values to control how often the LEDs toggle.

void TIM4_IRQHandler(void)
{
  // Check if the interrupt was triggered by Channel 1
  if (TIM4->SR & TIM_SR_CC1IF)
  {
    // Clear the interrupt flag for Channel 1
    TIM4->SR &= ~TIM_SR_CC1IF;

    // Update the compare value for Channel 1 (PD12)
    // Increase pulse width and wrap around at 1000
    pulse_CH1 = (pulse_CH1 + 1000) % 1000;  
    
// Set the new compare value for Channel 1
    TIM4->CCR1 = pulse_CH1;  
  }


  // Check if the interrupt was triggered by Channel 2
  if (TIM4->SR & TIM_SR_CC2IF)
  {
    // Clear the interrupt flag for Channel 2
    TIM4->SR &= ~TIM_SR_CC2IF;

    // Update the compare value for Channel 2 (PD13)
    // Increase pulse width and wrap around at 1000
    pulse_CH2 = (pulse_CH2 + 100) % 1000;  

    // Set the new compare value for Channel 2
    TIM4->CCR2 = pulse_CH2;  
  }
}

Interrupt Source: The ISR checks which channel triggered the interrupt by looking at the status register (TIM4->SR). If TIM_SR_CC1IF is set, it means Channel 1 caused the interrupt. If TIM_SR_CC2IF is set, it means Channel 2 caused it.


Clearing the Interrupt Flag: The ISR clears the corresponding interrupt flag to acknowledge that the interrupt has been handled. This prevents the ISR from being triggered repeatedly for the same event.


Updating Compare Register Values: The ISR then updates the compare register values (CCR1 and CCR2). By incrementing these values, we adjust the timing of the LED toggles. The pulse width is wrapped around using the modulo operation to keep the value within a range.


 

Source Code


#include "stm32f407xx.h"

// Function prototypes
void GPIO_Init(void);
void Clock_Init(void);
void Timer4_Init(void);
void TIM4_IRQHandler(void);

// Global variables to store pulse values for Timer Channels
volatile uint16_t pulse_CH1 = 0;  // Pulse width for Channel 1 (PD12)
volatile uint16_t pulse_CH2 = 0;  // Pulse width for Channel 2 (PD13)

int main(void)
{
  Clock_Init();  // Initialize the system clocks
  GPIO_Init();   // Configure GPIO pins
  Timer4_Init(); // Configure Timer 4 for PWM generation
  
  while(1)
  {
    // Main loop is empty; all functionality is handled in the timer interrupt
  }  
}

void Clock_Init(void)
{ 
  // Enable clock access to GPIOD peripheral
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
      
  // Enable clock access to Timer 4 peripheral
  RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
}

void GPIO_Init(void)
{
  // Configure PD12 and PD13 as Alternate Function mode (AF2 for TIM4_CH1 and TIM4_CH2)
  GPIOD->MODER &= ~(GPIO_MODER_MODE12 | GPIO_MODER_MODE13); // Clear mode bits for PD12 and PD13
  GPIOD->MODER |= (GPIO_MODER_MODE12_1 | GPIO_MODER_MODE13_1); // Set PD12 and PD13 to Alternate Function mode
  
  // Set PD12 and PD13 to AF2 (Alternate Function 2) for TIM4
  GPIOD->AFR[1] &= ~(GPIO_AFRH_AFSEL12 | GPIO_AFRH_AFSEL13); // Clear AF bits for PD12 and PD13
  GPIOD->AFR[1] |= GPIO_AFRH_AFSEL12_1 | GPIO_AFRH_AFSEL13_1; // Set AF2 (Alternate Function 2) for PD12 and PD13
}

void Timer4_Init(void)
{
  // Set Timer 4 prescaler to 15999 to achieve 1 kHz timer clock with a 16 MHz system clock
  TIM4->PSC = 16000 - 1;
  
  // Set Timer 4 auto-reload register to 999 for a 1-second interval at 1 kHz
  TIM4->ARR = 1000 - 1;

  // Configure Channel 1 (PD12) to toggle mode
  TIM4->CCMR1 |= TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0; // Set toggle mode for Channel 1
  TIM4->CCER |= TIM_CCER_CC1E; // Enable output for Channel 1
  TIM4->CCR1 = 0; // Initial compare value for Channel 1

  // Configure Channel 2 (PD13) to toggle mode
  TIM4->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_0; // Set toggle mode for Channel 2
  TIM4->CCER |= TIM_CCER_CC2E; // Enable output for Channel 2
  TIM4->CCR2 = 0; // Initial compare value for Channel 2
  
  // Enable interrupts for both channels
  TIM4->DIER |= TIM_DIER_CC1IE | TIM_DIER_CC2IE;

  // Clear Timer 4 counter register
  TIM4->CNT = 0;
  
  // Start Timer 4 by enabling the counter
  TIM4->CR1 |= TIM_CR1_CEN;

  // Enable Timer 4 interrupt in NVIC
  NVIC_EnableIRQ(TIM4_IRQn);
}

void TIM4_IRQHandler(void)
{
  // Check if interrupt is due to Channel 1 compare match
  if (TIM4->SR & TIM_SR_CC1IF)
  {
    // Clear the Channel 1 compare match interrupt flag
    TIM4->SR &= ~TIM_SR_CC1IF;
    
    // Update the CCR1 register with a new pulse width value for Channel 1
    // Increment pulse width by 100 and wrap around at 1000
	pulse_CH1 = (pulse_CH1 + 1000) % 1000; 
    
    // Set new compare value for Channel 1 
    TIM4->CCR1 = pulse_CH1;  
  }
  
  // Check if interrupt is due to Channel 2 compare match
  if (TIM4->SR & TIM_SR_CC2IF)
  {
    // Clear the Channel 2 compare match interrupt flag
    TIM4->SR &= ~TIM_SR_CC2IF;
    
    // Update the CCR2 register with a new pulse width value for Channel 2
   // Increment pulse width by 50 and wrap around at 1000    
   pulse_CH2 = (pulse_CH2 + 100) % 1000;  

   // Set new compare value for Channel 2
   TIM4->CCR2 = pulse_CH2;  
  }
}

 

Why Use Output Compare?


Output Compare mode is incredibly useful when you need precise control over timing events. Whether you’re generating PWM signals for motor control, creating a timed interrupt, or just need to blink an LED at a precise interval, OC mode makes it straightforward.


 

Comments


bottom of page