Understanding the Output Compare Mode of STM32F407
- Encode AND Decode
- Aug 30, 2024
- 8 min read
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
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.
Capture/Compare Register (CCR): You set a specific value in the Capture/Compare Register. This value is compared with the current timer counter value.
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.
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
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:
Determine the Source of the Interrupt: Check if the interrupt was triggered by Channel 1 or Channel 2.
Clear the Interrupt Flag: Reset the flag to indicate that the interrupt has been handled.
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