STM32F103嵌套向量中断控制器
一、STM32F103
中断介绍
1.1 什么是中断
中断:打断CPU
执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行;

举例:当你正在写作业时,做到一半又去吃饭,吃完饭后又回来接着原来的作业继续完成。
对于单片机来说,中断是指CPU
正在处理某个事件A
,发生了另一件事件B
,请求CPU
迅速去处理(中断发生);
CPU
暂时停止当前的工作(中断响应),转去处理事件B
(中断服务);
待CPU
处理事件B
完成后,再回到原来的事件A
(断点)继续执行,这一过程称之为中断。
中断的作用和含义:
- 实时控制:在确定的时间内对相应事件做出相应;例如:温度控制;
- 故障处理:检测到故障,需要第一时间进行处理;
- 数据传输:不确定数据何时会来,利用中断进行控制。
1.2 嵌套向量中断控制器(NVIC
)
NVIC
即嵌套向量中断控制器,全称Nested vectored interrupt controller
。属于是内核的器件,其作用是对STM32
中的中断进行管理,因为Contex M3
内核中的中断数量很多,当同时出现多个中断时,优先处理哪个中断?以及那些中断不处理等,都要靠NVIC
进行控制。
Contrex M3
内核都是支持256
个中断,其中包含了16
个内核异常和240
个外部中断,并且具有256
级的可编程中断设置。
但是对于ST
公司来说,用不了Contrex M3
内核中的所有中断以及中断优先级,进而对其进行了一定的裁剪。STM32F103
中共有:
10
个内核异常;60
个可屏蔽中断;(在STM32F107
系列才有68
个);
在中断的使用中有一个极其重要的一部分为中断服务函数(触发中断后,系统执行的部分,例如上文的吃饭过程)中断服务函数是中断的入口。
1.2.1 异常和中断向量表
定义一块固定的内存,以4字节对齐(32
位),用于存放中断服务函数的首地址,系统已经将中断服务函数定义好了,放在异常和中断向量表中,我们只需要进行调用即可。
STM32f103
异常和中断中断向量表如下:



其中前10
个中断为内核异常,剩下的60
个为可屏蔽中断,其编号为0~59
,优先级从7~66
。
优先级号越小,优先级越高。当表中的某处异常或中断被触发,程序计数器指针(PC
)将跳转到该异常或中断的地址处执行,该地址处存放这一条跳转指令,跳转到该异常或中断的服务函数处执行相应的功能。因此,异常和中断向量表只能用汇编语言编写。
1.3 工作原理

当中断被出发时,首先进入ICER
、ISER
寄存器,用于控制是否开对应的中断,打开的中断进入IPR
寄存器,进行中断优先级的判断,IPR
寄存器受AIRCR
寄存器控制,最后按照中断优先级依次进入CPU
被执行。
1.3.1 中断优先级
STM32
中断优先级基本概念:
- 抢占优先级(
pre
):高抢占优先级可以打断正在执行的低抢占优先级中断;即具有高抢占优先级的中断可以在具有低抢占优先级的中断处理过程中被响应,即中断嵌套; - 响应优先级(
sub
):当抢占优先级相同时,响应优先级高的先执行,但是不能相互打断; - 抢占优先级和响应优先级都相同的情况下,则按照他们在中断表中的排位顺序决定响应顺序;
- 数值越小,表示优先级越高。
1.3.2 中断优先级分组
AIRCR
寄存器位[10:8]
用于控制优先级的分组,但是只取其中的五组优先级分组;
IPR
寄存器位[7:4]
用于控制中断的优先级,包括抢占优先级与响应优先级;
如下表所示:
优先级分组 | AIRCR[10:8] | IPR[7:4]分配 | 分配结果 |
---|---|---|---|
0 | 111 | None:[7:4] | 抢占优先级(0位、0级) 响应优先级(4位、16级) |
1 | 110 | [7]:[6:4] | 抢占优先级(1位、2级) 响应优先级(3位、8级) |
2 | 101 | [7:6]:[5:4] | 抢占优先级(2位、4级) 响应优先级(2位、4级) |
3 | 100 | [7:5]:[4] | 抢占优先级(3位、8级) 响应优先级(1位、2级) |
4 | 011 | [7:4]:None | 抢占优先级(4位、16级) 响应优先级(0位、0级) |
假定设置中断优先级分组为 2,每个中断的IPR
寄存器的高四位中的最高2位是抢占优先级,低 2位是响应优先级。每个中断,你可以设置抢占优先级为0~3
,响应优先级为0-3
。假设我们设置:
- 中断3的抢占优先级为 2,响应优先级为 1;
- 中断6的抢占优先级为 3,响应优先级为0;
- 中断7的抢占优先级为 2,响应优先级为 0;
那么这3个中断的优先级顺序为: 中断 7>中断 3>中断 6。 上面例子中的中断3和中断7都可以打断中断6的中断。而中断7和中断 3 却不可以相互打断!
二、中断相关寄存器
NVIC
相关的寄存器有很多,但是重要的有以下几个,也是需要进行掌握的:
名称 | 位数 | 个数 | 作用 |
---|---|---|---|
中断使能寄存器(ISER) | 32 | 8 | 每一位控制一个中断 |
中断失能寄存器(ICER) | 32 | 8 | 每一位控制一个中断 |
中断挂起控制寄存器组(ISPR) | 32 | 8 | 每一位控制一个中断 |
中断解挂起控制寄存器组(ICPR) | 32 | 8 | 每一位控制一个中断 |
应用程序中断及复位控制寄存器(AIRCR) | 32 | 1 | 位[10:8]控制中断优先级分组 |
中断优先级寄存器(IPR) | 8 | 240 | 8个位对应一个中断,而STM32只使用高4位 |
ISER
与ICER
寄存器共有32*8
=256
,用于控制240
个中断的打开与关闭。
AIRCR
寄存器,位10
、9
、8
三位用于控制优先级的分组,三位共2*2*2=8
种,取其中的5组作为中断优先级的分组情况;
IPR
寄存器,用于控制中断的优先级,包括抢占优先级与响应优先级,高4位控制,至于哪几位控制抢占,哪几位控制响应,由AIRCR
寄存器说了算。
2.1 中断使能寄存器(ISER
)

