.s启动文件详解
一、作用与背景:
MCU 上电或复位后,CPU 会从固定地址开始执行。
此处存放的是 向量表(Vector Table),第一个条目是栈顶地址(SP),第二个是复位处理函数地址(PC)。
启动文件负责配置这些初始值,并引导程序进入 C 语言环境。
流程:上电 → 执行启动代码 → 设置 SP 和 PC → 填充中断向量表 → main()
二、为什么修改:
(1)修改栈空间大小:
启动文件中定义
Stack_Size/Heap_Size(如EQU 0x00000400),修改后影响链接时分配的内存。更推荐修改链接脚本(
.ld):现代项目常将堆栈大小定义在链接脚本中,避免与启动文件冲突。
(2)中断向量表重定位
Bootloader 需将向量表移至 RAM(如
SCB->VTOR = RAM_ADDR),需在Reset_Handler中添加重定位代码。
(3)自定义复位流程
需在
SystemInit()前执行关键操作(如关闭看门狗、特殊时钟配置)。
三、详细配置内容:
(1)声明段
.syntax unified //告诉汇编器使用统一语法
.cpu cortex-m3 //指定目标处理器为 Cortex-M3 架构
.fpu softfp //指定浮点运算使用软件模拟
.thumb //使用 Thumb 指令集(16位压缩指令)
.syntax命令是ARM架构独有的命令,用法很简单.syntax [unified | divided]。作用是在汇编ARM汇编源时,指定按照怎样的语法规则进行汇编。如果在编写汇编语言时不使用该命令指定语法规则,那么默认采用.syntax divided,此时使用旧的汇编风格,ARM和THUMB指令有着各自的语法。
使用.syntax unified时,ARM和THUMB指令使用统一的语法,有着以下的主要特性:
立即数不需要
#前缀可能会出现
IT指令,如果出现,则根据后面的条件码进行验证。在ARM模式下,它不会生成机器码,而在THUMB模式下则会生成机器码对于ARM指令,条件码总是出现在指令的最后;对于THUMB指令,条件码可以被使用,但仅限于IT指令的范围内
.global g_pfnVectors
.global Default_Handler
声明一个名为
g_pfnVectors的全局变量,它指向中断向量表的起始地址。在 C 代码中可以引用这个符号,比如用于动态重定位向量表(如 Bootloader 场景)。
声明一个默认中断服务程序(ISR)。当某个中断没有专门处理函数时,会跳转到这里。 通常是一个空循环或死循环,防止程序崩溃。
//指示data bss段的起始位置,赋值在ld script文件中
.word _sidata //初始值地址 放置一个标识data段在RAM中开始地址的word型内存位置
//.data对应初始化了的全局变量,编译后将位于可执行文件中,由启动代码负责加载到数据区中(在单片机中这部分数据会存于flash中,需要由启动代码把这部分内容拷贝到ram中)
.word _sdata
.word _edata
//.bss块的起始地址,.bss段是没有初始值的全局变量,由启动代码把这 部分内容全初始化为0
.word _sbss
.word _ebss
关键作用:启动代码需要将 .data 段从 Flash 复制到 RAM,并将 .bss 段清零,才能让 C 程序正常运行。
(2)声明这是“复位入口”
.section .text.Reset_Handler // 【告诉编译器】把下面代码放进“复位专用章节”
.weak Reset_Handler // 【柔性声明】“如果别人也写了Reset_Handler,用别人的”
.type Reset_Handler, %function // 【标注】这是个函数(方便调试工具识别)
(3)设置stack和clock
ldr r0, =_estack // 把“栈顶地址”(比如0x20005000)抄到速记本r0
mov sp, r0 // 把r0的值设为“栈指针sp”——工作台搭好!
bl SystemInit // 跳去执行SystemInit函数,并记住“回来的位置”
bl 指令 跳转,并记住返回地址 SystemInit 是C语言写的函数(在 system_stm32f1xx.c 里)
为什么要去执行SystemInit函数呢?
(4)搬运“已打包的工具”(.data段)
ldr r0, =_sdata // RAM起点地址 → r0(新家地址)
ldr r1, =_edata // RAM终点地址 → r1(新家门牌号)
ldr r2, =_sidata // Flash起点地址 → r2(仓库地址)
movs r3, #0 // 偏移量清零 → r3(从第0个箱子开始搬)
b LoopCopyDataInit // 先跳去检查“还要搬吗?
CopyDataInit: // 【搬运动作】
ldr r4, [r2, r3] // 从 仓库(r2)+偏移(r3) 拿1箱工具 → r4
str r4, [r0, r3] // 把r4的工具 放到 新家(r0)+偏移(r3)
adds r3, r3, #4 // 偏移+4(下一箱,每箱4字节)
LoopCopyDataInit: // 【检查循环】
adds r4, r0, r3 // 计算“当前搬到哪了” → r4
cmp r4, r1 // 比较:当前地址 < 新家终点?
bcc CopyDataInit // 如果“还没搬完”(小于),跳回CopyDataInit继续搬
(5)交接main函数与原地踏步防跑飞
bl __libc_init_array // (c++构造全局对象) c可以跳过
bl main // 正式交给main函数!
LoopForever:
b LoopForever // 原地踏步
.size Reset_Handler, .-Reset_Handler //计算.Reset_Handler段的长度
(6)中断向量表
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
g_pfnVectors= “全局函数指针向量表”(Global Pointer to Function Vectors)%object= 告诉工具链:“这是数据块,不是函数”(调试器不会试图反汇编它)
→ 编译后,这段数据紧贴 Flash 起始地址 0x08000000
→ 启动时,STM32 硬件将 0x08000000 映射到 0x00000000,CPU 看到的就是“正门挂号台”!
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.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
.word WWDG_IRQHandler /* Window Watchdog interrupt */
.word PVD_IRQHandler /* PVD through EXTI line detection interrupt */
.word TAMPER_IRQHandler /* Tamper interrupt */
.word RTC_IRQHandler /* RTC global interrupt */
.word FLASH_IRQHandler /* Flash global interrupt */
.word RCC_IRQHandler /* RCC global interrupt */
.word EXTI0_IRQHandler /* EXTI Line0 interrupt */
.word EXTI1_IRQHandler /* EXTI Line1 interrupt */
.word EXTI2_IRQHandler /* EXTI Line2 interrupt */
.word EXTI3_IRQHandler /* EXTI Line3 interrupt */
.word EXTI4_IRQHandler /* EXTI Line4 interrupt */
.word DMA1_Channel1_IRQHandler /* DMA1 Channel1 global interrupt */
.word DMA1_Channel2_IRQHandler /* DMA1 Channel2 global interrupt */
.word DMA1_Channel3_IRQHandler /* DMA1 Channel3 global interrupt */
.word DMA1_Channel4_IRQHandler /* DMA1 Channel4 global interrupt */
.word DMA1_Channel5_IRQHandler /* DMA1 Channel5 global interrupt */
.word DMA1_Channel6_IRQHandler /* DMA1 Channel6 global interrupt */
.word DMA1_Channel7_IRQHandler /* DMA1 Channel7 global interrupt */
.word ADC1_2_IRQHandler /* ADC1 and ADC2 global interrupt */
.word USB_HP_CAN_TX_IRQHandler /* USB High Priority or CAN TX interrupts */
.word USB_LP_CAN_RX0_IRQHandler /* USB Low Priority or CAN RX0 interrupts */
.word CAN_RX1_IRQHandler /* CAN RX1 interrupt */
.word CAN_SCE_IRQHandler /* CAN SCE interrupt */
.word EXTI9_5_IRQHandler /* EXTI Line[9:5] interrupts */
.word TIM1_BRK_IRQHandler /* TIM1 Break interrupt */
.word TIM1_UP_IRQHandler /* TIM1 Update interrupt */
.word TIM1_TRG_COM_IRQHandler /* TIM1 Trigger and Commutation interrupts */
.word TIM1_CC_IRQHandler /* TIM1 Capture Compare interrupt */
.word TIM2_IRQHandler /* TIM2 global interrupt */
.word TIM3_IRQHandler /* TIM3 global interrupt */
.word TIM4_IRQHandler /* TIM4 global interrupt */
.word I2C1_EV_IRQHandler /* I2C1 event interrupt */
.word I2C1_ER_IRQHandler /* I2C1 error interrupt */
.word I2C2_EV_IRQHandler /* I2C2 event interrupt */
.word I2C2_ER_IRQHandler /* I2C2 error interrupt */
.word SPI1_IRQHandler /* SPI1 global interrupt */
.word SPI2_IRQHandler /* SPI2 global interrupt */
.word USART1_IRQHandler /* USART1 global interrupt */
.word USART2_IRQHandler /* USART2 global interrupt */
.word USART3_IRQHandler /* USART3 global interrupt */
.word EXTI15_10_IRQHandler /* EXTI Line[15:10] interrupts */
.word RTCAlarm_IRQHandler /* RTC Alarms through EXTI line interrupt */
.word 0 /* Reserved */
.word TIM8_BRK_IRQHandler /* TIM8 Break interrupt */
.word TIM8_UP_IRQHandler /* TIM8 Update interrupt */
.word TIM8_TRG_COM_IRQHandler /* TIM8 Trigger and Commutation interrupts */
.word TIM8_CC_IRQHandler /* TIM8 Capture Compare interrupt */
.word ADC3_IRQHandler /* ADC3 global interrupt */
.word FSMC_IRQHandler /* FSMC global interrupt */
.word SDIO_IRQHandler /* SDIO global interrupt */
.word TIM5_IRQHandler /* TIM5 global interrupt */
.word SPI3_IRQHandler /* SPI3 global interrupt */
.word UART4_IRQHandler /* UART4 global interrupt */
.word UART5_IRQHandler /* UART5 global interrupt */
.word TIM6_IRQHandler /* TIM6 global interrupt */
.word TIM7_IRQHandler /* TIM7 global interrupt */
.word DMA2_Channel1_IRQHandler /* DMA2 Channel1 global interrupt */
.word DMA2_Channel2_IRQHandler /* DMA2 Channel2 global interrupt */
.word DMA2_Channel3_IRQHandler /* DMA2 Channel3 global interrupt */
.word DMA2_Channel4_5_IRQHandler /* DMA2 Channel4 and DMA2 Channel5 global interrupt */
.size g_pfnVectors, .-g_pfnVectors
Q:为什么有些中断写 .word 0 而不是 Default_Handler?
→ ARM Cortex-M 规范强制要求:
保留位必须为 0(非0值可能导致未定义行为)
→ 触发保留中断时,CPU 会因“跳转到0地址”触发总线错误,最终进入 HardFault_Handler(安全机制)
(7)弱定义中断向量表内容
.weak DMA2_Channel4_5_IRQHandler
.thumb_set DMA2_Channel4_5_IRQHandler,Default_HandlerCortex-M 要求:所有函数地址最低位必须为 1(表示 Thumb 模式)。.thumb_set 自动处理此细节。
