观前提醒:本文以外部中断点灯为例,分析了HAL库中中断服务相关函数的使用及相互关联、底层原理
一、实例实现
实例简介:主函数控制LED2进行闪烁,中断控制LED0开关
我们先使用CubeMX创建项目文件:
1、在System Core中配置外部时钟,这里视板子的不同而改变,我这里只有外部高速晶振,所以开启外部高速晶振。
2、配置时钟树,如图:
3、配置GPIO,原理图如下:
我们配置PC2为上升沿检测中断模式,因为PC2引脚在电路上已经上拉,所以配置时选择无上下拉。PC0与PC14配置为推挽输出模式。这里对PC2命名为KEY,PC0与PC14命名为LED0与LED2
4、在SYS中开启SW调试
5、项目设置
6、生成项目文件
7、编写回调函数及main函数:
回调函数:
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函数部分:
while (1)
{
ms_Delay(200);
HAL_GPIO_TogglePin(GPIOC, LED2_Pin);
}
下附完整main.c代码
/* 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--);
}
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);
}
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
ms_Delay(200);
HAL_GPIO_TogglePin(GPIOC, LED2_Pin);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 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.Prediv1Source = RCC_PREDIV1_SOURCE_HSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
RCC_OscInitStruct.PLL2.PLL2State = 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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
Error_Handler();
}
/** Configure the Systick interrupt time
*/
__HAL_RCC_PLLI2S_ENABLE();
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @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 /* USE_FULL_ASSERT */
二、NVIC函数分析
温馨提示:阅读以下部分之前请完成NVIC及EXIT等前置任务
HAL_NVIC_SetPriority()
下面的一段代码初始化了PC2引脚,并对PC2的中断功能进行了初始化,
其中:HAL_GPIO_Init实现了GPIO的初始化,包括将KEY_Pin与EXTI2“连接”
HAL_NVIC_SetPriority()实现了对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()定义:
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;
声明一个prioritygroup变量,并初始化为0x00。 -
assert_param(IS_NVIC_SUB_PRIORITY(SubPriority));
使用断言(assert)来检查SubPriority参数的有效性。该宏IS_NVIC_SUB_PRIORITY用于检查SubPriority是否在有效范围内,如果不在有效范围内,将触发断言。 -
assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority));
使用断言(assert)来检查PreemptPriority参数的有效性。该宏IS_NVIC_PREEMPTION_PRIORITY用于检查PreemptPriority是否在有效范围内,如果不在有效范围内,将触发断言。 -
prioritygroup = NVIC_GetPriorityGrouping();
调用NVIC_GetPriorityGrouping函数获取当前的优先级分组设置,并将结果保存在prioritygroup变量中。 -
NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
调用NVIC_SetPriority函数来设置指定中断的优先级。IRQn参数表示要设置优先级的中断,NVIC_EncodePriority函数用于将优先级分组、抢占优先级和子优先级编码为优先级值,然后将编码后的优先级值传递给NVIC_SetPriority函数。承接上述分析,我们对其中所使用的函数继续进行深挖
NVIC_GetPriorityGrouping()
查看此函数的定义我们可以看到:
#define NVIC_GetPriorityGrouping __NVIC_GetPriorityGrouping
在C语言中,以双下划线开头的标识符是保留给编译器和库使用的。此标识符用于实现内部功能或提供特定的功能接口。在代码中,__NVIC_GetPriorityGrouping 函数的双下划线前缀表明这是一个内部函数。套一层宏定义可以方便识读
继续跳转定义:
__STATIC_INLINE uint32_t __NVIC_GetPriorityGrouping(void)
{
return ((uint32_t)((SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) >> SCB_AIRCR_PRIGROUP_Pos));
}
这段代码是一个内联函数(__NVIC_GetPriorityGrouping),用于获取NVIC(Nested Vectored Interrupt Controller)的优先级分组配置
该函数通过读取SCB_AIRCR寄存器的值,并使用位操作来提取优先级分组的配置值。具体步骤如下:
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk):使用位与操作(&)将SCB_AIRCR寄存器的值与SCB_AIRCR_PRIGROUP_Msk进行按位与操作,以屏蔽除了优先级分组位以外的其他位。
SCB_AIRCR_PRIGROUP_Msk是一个掩码,用于提取优先级分组位。
>> SCB_AIRCR_PRIGROUP_Pos:使用右移操作(>>)将结果向右移动到正确的位置,以便得到最终的优先级分组配置值。
return ((uint32_t)…):返回优先级分组配置值。
NVIC_SetPriority()
同样的,这里也套了一层宏定义
#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:判断中断号是否大于等于0,即判断中断号是否为设备特定中断。如果中断号为设备特定中断,则优先级设置在 NVIC->IP 寄存器中;否则,优先级设置在 SCB->SHP 寄存器中。
-
NVIC->IP[((uint32_t)IRQn)]:如果中断号为设备特定中断,将优先级值按位左移 (8U - __NVIC_PRIO_BITS) 个位置,并与掩码 (uint32_t)0xFFUL 进行按位与操作,以确保优先级值在8位范围内。然后将结果存储在 NVIC->IP 寄存器的相应位置上。
-
SCB->SHP[(((uint32_t)IRQn) & 0xFUL) - 4UL]:如果中断号不是设备特定中断,首先通过 (((uint32_t)IRQn) & 0xFUL) - 4UL 来计算在 SCB->SHP 寄存器中的位置。然后将优先级值按位左移 (8U - __NVIC_PRIO_BITS) 个位置,并与掩码 (uint32_t)0xFFUL 进行按位与操作,以确保优先级值在8位范围内。最后将结果存储在 SCB->SHP 寄存器的相应位置上。
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)))
);
}
此函数用于编码中断优先级。
该函数接受三个参数:PriorityGroup
、PreemptPriority
和 SubPriority
,用于指定中断的优先级信息。
函数主要实现了将优先级信息编码为一个32位的值,以便在设置中断优先级时使用。
具体步骤如下:
-
PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL)
:提取PriorityGroup
参数的低3位,即优先级分组的值(0-7)。 -
PreemptPriorityBits
的计算:根据优先级分组的值,计算可用于抢占优先级的位数。如果(7UL - PriorityGroupTmp)
大于__NVIC_PRIO_BITS
(定义的宏),则将PreemptPriorityBits
设置为__NVIC_PRIO_BITS
;否则,设置为(7UL - PriorityGroupTmp)
。 -
SubPriorityBits
的计算:根据优先级分组的值,计算可用于子优先级的位数。如果(PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS))
小于7UL
,则将SubPriorityBits
设置为0UL
;否则,设置为(PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS)
。 -
返回编码后的优先级值,通过以下操作完成:
((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL)) << SubPriorityBits)
:将抢占优先级按位与上适当的掩码,并将结果左移SubPriorityBits
位。((SubPriority & (uint32_t)((1UL << (SubPriorityBits)) - 1UL)))
:将子优先级按位与上适当的掩码。
该函数的目的是根据优先级分组的配置,将用户提供的抢占优先级和子优先级编码为一个32位的值,以便在设置中断优先级时可以直接使用。具体的掩码和位操作根据优先级分组的值进行计算,以确保编码后的优先级值在正确的位范围内。
评论(0)
您还未登录,请登录后发表或查看评论