STM32F103 中断详解

一、简介

中断,如其名,就是打断CPU正在执行的代码,转而去执行另外一个代码,结束后再返回执行之前的代码。举个例子,你正在看这个博客,突然感觉有点口渴,然后起身倒了杯水喝,完了继续看博客。这里的口渴就可以视为一个中断信号,起身喝水就是中断处理函数。整个流程就是一次中断。

从上面这个例子可以看出,我们的中断需要一个中断信号来触发,还需要一个中断处理函数来处理这个中断事件。下面就以自底向上从GPIO发出中断信号到CPU执行中断处理函数这个过程,来描述中断的配置与流程。

二、中断流程

先放一张中断的流程图。中断流程从上图可见,GPIO发出中断信号,到CPU执行中断处理,中间还要经过AFIO、EXTI和NVIC。GPIO相关细节内容可以看看这篇博客

2.1 GPIO

首先,按下一个接地的按键(低电平有效),GPIO输入一个低电平,为了能够以下降沿触发中断,在初始化这个按键时,我们要将gpio的模式设置为GPIO_MODE_IT_FALLING。

GPIO_InitTypeDef gpio_init_struct = {0};
gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;  /* 下降沿触发 */
gpio_init_struct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct);    /* KEY0配置为下降沿触发中断 */

结构体GPIO_InitTypeDef的Mode支持多种模式,外部中断用到的有三种模式。在stm32f1xx_hal_gpio.h中有具体的定义,代码如下

#define GPIO_MODE_IT_RISING (0x10110000u) /* 外部中断,上升沿触发检测 */
#define GPIO_MODE_IT_FALLING (0x10210000u) /* 外部中断,下降沿触发检测 */
#define GPIO_MODE_IT_RISING_FALLING (0x10310000u) /* 外部中断,上升和下降双沿触发检测 */

根据配置不同的模式,中断会以不同的电平来触发。

2.2 AFIO

了解AFIO前,我们需要知道通用与复用的概念。通用是指IO端口的输入输出是由GPIO的外设控制,也就是由BSRR、ODR和IDR寄存器控制。复用指的是IO端口的输入输出是由其他非GPIO外设控制,比如由USART、TIM、ADC等控制。简言之,当我们将IO端口作为普通的输入输出使用时就是通用,当我们将IO端口作为串口、ADC等复杂的功能使用时,就是复用。而如何使用复用功能,就是AFIO提供的功能了。
在STM32微控制器中,AFIO(Alternate Function Input/Output,复用功能输入输出)负责管理引脚功能的灵活配置,有复用功能配置、引脚重映射(Remap)、‌**外部中断(EXTI)配置**和调试端口管理。
这里我们用到的就是外部中断(EXTI)配置,通过AFIO将GPIO与EXTI进行一一映射。使用到的寄存器是AFIO_EXTICR1~AFIO_EXTICR4。

AFIO_EXTICR1
上图就是AFIO_EXTICR1寄存器的配置。比如像低四位EXTI0[3:0]写入0x0000,就是将IO引脚PA0映射到EXTI0。
AFIO的配置不需要我们去实现,只需要我们设置好GPIO的模式,在初始化GPIO时调用的HAL_GPIO_Init()函数会帮我们进行配置。

2.3 EXTI

EXTI 是外部中断和事件控制器,它是由 20 个产生事件/中断请求的边沿检测器组成。这20个边沿检测器即20条中断线,对应EXTI0~EXTI19。
其中EXTI0~EXTI15对应GPIO_PIN 0-15的中断。总共有16个中断线,每个AFIO_EXTICRx寄存器配置4个中断线,所以用到了4个AFIO_EXTICRx寄存器。下面来看看EXTI是怎么与GPIO对应的。
GPIO_EXTI
从上图可以看出PA[x]-PG[x]映射到了EXTIx,具体的映射方法在AFIO中已经描述,通过4个AFIO_EXTICRx寄存器即可。
将具体的GPIO映射到EXTI后,EXTI还要对中断信号进行处理,来判断哪些信号需要挂起屏蔽等。这里用到了多个寄存器。
下面是EXTI功能图
EXTI
这里共有6个寄存器来处理中断信号。
下降沿触发选择寄存器(EXTI_FTSR)
FTSR

