05-EXTI外部中断.md 11 KB

05-EXTI外部中断

中断系统概念

graph TD
    subgraph NORMAL
        A["主程序<br/>Task A"] --> B["Task B"]
    end
    
    subgraph INTERRUPT
        IRQ["中断请求<br/>(硬件事件)"] --> SAVE["保存现场<br/>(自动压栈)"]
        SAVE --> SERVICE["中断服务函数<br/>ISR"]
        SERVICE --> RESTORE["恢复现场<br/>(出栈)"]
        RESTORE --> RETURN["返回主程序"]
    end
    
    NORMAL --> IRQ
    RETURN --> NORMAL

关键术语

术语 说明
中断 CPU 暂停当前任务,处理紧急事件
中断优先级 决定多个中断同时触发时的响应顺序
中断嵌套 高优先级中断可以打断低优先级中断服务函数
向量表 存储所有中断函数入口地址的表(位于 0x08000000 起始)
尾链(Tail Chaining) Cortex-M3 优化:两个中断连续进出时省去多余出栈入栈

STM32 中断架构

特性 STM32F103
可屏蔽中断通道 68 个
可编程优先级 16 级 (4bit)
固定优先级 中断号越小优先级越高
系统异常 8 个 (Reset, NMI, HardFault, MemManage, BusFault, UsageFault, SVCall, PendSV, SysTick)
外部中断 EXTI0~EXTI15 (16 条线)

NVIC 优先级分组

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
分组宏 抢占位 子优先级位 抢占级数 子优先级数
NVIC_PriorityGroup_0 0 4 1 16
NVIC_PriorityGroup_1 1 3 2 8
NVIC_PriorityGroup_2 2 2 4 4
NVIC_PriorityGroup_3 3 1 8 2
NVIC_PriorityGroup_4 4 0 16 1

建议:全系统只用一种分组,在 main() 入口配置一次。推荐 NVIC_PriorityGroup_2

中断抢占规则

  1. 抢占优先级不同 → 高抢占优先级的先响应,可打断低优先级
  2. 抢占优先级相同、子优先级不同 → 不能嵌套,等当前 ISR 执行完再响应
  3. 抢占、子优先级都相同 → 按中断号排队(号小先响应)

EXTI 基本结构

graph TD
    subgraph GPIO_PINS
        PA0["PA0"]
        PB0["PB0"]
        PC0["PC0"]
        PA1["PA1"]
        PB14["PB14"]
        PE0["PE0"]
    end
    
    subgraph AFIO
        AFIO_MUX["AFIO 中断引脚选择"]
    end
    
    subgraph EXTI
        EXTI0["EXTI0"]
        EXTI1["EXTI1"]
        EXTI14["EXTI14"]
    end
    
    subgraph NVIC
        NVIC0["NVIC EXTI0_IRQn"]
        NVIC1["NVIC EXTI1_IRQn"]
        NVIC9_5["NVIC EXTI9_5_IRQn"]
        NVIC15_10["NVIC EXTI15_10_IRQn"]
    end
    
    subgraph CORE
        CPU["Cortex-M3"]
    end
    
    PA0 --> AFIO_MUX --> EXTI0 --> NVIC0 --> CPU
    PB0 --> AFIO_MUX --> EXTI0
    PC0 --> AFIO_MUX --> EXTI0
    PA1 --> AFIO_MUX --> EXTI1 --> NVIC1 --> CPU
    PB14 --> AFIO_MUX --> EXTI14 --> NVIC9_5 --> CPU
    PE0 --> AFIO_MUX --> EXTI0

中断线分配

EXTI 线 中断号 NVIC 通道 IRQHandler
EXTI0 6 EXTI0_IRQn EXTI0_IRQHandler
EXTI1 7 EXTI1_IRQn EXTI1_IRQHandler
EXTI2 8 EXTI2_IRQn EXTI2_IRQHandler
EXTI3 9 EXTI3_IRQn EXTI3_IRQHandler
EXTI4 10 EXTI4_IRQn EXTI4_IRQHandler
EXTI5~9 23 EXTI9_5_IRQn EXTI9_5_IRQHandler
EXTI10~15 40 EXTI15_10_IRQn EXTI15_10_IRQHandler

EXTI5-9 共用中断函数 EXTI9_5_IRQHandler,需在函数内判断 EXTI_GetITStatus(EXTI_Line5) ~ EXTI_GetITStatus(EXTI_Line9)EXTI10-15 共用 EXTI15_10_IRQHandler,同理。

EXTI 输入线选择

// 将 PB14 连接到 EXTI14
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);

API 表格

EXTI 初始化

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
EXTI_InitTypeDef 成员 取值 说明
EXTI_Line EXTI_Line0 ~ EXTI_Line15 中断线
EXTI_Mode EXTI_Mode_Interrupt / EXTI_Mode_Event 中断模式 / 事件模式
EXTI_Trigger EXTI_Trigger_Rising / EXTI_Trigger_Falling / EXTI_Trigger_Rising_Falling 触发方式
EXTI_LineCmd ENABLE / DISABLE 使能

