4. 中断

📌 本节学习目标
  • 理解 Cortex-M3 的异常与中断机制
  • 掌握 NVIC 中断控制器的使用
  • 学会配置外部中断 EXTI
  • 理解中断优先级分组的概念

4.1 中断概述

Cortex-M3 内核内置了 NVIC(嵌套向量中断控制器),支持 240 个中断源,具有 16 级可编程优先级。STM32F103 使用了其中的 43 个中断通道。

4.2 NVIC 中断优先级

Cortex-M3 的中断优先级分为 抢占优先级响应优先级(子优先级)。通过 SCB->AIRCR 寄存器设置优先级分组:

分组抢占位数子优先级位数描述
NVIC_PriorityGroup_004所有中断同抢占比,仅有子优先级
NVIC_PriorityGroup_1132 级抢占,8 级子优先级
NVIC_PriorityGroup_2224 级抢占,4 级子优先级
NVIC_PriorityGroup_3318 级抢占,2 级子优先级
NVIC_PriorityGroup_44016 级抢占,无子优先级

4.3 EXTI 外部中断

STM32 的 EXTI(外部中断/事件控制器)支持 19 个事件/中断线,每个 GPIO 端口对应的引脚共享同一个 EXTI 线(如 PA0、PB0、PC0 共享 EXTI0)。

配置外部中断的一般步骤:

  1. 使能 GPIO 时钟和 AFIO 时钟
  2. 配置 GPIO 为输入模式
  3. 配置 GPIO 引脚与 EXTI 线的映射
  4. 配置 EXTI 中断线(触发方式、使能)
  5. 配置 NVIC(优先级、使能)
  6. 编写中断服务函数

4.4 外部中断编程示例

C 代码(外部中断配置)
#include "stm32f10x.h"

// PA0 按键检测(外部中断,下降沿触发)
void EXTI0_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    // 2. PA0 浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 3. 映射 PA0 到 EXTI0
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

    // 4. EXTI0 配置:下降沿触发
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    // 5. NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

// 中断服务函数
void EXTI0_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
        // 按键处理
        GPIO_ToggleBits(GPIOB, GPIO_Pin_0);

        EXTI_ClearITPendingBit(EXTI_Line0);  // 清中断标志
    }
}
💡 这段代码怎么用?
  1. 完整流程就是 6 步:时钟 → GPIO → 映射 → EXTI → NVIC → 服务函数,实验 09/10 按此模板扩展
  2. 中断服务函数要短小精悍,不要在中断里做长时间延时,否则会阻塞主程序
  3. EXTI_GetITStatus() 判断是否本线中断,处理完务必 EXTI_ClearITPendingBit() 清标志,否则会反复进入