中断屏蔽寄存器(EXTI_IMR)
IMR
挂起寄存器(EXTI_PR)
PR
这里列出了三个寄存器,可以看出,这些寄存器的原理基本类似,都是根据20根中断线来输出0或1,另外三个寄存器同理。中断信号经过EXTI的层层处理后,将信号输出给NVIC。

2.4 NVIC

NVIC(Nested vectored interrupt controller)嵌套向量中断控制器,负责管理所有的中断。Cortex-M3内核支持256个中断,而STM32F103仅使用了部分,其提供了10个系统中断,60个外部中断。这些中断可以在启动文件startup_stm32f103xe.s中的中断向量表中查看。每一个中断处理函数(xxx_Handler)就对应着一个中断。如下所示:

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
				......
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; 外部中断
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                DCD     EXTI2_IRQHandler           ; EXTI Line 2
                DCD     EXTI3_IRQHandler           ; EXTI Line 3
                DCD     EXTI4_IRQHandler           ; EXTI Line 4
                ......                
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

我们主要用到NVIC对中断优先级的设定功能。先来了解一下中断优先级。stm32的中断优先级分为三种。

  1. 抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
  2. 响应优先级:也叫子优先级。抢占优先级相同,响应优先级高的中断先执行,但不能打断响应优先级低的中断。
  3. 自然优先级:中断向量表中的优先级,当抢占优先级和响应优先级都相同时,自然优先级高的先执行。

我们可以在stm32f103xe.h文件中的枚举IRQn_Type看到自然优先级的中断号。如下所示。

 /*!< Interrupt Number Definition */
