4. 实现启动代码之向量表
任务目标
在汇编启动文件中定义ARM Cortex-M处理器所需的初始中断向量表,并正确设置初始栈指针(MSP)。向量表首字为栈顶地址,第二个字为复位处理函数入口,后续为各异常/中断服务函数入口。
实现方案
本案例以STM32F103(Cortex-M3)为例,使用GNU汇编语法编写startup.s。向量表通过汇编指令.word将各处理函数地址按固定顺序排列,其中第一个.word必须指向栈顶(由链接脚本中的_estack符号提供)。复位处理函数Reset_Handler中仅执行一条跳转指令,将控制权转交给C语言入口main,完成最小启动闭环。
关键完整代码
/* startup.s - 最小启动文件:向量表 + 初始栈指针 */
.syntax unified
.cpu cortex-m3
.thumb
/* 栈顶地址由链接脚本定义,此处声明为外部符号 */
.global _estack
/* 导出向量表与复位入口,供链接器使用 */
.global g_pfnVectors
.global Reset_Handler
/* 向量表必须放置在0x00000000或0x08000000起始处(由链接脚本决定) */
.section .isr_vector, "a", %progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack /* 初始栈指针(MSP) */
.word Reset_Handler /* 复位向量 */
.word NMI_Handler /* NMI */
.word HardFault_Handler /* 硬错误 */
.word MemManage_Handler /* 内存管理错误(可选) */
.word BusFault_Handler /* 总线错误(可选) */
.word UsageFault_Handler /* 用法错误(可选) */
.word 0 /* 保留 */
.word 0 /* 保留 */
.word 0 /* 保留 */
.word 0 /* 保留 */
.word SVC_Handler /* 系统服务调用 */
.word DebugMon_Handler /* 调试监视器(可选) */
.word 0 /* 保留 */
.word PendSV_Handler /* 可挂起系统服务 */
.word SysTick_Handler /* 系统滴答定时器 */
/* 默认异常处理函数:死循环(弱定义,可被C文件覆盖) */
.section .text
.thumb_func
.weak NMI_Handler
.weak HardFault_Handler
.weak MemManage_Handler
.weak BusFault_Handler
.weak UsageFault_Handler
.weak SVC_Handler
.weak DebugMon_Handler
.weak PendSV_Handler
.weak SysTick_Handler
NMI_Handler:
HardFault_Handler:
MemManage_Handler:
BusFault_Handler:
UsageFault_Handler:
SVC_Handler:
DebugMon_Handler:
PendSV_Handler:
SysTick_Handler:
default_handler:
b default_handler
/* 复位处理:跳转至C入口 */
.thumb_func
.type Reset_Handler, %function
Reset_Handler:
ldr r0, =main
bx r0
.size Reset_Handler, .-Reset_Handler
/* 最小C入口(若工程无其他main定义) */
.section .text.main
.thumb_func
.type main, %function
main:
b main
.size main, .-main
链接脚本关键配置
向量表必须位于存储区起始地址。以下为最小链接脚本片段,将.isr_vector段放置在FLASH起始处,并定义栈顶地址_estack:
/* minimal.ld */
ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
_estack = ORIGIN(RAM) + LENGTH(RAM); /* 栈顶 = RAM最高地址 */
SECTIONS
{
.isr_vector :
{
KEEP(*(.isr_vector))
} > FLASH
.text :
{
*(.text*)
} > FLASH
}
编译与验证
- 编译:使用arm-none-eabi工具链,执行以下命令生成ELF文件:
arm-none-eabi-as -mcpu=cortex-m3 -mthumb startup.s -o startup.o
arm-none-eabi-ld -T minimal.ld startup.o -o startup.elf
- 检查向量表内容:使用objdump验证首两个字是否正确:
arm-none-eabi-objdump -s -j .isr_vector startup.elf
预期输出(地址与具体值取决于链接地址):
08000000 20005000 08000009 0800000d 0800000d ...
其中0x20005000为栈顶地址(RAM 20K末尾),0x08000009为复位入口地址(Thumb模式最低位为1)。
- 判定通过条件:
- 向量表首字等于
_estack的值(即0x20005000); - 第二个字为
Reset_Handler的地址且最低位为1(Thumb指令标志); - 后续异常入口地址均指向
default_handler(因弱定义且未覆盖)。
常见问题
- 栈指针未生效:确认链接脚本中
_estack符号已定义且被向量表引用,否则首字为0,上电即跑飞。 - 向量表位置错误:若使用Bootloader跳转,需确保应用向量表重定位(通过SCB->VTOR设置),本案例为裸机最小实现,向量表位于Flash起始。
- Thumb位缺失:函数地址最低位必须为1,否则硬件将进入错误状态。汇编器通常自动处理,但手动计算地址时需注意。