12-STM32-中断-NVIC

中断:打断CPU执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行。

1 中断类型

F429 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中系统异常有 10 个,外部中断有 91 个。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的。

  • 外部中断清单可查看中文参考手册的中断篇章
  • 系统中断清单:
F429 系统异常清单

2 NVIC

NVIC(Nested Vectored Interrupt Controller)是嵌套向量中断控制器的缩写,负责管理微控制器中的中断请求。

  • 控制中断的优先级,内核中的中断数量很多,当同时出现多个中断时,优先处理哪个中断?以及那些中断不处理等,都要靠NVIC 进行控制。
  • 支持嵌套中断,即高优先级的中断可以打断低优先级的中断,main函数的优先级最低。
  • 提供中断向量表,便于 CPU 确定中断处理程序的位置。
    • 定义一块固定的内存,以4字节对齐(32位),用于存放终端服务函数的首地址,系统已经将中断服务函数定义好了,放在中断向量表中,我们只需要进行调用即可,如上图F429 系统异常清单。
  • NVIC 与 Cortex-M4 内核密切耦合,是内核的一个重要组成部分。它直接影响中断处理的效率和响应时间。
  • 虽然 NVIC 是内核的一部分,但它也可以被视为一个外设,提供了对中断的控制和管理功能。
  • 同芯片厂商在设计时会根据特定需求对 Cortex-M4 内核中的 NVIC 进行裁剪, 因此,STM32 的 NVIC 可以被视为 Cortex-M4 NVIC 的一个子集,包含了基本的中断管理功能,但可能缺少某些高级功能。

2.1 NVIC 工作原理

  • 当外部事件触发中断时,系统首先需要识别并控制这些中断。
  • ICER(Interrupt Clear Enable Register): 用于清除对应中断的使能位,通常在处理中断时使用。
  • ISER(Interrupt Set Enable Register): 用于设置和使能对应的中断。这两个寄存器共同控制中断的使能状态。
  • IPR(Interrupt Priority Register): 经过 ISER 使能的中断会进入 IPR 寄存器进行优先级判断。IPR 寄存器的优先级配置由 SCB 的 AIRCR(Application Interrupt and Reset Control Register)寄存器中的 PRIGROUP 位控制。
  • SHPR(System Handler Priority Register): 用于控制内核中断(例如系统异常)的优先级。SHPR 寄存器与 IPR 寄存器处于同一级别,意味着它们在优先级控制方面具有相同的重要性。
  • 根据中断的优先级,系统将按照优先级高低的顺序依次进入 CPU 执行处理。

2.2 NVIC 寄存器

// core_cm4.h 

/** 
  * \brief  访问嵌套向量中断控制器 (NVIC) 的结构类型
  */
typedef struct
{
    __IO uint32_t ISER[8];                 /*!< 偏移: 0x000 (R/W)  中断使能寄存器 */
    uint32_t RESERVED0[24];                /*!< 保留区域 */
    __IO uint32_t ICER[8];                 /*!< 偏移: 0x080 (R/W)  中断清除使能寄存器 */
    uint32_t RESERVED1[24];                /*!< 保留区域 */
    __IO uint32_t ISPR[8];                 /*!< 偏移: 0x100 (R/W)  中断设置挂起寄存器 */
    uint32_t RESERVED2[24];                /*!< 保留区域 */
    __IO uint32_t ICPR[8];                 /*!< 偏移: 0x180 (R/W)  中断清除挂起寄存器 */
    uint32_t RESERVED3[24];                /*!< 保留区域 */
    __IO uint32_t IABR[8];                 /*!< 偏移: 0x200 (R/W)  中断活动位寄存器 */
    uint32_t RESERVED4[56];                /*!< 保留区域 */
    __IO uint8_t  IP[240];                 /*!< 偏移: 0x300 (R/W)  中断优先级寄存器(8位宽) */
    uint32_t RESERVED5[644];               /*!< 保留区域 */
    __O  uint32_t STIR;                    /*!< 偏移: 0xE00 ( /W)  软件触发中断寄存器 */
} NVIC_Type;

