• Home
  • Configuring Interrupts on STM32F446RE

Configuring Interrupts on STM32F446RE

Configuring Interrupts on STM32F446RE
  • Raja Gupta
  • April 27, 2025

In the previous article, we implemented a button-controlled LED toggle using a polling method. While simple and effective for basic projects, polling is inefficient for real-time or low-power embedded systems.

In this article, we will improve the design by configuring the button press to trigger an external interrupt (EXTI) instead of continuously polling the button pin. This approach frees up CPU resources and provides faster response times.

Hardware Overview

The STM32F446RE Nucleo-64 board provides an onboard LED and user button:

  • LED (LD2): Connected to PA5 (GPIOA, Pin 5)
  • User Button (B1): Connected to PC13 (GPIOC, Pin 13)

Electrical Behavior:

  • The LED is active-high. Setting PA5 high turns the LED ON.
  • The User Button is internally pulled high. Pressing the button pulls PC13 low.

Thus, when the button is pressed, it generates a falling edge on PC13, which we use to trigger the interrupt.

Project Overview

  • Microcontroller: STM32F446RE (Cortex-M4)
  • Board: Nucleo-64 MB1136
  • IDE: STM32CubeIDE
  • LED: Connected to PA5
  • Button: Connected to PC13

The objective is to toggle the LED immediately when the button is pressed, using an interrupt triggered on a falling edge.

Project Flow

The project is structured as follows:

  1. Initialize the HAL drivers.
  2. Configure the system clock to 84 MHz.
  3. Setup GPIOs:
    • Configure PA5 as output for LED.
    • Configure PC13 as input with external interrupt (falling edge detection).
  4. Enable the EXTI line interrupt in NVIC.
  5. Handle the interrupt and toggle the LED inside the user callback function.

Full Code

main.c

C

/*
 * main.c
 *
 *  Created on: Apr 24, 2025
 *      Author: erraj
 */

#include "stm32f4xx_hal.h"

void SystemClock_Config(void);
void GPIO_Config(void);
void Error_Handler(void);

int main(void) {

	/** Initialize HAL Driver*/
	HAL_Init();

	/** Configure System Clock */
	SystemClock_Config();

	/** Setup GPIO for LED */
	GPIO_Config();

	while (1) {
		// Main loop does nothing - handled by EXTI
	}

}

void GPIO_Config(void) {
	__HAL_RCC_GPIOA_CLK_ENABLE();     // LED (PA5)
	__HAL_RCC_GPIOC_CLK_ENABLE();     // Button (PC13)
	__HAL_RCC_SYSCFG_CLK_ENABLE();    // For EXTI line mapping

	GPIO_InitTypeDef GPIO_InitStruct = { 0 };

	// Configure PA5 (LED) as Output
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	// Configure PC13 (Button) as External Interrupt Input
	GPIO_InitStruct.Pin = GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

	// Enable EXTI Line 15 to 10 IRQ (covers PC13)
	HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0);
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

void SystemClock_Config(void) {
	RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
	RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

	/** Step 1: Enable Power Control clock */
	__HAL_RCC_PWR_CLK_ENABLE();

	/** Step 2: Set voltage scaling to match 84 MHz operation */
	__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

	/** Step 3: Configure HSI as PLL source and setup PLL to 84 MHz */
	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
	RCC_OscInitStruct.HSIState = RCC_HSI_ON;
	RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
	RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;

	// PLL Config for SYSCLK = 84 MHz (VCO = 336 MHz)
	RCC_OscInitStruct.PLL.PLLM = 16;      // HSI = 16 MHz → /16 = 1 MHz
	RCC_OscInitStruct.PLL.PLLN = 336;     // *336 = 336 MHz
	RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;  // /4 = 84 MHz (SYSCLK)
	RCC_OscInitStruct.PLL.PLLQ = 7;       // USB clock = 336 / 7 = 48 MHz ✅

	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
		Error_Handler(); // Add your error response here
	}

	/** Step 4: Select PLL as system clock and configure bus clocks */
	RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
			| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; // 42 MHz
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // 84 MHz

	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
		Error_Handler(); // Add your error response here
	}
}

// Callback function called by HAL when EXTI13 triggers
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == GPIO_PIN_13) {
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  // Toggle LED
	}
}

void Error_Handler(void) {
	// Simple error handler: blink LED rapidly or stay in infinite loop
	while (1) {
		// Optional: Toggle LED to indicate error
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
		HAL_Delay(100); // Fast blink indicates error
	}
}