NVIC 初始化

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
NVIC_InitTypeDef 成员 取值 说明
NVIC_IRQChannel EXTI0_IRQn 中断通道
NVIC_IRQChannelPreemptionPriority 0~15 (取决于分组) 抢占优先级
NVIC_IRQChannelSubPriority 0~15 (取决于分组) 子优先级
NVIC_IRQChannelCmd ENABLE / DISABLE 使能

中断标志位函数

函数 原型 说明
EXTI_GetITStatus ITStatus EXTI_GetITStatus(uint32_t EXTI_Line) 获取中断标志位
EXTI_ClearITPendingBit void EXTI_ClearITPendingBit(uint32_t EXTI_Line) 清除中断标志位

标准中断函数模板

void EXTIx_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_LineX) != RESET)
    {
        // 处理中断事件
        
        EXTI_ClearITPendingBit(EXTI_LineX);     // 清除标志位
    }
}

void EXTI9_5_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14) != RESET)
    {
        // PB14 中断处理
        
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
}

完整代码示例

对射式红外传感器计次 (PB14)

#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"

uint16_t SensorCount = 0;

void Sensor_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 1. 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
    // 2. GPIO 配置 (上拉输入)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 3. AFIO 中断引脚选择
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
    
    // 4. EXTI 配置 (下降沿触发)
    EXTI_InitStructure.EXTI_Line = EXTI_Line14;
    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_PriorityGroupConfig(NVIC_PriorityGroup_2);
    
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

uint16_t Sensor_Get(void)
{
    return SensorCount;
}

// EXTI15_10 中断服务函数 (PB14 在 EXTI14 上)
void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14) != RESET)
    {
        SensorCount++;
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
}

int main(void)
{
    Delay_Init();
    OLED_Init();
    Sensor_Init();
    
    OLED_ShowString(1, 1, "Sensor Count:");
    
    while (1)
    {
        OLED_ShowNum(2, 1, Sensor_Get(), 5);
        Delay_ms(100);
    }
}

旋转编码器 (A相PB12, B相PB13)

graph TD
    subgraph ENCODER
        A_PHASE["A 相 (PB12)"] -->|"EXTI12<br/>双边缘触发"| MCU
        B_PHASE["B 相 (PB13)"] -->|"读取电平<br/>判断方向"| MCU
    end
    MCU --> COUNT["计数值<br/>+1 / -1"]
#include "stm32f10x.h"

int16_t EncoderCount = 0;

void Encoder_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
    // A相 PB12 — 上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // B相 PB13 — 同上
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // AFIO 映射
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource12);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource13);
    
    // EXTI12 — 双边缘触发
    EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

int16_t Encoder_Get(void)
{
    int16_t temp;
    temp = EncoderCount;
    EncoderCount = 0;       // 读取后清零
    return temp;
}

void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line12) != RESET)
    {
        // 判断方向: 读取 B 相电平
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0)
            EncoderCount++;
        else
            EncoderCount--;
        
        EXTI_ClearITPendingBit(EXTI_Line12);
    }
}

完整初始化步骤总结

1. RCC_APB2PeriphClockCmd(GPIOx, ENABLE);
2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);  // AFIO 时钟
3. GPIO_Init() — 配置输入模式
4. GPIO_EXTILineConfig() — 选择引脚到 EXTI 线
5. EXTI_Init() — 配置触发方式
6. NVIC_PriorityGroupConfig() — 一次
7. NVIC_Init() — 配置中断优先级
8. 写 ISR 函数, 检查标志位并清除

常见坑点/注意事项

  1. AFIO 时钟必须开启RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE),否则 GPIO_EXTILineConfig 无效
  2. 同一条 EXTI 线只能对应一个 GPIO — 例如 EXTI0 可以选 PA0/PB0/PC0,但同一时间只能选一个。选了 PB0 后 PA0 不再触发
  3. EXTI5-9 和 EXTI10-15 共用中断函数 — 必须在 ISR 内判断具体哪条线的标志位
  4. 清除标志位EXTI_ClearITPendingBit 必须在 ISR 结束前调用,否则一直进中断
  5. 中断优先级分组只设一次 — 重复调用 NVIC_PriorityGroupConfig 行为未定义
  6. 中断函数名必须正确 — Startup 文件中已定义弱符号,写错名字不会编译报错但不会进入中断
  7. 不要在 ISR 中做复杂操作 — 中断应短而快,标志位+主循环处理;延时、打印等在 ISR 中要尽量避免
  8. 下拉输入与上升沿 — 如果按键接 VCC 按下高电平,用 IPD + EXTI_Trigger_Rising;按键接 GND 用 IPU + EXTI_Trigger_Falling
  9. 中断同时触发 — 同优先级中断按中断号排队响应,不同级中断按抢占/子优先响应