• 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
  • 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
  • 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回

NVIC优先级分组

分组方式 抢占优先级 响应优先级
分组0 0位,取值为0 4位,取值为0~15
分组1 1位,取值为0~1 3位,取值为0~7
分组2 2位,取值为0~3 2位,取值为0~3
分组3 3位,取值为0~7 1位,取值为0~1
分组4 4位,取值为0~15 0位,取值为0

EXTI 外部中断

  • EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序
  • 支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断
  • 通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒
graph LR
    %% 定义样式类
    classDef gpio fill:#e1f5fe,stroke:#4fc3f7,stroke-width:2px,color:#0277bd,rx:8,ry:8;
    classDef afio fill:#ffebee,stroke:#ef9a9a,stroke-width:2px,color:#c62828,rx:8,ry:8;
    classDef exti fill:#fff3e0,stroke:#ffcc80,stroke-width:2px,color:#ef6c00,rx:8,ry:8;
    classDef nvic fill:#e8f5e9,stroke:#a5d6a7,stroke-width:2px,color:#2e7d32,rx:8,ry:8;
    classDef source fill:#f3e5f5,stroke:#ce93d8,stroke-width:2px,color:#6a1b9a,rx:8,ry:8;
    classDef other fill:#eceff1,stroke:#b0bec5,stroke-width:2px,color:#455a64,rx:8,ry:8;
    
    %% 左侧 GPIO 组
    subgraph GPIO [GPIO 端口]
        direction TB
        A[GPIOA]:::gpio
        B[GPIOB]:::gpio
        C[GPIOC]:::gpio
        D[...]:::gpio
    end
    
    %% AFIO
    AFIO[AFIO
中断引脚选择]:::afio %% 下方信号源组 subgraph Signals [内部/外部信号源] direction TB PVD[PVD]:::source RTC[RTC]:::source USB[USB]:::source ETH[ETH]:::source end %% EXTI EXTI[EXTI
边沿检测及控制]:::exti %% NVIC NVIC[NVIC
嵌套向量中断控制器]:::nvic %% 其它外设 Other[其它外设]:::other %% 连接关系 A -- "16根线" --> AFIO B -- "16根线" --> AFIO C -- "16根线" --> AFIO AFIO -- "16根线" --> EXTI PVD --> EXTI RTC --> EXTI USB --> EXTI ETH --> EXTI EXTI --> EXTI0[EXTI0] EXTI --> EXTI1[EXTI1] EXTI --> EXTI2[EXTI2] EXTI --> EXTI9_5[EXTI9_5] EXTI --> EXTI15_10[EXTI15_10] EXTI0 --> NVIC EXTI1 --> NVIC EXTI2 --> NVIC EXTI9_5 --> NVIC EXTI15_10 --> NVIC EXTI --> PVD_in[PVD] --> NVIC EXTI --> RTC_in[RTC] --> NVIC EXTI --> USB_in[USB] --> NVIC EXTI --> ETH_in[ETH] --> NVIC EXTI -- "20根线" --> Other %% 整体样式设置 style GPIO fill:#f8fbff,stroke:#d6e4f0,stroke-width:2px,stroke-dasharray: 5 5 style Signals fill:#f8fbff,stroke:#d6e4f0,stroke-width:2px,stroke-dasharray: 5 5

AFIO 复用IO口

  • 在STM32中,AFIO主要完成两个任务:复用功能引脚重映射、中断引脚选择

掌握函数

函数 说明
GPIO_EXTILineConfig 将外部中断的线映射到GPIOX
EXTI_InitTypeDef 定义结构体变量
EXTI_Init 配置EXTI外设
NVIC_PriorityGroupConfig NVIC中断分组
NVIC_InitTypeDef 定义结构体变量
NVIC_Init 配置NVIC外设
EXTI_GetITStatus 确认某个 EXTI 中断线是否真正产生了有效的中断请求
EXTI_ClearITPendingBit 清除外部中断的中断标志位

配置步骤

  • 开启时钟
  • GPIO初始化
  • AFIO选择中断引脚
  • EXTI初始化
  • NVIC中断分组
  • NVIC配置

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入

/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚

/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设

/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}