2.3 NVIC 中断函数

固件库文件 core_cm4.h 提供了 NVIC 的一些函数,这些函数遵循 CMSI 规则,只要是Cortex-M4 的处理器都可以使用:

3 优先级的定义

NVIC(Nested Vectored Interrupt Controller)中有一个重要的寄存器,用于配置外部中断的优先级。

3.1  寄存器定义

  • 中断优先级寄存器: NVIC_IPRx 寄存器用于配置外部中断的优先级。
    • 在 STM32F429 中,寄存器的编号为 x,从 0 到 90,表示可以配置最多 91 个外部中断的优先级。
  • 宽度: 每个 IPR 寄存器的宽度为 8 位(8 bits),用于存储中断的优先级设定。

3.2 优先级配置

  • 优先级范围: 理论上,每个外部中断可配置的优先级范围为 0 到 255,数值越小,优先级越高。
  • 实际支持的优先级: 由于大多数 Cortex-M4 芯片的设计精简,实际上支持的优先级数会减少。以 STM32F429 为例,寄存器只使用了高 4 位(0-15),这意味着实际可配置的优先级数量只有 16 个。

3.3 抢占优先级与子优先级

  • 抢占优先级: 在 NVIC 中,配置的高 4 位通常用于表示抢占优先级。抢占优先级高的中断可以打断低优先级的中断,确保高优先级的任务能优先执行。
  • 子优先级: 如果多个中断同时响应,并且它们的抢占优先级相同,则会进一步比较子优先级。
  • 硬件中断编号: 如果抢占优先级和子优先级都相同,NVIC 会根据中断的硬件编号进行比较,编号越小的中断优先级越高。

3.4 优先级分组

在 STM32F429 微控制器中,中断优先级的分组由内核外设 SCB(系统控制块)的应用程序中断及复位控制寄存器 AIRCR(Application Interrupt and Reset Control Register)中的 PRIGROUP[10:8] 位决定,F429 分为了 5 组。

  • AIRCR 寄存器在 Cortex-M4权威指南 文件中

设置优先级分组可调用库函数 NVIC_PriorityGroupConfig() 实现,有关 NVIC 中断相关的库函数都在库文件 misc.c 和 misc.h 中。

/**
  * @brief  配置优先级分组:抢占优先级和子优先级。
  * @param  NVIC_PriorityGroup: 指定优先级分组位长度。
  *   此参数可以是以下值之一:
  *     @arg NVIC_PriorityGroup_0: 0 位用于抢占优先级
  *                                4 位用于子优先级
  *     @arg NVIC_PriorityGroup_1: 1 位用于抢占优先级
  *                                3 位用于子优先级
  *     @arg NVIC_PriorityGroup_2: 2 位用于抢占优先级
  *                                2 位用于子优先级
  *     @arg NVIC_PriorityGroup_3: 3 位用于抢占优先级
  *                                1 位用于子优先级
  *     @arg NVIC_PriorityGroup_4: 4 位用于抢占优先级
  *                                0 位用于子优先级
  * @note   当选择 NVIC_PriorityGroup_0 时,抢占中断不再可能。
  *         挂起的 IRQ 优先级将仅由子优先级管理。
  * @retval 无
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
    /* 检查参数 */
    assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
    /* 根据 NVIC_PriorityGroup 值设置 PRIGROUP[10:8] 位 */
    SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
优先级分组真值表

