External Interrupts with STM32 Nucleo-64 Board with HAL Library | STM32 Programming Tutorial 3

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.

External Interrupts with STM32 Nucleo-64 Board
Software Tools

Step-by-Step Guide

Step 1: Configure the Project in STM32CubeIDE

  1. Create a New Project :

    • Open STM32CubeIDE and create a new project for your STM32 Nucleo-64 board (e.g., STM32F401RE).
  2. Configure the System Clock :

    • In the Pinout & Configuration tab, set the system clock to use the internal HSI oscillator (default configuration).
  3. 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).
  4. 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).
  5. 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

  1. 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.
  2. 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.
  3. 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.
  4. Main Loop :

    • The main loop is empty because all functionality is handled by the interrupt.

Step 4: Build and Test

  1. Build the Project :

    • In STM32CubeIDE, click Project > Build All to compile the code.
  2. Flash the Firmware :

    • Connect the STM32 Nucleo-64 board to your computer and flash the firmware using the built-in ST-LINK debugger.
  3. Test the Setup :

    • Press the button connected to PC8. The LED connected to PB1 should toggle its state each time the button is pressed.

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.
  • Error Handling :

    • The Error_Handler() function disables interrupts and enters an infinite loop if an error occurs during clock or peripheral initialization.

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.

Post a Comment

Previous Post Next Post