Reminder: This article takes external interrupt for lighting as an example, analyzing the usage of interrupt service related functions in the HAL library, their interconnections, and underlying principles.
1. Example Implementation
Example Overview: The main function controls LED2 to blink, while the interrupt controls the switch of LED0:
First, we use CubeMX to create the project file:
1. Configure the external clock in System Core. This varies depending on the board; I only have an external high-speed crystal oscillator, so I enable the external high-speed crystal oscillator.

2. Configure the clock tree, as shown:

3. Configure GPIO, the schematic is as follows:

We configure PC2 for rising edge detection interrupt mode, since the PC2 pin is already pulled up in the circuit, we select no pull-up or pull-down during configuration. PC0 and PC14 are configured as push-pull output mode. Here, PC2 is named KEY, while PC0 and PC14 are named LED0 and LED2.

4. Enable SW debugging in SYS.

5. Project settings.


6. Generate project files.

7. Write the callback function and the main function:
Callback function:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ ms_Delay(50); if(GPIO_Pin == KEY_Pin) { if(HAL_GPIO_ReadPin(GPIOE, KEY_Pin)==0) { HAL_GPIO_TogglePin(GPIOC, LED0_Pin); } }}
Main function part:
while (1) { ms_Delay(200); HAL_GPIO_TogglePin(GPIOC, LED2_Pin); }
Below is the complete main.c code:
/* USER CODE BEGIN Header *//** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2024 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 "gpio.h"/* Private includes ----------------------------------------------------------*//* USER CODE BEGIN Includes *//* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*//* USER CODE BEGIN PTD *//* USER CODE END PTD *//* 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 -----------------------------------------------*/void SystemClock_Config(void);/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*//* USER CODE BEGIN 0 */void ms_Delay(uint16_t t_ms){ uint32_t t=t_ms*3127; while(t--);}/** * @brief This function is executed in case of error occurrence. * @retval None */void Error_Handler(void){ /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */}#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){ /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */}#endif /
2. NVIC Function Analysis
Friendly Reminder: Please complete prerequisites like NVIC and EXIT before reading the following sections.
HAL_NVIC_SetPriority()
The following code initializes the PC2 pin and initializes the interrupt functionality for PC2,
where: HAL_GPIO_Init implements GPIO initialization, including “connecting” KEY_Pin with EXTI2.
HAL_NVIC_SetPriority() implements the priority setting for EXTI2.
/*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = KEY_Pin; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(KEY_GPIO_Port, &GPIO_InitStruct);/* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI2_IRQn);
HAL_NVIC_SetPriority() definition:
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority){ uint32_t prioritygroup = 0x00U; /* Check the parameters */ assert_param(IS_NVIC_SUB_PRIORITY(SubPriority)); assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority)); prioritygroup = NVIC_GetPriorityGrouping(); NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));}
uint32_t prioritygroup = 0x00U;
Declares a prioritygroup variable and initializes it to 0x00.
assert_param(IS_NVIC_SUB_PRIORITY(SubPriority)); uses assertions to check the validity of the SubPriority parameter. This macro IS_NVIC_SUB_PRIORITY checks whether SubPriority is within a valid range; if not, it will trigger an assertion.
assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority)); uses assertions to check the validity of the PreemptPriority parameter. This macro IS_NVIC_PREEMPTION_PRIORITY checks whether PreemptPriority is within a valid range; if not, it will trigger an assertion.
prioritygroup = NVIC_GetPriorityGrouping(); calls the NVIC_GetPriorityGrouping function to get the current priority grouping setting and stores the result in the prioritygroup variable.
NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
Calls the NVIC_SetPriority function to set the priority of the specified interrupt. The IRQn parameter indicates the interrupt for which the priority is to be set, and the NVIC_EncodePriority function is used to encode the priority group, preemptive priority, and sub-priority into a priority value, which is then passed to the NVIC_SetPriority function.
Continuing from the above analysis, we further explore the functions used.
NVIC_GetPriorityGrouping()
Looking at the definition of this function, we can see:
#define NVIC_GetPriorityGrouping __NVIC_GetPriorityGrouping
In C language, identifiers starting with double underscores are reserved for compiler and library use. This identifier is used to implement internal functions or provide specific functional interfaces. In the code, the __NVIC_GetPriorityGrouping function’s double underscore prefix indicates that this is an internal function. Wrapping it in a macro definition can facilitate readability and further definition jumps:
__STATIC_INLINE uint32_t __NVIC_GetPriorityGrouping(void){ return ((uint32_t)((SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) >> SCB_AIRCR_PRIGROUP_Pos));}
This code is an inline function (__NVIC_GetPriorityGrouping), used to obtain the priority grouping configuration of the NVIC (Nested Vectored Interrupt Controller).
The function retrieves the value of the SCB_AIRCR register and uses bit manipulation to extract the configuration value of the priority grouping. The specific steps are as follows:
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk): Uses a bitwise AND operation (&) to mask out all bits except the priority grouping bits from the value of the SCB_AIRCR register.
SCB_AIRCR_PRIGROUP_Msk is a mask used to extract the priority grouping bits.
>> SCB_AIRCR_PRIGROUP_Pos: Uses a right shift operation (>>) to move the result to the correct position to obtain the final priority grouping configuration value.
return ((uint32_t)…): Returns the priority grouping configuration value.
NVIC_SetPriority()
Similarly, this also wraps a macro definition.
#define NVIC_SetPriority __NVIC_SetPriority
__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority){ if ((int32_t)(IRQn) >= 0) { NVIC->IP[((uint32_t)IRQn)] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL); } else { SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL); }}
(int32_t)(IRQn) >= 0: Determines if the interrupt number is greater than or equal to 0, i.e., checks if the interrupt number is a device-specific interrupt. If the interrupt number is a device-specific interrupt, the priority is set in the NVIC->IP register; otherwise, the priority is set in the SCB->SHP register.
NVIC->IP[((uint32_t)IRQn)]: If the interrupt number is a device-specific interrupt, the priority value is left-shifted by (8U – __NVIC_PRIO_BITS) positions and ANDed with the mask (uint32_t)0xFFUL to ensure the priority value is within the 8-bit range. The result is then stored in the corresponding position of the NVIC->IP register.
SCB->SHP[(((uint32_t)IRQn) & 0xFUL) – 4UL]: If the interrupt number is not a device-specific interrupt, the position in the SCB->SHP register is calculated using (((uint32_t)IRQn) & 0xFUL) – 4UL. The priority value is then left-shifted by (8U – __NVIC_PRIO_BITS) positions and ANDed with the mask (uint32_t)0xFFUL to ensure the priority value is within the 8-bit range. Finally, the result is stored in the corresponding position of the SCB->SHP register.
NVIC_EncodePriority()
_STATIC_INLINE uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority){ uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL); /* only values 0..7 are used */ uint32_t PreemptPriorityBits; uint32_t SubPriorityBits; PreemptPriorityBits = ((7UL - PriorityGroupTmp) > (uint32_t)(__NVIC_PRIO_BITS)) ? (uint32_t)(__NVIC_PRIO_BITS) : (uint32_t)(7UL - PriorityGroupTmp); SubPriorityBits = ((PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) < (uint32_t)7UL) ? (uint32_t)0UL : (uint32_t)((PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS)); return ( ((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL)) << SubPriorityBits) | ((SubPriority & (uint32_t)((1UL << (SubPriorityBits )) - 1UL))) );}
This function is used to encode interrupt priorities.
The function accepts three parameters: PriorityGroup, PreemptPriority, and SubPriority, which specify the interrupt priority information.
The function primarily encodes the priority information into a 32-bit value for use when setting interrupt priorities.
The specific steps are as follows:
1. PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL): Extracts the low 3 bits of the PriorityGroup parameter, i.e., the value of the priority group (0-7).
2. Calculation of PreemptPriorityBits: Based on the value of the priority group, calculates the number of bits available for preemptive priority. If (7UL – PriorityGroupTmp) is greater than __NVIC_PRIO_BITS (defined macro), PreemptPriorityBits is set to __NVIC_PRIO_BITS; otherwise, it is set to (7UL – PriorityGroupTmp).
3. Calculation of SubPriorityBits: Based on the value of the priority group, calculates the number of bits available for sub-priority. If (PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) is less than 7UL, SubPriorityBits is set to 0UL; otherwise, it is set to (PriorityGroupTmp – 7UL) + (uint32_t)(__NVIC_PRIO_BITS).
4. Returns the encoded priority value, completed by the following operations:
((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) – 1UL)) << SubPriorityBits): Left shifts the preemptive priority by SubPriorityBits positions.
((SubPriority & (uint32_t)((1UL << (SubPriorityBits)) – 1UL))): ANDs the sub-priority with the appropriate mask.
The purpose of this function is to encode the user-provided preemptive priority and sub-priority into a 32-bit value based on the priority group configuration, so that the encoded priority value can be directly used when setting interrupt priorities. The specific masks and bit manipulations are calculated based on the value of the priority group to ensure that the encoded priority value is within the correct bit range.
Click Read Original to purchase the OriginCar smart robot kit.