STM32中断系统
1.1 中断的概念
中断指在程序正常执行过程中,因某个特定事件发生,使CPU暂停当前任务,转去执行专门的服务程序,执行完毕恢复断点处的程序执行,有点像给程序打了一个断点调试。
中断源就是引发中断的“原因”或“事件”。
- 可能来自 外部硬件(例如定时器溢出、串口接收到数据、按键触发、外部引脚电平变化等)。
- 也可能来自 内部异常/软件请求(例如除零错误、软件触发的中断指令),内部的中断有时候也叫异常。
断点指当中断发生时,CPU会自动保存当前正在执行的指令地址(也就是下一条要执行的位置),这个保存下来的位置就是所谓的“断点”。这样,当中断处理完毕后,CPU才能“回到刚才被打断的地方继续执行”。
中断服务程序(ISR):执行一段专门的代码去处理事件,这段程序就叫中断服务程序
正在执行中断程序的时候,这个时候有可能被另外一个中断源给中断,CPU转而去执行另外一个中断源的中断处理程序,这叫中断嵌套。
中断B能否打断中断A,要看他们的优先级,优先级高的可以打断优先级低的,优先级低的无法打断优先级高的。
1.2 为什么需要中断
比如我们要检测按键是否按下,如果没有中断,则需要循环的方式不断的去检测按键对应的IO口的电平,这是比较耗费CPU的时间的。如果要检测的更多的话,CPU有可能会导致阻塞。
- 节省cpu资源
使用中断后,CPU只在事件发生时才被打断处理,平时不用关心。
避免阻塞
如果系统要同时检测多个外设(按键、串口、定时器、传感器),用轮询会占满CPU时间,主程序可能没机会执行其它任务。中断方式下,主程序专心做自己的事,外设自己触发中断来通知CPU
提高实时性
有些事件(比如串口接收数据、定时器溢出)需要及时处理,否则可能丢数据。中断保证CPU能快速响应这些关键事件,而不是“等轮询到的时候才处理”
没有中断,单片机像一个人“盯着手机等通知”;
有了中断,单片机就像“有人按门铃才去开门”,效率高很多。
1.3 STM32的中断
Cortex-M3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。一般情况下,芯片厂商会对Cortex-M3的中断进行裁剪。
STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
STM32F103系列70个中断有10个内核中断和60个可编程的外部中断。
下面的列表中,灰色背景的是内部中断(或者异常),其他的为外部中断。
1.4 STM32的中断体系架构
1.4.1 NVIC
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是 ARM Cortex-M 内核里一个非常核心的模块。它的主要作用就是——帮CPU管理所有中断。
作用:
- 统一管理中断源
- Cortex-M 系列单片机里,中断源很多(外设中断、系统异常、硬Fault等)。
- NVIC 就像“中断的大管家”,负责接收这些中断请求,决定哪个能打断CPU。
- 中断优先级管理
- 每个中断都可以分配一个优先级。
NVIC根据优先级决定:
- 谁可以先执行(高优先级先执行)。
- 是否允许“嵌套”(在执行某个中断时,能否被更高优先级的中断再次打断)。
- 向量表管理
- 每个中断有一个“入口地址”,即 ISR 的起始位置。
- NVIC 根据中断号自动找到向量表里的入口地址,跳转到对应中断服务程序。
- 屏蔽/使能中断
- 通过 NVIC 的寄存器,软件可以随时开启或关闭某个中断。
- 比如临时不想处理某个外设的中断,可以直接在 NVIC 里屏蔽。
在 Cortex-M 系列内核里,NVIC 统一管理三类“中断/异常”来源:
1.4.2 AFIO
GPIOA-G分别有16个引脚,16条线,AFIO将GPIOA-G端口的相同编号引脚组合成一根线接到EXTI,比如A0/B0/C0...接到EXTI0。
一条 EXTI 线一次只能选择一个端口,不能同时由 PA0 和 PB0 触发。写程序时,需要在程序里通过 AFIO 寄存器配置,告诉 EXTI 哪个 GPIO 引脚来触发中断,否则 EXTI 不知道要监听哪一条线。例如:EXTI0 默认接 PA0,如果写入 AFIO_EXTICR1 指定 PB0,就改为由 PB0 触发。
AFIO 模块主要干三件事:
- 把 GPIO 引脚接到 EXTI 线(外部中断配置)。
- 把 外设功能映射到不同引脚(重映射)。
- 把 调试端口的管脚释放出来(JTAG/SWD 配置)。
- 外部中断 (EXTI) 配置
- 作用:决定某个 EXTI 线由哪个 GPIO 引脚来触发。
- 实现:通过
AFIO_EXTICR[1..4]
寄存器配置。 特点:
- 每条 EXTI 线(EXTI0~EXTI15)只能对应到某个端口的相同编号引脚。
- 比如:EXTI0 可以选择 PA0 / PB0 / PC0 / …,但一次只能选一个。
- 复用功能重映射 (Peripheral Remap)
- 作用:允许改变某些外设的管脚分布,避免引脚冲突或优化 PCB 布局。
- 实现:通过
AFIO_MAPR
、AFIO_MAPR2
寄存器配置。 例子:
- USART1 默认 TX/RX = PA9 / PA10
- 重映射后 TX/RX = PB6 / PB7
- TIM2 通道可以映射到不同的 IO。
- 调试端口配置 (JTAG/SWD Remap)
- 作用:释放调试接口占用的引脚,让它们当普通 GPIO 使用。
- 实现:在
AFIO_MAPR
中配置SWJ_CFG
位。 选项:
- 全功能 JTAG-DP + SW-DP(默认,占用 5 根管脚)。
- 仅 SW-DP(占用 2 根管脚,释放 PB3、PB4、PA15)。
- 完全关闭 JTAG/SWD(释放所有调试引脚,但调试功能不可用)。
1.4.3 EXTI
AFIO = 信号路由器:负责把 GPIO 引脚“接”到某条 EXTI 线。
EXTI = 中断检测器:负责检测这条 EXTI 线上有没有触发条件,并产生中断请求。它只知道有中断线 EXTI0 ~ EXTI15,但是不知道这些线接在哪个 GPIO 引脚上。
EXTI根据配置的触发方式(上升沿/下降沿/双沿),判断是否触发中断/事件;
AFIO:决定“谁来汇报”
EXTI:决定“什么时候汇报”
NVIC:决定“怎么打断皇帝(CPU)去处理”
1.4.4 NVIC中断优先级
NVIC为了方便管理中断,可以通过软件给每个中断设置优先级。NVIC用4个位来控制优先级,值小的优先级高。把优先级分为两种:抢占优先级和响应优先级。
规则:
- 优先级值越小,优先级越高。
- 如果不设置优先级,则默认优先级为0。
- 先比较抢占优先级。抢占优先级高的可以打断抢占优先级低的。
- 若抢占优先级一样,再比较响应优先级。但是响应优先级不会导致中断嵌套。
- 若抢占优先级一样的同时挂起,则优先处理响应抢占优先级高的。
- 若挂起的优先级(抢占和响应)都一样,则查找中断向量表,值小的先响应。
NVIC对优先级分了5组,在程序中先对中断进行分组,而且分组只能分一次,若多次分,只有最后一次生效。下图[7:4]意思是7-4位:
NVIC_SetPriorityGrouping(n) | PRIGROUP | 抢占优先级bits | 响应优先级bits | 抢占优先级数(Group) | 响应优先级数(Sub) | 解释 |
---|---|---|---|---|---|---|
4 | 0b011 | [7:4] | None | 16 | None | 4 位全部作为抢占优先级,没有子优先级。中断可以被 16 级抢占。 |
3 | 0b100 | [7:5] | [4] | 8 | 2 | 3 位抢占优先级 + 1 位子优先级。可以有 8 级抢占优先级,每级有 2 级响应顺序。 |
2 | 0b101 | [7:6] | [5:4] | 4 | 4 | 2 位抢占优先级 + 2 位子优先级。 |
1 | 0b110 | [7] | [6:4] | 2 | 8 | 1 位抢占优先级 + 3 位子优先级。 |
0 | 0b111 | None | [7:4] | None | 16 | 全部 4 位作为子优先级,没有抢占能力。中断之间不会互相打断。 |
NVIC 中通过 SCB->AIRCR 寄存器 的 PRIGROUP 位来配置中断优先级的划分方式。
PRIGROUP 决定 抢占优先级(Group Priority) 和 响应优先级(Subpriority) 的位数分配。
中断 A 抢占优先级比 B 高时,A 可以打断 B。
1.4.5 外部中断控制器EXTI
如下图,注意请求挂起寄存器被自动置1后,中断执行完手动置0
整体流程
1.5 配置片外中断步骤
1.5.1 使能 GPIO 时钟
首先需要使能对应 GPIO 端口的时钟:
// 例如使能 GPIOA 时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
1.5.2 配置 GPIO 为输入模式
// 以 PA0 为例,配置为上拉输入
GPIOA->CRL &= ~(0xF << (0*4)); // 清除 PA0 的配置
GPIOA->CRL |= (0x8 << (0*4)); // 配置为上拉/下拉输入模式
GPIOA->ODR |= (1 << 0); // 上拉
1.5.3. 配置 AFIO 时钟和 EXTI 线路
// 使能 AFIO 时钟
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
// 将 GPIO 引脚连接到 EXTI 线
// 例如 PA0 连接到 EXTI0
AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0; // 清除原有设置
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA; // PA0 连接到 EXTI0
1.5.4. 配置 EXTI 触发方式
// 配置触发方式(上升沿、下降沿或双边沿)
EXTI->RTSR |= EXTI_RTSR_TR0; // 使能 EXTI0 上升沿触发
EXTI->FTSR |= EXTI_FTSR_TR0; // 使能 EXTI0 下降沿触发
// 如果只需要一种触发方式,只需设置其中一个
1.5.5. 使能 EXTI 中断
EXTI->IMR |= EXTI_IMR_MR0; // 不屏蔽 EXTI0 中断
1.5.6. 配置 NVIC
// 设置优先级
NVIC_SetPriority(EXTI0_IRQn, 0); // 设置优先级为0(最高)
// 使能中断
NVIC_EnableIRQ(EXTI0_IRQn); // 使能 EXTI0 中断
1.5.7. 编写中断服务函数
void EXTI0_IRQHandler(void)
{
if (EXTI->PR & EXTI_PR_PR0) // 检查是否是 EXTI0 中断
{
// 处理中断
EXTI->PR = EXTI_PR_PR0; // 清除中断标志
}
}
1 游客 2025-09-01 11:26 回复
1
1 游客 2025-09-01 12:01 回复
1
@@NHZu2 游客 2025-09-01 12:02 回复
1
1 游客 2025-09-01 12:03 回复
1
1 游客 2025-09-01 12:05 回复
1
1 游客 2025-09-01 12:06 回复
1