重新整理一下笔记。
来自CPU外部的外设产生的紧急事件叫做中断,比如GPIO引脚电平变化;来自CPU内部产生的紧急事件叫做异常。 异常和中断的效果基本一致,都是暂停当前任务,优先执行紧急事件,因此一般将中断和异常统称为中断
中断优先级(16个可编程的优先等级 4位中断优先级 2的4次方=16)
stm32 有20个外部中断线,其中常用的有16个外部中断线
常用的16个外部中断线
配置过程如下:
以按键外部中断控制灯和蜂鸣器为例,在STM32cubemx 创建工程配置RCC选择外部晶振
修改时钟频率
配置GPIO 这里只放了按键的配置(外部中断)
配置输入模式 上升沿触发 上拉输入
使能中断向量
保存工程 。工程创建完毕
在keil 中打开工程。代码分析如下:
gpio.c文件中的 GPIO 初始化函数 使能GPIOA、GPIOB时钟
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
E2、E3引脚为按键外部中断
配置引脚触发模式 上升沿触发
上拉输入
LED、Beep 为普通GPIO
配置为推挽输出模式
没有和上拉和下拉
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(Beep_GPIO_Port, Beep_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : PE2 PE3 */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;//上升沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP;//上拉输入
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/*Configure GPIO pins : PBPin PBPin */
GPIO_InitStruct.Pin = LED_Pin|Beep_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;//推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;//没有上拉和下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
配置NVIC对应的中断线
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI2_IRQn, 4, 0);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);//使能中断线
HAL_NVIC_SetPriority(EXTI3_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);//使能中断线
}
/**
* @brief Sets the priority of an interrupt.
* @param IRQn: External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xx.h))
* @param PreemptPriority: The preemption priority for the IRQn channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority
* @param SubPriority: the subpriority level for the IRQ channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority.
* @retval None
*/
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
//参数1 中断号
//参数2 抢占优先级
//参数3 子优先级
在stm32f1xx hal_gpio.c 文件HAL_GPIO_Init()函数中,进行配置外部中断,这里初始化了GPIO的输入输出模式的选择、外部中断的配置(使能了AFIO复用时钟)。
/*--------------------- EXTI Mode Configuration ------------------------*/
/* Configure the External Interrupt or event for the current IO */
if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
{
/* Enable AFIO Clock */
__HAL_RCC_AFIO_CLK_ENABLE();
temp = AFIO->EXTICR[position >> 2u];
CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
AFIO->EXTICR[position >> 2u] = temp;
/* Enable or disable the rising trigger */
if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
{
SET_BIT(EXTI->RTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->RTSR, iocurrent);
}
/* Enable or disable the falling trigger */
if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
{
SET_BIT(EXTI->FTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->FTSR, iocurrent);
}
/* Configure the event mask */
if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
{
SET_BIT(EXTI->EMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->EMR, iocurrent);
}
/* Configure the interrupt mask */
if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
{
SET_BIT(EXTI->IMR, iocurrent);//使能外部中断
}
else
{
CLEAR_BIT(EXTI->IMR, iocurrent);
}
}
}
在stm32f1xx_it.c 中生成中断触发函数,但在HAL库中我们并不能在这里面进行相关中断处理,在HAL库有一个callback回调函数。
回调函数是一种常见的编程技术,它可以在异步操作完成后调用一个预定义的函数来处理结果。回调函数通常用于处理事件、执行异步操作或响应用户输入等场景。回调函数的作用是将代码逻辑分离出来,使得代码更加模块化和可维护。使用回调函数可以避免阻塞程序的运行,提高程序的性能和效率。另外,回调函数还可以实现代码的复用,因为它们可以被多个地方调用。
/**
* @brief This function handles EXTI line2 interrupt.
*/
void EXTI2_IRQHandler(void)
{
/* USER CODE BEGIN EXTI2_IRQn 0 */
/* USER CODE END EXTI2_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
/* USER CODE BEGIN EXTI2_IRQn 1 */
/* USER CODE END EXTI2_IRQn 1 */
}
/**
* @brief This function handles EXTI line3 interrupt.
*/
void EXTI3_IRQHandler(void)
{
/* USER CODE BEGIN EXTI3_IRQn 0 */
/* USER CODE END EXTI3_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
/* USER CODE BEGIN EXTI3_IRQn 1 */
/* USER CODE END EXTI3_IRQn 1 */
}
打开HAL_GPIO_EXTI_IRQHandler函数的定义,在这里我们发现这个函数调用了HAL_GPIO_EXTI_Callback()函数,而这个函数前面有一个_weak弱声明。 用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数。那么我们需要在用户文件(main.c)中,自己再定义一个一模一样的函数,只是我们自己定义的函数,不需要指明是弱函数。
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
所以在main.c中,我们直接复制这个回调函数,在这里我们进行中断处理。
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_2)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
if(GPIO_Pin == GPIO_PIN_3 )
{
HAL_GPIO_TogglePin(Beep_GPIO_Port, Beep_Pin);
}
}
/* USER CODE END 4 */
外部中断相关寄存器
这些寄存器(除了PR寄存器)都在HAL_GPIO_Init()函数中进行配置,EXTI_PR寄存器在
/**
* @brief This function handles EXTI interrupt request.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
函数中配置,打开__HAL_GPIO_EXTI_GET_IT()发现这是宏。所以在这个函数中进行外部中断标志进行检查、检查是否有边沿事件,若有系统会把PR寄存器置零,需要我们清除标志,所以,置1清除标志,等待下一个边沿事件。这里我们可以对比51单片机的串口中断的RI和TI标志位的检测和清除。
/**
* @brief Checks whether the specified EXTI line is asserted or not.
* @param __EXTI_LINE__: specifies the EXTI line to check.
* This parameter can be GPIO_PIN_x where x can be(0..15)
* @retval The new state of __EXTI_LINE__ (SET or RESET).
*/
#define __HAL_GPIO_EXTI_GET_IT(__EXTI_LINE__) (EXTI->PR & (__EXTI_LINE__))
/**
* @brief Clears the EXTI's line pending bits.
* @param __EXTI_LINE__: specifies the EXTI lines to clear.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15)
* @retval None
*/
#define __HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__) (EXTI->PR = (__EXTI_LINE__))
总结
在这里我虽然使用了HAL库进行学习,但是HAL库与标准库本质上都是通过配置寄存器来是单片机工作的,只是它们的API不同。配置普通GPIO需要使能时钟、配置引脚的输入输出模式,同样外部中断也是通过GPIO,但是需要我们复用为外部中断,所以还要使能复用时钟AFIO。