0Cortex M3
内核支持256
个中断,这里用8
个32
位寄存器(即8
个ISER
)来控制,每个位控制一个中断。
但是STM32
的可屏蔽中断最多只有68
个(互联型),所以就是三个(ISER[0~2]
]),总共可以表示96
个中断,而STM32
只用了其中的前68
位;
ISER[0]
的bit0~31
分别对应中断0~31
;ISER[1]
的bit0~32
对应中断32~63
;ISER[2]
的bit0~3
对应中断64~67
;
这样总共68
个中断就分别对应上了。 若使能某个中断,必须设置相应的ISER
位为1 。具体某一位代表哪个中断,可以参前面介绍的异常和中断向量表。
2.2 中断失能寄存器(ICER
)

该寄存器组与ISER
的作用恰好相反,是用来清除某个中断的使能的。若除能某个中断,必须设置相应的ICER
位为1。
2.3 中断挂起控制寄存器组(ISPR
)

通过置1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。
2.4 中断解挂起控制寄存器组(ICPR
)

通过置 1,可以将挂起的中断解挂。
2.5 应用程序中断及复位控制寄存器(AIRCR
)

STM32
将中断分为5个组,组0-4,该分组由AIRCR
的[10:8]
决定,最终影响到IPR
的[7:4
]位分配情况。具体参考前面介绍的中断优先级分组。
2.6 中断优先级寄存器(IPR
)