typedef enum
{
/******  Cortex-M3 Processor Exceptions Numbers ***************************************************/
  NonMaskableInt_IRQn         = -14,    /*!< 2 Non Maskable Interrupt                             */
  HardFault_IRQn              = -13,    /*!< 3 Cortex-M3 Hard Fault Interrupt                     */
  MemoryManagement_IRQn       = -12,    /*!< 4 Cortex-M3 Memory Management Interrupt              */
  BusFault_IRQn               = -11,    /*!< 5 Cortex-M3 Bus Fault Interrupt                      */
  UsageFault_IRQn             = -10,    /*!< 6 Cortex-M3 Usage Fault Interrupt                    */
  SVCall_IRQn                 = -5,     /*!< 11 Cortex-M3 SV Call Interrupt                       */
  DebugMonitor_IRQn           = -4,     /*!< 12 Cortex-M3 Debug Monitor Interrupt                 */
  PendSV_IRQn                 = -2,     /*!< 14 Cortex-M3 Pend SV Interrupt                       */
  SysTick_IRQn                = -1,     /*!< 15 Cortex-M3 System Tick Interrupt                   */

/******  STM32 specific Interrupt Numbers *********************************************************/
  WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt                            */
  PVD_IRQn                    = 1,      /*!< PVD through EXTI Line detection Interrupt            */
  TAMPER_IRQn                 = 2,      /*!< Tamper Interrupt                                     */
  RTC_IRQn                    = 3,      /*!< RTC global Interrupt                                 */
  FLASH_IRQn                  = 4,      /*!< FLASH global Interrupt                               */
  RCC_IRQn                    = 5,      /*!< RCC global Interrupt                                 */
  EXTI0_IRQn                  = 6,      /*!< EXTI Line0 Interrupt                                 */
  EXTI1_IRQn                  = 7,      /*!< EXTI Line1 Interrupt                                 */
  EXTI2_IRQn                  = 8,      /*!< EXTI Line2 Interrupt                                 */
  EXTI3_IRQn                  = 9,      /*!< EXTI Line3 Interrupt                                 */
  EXTI4_IRQn                  = 10,     /*!< EXTI Line4 Interrupt                                 */
  DMA1_Channel1_IRQn          = 11,     /*!< DMA1 Channel 1 global Interrupt                      */
  DMA1_Channel2_IRQn          = 12,     /*!< DMA1 Channel 2 global Interrupt                      */
  DMA1_Channel3_IRQn          = 13,     /*!< DMA1 Channel 3 global Interrupt                      */
  DMA1_Channel4_IRQn          = 14,     /*!< DMA1 Channel 4 global Interrupt                      */
  DMA1_Channel5_IRQn          = 15,     /*!< DMA1 Channel 5 global Interrupt                      */
  DMA1_Channel6_IRQn          = 16,     /*!< DMA1 Channel 6 global Interrupt                      */
  DMA1_Channel7_IRQn          = 17,     /*!< DMA1 Channel 7 global Interrupt                      */
  ADC1_2_IRQn                 = 18,     /*!< ADC1 and ADC2 global Interrupt                       */
  USB_HP_CAN1_TX_IRQn         = 19,     /*!< USB Device High Priority or CAN1 TX Interrupts       */
  USB_LP_CAN1_RX0_IRQn        = 20,     /*!< USB Device Low Priority or CAN1 RX0 Interrupts       */
  CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt                                   */
  CAN1_SCE_IRQn               = 22,     /*!< CAN1 SCE Interrupt                                   */
  EXTI9_5_IRQn                = 23,     /*!< External Line[9:5] Interrupts                        */
  TIM1_BRK_IRQn               = 24,     /*!< TIM1 Break Interrupt                                 */
  TIM1_UP_IRQn                = 25,     /*!< TIM1 Update Interrupt                                */
  TIM1_TRG_COM_IRQn           = 26,     /*!< TIM1 Trigger and Commutation Interrupt               */
  TIM1_CC_IRQn                = 27,     /*!< TIM1 Capture Compare Interrupt                       */
  TIM2_IRQn                   = 28,     /*!< TIM2 global Interrupt                                */
  TIM3_IRQn                   = 29,     /*!< TIM3 global Interrupt                                */
  TIM4_IRQn                   = 30,     /*!< TIM4 global Interrupt                                */
  I2C1_EV_IRQn                = 31,     /*!< I2C1 Event Interrupt                                 */
  I2C1_ER_IRQn                = 32,     /*!< I2C1 Error Interrupt                                 */
  I2C2_EV_IRQn                = 33,     /*!< I2C2 Event Interrupt                                 */
  I2C2_ER_IRQn                = 34,     /*!< I2C2 Error Interrupt                                 */
  SPI1_IRQn                   = 35,     /*!< SPI1 global Interrupt                                */
  SPI2_IRQn                   = 36,     /*!< SPI2 global Interrupt                                */
  USART1_IRQn                 = 37,     /*!< USART1 global Interrupt                              */
  USART2_IRQn                 = 38,     /*!< USART2 global Interrupt                              */
  USART3_IRQn                 = 39,     /*!< USART3 global Interrupt                              */
  EXTI15_10_IRQn              = 40,     /*!< External Line[15:10] Interrupts                      */
  RTC_Alarm_IRQn              = 41,     /*!< RTC Alarm through EXTI Line Interrupt                */
  USBWakeUp_IRQn              = 42,     /*!< USB Device WakeUp from suspend through EXTI Line Interrupt */
  TIM8_BRK_IRQn               = 43,     /*!< TIM8 Break Interrupt                                 */
  TIM8_UP_IRQn                = 44,     /*!< TIM8 Update Interrupt                                */
  TIM8_TRG_COM_IRQn           = 45,     /*!< TIM8 Trigger and Commutation Interrupt               */
  TIM8_CC_IRQn                = 46,     /*!< TIM8 Capture Compare Interrupt                       */
  ADC3_IRQn                   = 47,     /*!< ADC3 global Interrupt                                */
  FSMC_IRQn                   = 48,     /*!< FSMC global Interrupt                                */
  SDIO_IRQn                   = 49,     /*!< SDIO global Interrupt                                */
  TIM5_IRQn                   = 50,     /*!< TIM5 global Interrupt                                */
  SPI3_IRQn                   = 51,     /*!< SPI3 global Interrupt                                */
  UART4_IRQn                  = 52,     /*!< UART4 global Interrupt                               */
  UART5_IRQn                  = 53,     /*!< UART5 global Interrupt                               */
  TIM6_IRQn                   = 54,     /*!< TIM6 global Interrupt                                */
  TIM7_IRQn                   = 55,     /*!< TIM7 global Interrupt                                */
  DMA2_Channel1_IRQn          = 56,     /*!< DMA2 Channel 1 global Interrupt                      */
  DMA2_Channel2_IRQn          = 57,     /*!< DMA2 Channel 2 global Interrupt                      */
  DMA2_Channel3_IRQn          = 58,     /*!< DMA2 Channel 3 global Interrupt                      */
  DMA2_Channel4_5_IRQn        = 59,     /*!< DMA2 Channel 4 and Channel 5 global Interrupt        */
} IRQn_Type;

