在嵌入式系统开发领域,STM32 系列微控制器凭借其强大的性能和丰富的资源被广泛应用。中断系统作为 STM32 的关键特性之一,能够极大地提升系统的实时响应能力和多任务处理效率。本文将基于 STM32F4 系列芯片,深入剖析中断与外设中断的原理、配置及应用,帮助开发者全面掌握这一核心技术。
一、中断简介
中断是指当 CPU 执行程序时,由于外部或内部随机事件的发生,导致 CPU 暂时停止正在运行的程序,转而执行一段特殊的服务程序(中断服务子程序或中断处理程序)。待事件处理完毕后,CPU 再返回被中断的程序继续执行,引发中断的事件源被称为中断源。
STM32 的中断功能极为强大,每个外设都具备产生中断的能力。其中,可屏蔽中断通道多达 82 个(外部),系统异常有 10 个,并且拥有 16 个可编程优先级(使用 4 位中断优先级) 。除个别异常优先级固定外,其他异常优先级均可编程。在标准库文件 stm32f4xx.h
的 IRQn_Type
结构体中,包含了 F4 系列全部的异常声明。以 STM32F405xx/07xx 和 STM32F415xx/17xx 为例,其向量表如下:
序号 | 优先级类型 | 名称 | 说明 | 地址 | |
---|---|---|---|---|---|
保留 | 0x00000000 | ||||
3 | 固定 | Reset | 复位 | 0x00000004 | |
W | 固定 | NMI | 不可屏蔽中断,RCC 时钟安全系统(CSS)连接到此向量 | 0x00000008 | |
-1 0 | 固定 可设置 | HardFault MemManage | 所有类型的错误 存储器管理 | 0x0000000C 0x00000010 | |
1 | 可设置 | BusFault | 预取指失败,存储器访问失败 | 0x00000014 | |
2 | 可设置 | UsageFault | 未定义的指令或非法状态 | 0x00000018 | |
保留 | 0x0000001C - 0x0000002B | ||||
3 | 可设置 | SVCall | 通过 SWI 指令调用的系统服务 | 0x0000002C | |
4 | 可设置 | DebugMonitor | 调试监控器 | 0x00000030 | |
保留 | 0x00000034 | ||||
5 | 可设置 | Pendsv | 可挂起的系统服务 | 0x00000038 | |
0 | 7 6 | 可设置 可设置 | WWDG SysTick | 窗口看门狗中断 系统滴答定时器 | 0x00000040 0x0000003C |
1 | 8 | 可设置 | PVD | 连接到 EXTI 线的可编程电压检测 | 0x00000044 |
2 | 9 | TAMPSTAMP | (PVD)中断 | 0x00000048 | |
3 | 10 | 可设置 可设置 | RTC_WKUP | 连接到 EXTI 线的入侵和时间蔽中断 连接到 EXTI 线的 RTC 唤醒中断 | 0x0000004C |
4 5 6 7 | 11 12 13 14 | 可设置 可设置 可设置 | FLASH RCC EXTI0 EXTI1 | FLASH 全局中断 RCC 全局中断 EXTI 线 0 中断 | 0x00000050 0x00000054 0x00000058 0x0000005C |
8 9 10 | 15 16 17 | 可设置 可设置 可设置 | EXTI2 EXTI3 EXTI4 | EXTI 线 1 中断 EXTI 线 2 中断 EXTI 线 3 中断 EXTI 线 4 中断 | 0x00000060 0x00000064 0x00000068 |
... | ... | ... | ... | ... | |
80 81 87 88 | 可设置 可设置 | 哈希和随机数发生器全局中断 FPU 全局中断 | 0x00000180 0x00000184 |
二、NVIC(Nested vectored interrupt controller)
NVIC 即嵌套向量中断控制器,在中断向量表中,优先级 7 - 88(中断号从 0 - 82)代表着 STM32F103 的 91 个中断 ,优先级号越小,优先级越高。当异常或中断被触发时,程序计数器指针(PC)会跳转到对应地址执行,该地址存放跳转指令,进而跳转到服务函数执行相应功能。
在 MDK 开发环境中,可使用标准的异常和中断向量表文件 startup_stm32f40_41xxx.s
,其中明确规定了中断处理函数的名称,开发者不可随意定义。
在 STM32 中,中断优先级由抢占式优先级和响应优先级共同决定,二者组成 4 位控制字,数值越小优先级越高。通过不同的优先级分组,可灵活配置抢占式优先级和子优先级的位数:
优先级分组 | 抢占式优先级 | 子优先级 | 高 4 位使用情况描述 |
---|---|---|---|
NVICPriorityGroup_0 | 0 级抢占优先级 | 0 - 15 级子优先级 | 4bit 全用于子优先级 |
NVICPriorityGroup_1 | 0 - 1 级抢占优先级 | 0 - 7 级子优先级 | 1bit 用于抢占优先级,3bit 用于子优先级 |
NVICPriorityGroup_2 | 0 - 3 级抢占优先级 | 0 - 3 级子优先级 | 2bit 用于抢占优先级,2bit 用于子优先级 |
NVICPriorityGroup_3 | 0 - 7 级抢占优先级 | 0 - 1 级子优先级 | 3bit 用于抢占优先级,1bit 用于子优先级 |
NVICPriorityGroup_4 | 0 - 15 级抢占优先级 | 0 级子优先级 | 4bit 全用于抢占优先级 |
具有高抢占式优先级的中断能够在低抢占式优先级中断服务程序执行过程中被响应,即支持中断嵌套;而在抢占式优先级相同的情况下,若多个子优先级不同的中断同时到来,高子优先级的中断优先被响应 。不过,子优先级不支持中断嵌套,当低子优先级中断正在执行时,高子优先级中断需等待其执行结束才能得到响应。此外,Reset、NMI、Hard Fault 的优先级为负数,高于普通中断优先级且不可配置。
NVIC 相关的库函数位于 misc.c
中:
-
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
:用于设置抢占式优先级的位数。 -
NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
:初始化配置函数,其中NVIC_InitTypeDef
结构体定义如下:
typedef struct {
uint8_t NVIC_IRQChannel; // 指定IRQ通道,可从IRQn_Type枚举中选择
uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占式优先级,取值0 - 15,值越小优先级越高
uint8_t NVIC_IRQChannelSubPriority; // 子优先级,取值0 - 15,值越小优先级越高
FunctionalState NVIC_IRQChannelCmd; // 使能或禁用IRQ通道,可设置为ENABLE或DISABLE
} NVIC_InitTypeDef;
三、外部中断
3.1 外部中断简介
外部中断 / 事件控制器(EXTI)负责管理控制器的 23 个中断 / 事件线 。每个中断 / 事件线都配备边沿检测器,可实现对输入信号的上升沿和下降沿检测。EXTI 能够对每条中断 / 事件线进行单独配置,可设置为中断或事件,并定义触发事件的属性。中断线路将输入信号传输至 NVIC,进而运行中断服务函数实现软件层面的功能;而事件线路则是向其他外设传输脉冲信号,属于硬件级别的电路信号传输。
STM32F4 的中断控制器支持 22 个外部中断 / 事件请求(中断线) 。对于每条中断线,开发者可设置相应的触发方式(上升沿触发、下降沿触发、边沿触发)并使能。各中断线的映射关系如下:
-
EXTI 线 0 - 15:对应外部 IO 口的输入中断。
-
EXTI 线 16:连接到 PVD 输出。
-
EXTI 线 17:连接到 RTC 闹钟事件。
-
EXTI 线 18:连接到 USB OTG FS 唤醒事件。
-
EXTI 线 19:连接到以太网唤醒事件。
-
EXTI 线 20:连接到 USB OTG HS(在 FS 中配置)唤醒事件。
-
EXTI 线 21:连接到 RTC 入侵和时间戳事件。
-
EXTI 线 22:连接到 RTC 唤醒事件。
系统配置控制器(SYSCFG)主要用于管理可执行代码存储区域的地址重映射、选择以太网 PHY 接口以及管理 GPIO 的外部中断线连接 。例如,SYSCFGEXTICR1 寄存器中的 EXTI0 [3:0] 位和 EXTI1 [3:0] 位分别用于映射 PA0 - PI0 和 PA1 - PI1 到相应的 EXTI 线。
3.2 配置流程
-
使能 GPIO 外设时钟:为使用的 GPIO 端口提供时钟,确保其正常工作。例如,若使用 PA0 和 PF9 端口,则需使能 GPIOA 和 GPIOF 的时钟:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOF, ENABLE);
-
配置外部中断占用的 GPIO:将用于外部中断的 GPIO 端口配置为输入模式,无需设置复用功能。例如,配置 PA0 为按键输入端口:
GPIO_InitTypeDef gpio_struct; gpio_struct.GPIO_Pin = GPIO_Pin_0; gpio_struct.GPIO_Mode = GPIO_Mode_IN; gpio_struct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &gpio_struct);
-
使能 SYSCFG 外设时钟:SYSCFG 用于处理外部中断映射,因此需要使能其时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
-
设置中断线与 IO 的映射关系:通过
SYSCFG_EXTILineConfig()
函数设置 GPIO 端口与 EXTI 中断线的映射。例如,将 PA0 映射到 EXTI0:SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
-
配置外部中断参数:使用
EXTI_InitTypeDef
结构体配置外部中断的相关参数,如中断线、模式、触发方式和使能状态等。例如,配置 EXTI0 为上升沿触发的中断:EXTI_InitTypeDef EXTI_InitStructure; EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
-
设置中断优先级(NVIC):通过
NVIC_InitTypeDef
结构体设置外部中断的优先级,包括抢占式优先级和子优先级,并使能中断通道。例如,设置 EXTI0 的优先级:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitTypeDef mynvic; mynvic.NVIC_IRQChannel = EXTI0_IRQn; mynvic.NVIC_IRQChannelPreemptionPriority = 0; mynvic.NVIC_IRQChannelSubPriority = 3; mynvic.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&mynvic);
-
编写中断处理函数:中断处理函数的名称必须与启动代码向量表中定义的一致。在函数中,需检测中断标志位,处理完中断后清除标志位。例如,EXTI0 的中断处理函数:
void EXTI0_IRQHandler() {
if(EXTI_GetFlagStatus(EXTI_Line0) == SET) {
// 处理中断事件,如设置标志位
led_flag = 1;
EXTI_ClearFlag(EXTI_Line0);
}
}
3.3 实战案例:按键控制 LED 闪烁
以按下按键使 D1 灯闪烁五次为例,完整代码如下:
#include "stm32f4xx.h"
uint8_t led_flag = 0;
// 配置GPIO端口
void config_port() {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOF, ENABLE);
// 配置LED1端口为推挽低速输出
GPIO_InitTypeDef gpio_struct;
gpio_struct.GPIO_Pin = GPIO_Pin_9;
gpio_struct.GPIO_Mode = GPIO_Mode_OUT;
gpio_struct.GPIO_OType = GPIO_OType_PP;
gpio_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
gpio_struct.GPIO_Speed = GPIO_Low_Speed;
GPIO_Init(GPIOF, &gpio_struct);
// 按键端口配置
gpio_struct.GPIO_Pin = GPIO_Pin_0;
gpio_struct.GPIO_Mode = GPIO_Mode_IN;
gpio_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &gpio_struct);
}
// 设置外部中断(EXTI与syscfg)
void config_exti() {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
// 配置NVIC
void config_nvic() {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitTypeDef mynvic;
mynvic.NVIC_IRQChannel = EXTI0_IRQn;
mynvic.NVIC_IRQChannelPreemptionPriority = 0;
mynvic.NVIC_IRQChannelSubPriority = 3;
mynvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&mynvic);
}
// 中断函数
void EXTI0_IRQHandler() {
if(EXTI_GetFlagStatus(EXTI_Line0) == SET) {
led_flag = 1;
EXTI_ClearFlag(EXTI_Line0);
}
}
// 简单延时函数
void delay() {
int i = 1000000;
while(i--);
}
int main() {
config_port();
config_exti();
config_nvic();
while(1) {
if(led_flag == 1) {
for(int i = 0; i < 5; i++) {
GPIO_SetBits(GPIOF, GPIO_Pin_9);
delay();
delay();
GPIO_ResetBits(GPIOF, GPIO_Pin_9);
delay();
delay();
}
led_flag = 0;
}
}
}
在上述代码中,首先通过 config_port()
函数配置了按键输入端口(PA0)和 LED 输出端口(PF9);接着在 config_exti()
函数中完成了外部中断的配置,将 PA0 映射到 EXTI0 并设置为上升沿触发;config_nvic()
函数则设置了 EXTI0 的中断优先级;最后在主函数中,根据按键触发的中断标志 led_flag
控制 LED 闪烁五次。
四、总结
本文全面深入地介绍了 STM32 中断系统中的中断基本概念、NVIC 的工作机制以及外部中断的详细原理和配置方法,并通过实际案例展示了外部中断在按键控制 LED 闪烁中的应用。掌握这些知识对于开发高效、稳定的 STM32 嵌入式系统至关重要。在实际项目中,开发者可根据具体需求灵活配置中断优先级、触发方式等参数,充分发挥 STM32 中断系统的强大功能,提升系统的性能和响应速度。希望本文能为广大嵌入式开发者在 STM32 中断开发方面提供有益的参考和帮助。