External interrupts are a powerful feature of microcontrollers that allow you to respond to external events (e.g., button presses, sensor triggers) without constantly polling the input pins. This tutorial demonstrates how to configure and use external interrupts on the STM32 Nucleo-64 board using the STM32 HAL library. By the end of this tutorial, you will be able to toggle an LED connected to PB1
whenever a button connected to PC8
is pressed.
This is the third part of the tutorial series STM32 Programming Tutorial with Proteus and STM32Cube. The previous tutorials are below:
Hardware Setup
For this tutorial, we will use the following hardware configuration:
- STM32 Nucleo-64 Board (e.g., STM32F401RE).
- LED connected to pin
PB1
. - Push Button connected to pin
PC8
.
The button is configured as an input with a pull-up resistor, and the LED is configured as an output. When the button is pressed, an interrupt is triggered, and the LED toggles its state. See the circuit diagram for testing external interrupt with STM32 board along with push button and LED.
- STM32CubeIDE : For generating initialization code and writing the application logic.
- STM32 HAL Library : Provides APIs for configuring peripherals like GPIO and interrupts.
- Proteus Professional(Download Proteus Professional v8.17 SP2 Free)
- STM32 Nucleo-64 simulation part (Download STM32 Nucleo Simulation Model)
Step-by-Step Guide
Step 1: Configure the Project in STM32CubeIDE
Create a New Project :
- Open STM32CubeIDE and create a new project for your STM32 Nucleo-64 board (e.g., STM32F401RE).
Configure the System Clock :
- In the Pinout & Configuration tab, set the system clock to use the internal HSI oscillator (default configuration).
Configure GPIO Pins :
- Set
PC8
as an input pin with an external interrupt (GPIO_MODE_IT_FALLING
) and enable the pull-up resistor (GPIO_PULLUP
). - Set
PB1
as an output pin (GPIO_MODE_OUTPUT_PP
).
- Set
Enable NVIC Interrupts :
- Go to the NVIC Settings tab and enable the interrupt for
EXTI line[9:5]
. Set the priority as needed (e.g., Preemption Priority = 0, Sub Priority = 0).
- Go to the NVIC Settings tab and enable the interrupt for
Generate Code :
- Click Project > Generate Code to generate the initialization code.
Step 2: Write the Application Logic
The generated code provides the basic structure for initializing the peripherals. Add the following logic to handle external interrupts and toggle the LED:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* Interrupt Handler for EXTI Line[9:5] */
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(BTN_Pin);
}
/* External interrupt callback function */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
static uint32_t last_interrupt_time = 0;
uint32_t current_time = HAL_GetTick();
if (GPIO_Pin == BTN_Pin)
{
if ((current_time - last_interrupt_time) > 200)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
last_interrupt_time = current_time;
}
}
}
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* Infinite loop */
while (1)
{
// Main loop does nothing; everything is handled by the interrupt
}
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE(); // Enable clock for PC8 (button)
__HAL_RCC_GPIOB_CLK_ENABLE(); // Enable clock for PB1 (LED)
/* Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
/* Configure GPIO pin : LED_Pin (PB1 - LED) */
GPIO_InitStruct.Pin = LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
/* Configure GPIO pin : BTN_Pin (PC8 - Button) */
GPIO_InitStruct.Pin = BTN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // Interrupt on falling edge
GPIO_InitStruct.Pull = GPIO_PULLUP; // Pull-up resistor enabled
HAL_GPIO_Init(BTN_GPIO_Port, &GPIO_InitStruct);
/* Enable and set EXTI line interrupt priority */
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); // Set priority for EXTI line 9-5 (PC8)
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // Enable EXTI interrupt
}
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif /* USE_FULL_ASSERT */
Step 3: Explanation of the Code
Interrupt Handler (
EXTI9_5_IRQHandler
) :- This function is automatically called when an interrupt occurs on EXTI line 9-5 (which includes
PC8
). - It calls the HAL library's
HAL_GPIO_EXTI_IRQHandler()
function to process the interrupt.
- This function is automatically called when an interrupt occurs on EXTI line 9-5 (which includes
Callback Function (
HAL_GPIO_EXTI_Callback
) :- The HAL library calls this function after processing the interrupt.
- It checks if the interrupt was triggered by the button (
BTN_Pin
) and toggles the LED (LED_Pin
). - A simple debounce mechanism is implemented using
HAL_GetTick()
to ignore rapid consecutive interrupts.
GPIO Initialization (
MX_GPIO_Init
) :- Configures
PC8
as an input with a pull-up resistor and enables falling-edge interrupts. - Configures
PB1
as an output for the LED.
- Configures
Main Loop :
- The main loop is empty because all functionality is handled by the interrupt.
Step 4: Build and Test
Build the Project :
- In STM32CubeIDE, click Project > Build All to compile the code.
Flash the Firmware :
- Connect the STM32 Nucleo-64 board to your computer and flash the firmware using the built-in ST-LINK debugger.
Test the Setup :
- Press the button connected to
PC8
. The LED connected toPB1
should toggle its state each time the button is pressed.
- Press the button connected to
Key Notes
Debouncing :
- Mechanical buttons often suffer from bouncing, which can cause multiple interrupts for a single press. The debounce mechanism in the callback function ensures that only one interrupt is processed within a 200ms window.
Interrupt Priorities :
- You can adjust the interrupt priority in
HAL_NVIC_SetPriority()
based on your application's requirements.
- You can adjust the interrupt priority in
Error Handling :
- The
Error_Handler()
function disables interrupts and enters an infinite loop if an error occurs during clock or peripheral initialization.
- The
Video demonstration
The following demonstrates how the external interrupt is used with a button to turn on/off a LED. The simulation was done in proteus. You can download the STM32 nucleo proteus part for free.
Conclusion
This tutorial demonstrated how to use external interrupts on the STM32 Nucleo-64 board with the HAL library. By configuring the GPIO pins and handling interrupts through the HAL_GPIO_EXTI_Callback()
function, you can efficiently respond to external events like button presses. This approach is highly scalable and can be extended to other applications, such as interfacing sensors such as PIR sensor, IR sensor etc or implementing real-time event-driven systems.