当芯片接收到中断信号时,是如何精准找到对应的处理函数?问题的答案同样也藏在经常被我们忽略掉的启动文件当中。在上一篇内存篇中,我们探索了堆栈的分配与RAM的布局。今天,我们来继续深入启动文件当中另一个核心部分——中断向量表。
在我们的启动文件当中,肯定会存在以下一部分的内容。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
;....其余中断函数
__Vectors_End
这段看似简单的代码,实际上构建了整个中断系统的核心。在深入分析之前,我们首先要掌握一个关键的汇编指令:DCD——它是构建整个中断向量表的基石。
DCD(Data Constant Double-word)是ARM汇编器中的一个伪指令,用于在当前位置分配一个或多个32位(4字节)的字,并用指定的值初始化这些字。可以这样理解:使用DCD的过程,类似于在C语言中定义并初始化一个全局数组。
DCD的基本语法:
标号 DCD 表达式1, 表达式2, …, 表达式N
- 标号:代表这段内存区域的起始地址,相当于给这个位置起了个名字。
- 表达式:要存储的具体数值,可以是地址、常数或符号
现在让我们用DCD的知识,重新审视启动文件中的向量表定义。
第一行建立栈环境:
__Vectors DCD __initial_sp
这行代码的含义是:‘
- 在
__Vectors标识的地址处,分配4字节空间 - 在这个空间中存储
__initial_sp的值(栈顶地址)
那么这个__Vectors的地址在哪里?答案在编译后生成的Map文件中:

可以看到,__Vectors对应地址就是0x08000000——而这个正是flash的起始地址!同样也是stm32第一次取指的地方。
假设栈顶地址__initial_sp = 0x20000400,那么这行代码的实际效果就是:
在Flash的0x08000000地址处,存储数值0x20000400。
第二行设定程序起点:
DCD Reset_Handler
这行代码有两个关键点:
- 在
0x08000004地址处(前一个DCD + 4字节) - 存储
Reset_Handler函数的地址
同样Reset_Handler的地址也可以到.map文件里找得到。

按照这个规律,整个向量表在Flash中的布局如下:
地址 数据(32位,小端格式)
0x08000000: 0x20000400 ; __initial_sp的值
0x08000004: 0x08000101 ; Reset_Handler函数的地址
0x08000008: 0x0800018D ; NMI_Handler函数的地址
0x0800000C: 0x08000191 ; HardFault_Handler函数的地址
;注意:由于STM32采用小端字节序,实际存储时低位字节在前。
既然把这些地址都记录下来了,那么硬件是如何使用这张表,来找到对应的中断处理函数呢?
上电启动过程
当芯片上电复位时,Cortex-M内核硬件自动执行以下操作:
- 从0x08000000读取值 → 存入MSP(主栈指针寄存器)
- 建立C语言的栈环境
- 从0x08000004读取值 → 存入PC(PC指向哪,程序就执行哪)
- 跳转到
Reset_Handler函数开始执行
- 跳转到
- 执行
Reset_Handler→ 初始化系统,最终调用main()
按照这么取指赋值的方式,处理器就明白了每个处理函数的具体地址。
中断响应过程
当中断发生时(比如按键触发EXTI0中断)
- 硬件自动暂停当前程序
- 根据中断号计算向量表位置
- EXTI0的中断号是6
- 向量表位置 = 0x08000000 + (6 + 16) × 4 = 0x08000058
- 从0x08000058读取值 → 存入PC
- 跳转到
EXTI0_IRQHandler函数
- 跳转到
- 执行中断处理程序
- 返回原程序继续执行
理解了硬件如何查找中断函数后,我们自然会想到一个问题:如果某个中断我暂时不需要处理,该怎么办?答案就是WEAK——弱符号机制。我们可以看如下的一段代码:
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B . ;默认实现,死循环
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B . ;默认实现,死循环
ENDP
;.....其他中断处理程序也采用相同方式定义
这个机制同c语言的__weak关键字是同样一个道理,他允许被开发者重新定义。当存在新的定义之后,原本的带有weak修饰的部分就会被覆盖掉。
假设我们需要处理按键中断,但不需要处理窗口看门狗中断:
// 在C文件中重写需要的中断函数
void EXTI0_IRQHandler(void) {
EXTI->PR = EXTI_PR_PR0; // 清除中断标志
handle_button_press(); // 实际业务逻辑
}
// 不需要重写WWDG_IRQHandler,使用默认实现
在这种情况下:
- EXTI0中断:由于我们提供了自定义实现,系统会调用我们编写的
EXTI0_IRQHandler函数 - 看门狗中断:由于没有自定义实现,系统使用启动文件中默认的死循环处理
这样的设计架构也避免未使用的中断不会导致程序跑飞,而是进入可控的死循环。