要注意的是,中断号越小,中断优先级越高。
仔细对比中断向量表和IRQn_Type中断号会发现我们的复位中断处理貌似没有设置中断号,这是因为它属于处理器内核的异常(Exception),而非普通的外设中断,它的优先级是最高的,触发后会直接执行启动文件startup_stm32f103xe.s中的Reset_Handler,由Reset_Handler去调用main(),而不会触发中断服务函数(ISR)‌,因此‌无需配置中断号。
知道了中断优先级,接下来就轮到中断优先级分组了。STM32F103 将中断分为 5 个组,组 0~4。
该分组的设置是由 SCB->AIRCR 寄存器的 bit10~8 来定义的,如下所示。
中断分组
设置中断优先级分组是通过调用HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)函数来实现的,这一步在HAL_Init()函数中已经调用过了,所以我们在使用中断的时候不需要再次调用。需要注意的是,如果多次调用HAL_NVIC_SetPriorityGrouping()函数,会导致中断优先级的混乱,所以只能设置一次中断优先级分组。HAL_Init()函数中的调用如下:

/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

HAL库将优先级分组设置为2位抢占优先级,2 位响应优先级。也就是说,抢占优先级有0-3,响应优先级也有0-3。
在设置完优先级分组后,就可以在GPIO初始化的时候,对中断优先级进行设置,之后再使能中断。代码如下:

HAL_NVIC_SetPriority(EXTI4_IRQn, 0, 2);        /* 抢占0,子优先级2 */
HAL_NVIC_EnableIRQ(EXTI4_IRQn);                /* 使能中断线4 */

在我们使能中断后,一但触发中断,CPU就会去执行中断号对应的中断处理函数,比如这里的EXTI4_IRQHandler()。
中断处理函数EXTI4_IRQHandler()需要我们自己实现,代码如下:

void EXTI4_IRQHandler(void){
    HAL_GPIO_EXTI_IRQHandler(KEY_GPIO_PIN);         /* 调用中断处理公用函数,清除KEY所在中断线的中断标志位 */
    __HAL_GPIO_EXTI_CLEAR_IT(KEY_GPIO_PIN);         /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}

可以看到,这里的参数是KEY_GPIO_PIN,这样我们的GPIO就与中断处理函数联系了起来。
在HAL库中,会由HAL_GPIO_EXTI_IRQHandler()函数调用__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)()函数。

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_Callback()是一个虚函数,一般中断处理函数的具体实现逻辑我们会在这个函数中实现,比如根据不同的GPIO_Pin选择是点灯还是响蜂鸣器。这里使用到的是回调函数的编程方法。当然,你也可以直接在实现EXTI4_IRQHandler()的时候,直接实现相应的逻辑,而不去调用HAL_GPIO_EXTI_IRQHandler()。

总结

到这里,中断的流程与配置就结束了。我们来简单总结一下,将GPIO配置成中断对应的模式,初始化GPIO时HAL库会帮我们完成AFIO的配置,EXTI负责筛选中断信号并将中断信号发送给NVIC,最后由NVIC根据中断优先级来使CPU触发中断处理函数。
我们要使用中断功能要实现的地方不多,有三处:

  1. 配置GPIO
  2. 配置中断优先级并使能中断
  3. 实现中断处理函数

最后,再通过接地按键KEY0触发中断点亮LED0来走一遍中断流程,假设按键KEY0接通的GPIO是PA0:首先,按下接地按键KEY0,IO引脚PA0输入一个低电平即下降沿,AFIO将PA0映射到中断线EXTI0,中断线EXTI0检测到下降沿,经过多个EXTI寄存器筛选后,确认触发中断,将信号发送给NVIC中断控制器,NVIC再根据中断优先级来判断中断号EXTI0_IRQn是否满足触发条件,确认满足后,CPU会调用我们实现的中断处理函数EXTI0_IRQHandler(),再由EXTI0_IRQHandler()调用HAL_GPIO_EXTI_Callback(),最后,通过HAL_GPIO_EXTI_Callback()实现点亮LED0。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值