4、启动文件中的向量表定义:使用汇编语言在启动文件(startup_xx.s)中定义初始向量表
中断向量表是 ARM Cortex-M 处理器启动与异常处理的核心数据结构。它必须位于存储器的起始地址(通常是 0x00000000 或通过 VTOR 寄存器重映射后的基地址),且严格遵循 Cortex-M 规定的向量表布局。在启动文件 startup_xx.s 中,我们使用汇编伪指令和标签来定义这张表,确保每个异常入口地址被正确放置。
4.1 向量表的内存布局与对齐规则
Cortex-M 向量表的前 16 个向量为系统异常(包括初始堆栈指针和复位向量),之后为外部中断向量。每个向量占用 4 字节(一个字),因此整个向量表必须 4 字节对齐。若启用 VTOR 重定位,则要求表基地址按 2(N+1) 字节对齐(N 为中断源数量,通常取 512 字节对齐)。
地址 % (2^(中断源数+1)) == 0。对于 240 个外部中断的系统,对齐要求为 512 字节。
4.2 汇编语言定义向量表的标准模板
在 startup_xx.s 中,向量表通常定义在名为 __Vectors 的段(section)中。以下是一个典型的 Cortex-M4 启动文件向量表定义模板:
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack /* 0x0000: 栈顶地址 */
.word Reset_Handler /* 0x0004: 复位向量 */
.word NMI_Handler /* 0x0008: NMI */
.word HardFault_Handler /* 0x000C: 硬错误 */
.word MemManage_Handler /* 0x0010: 内存管理错误 */
.word BusFault_Handler /* 0x0014: 总线错误 */
.word UsageFault_Handler /* 0x0018: 使用错误 */
.word 0 /* 0x001C: 保留 */
.word 0 /* 0x0020: 保留 */
.word 0 /* 0x0024: 保留 */
.word 0 /* 0x0028: 保留 */
.word SVC_Handler /* 0x002C: SVC 系统调用 */
.word DebugMon_Handler /* 0x0030: 调试监视器 */
.word 0 /* 0x0034: 保留 */
.word PendSV_Handler /* 0x0038: PendSV */
.word SysTick_Handler /* 0x003C: SysTick 定时器 */
/* 外部中断向量表(以 STM32F4 为例) */
.word WWDG_IRQHandler /* 0x0040: 窗口看门狗 */
.word PVD_IRQHandler /* 0x0044: PVD 检测 */
.word TAMP_STAMP_IRQHandler /* 0x0048: 时间戳 */
.word RTC_WKUP_IRQHandler /* 0x004C: RTC 唤醒 */
.word FLASH_IRQHandler /* 0x0050: Flash 全局 */
.word RCC_IRQHandler /* 0x0054: RCC 全局 */
.word EXTI0_IRQHandler /* 0x0058: EXTI 线0 */
.word EXTI1_IRQHandler /* 0x005C: EXTI 线1 */
/* ... 后续中断按芯片手册顺序填充 ... */
.word DMA2_Stream7_IRQHandler /* 最后一个外部中断 */
.word 0 填充保留向量位置,确保后续向量地址偏移正确。不要跳过或删除保留项,否则会导致中断映射错乱。
4.3 向量表段属性与链接器脚本配合
向量表必须放置在链接器脚本指定的起始地址。段声明 .section .isr_vector,"a",%progbits 中的 "a" 表示可分配(allocatable),%progbits 表示段包含数据。链接器脚本中通常这样处理:
/* 链接器脚本片段 (linker.ld) */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* 保留向量表段 */
. = ALIGN(4);
} > FLASH
.text :
{
/* 代码段 */
} > FLASH
/* ... 其他段 ... */
}
链接器通过 KEEP() 确保向量表不被优化移除,并放置在 FLASH 起始地址(此处为 0x08000000,对应 CPU 启动地址 0x00000000 的别名区域)。
4.4 弱定义与默认异常处理函数
为避免未实现的中断处理函数导致链接错误,启动文件通常为每个异常提供 弱定义(weak alias),指向一个无限循环的默认处理函数:
.weak NMI_Handler
.type NMI_Handler, %function
NMI_Handler:
b NMI_Handler
.weak HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
b HardFault_Handler
/* 外部中断弱定义示例 */
.weak EXTI0_IRQHandler
.type EXTI0_IRQHandler, %function
EXTI0_IRQHandler:
b EXTI0_IRQHandler
4.5 向量表定义的关键参数对比
| 参数 | 说明 | 典型值/要求 |
|---|---|---|
| 段名 | 向量表所在的 section 名称 | .isr_vector |
| 对齐 | 向量表基地址对齐要求 | 4 字节(无 VTOR)/ 512 字节(VTOR 重定位) |
| 第一个向量 | 初始主堆栈指针(MSP)值 | _estack(RAM 顶端地址) |
| 第二个向量 | 复位后第一条指令地址 | Reset_Handler |
| 保留向量 | 必须用 .word 0 填充 |
偏移 0x001C~0x0028, 0x0034 |
| 弱定义 | 默认处理函数(无限循环) | 每个异常入口一个 .weak 标签 |
4.6 向量表安装后的验证方法
在调试器或仿真器中,可以通过以下方式验证向量表是否正确安装:
- 检查存储器内容: 读取 FLASH 起始地址(如 0x08000000)的前 4 字节,确认等于
_estack的值。 - 检查复位向量: 读取地址 0x08000004 的内容,确认指向
Reset_Handler的地址(注意 Cortex-M 使用 Thumb 模式,地址最低位应为 1)。 - 检查 VTOR 寄存器: 若使用向量表重定位,读取 SCB->VTOR(地址 0xE000ED08)的值,确认与链接器分配的向量表基地址一致。
- 触发测试异常: 软件触发 SVC 或 PendSV,观察 PC 是否跳转到对应的处理函数入口。
4.7 常见错误与排查
典型错误 2: 保留向量位置被跳过,导致后续中断偏移错误。症状:触发某个中断时,实际调用的是另一个中断的处理函数。
典型错误 3: 弱定义函数与用户函数同名但未使用
.weak,导致链接冲突。症状:链接器报多重定义错误。
通过严格遵循上述汇编模板、对齐规则和弱定义机制,可以确保中断向量表在启动文件中正确定义,为后续的异常处理初始化打下坚实基础。