stm32f4xx_it.c

C

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    stm32f4xx_it.c
  * @brief   Interrupt Service Routines.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */

/* USER CODE END TD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/

/* USER CODE BEGIN EV */

/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M4 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
  while (1)
  {
  }
  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles Hard fault interrupt.
  */
void HardFault_Handler(void)
{
  /* USER CODE BEGIN HardFault_IRQn 0 */

  /* USER CODE END HardFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_HardFault_IRQn 0 */
    /* USER CODE END W1_HardFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Pre-fetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles System service call via SWI instruction.
  */
void SVC_Handler(void)
{
  /* USER CODE BEGIN SVCall_IRQn 0 */

  /* USER CODE END SVCall_IRQn 0 */
  /* USER CODE BEGIN SVCall_IRQn 1 */

  /* USER CODE END SVCall_IRQn 1 */
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/**
  * @brief This function handles Pendable request for system service.
  */
void PendSV_Handler(void)
{
  /* USER CODE BEGIN PendSV_IRQn 0 */

  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
}

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************/

/* USER CODE BEGIN 1 */

/**
  * @brief This function handles EXTI line[15:10] interrupts.
  */
void EXTI15_10_IRQHandler(void)
{
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}


/* USER CODE END 1 */

Output

Code Explanation

main.c

The main function initializes the HAL drivers, configures the system clock, and setups the GPIO pins.
Unlike the polling method, the infinite loop remains empty because the event handling is now interrupt-driven.

C

int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Config();
    while (1) {
        // Main loop does nothing - everything handled by interrupt
    }
}

In the super loop (while(1)), there is no code for polling. The system will wait idle until an interrupt occurs.

GPIO Configuration

The GPIO_Config() function sets up the necessary hardware peripherals:

C

void GPIO_Config(void) {
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_SYSCFG_CLK_ENABLE();

    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // Configure PA5 as output
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // Configure PC13 as external interrupt input
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    // Enable interrupt for EXTI line 15 to 10
    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

Key points to note:

  • PA5 is set as a push-pull output for the LED.
  • PC13 is set to trigger an interrupt on a falling edge.
  • SYSCFG clock is enabled because EXTI configurations involve the system configuration controller.
  • The interrupt is configured in NVIC with a priority level of 2.

System Clock Configuration

The SystemClock_Config() function configures the system to run at 84 MHz using HSI and PLL.

C

void SystemClock_Config(void) {
    // System clock setup
}
  • The internal 16 MHz HSI clock is used as input to the PLL.
  • PLL settings are used to generate 84 MHz system clock.
  • APB1 is configured at 42 MHz and APB2 at 84 MHz.

Interrupt Handling

The file stm32f4xx_it.c handles all core exceptions and peripheral interrupts.

The important interrupt handler implemented is:

C

void EXTI15_10_IRQHandler(void) {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
  • When the button is pressed, the EXTI interrupt for line 13 is triggered.
  • HAL_GPIO_EXTI_IRQHandler() internally clears the interrupt flag and calls the user-defined callback HAL_GPIO_EXTI_Callback().

HAL GPIO EXTI Callback

The actual action (LED toggling) is implemented inside the user callback:

C

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_13) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    }
}
  • This function checks if the interrupt came from PC13.
  • If yes, it toggles the LED connected to PA5.

Error Handler

In case of a system fault or clock configuration failure, the Error_Handler toggles the LED rapidly to indicate a fault:

C

void Error_Handler(void) {
    while (1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        HAL_Delay(100);
    }
}

Summary of Pin Configuration

Peripheral Pin Mode Notes
LED (LD2) PA5 Output, Push-Pull Active High, toggled manually
Button (B1) PC13 Input, External Interrupt Falling edge triggers EXTI

Observations

Compared to polling, interrupt-driven designs offer several advantages:

  • The CPU is free to perform other operations while waiting for events.
  • Faster and deterministic reaction time.
  • Better power management by entering low-power modes between events.

This interrupt-driven design is the recommended approach for real-time and embedded system projects.

Conclusion

This article demonstrated how to implement external interrupt handling on the STM32F446RE microcontroller to toggle an LED on a button press.
Instead of constantly polling the button status, the system now reacts immediately and efficiently to user actions using external interrupts.

This lays the foundation for developing more responsive and energy-efficient embedded applications.

In future articles, we will explore techniques like software debouncing to make button handling more reliable in real-world conditions.

Leave a Reply

Your email address will not be published. Required fields are marked *