IPR
是控制中断优先级的一个非常重要的寄存器,STM32
的中断分组与这个寄存器组密切相关。IPR
寄存器组由240
个8bit
的寄存器组成,每个可屏蔽中断占用8bit
,这样总共可以表示240
个可屏蔽中断。
STM32
只用到了其中的68
个,IPR[67]~IP[0
]分别对应中断 67~0
。而每个可屏蔽中断占用的8bit
并没有全部使用,而是只用了高4
位。这4
位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据AIRCR
中的中断优先级分组设置来决定。
三、中断配置源码
3.1 系统启动
无论是是何种MCU
,从简单的51
,MSP430
,到ARM9
,ARM11
,A7
都必须有启动文件,因为对于嵌入式开发,绝大部分情况都是使用C语言,而C
语言一般都是从main
函数开始,但是对于MCU
来说,他是如何找到并执行main
函数的,就需要用到“启动文件”。
STM32F103
的启动文件为stm32f10x_vectors.s
,启动文件是使用机器认识的汇编语言,经过一些必要的配置,最终能够调用main
函数,使得用户程序能够在MCU
上正常运行起来的必备文件。
本文以MDK
环境下的stm32f10x_vectors.s
为模板讲解,不同编译器下的启动文件不同,此外不同编译器的伪汇编指令是不一样的,由于需要和gcc
编译器的伪汇编指令区分开来。
3.1.1 栈初始化
代码的开始,就是开辟栈空间,用于局部变量,函数调用,函数参数等;
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
汇编伪指令讲解:
EQU
:宏定义的伪指令,相当于等于,类似于C
语言中的宏定义define
;AREA
: 告诉汇编器汇编一个新的代码段或者数据段;SPACE
:用于分配一定大小的内存空间,单位字节;
因此上面的代码结合起来就是分配一个长度为1kb
的栈空间,命名为STACK
,并且不初始化,可读可写,按照2的3次方(8个字节)对齐开辟。
__initial_sp
只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于C
语言中的地址概念。地址仅仅表示存储空间的位置。
__initial_sp
紧挨着SPACE
语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。
3.1.2 堆初始化
接下来是开辟堆空间,主要用于动态内存分配,使用malloc
,calloc
等函数分配的变量空间是在堆上的;
Heap_Size EQU 0x00000400
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
同样分配一个长度为1kb
的堆空间,命名为HEAP
,并且不初始化,可读可写,按照2的3次方(8个字节)对齐开辟。
标号_heap_base
表示堆的起始地址,标号__heap_limit
紧挨着SPACE
语句放置,表示堆的结束地址,堆是由低向高生长的(与栈相反)。
3.1.3 导入异常和中断处理函数
接着是导入外部文件定义的异常和中断处理函数,为建立异常和中断向量表做准备;
THUMB
PRESERVE8
; Import exceptions handlers
IMPORT NMIException
IMPORT HardFaultException
IMPORT MemManageException
IMPORT BusFaultException
IMPORT UsageFaultException
......
IMPORT FLASH_IRQHandler
IMPORT RCC_IRQHandler
IMPORT EXTI0_IRQHandler
IMPORT EXTI1_IRQHandler
IMPORT EXTI2_IRQHandler
IMPORT EXTI3_IRQHandler
IMPORT EXTI4_IRQHandler
......
IMPORT TIM1_BRK_IRQHandler
IMPORT TIM1_UP_IRQHandler
IMPORT TIM1_TRG_COM_IRQHandler
IMPORT TIM1_CC_IRQHandler
IMPORT TIM2_IRQHandler
IMPORT TIM3_IRQHandler
IMPORT TIM4_IRQHandler
IMPORT I2C1_EV_IRQHandler
IMPORT I2C1_ER_IRQHandler
......
IMPORT DMA2_Channel1_IRQHandler
IMPORT DMA2_Channel2_IRQHandler
IMPORT DMA2_Channel3_IRQHandler
IMPORT DMA2_Channel4_5_IRQHandler
;*******************************************************************************
; Fill-up the Vector Table entries with the exceptions ISR address
;*******************************************************************************
; 定义一个数据段 命名为RESET,DATA表示数据, READONLY表示可读
AREA RESET, DATA, READONLY
EXPORT __Vectors
其中:
PRESERVE8
表示指定当前文件的推栈按照8字节对齐;THUMB
表示后面指令位THUMB
指令;IMPORT
用于声明外部符号,告诉汇编器这些符号来自其他模块或库,需要在链接时进行解析。这种机制使得汇编程序能够调用外部库中的函数或访问外部定义的全局变量,实现代码的模块化和复用;EXPORT
:声明一个标号具有全局属性,用于将符号暴露给当前源文件以外的其他文件。
比如我们在《STM32F103 GPIO
和串口配置》中介绍的串口1接收中断处理函数USART1_IRQHandler
,其实现位于usart.c
文件中。
3.1.4 异常和中断向量表
Cortex M3
内核规定起始地址必须存放栈顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3
内核复位后,会自动从起始地址的下一个32
位空间取出复位中断入口向量,跳转执行复位中断服务程序。
Cortex-M3
内核固定了异常和中断向量表的位置, 但是起始地址是可变化的。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler
DCD NMIException
DCD HardFaultException
DCD MemManageException
DCD BusFaultException
DCD UsageFaultException
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVCHandler
DCD DebugMonitor
DCD 0 ; Reserved
DCD PendSVC
DCD SysTickHandler
DCD WWDG_IRQHandler
DCD PVD_IRQHandler
DCD TAMPER_IRQHandler
DCD RTC_IRQHandler
DCD FLASH_IRQHandler
DCD RCC_IRQHandler
DCD EXTI0_IRQHandler
DCD EXTI1_IRQHandler
DCD EXTI2_IRQHandler
DCD EXTI3_IRQHandler
DCD EXTI4_IRQHandler
DCD DMA1_Channel1_IRQHandler
DCD DMA1_Channel2_IRQHandler
DCD DMA1_Channel3_IRQHandler
DCD DMA1_Channel4_IRQHandler
DCD DMA1_Channel5_IRQHandler
DCD DMA1_Channel6_IRQHandler
DCD DMA1_Channel7_IRQHandler
DCD ADC1_2_IRQHandler
DCD USB_HP_CAN_TX_IRQHandler
DCD USB_LP_CAN_RX0_IRQHandler
DCD CAN_RX1_IRQHandler
DCD CAN_SCE_IRQHandler
DCD EXTI9_5_IRQHandler
DCD TIM1_BRK_IRQHandler
DCD TIM1_UP_IRQHandler
DCD TIM1_TRG_COM_IRQHandler
DCD TIM1_CC_IRQHandler
DCD TIM2_IRQHandler
DCD TIM3_IRQHandler
DCD TIM4_IRQHandler
DCD I2C1_EV_IRQHandler
DCD I2C1_ER_IRQHandler
DCD I2C2_EV_IRQHandler
DCD I2C2_ER_IRQHandler
DCD SPI1_IRQHandler
DCD SPI2_IRQHandler
DCD USART1_IRQHandler
DCD USART2_IRQHandler
DCD USART3_IRQHandler
DCD EXTI15_10_IRQHandler
......
以上代码定义了处理器复位后各个异常和中断对应的处理函数。异常和中断向量表的第一个元素存放的是__initial_sp
栈顶的指针;第二个元素存放的是Reset_Handler
函数入口地址。
3.1.5 Reset_Handler
系统上电或者复位后后,首先执行的代码就是Reset_Handler
:
; Reset handler routine
Reset_Handler PROC
EXPORT Reset_Handler
IF DATA_IN_ExtSRAM == 1
; FSMC Bank1 NOR/SRAM3 is used for the STM3210E-EVAL, if another Bank is
; required, then adjust the Register Addresses
; Enable FSMC clock
LDR R0,= 0x00000114
LDR R1,= 0x40021014
STR R0,[R1]
; Enable GPIOD, GPIOE, GPIOF and GPIOG clocks
LDR R0,= 0x000001E0
LDR R1,= 0x40021018
STR R0,[R1]
; SRAM Data lines, NOE and NWE configuration
; SRAM Address lines configuration
; NOE and NWE configuration
; NE3 configuration
; NBL0, NBL1 configuration
LDR R0,= 0x44BB44BB
LDR R1,= 0x40011400
STR R0,[R1]
LDR R0,= 0xBBBBBBBB
LDR R1,= 0x40011404
STR R0,[R1]
LDR R0,= 0xB44444BB
LDR R1,= 0x40011800
STR R0,[R1]
LDR R0,= 0xBBBBBBBB
LDR R1,= 0x40011804
STR R0,[R1]
LDR R0,= 0x44BBBBBB
LDR R1,= 0x40011C00
STR R0,[R1]
LDR R0,= 0xBBBB4444
LDR R1,= 0x40011C04
STR R0,[R1]
LDR R0,= 0x44BBBBBB
LDR R1,= 0x40012000
STR R0,[R1]
LDR R0,= 0x44444B44
LDR R1,= 0x40012004
STR R0,[R1]
; FSMC Configuration
; Enable FSMC Bank1_SRAM Bank
LDR R0,= 0x00001011
LDR R1,= 0xA0000010
STR R0,[R1]
LDR R0,= 0x00000200
LDR R1,= 0xA0000014
STR R0,[R1]
ENDIF
IMPORT __main
LDR R0, =__main
BX R0
ENDP
PROC
、ENDP
这一对伪指令把程序分为若干个过程,使程序结构更加清晰。
_main
标号表示C/C++
标准实时库函数里的一个初始化子程序main
的入口地址。该程序的一个主要作用是初始化堆栈(跳转_user_initial_stackheap
标号进行初始化堆栈),并初始化映像文件,最后跳转到C
程序中的main
函数。这也正解释了为什么所有的C程序必须有一个main
函数作为程序的起点,因为这是由C/C++
标准实时库所规定的。
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
3.1.6 地址空间分布
我们打开我们的项目,查看Options for target
:

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了