4 中断程序

  • 使能外设中断
    • 每个外设的中断使能由特定的控制寄存器中的中断使能位来控制。例如,串口外设可能有发送完成中断和接收完成中断,这两个中断的使能位在串口控制寄存器中进行配置。确保正确设置这些寄存器以使能所需的中断。
  • 初始化 NVIC_InitTypeDef 结构体:用于配置中断优先级和使能中断请求
    • NVIC_IRQChannel: 设置中断源。每个中断的源是唯一的,并且必须正确配置。若配置错误,程序不会报错,但会导致中断失效。中断源在stm32f4xx.h 头文件中的 IRQn_Type 结构体定义
    • NVIC_IRQChannelPreemptionPriority: 设置抢占优先级,具体值需根据优先级分组进行确定,参考优先级分组真值表。
    • NVIC_IRQChannelSubPriority: 设置子优先级,具体值同样需根据优先级分组进行确定,参考优先级分组真值表。
    • NVIC_IRQChannelCmd: 控制中断使能(ENABLE)或失能(DISABLE)。该操作会影响 NVIC_ISER 和 NVIC_ICER 这两个寄存器。
/** 
  * @brief  NVIC 初始化结构体定义  
  */
typedef struct
{
    uint8_t NVIC_IRQChannel;                    /*!< 指定要使能或禁用的 IRQ 通道。
                                                   此参数可以是 @ref IRQn_Type 枚举的一个枚举值 
                                                   (有关完整的 STM32 设备 IRQ 通道列表,请参阅 stm32f4xx.h 文件) */

    uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< 指定所述 IRQ 通道的抢占优先级
                                                   此参数可以是 0 到 15 之间的值,如 @ref MISC_NVIC_Priority_Table 表所示
                                                   较低的优先级值表示较高的优先级 */

    uint8_t NVIC_IRQChannelSubPriority;         /*!< 指定所述 IRQ 通道的子优先级级别
                                                   此参数可以是 0 到 15 之间的值,如 @ref MISC_NVIC_Priority_Table 表所示
                                                   较低的优先级值表示较高的优先级 */

    FunctionalState NVIC_IRQChannelCmd;         /*!< 指定在 NVIC_IRQChannel 中定义的 IRQ 通道
                                                   是启用还是禁用。
                                                   此参数可以设置为 ENABLE 或 DISABLE */   
} NVIC_InitTypeDef;
  • 编写中断服务函数
    • 在启动文件 startup_stm32f429_439xx.s 中,系统预设了每个中断的服务函数,但这些函数通常为空,目的是初始化中断向量表。实际的中断服务函数需要开发者自行编写,通常放在 stm32f4xx_it.c 文件中。
    • 函数名要求: 中断服务函数的名称必须与启动文件中预定义的名称一致。如果名称不正确,系统将在中断向量表中找不到正确的中断服务函数入口,结果将直接跳转到启动文件中的空函数,并在其中进入无限循环,这将导致中断无法正常处理。

5 中断优先级设置

在 STM32F429 的外设配置过程中,设置中断优先级是一个重要步骤。

5.1 中断优先级分组配置

  • 在配置每个外设的中断时,首先需要调用 NVIC_PriorityGroupConfig() 函数来设置中断优先级分组。这一步骤只需在程序中执行一次,确保整个程序使用统一的优先级分组设置。
  • 例如,如果你选择了 NVIC_PriorityGroup_0 或 NVIC_PriorityGroup_4,那么所有外设的中断优先级都将基于这个设置进行解析

5.2 中断向量和优先级配置

  • 设置好优先级分组后,使用 NVIC_Init(&NVIC_InitStructure) 函数为每个外设配置中断向量和其优先级。此时,NVIC_InitStructure 结构体的 NVIC_IRQChannelPreemptionPriority 和 NVIC_IRQChannelSubPriority 字段应根据已经设置的优先级分组进行适当配置。
  • 如果在程序中已经定义了优先级分组,则在给多个外设的中断向量填充优先级时,确保这些优先级的配置与分组设置相符合。

5.3 示例

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 配置NVIC为优先级组1 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中断源:按键1 */
  NVIC_InitStructure.NVIC_IRQChannel = KEY1_INT_EXTI_IRQ;
  /* 配置抢占优先级:1 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 配置子优先级:1 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断通道 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);
}

发表评论