⑤ STM32基础学习— EXTI外部中断/事件控制器

1 EXTI 简介

EXTI(External interrupt/event controller)—外部中断/事件控制器,管理了控制器的 20 个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。

2 EXTI功能框图

EXTI 的功能框图包含了 EXTI 最核心内容,掌握了功能框图,对 EXTI 就有一个整体的把握,在编程时思路就非常清晰。

EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。框图 中红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内。

1==>中是输入线,EXTI 控制器有 19 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件,这部分内容我们将在后面专门讲解。输入线一般是存在电平变化的信号。

2 ==> 是一个边沿检测电路,它会根据上升沿触发选择寄存器 (EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而 EXTI_RTSR 和EXTI_FTSR 两个寄存器可以控制器需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。

` 3 ==>`` 电路实际就是一个或门电路,它一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器 (EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线,这在某些地方非常有用。我们知道或门的作用就是有 1 就为 1,所以这两个输入随便一个有有效信号 1就可以输出 1 给编号 4 和编号 6 电路。

4 ==>电路是一个与门电路,它一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器(EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果 EXTI_IMR设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器(EXTI_PR) 内,如果确定编号 4 电路输出为 1 就会把 EXTI_PR 对应位置 1。

5 ==>是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。产生事件线路是在编号 3 电路之后与中断线路有所不同,之前电路都是共用的。

6 ==>电路是一个与门,它一个输入来自编号 3 电路,另外一个输入来自事件屏蔽寄存器 (EXTI_EMR)。如果EXTI_EMR 设置为 0 时,那不管编号 3 电路的输出信号是 1 还是 0,最终编号 6 电路输出的信号都为 0;如果 EXTI_EMR 设置为 1 时,最终编号 6 电路输出的信号才由编号 3 电路的输出信号决定。

7 ==>是一个脉冲发生器电路,当它的输入端,即编号 6 电路的输出端,是一个有效信号 1 时就会产生一个脉冲;如果输入端是无效信号就不会输出脉冲。

8 ==> `是一个脉冲信号,就是产生事件的线路最终的产物,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。

3 中断事件线

EXTI20个中断/事件线,每个 GPIO都可以被设置为输入线,占用 EXTI0EXTI15

EXTI0` EXTI15 用于GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。由表EXTI_ 中断 __* 事件线 可知,EXTI0 可以通过 AFIO 的外部中断配置寄存器 1(AFIO_EXTICR1) 的EXTI0[3:0] 位选择配置为PA0、PB0、PC0、PD0等。

4 EXTI 初始化结构体详解

标准库函数对每个外设都建立了一个初始化结构体,比如EXTI_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 EXTI_Init() 调用。初始化结构体定义在 stm32f10x_exti.h 文件中,初始化库函数定义在 stm32f10x_exti.c 文件中。

typedef struct {
    uint32_t EXTI_Line; // 中断/事件线
    EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
    EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
    FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;
  • EXTI_Line:EXTI 中断/事件线选择,可选EXTI0EXTI19,可参考表 _EXTI_ 中断 ___ 事件线选择。
  • EXTI_Mode:EXTI 模式选择,可选为产生中断 (EXTI_Mode_Interrupt) 或者产生事件(EXTI_Mode_Event)。
  • EXTI_Trigger:EXTI 边沿触发事件,可选上升沿触发 (EXTI_Trigger_Rising)、下降沿触发 (EXTI_Trigger_Falling) 或者上升沿和下降沿都触发 ( EXTI_Trigger_Rising_Falling)。
  • EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线 (ENABLE) 或禁用 (DISABLE)。

5 编程

  • 初始化用来产生中断的 GPIO
  • 初始化 EXTI
  • 配置 NVIC
  • 编写中断服务函数

6 嵌套中断向量控制器NVIC

6.1 NVIC介绍

NVIC(Nest Vector Interrupt Controller),嵌套中断向量控制器,作用是管理中断嵌套,核心任务是管理中断优先级

特点

  • 68个可屏蔽中断通道(不包含16个Cortex-M3的中断线)
  • 16个可编程的优先等级(使用了4位中断优先级)
  • 低延迟的异常和中断处理
  • 电源管理控制
  • 系统控制寄存器的实现

嵌套向量中断控制器(NVIC)和处理器核的接口紧密相连,可以实现中断的低延迟处理和高效地处理晚到的中断。

NVIC给每个中断赋予抢占优先级和响应优先级。关系如下

  • 拥有较高抢占优先级的中断可以打断抢占优先级较低的中断
  • 若两个抢占优先级的中断同时挂起,则优先执行响应优先级较高的中断
  • 若两个挂起的中断优先级都一致,则优先执行位于中断向量表中位置较高的中断
  • 响应优先级不会造成中断嵌套,也就是说中断嵌套是由抢占优先级决定的

6.2 优先级

每个中断源都需要被指定这两种优先级,Cortex-M3核定义了8个bit用于设置中断源的优先级,这8个bit可以有以下8种分配方式

  • 所有8位全部用于指定响应优先级
  • 7位用于指定抢占式优先级,0-6位用于指定响应优先级
  • 6-7位用于指定抢占式优先级,0-5位用于指定响应优先级
  • 5-7位用于指定抢占式优先级,0-4位用于指定响应优先级
  • 4-7位用于指定抢占式优先级,0-3位用于指定响应优先级
  • 3-7位用于指定抢占式优先级,0-2位用于指定响应优先级
  • 2-7位用于指定抢占式优先级,0-1位用于指定响应优先级
  • 1-8位用于指定抢占式优先级,0位用于指定响应优先级

Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32中断优先级的寄存器位只用到AIRCR高四位,共有以下几种组合

  • 第0组:所有4位用于指定响应优先级
  • 第1组:第7位用于指定抢占式优先级,4-6位用于指定响应优先级
  • 第2组:第6-7位用于指定抢占式优先级,4-5位用于指定响应优先级
  • 第3组:第5-7位用于指定抢占式优先级,第4位用于指定响应优先级
  • 第4组:所有4位用于指定抢占式优先级

使用固件库函数配置中断优先级得方法(misc.c中定义)

/**
 * @brief Configures the priority grouping: pre‐emption priority and
 * subpriority.
 * @param NVIC_PriorityGroup: specifies the priority grouping bits
 * length.
 * This parameter can be one of the following values:
 * @arg NVIC_PriorityGroup_0: 0 bits for pre‐emption priority
 * 4 bits for subpriority
 * @arg NVIC_PriorityGroup_1: 1 bits for pre‐emption priority
 * 3 bits for subpriority
 * @arg NVIC_PriorityGroup_2: 2 bits for pre‐emption priority
 * 2 bits for subpriority
 * @arg NVIC_PriorityGroup_3: 3 bits for pre‐emption priority
 * 1 bits for subpriority
 * @arg NVIC_PriorityGroup_4: 4 bits for pre‐emption priority
 * 0 bits for subpriority
 * @retval None
*/
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)

优先级组确定后,可以根据优先级组来配置对应IRQ的抢占优先级和响应优先级

6.3 NVIC初始化

每个外部中断都由NVIC统一进行管理,所以NVIC包含了中断功能的使能和失能,优先级的配置等功能。

固件库中关于NVIC的初始化函数

/**
 * @brief Initializes the NVIC peripheral according to the specified
 * parameters in the NVIC_InitStruct.
 * @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure
 * that contains
 * the configuration information for the specified NVIC
 * peripheral.
 * @retval None
*/
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)

形参变量说明

typedef struct
{
    uint8_t NVIC_IRQChannel;
    uint8_t NVIC_IRQChannelPreemptionPriority;
    uint8_t NVIC_IRQChannelSubPriority;
    FunctionalState NVIC_IRQChannelCmd;
}

NVIC_IRQChannel:外部中断通道(在stm32f10x.h中定义)

typedef enum IRQn
{
    NonMaskableInt_IRQn = ‐14, /*!< 2 Non Maskable Interrupt*/
        MemoryManagement_IRQn = ‐12, /*!< 4 Cortex‐M3 Memory Management Interrup
t*/
        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*/

        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*/
        ...
}

NVIC_IRQChannelPreemptionPriority:抢占优先级(最大取值15)

NVIC_IRQChannelSubPriority:响应优先级(最大取值15)

NVIC_IRQChannelCmd:(ENABLE/DISABLE)使能/失能对应的中断通道

6.4 中断的具体行为

当CM3开始响应一个中断时,会做如下动作:

入栈: 把8个寄存器的值压入栈

取向量:从向量表中找出对应的服务程序入口地址

选择堆栈指针MSP(主堆栈)/PSP(进程堆栈),更新堆栈指针SP,更新链接寄存器LR,更新程序计数器PC。

6.4.1 入栈

响应异常的第一个动作,就是自动保存现场,依次把xPSR、PC, LR, R12以及R3-R0由硬件寄存器自动压入适当的堆栈中。

6.4.2 取向量

数据总线(系统总线)在执行入栈的时候,指令总线从向量表中找出正确的异常向量,然后在服务程序的入口处预取指。(由此可以看到各自都有专用总线的好处:入栈与取指这两个工作能同时进行)

6.4.3 更新寄存器

在入栈和取向量操作完成之后,执行服务例程之前,还要更新一系列的寄存器

  • SP:在入栈后会把堆栈指针更新到新的位置。在执行服务例程时,将由MSP负责对堆栈的访问。
  • PSR:更新IPSR位段的值为新响应的异常编号。
  • PC:在取向量完成后,PC将指向服务例程的入口地址,
  • LR:在出入ISR(Interrupt Service Routines)中断服务程序的时候,LR的值将得到更新

(在异常进入时由系统计算并赋给LR,并在异常返回时使用它)

6.5 异常/中断返回

当异常服务例程执行完毕后,需要恢复先前的系统状态,才能使被中断的程序得以继续执行

异常/中断处理完成后,执行如下处理

  • 出栈:恢复先前压入栈中的寄存器,堆栈指针的值也改回先前的值
  • 更新NVIC寄存器:伴随着异常的返回,它的活动位也被硬件清除

7 外部中断

对于互联型产品,外部中断/事件控制器由20个产生事件/中断请求的边沿检测器组成,对于其它产品,则有19个能产生事件/中断请求的边沿检测器。每个输入线可以独立地配置输入类型和对应的触发事件(上升沿或下降沿或者双边沿都触发)。每个输入线都可以独立地被屏蔽。挂起寄存器保持着状态线的中断请求。

EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件

  • 中断

信号从输入线输入,经过边沿检测电路来控制信号触发(根据上升沿下降沿触发选择寄存器的设置来控制),如果检测到有效信号后,将该有效信号输出到或门电路,由红色箭头方向经过请求挂起寄存器和中断屏蔽寄存器,到达与门电路,条件满足送至NVIC

  • 事件

信号从输入线输入,经过边沿检测电路来控制信号触发(根据上升沿下降沿触发选择寄存器的设置来控制),如果检测到有效信号后,将该有效信号输出到或门电路,由蓝色箭头方向经过与门电路,送至买脉冲发生器,产生脉冲。这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC等等,这样的脉冲信号一般用来触发 TIM 或者 ADC开始转换。

事件和中断的区别

产生中断线路目的是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。而产生事件线路目的就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。

7.1 主要特性

  • 每个中断/事件都有独立的触发和屏蔽
  • 每个中断线都有专用的状态位
  • 支持多达20个软件的中断/事件请求
  • 检测脉冲宽度低于APB2时钟宽度的外部信号

7.2 外部中断/事件线路映像

通用I/O端口以下图的方式连接到16个外部中断/事件线上

STM32F103 的19 个外部中断为:

  • 线 0~15:对应外部 IO 口的输入中断。
  • 线 16:连接到 PVD 输出。
  • 线 17:连接到 RTC 闹钟事件。
  • 线 18:连接到 USB 唤醒事件。

另外四个EXTI线的连接方式如下

  • EXTI线16连接到PVD输出
  • EXTI线17连接到RTC闹钟事件
  • EXTI线18连接到USB唤醒事件
  • EXTI线19连接到以太网唤醒事件

7.3 EXTI相关数据结构与函数说明

7.3.1 初始化函数

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)

参数EXTI_InitStruct

typedef struct
{
    uint32_t EXTI_Line; //中断线
    EXTIMode_TypeDef EXTI_Mode; //中断模式
    EXTITrigger_TypeDef EXTI_Trigger; //中断触发方式
    FunctionalState EXTI_LineCmd; //中断功能使能
}

==EXTI_Line==

#define EXTI_Line0 ((uint32_t)0x00001) /*!< External interrupt line 0 */
#define EXTI_Line1 ((uint32_t)0x00002) /*!< External interrupt line 1 */
#define EXTI_Line2 ((uint32_t)0x00004) /*!< External interrupt line 2 */
#define EXTI_Line3 ((uint32_t)0x00008) /*!< External interrupt line 3 */
#define EXTI_Line4 ((uint32_t)0x00010) /*!< External interrupt line 4 */
#define EXTI_Line5 ((uint32_t)0x00020) /*!< External interrupt line 5 */
#define EXTI_Line6 ((uint32_t)0x00040) /*!< External interrupt line 6 */
#define EXTI_Line7 ((uint32_t)0x00080) /*!< External interrupt line 7 */
#define EXTI_Line8 ((uint32_t)0x00100) /*!< External interrupt line 8 */
#define EXTI_Line9 ((uint32_t)0x00200) /*!< External interrupt line 9 */
#define EXTI_Line10 ((uint32_t)0x00400) /*!< External interrupt line 10
*/
#define EXTI_Line11 ((uint32_t)0x00800) /*!< External interrupt line 11
*/
#define EXTI_Line12 ((uint32_t)0x01000) /*!< External interrupt line 12
*/
#define EXTI_Line13 ((uint32_t)0x02000) /*!< External interrupt line 13
*/
#define EXTI_Line14 ((uint32_t)0x04000) /*!< External interrupt line 14
*/
#define EXTI_Line15 ((uint32_t)0x08000) /*!< External interrupt line 15
*/
#define EXTI_Line16 ((uint32_t)0x10000) /*!< External interrupt line 16
Connected to the PVD Output */
#define EXTI_Line17 ((uint32_t)0x20000) /*!< External interrupt line 17
Connected to the RTC Alarm event */
#define EXTI_Line18 ((uint32_t)0x40000) /*!< External interrupt line 18
Connected to the USB Device/USB OTG FS
 Wakeup from suspend event */
#define EXTI_Line19 ((uint32_t)0x80000) /*!< External interrupt line 19
Connected to the Ethernet Wakeup event */

==EXTI_Mode==

typedef enum
{
    EXTI_Mode_Interrupt = 0x00, //中断模式
    EXTI_Mode_Event = 0x04 //事件模式
}EXTIMode_TypeDef;

==EXTI_Trigger==

typedef enum
{
    EXTI_Trigger_Rising = 0x08, //上升沿触发
    EXTI_Trigger_Falling = 0x0C, //下降沿触发
    EXTI_Trigger_Rising_Falling = 0x10 //双边沿触发
}EXTITrigger_TypeDef;

==EXTI_LineCmd==

ENABLE
DISABLE

7.3.2 获取中断状态

//参数:中断线
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);

7.3.3 清空中断标志位

//参数:中断线
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);

7.3.4 软件产生中断

void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line)

7.4 中断回调函数

在startup_stm32f10x_xx.s中的中断向量表里

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 EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10

7.5 EXit Code

我们采用一个按键控制一个LED,STM32F103C8T可以使用线连接一个按键控制PC13 是LED。

#include "exti.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"

//外部中断0服务程序
void EXTIX_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    KEY_Init();     //    按键端口初始化

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);    //使能复用功能时钟

    //GPIOA.0      中断线以及中断初始化配置 上升沿触发 PA0  WK_UP
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); 

    EXTI_InitStructure.EXTI_Line=EXTI_Line0;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_Init(&EXTI_InitStructure);        //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器


    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;            //使能按键WK_UP所在的外部中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;    //抢占优先级2, 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;                    //子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure); 


    NVIC_Init(&NVIC_InitStructure);        //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

}

//外部中断0服务程序 
void EXTI0_IRQHandler(void)
{
    delay_ms(10);//消抖
    if(WK_UP==1)          //WK_UP按键
    {                 
        LED1=!LE1;    
    }
    EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位  
}