STM32F103X启动代码详细分析

2012/9/2
1.在分析启动代码之前,先把STM32的存储结构说一下:


上面这张图来自CORTEX M3权威指南
STM32F103RTB6 有20k的SRAM,起始地址为0x20000000,128k的闪存flash,起始地址为0x08000000(处于code区域).
从主闪存存储器启动时,主闪存存储器被映射到启动空间(0x00000000),但仍然能够在它原有的地址(0x08000000)访问它,即闪存存储器的内容可以在两个地址区域访问,0x00000000或0x0800 0000。
通过boot0和boot1的配置可以选着三种启动方式:

当我们下载程序时,选着的是系统存储器启动,当我们一上电开始跑程序的时候,应选择闪存flash启动.
1.再明确一下一些代码对齐的规则:
1.1字对齐: 4字节对齐,ARM指令时32位一跳的; 半字对齐: 2字节对齐.thumb指令是16位一跳的。而CORTEX-M3是thumb-2指令集,16位32位都有
1.2注意align的不同:
AREASTACK, NOINIT, READWRITE, ALIGN=3
此处的align表示接下来的堆栈段是2^3=8字节对齐的

单纯.align或ALIGN表示字对齐(32位)
3.我想我们还要明确比较重要的一点,既然我们的程序是从flash闪存上开始执行的,在STM32中文手册上由这么一句话:,CPU从地址0x0000 0000获取堆栈顶的地址,并从启动存储器的0x00000004指示的地址开始执行代码。而0x00000004开始的地址放的是什么呢?我可以告诉你从该处开始放的是cortex-m3规定的异常向量表。而且我们还可以确定,0x0000 0004出放的必然是复位异常向量。
4.下面好好来分析启动代码,我从keil得到的文件是STM32F10X.s,跟着我的注释走吧
//栈大小
Stack_SizeEQU0x00000200
//定义堆栈段,8字节对齐,注意,是可读写!!而我们的闪存flash是要擦除后才能写的,这就有问题了,说明我们的堆栈段不可能是安排在Flash闪存上的,那安排在哪呢?当然是内部20k的SRAM上啦!
AREASTACK, NOINIT, READWRITE, ALIGN=3
//stack_Mem为堆栈的底部,SPACE表示存储Stack_size个 分配单元
Stack_MemSPACEStack_Size
//__initial_sp很显然表示的是栈顶地址
__initial_sp
//下面是在SRAM上分配堆空间,大小为0
Heap_SizeEQU0x00000000

AREAHEAP, NOINIT, READWRITE, ALIGN=3
//__heap_base和Heap_Mem为堆起始地址
__heap_base
Heap_MemSPACEHeap_Size
//__heap_limit为堆结束的地址
__heap_limit
//8字节对齐,在keil中必须要这一条
PRESERVE8
//使用的是thumb指令集,但是cortex-m3是thumb-2版本,16位和32位的指令都有
THUMB
//只读代码段
AREARESET, DATA, READONLY
//导出__Vectors中断向量表起始地址,相当于在此声明__Vectors为一个全局变量,别的文件时可以访问到的
EXPORT __Vectors
//前面说过0x0地址必须放堆栈顶地址
__VectorsDCD__initial_sp; Top ofStack
//接下来是一些异常向量表,当发生该异常时,PC将会跳到这里执行
DCDReset_Handler; Reset Handler
DCDNMI_Handler; NMI Handler
DCDHardFault_Handler; Hard Fault Handler
DCDMemManage_Handler; MPU Fault Handler
DCDBusFault_Handler; Bus Fault Handler
DCDUsageFault_Handler; Usage Fault Handler
DCD0; Reserved
DCD0; Reserved
DCD0; Reserved
DCD0; Reserved
DCDSVC_Handler; SVCall Handler
DCDDebugMon_Handler; Debug Monitor Handler
DCD0; Reserved
DCDPendSV_Handler; PendSV Handler
DCDSysTick_Handler; SysTick Handler
//下面是外部中断的向量表
; External Interrupts
DCDWWDG_IRQHandler; Window Watchdog
DCDPVD_IRQHandler; PVD through EXTI Line detect
DCDTAMPER_IRQHandler; Tamper
DCDRTC_IRQHandler; RTC
DCDFLASH_IRQHandler; Flash
DCDRCC_IRQHandler; RCC
DCDEXTI0_IRQHandler; EXTI Line 0
DCDEXTI1_IRQHandler; EXTI Line 1
DCDEXTI2_IRQHandler; EXTI Line 2
DCDEXTI3_IRQHandler; EXTI Line 3
DCDEXTI4_IRQHandler; EXTI Line 4
DCDDMAChannel1_IRQHandler; DMA Channel 1
DCDDMAChannel2_IRQHandler; DMA Channel 2
DCDDMAChannel3_IRQHandler; DMA Channel 3
DCDDMAChannel4_IRQHandler; DMA Channel 4
DCDDMAChannel5_IRQHandler; DMA Channel 5
DCDDMAChannel6_IRQHandler; DMA Channel 6
DCDDMAChannel7_IRQHandler; DMA Channel 7
DCDADC_IRQHandler; ADC
DCDUSB_HP_CAN_TX_IRQHandler; USB High Priority or CAN TX
DCDUSB_LP_CAN_RX0_IRQHandler ; USB LowPriority or CAN RX0
DCDCAN_RX1_IRQHandler; CAN RX1
DCDCAN_SCE_IRQHandler; CAN SCE
DCDEXTI9_5_IRQHandler; EXTI Line 9..5
DCDTIM1_BRK_IRQHandler; TIM1 Break
DCDTIM1_UP_IRQHandler; TIM1 Update
DCDTIM1_TRG_COM_IRQHandler; TIM1 Trigger and Commutation
DCDTIM1_CC_IRQHandler; TIM1 Capture Compare
DCDTIM2_IRQHandler; TIM2
DCDTIM3_IRQHandler; TIM3
DCDTIM4_IRQHandler; TIM4
DCDI2C1_EV_IRQHandler; I2C1 Event
DCDI2C1_ER_IRQHandler; I2C1 Error
DCDI2C2_EV_IRQHandler; I2C2 Event
DCDI2C2_ER_IRQHandler; I2C2 Error
DCDSPI1_IRQHandler; SPI1
DCDSPI2_IRQHandler; SPI2
DCDUSART1_IRQHandler; USART1
DCDUSART2_IRQHandler; USART2
DCDUSART3_IRQHandler; USART3
DCDEXTI15_10_IRQHandler; EXTI Line 15..10
DCDRTCAlarm_IRQHandler; RTC Alarm through EXTI Line
DCDUSBWakeUp_IRQHandler; USB Wakeup from suspend
//声明代码段|.text|是约定的代码段名称
AREA|.text|, CODE,READONLY
//中断异常向量的函数指针
Reset_HandlerPROC
//到处该符号,[WEAK]表示如果外部有同名符号,外部该同名符号是优先使用的
EXPORTReset_Handler[WEAK]
//__main标号起始不是我们c语言中的main函数,而是C/C++标准实时库函数里的一个初始化子程序__main的入口地址,该程序的一个主要作用是初始化堆栈(对于我们的程序来说是跳转到后面的_user_initial_stackheap标号出进行初始化堆栈),并初始化映象文件,最后跳转到C程序的main函数。我们就理解为__main最后就跳到了main吧
IMPORT__main
LDRR0, =__main
BXR0
ENDP
//下面就是其他向量的函数地址了,这里只是简单的死循环,不做任何处理,然而很有意思的是,他们都EXPORT…[WEAK]了,所以,当我们要设置该异常的中断向量函数时,以我们设置的同名标号为准,这就是我们经常在keil中写的各个外部中断的handler了,然而,当没有设置这个异常的中断函数就引发了该中断,程序就会死在中断函数入口了。
; Dummy Exception Handlers (infinite loopswhich can be modified)

NMI_HandlerPROC
EXPORTNMI_Handler[WEAK]
B.
ENDP
HardFault_Handler\
PROC
EXPORTHardFault_Handler[WEAK]
B.
ENDP
MemManage_Handler\
PROC
EXPORTMemManage_Handler[WEAK]
B.
ENDP
BusFault_Handler\
PROC
EXPORTBusFault_Handler[WEAK]
B.
ENDP
UsageFault_Handler\
PROC
EXPORTUsageFault_Handler[WEAK]
B.
ENDP
SVC_HandlerPROC
EXPORTSVC_Handler[WEAK]
B.
ENDP
DebugMon_Handler\
PROC
EXPORTDebugMon_Handler[WEAK]
B.
ENDP
PendSV_HandlerPROC
EXPORTPendSV_Handler[WEAK]
B.
ENDP
SysTick_Handler PROC
EXPORTSysTick_Handler[WEAK]
B.
ENDP

Default_Handler PROC

EXPORTWWDG_IRQHandler[WEAK]
EXPORTPVD_IRQHandler[WEAK]
EXPORTTAMPER_IRQHandler[WEAK]
EXPORTRTC_IRQHandler[WEAK]
EXPORTFLASH_IRQHandler[WEAK]
EXPORTRCC_IRQHandler[WEAK]
EXPORTEXTI0_IRQHandler[WEAK]
EXPORTEXTI1_IRQHandler[WEAK]
【STM32F103X启动代码详细分析】EXPORTEXTI2_IRQHandler[WEAK]
EXPORTEXTI3_IRQHandler[WEAK]
EXPORTEXTI4_IRQHandler[WEAK]
EXPORTDMAChannel1_IRQHandler[WEAK]
EXPORTDMAChannel2_IRQHandler[WEAK]
EXPORTDMAChannel3_IRQHandler[WEAK]
EXPORTDMAChannel4_IRQHandler[WEAK]
EXPORTDMAChannel5_IRQHandler[WEAK]
EXPORTDMAChannel6_IRQHandler[WEAK]
EXPORTDMAChannel7_IRQHandler[WEAK]
EXPORTADC_IRQHandler[WEAK]
EXPORTUSB_HP_CAN_TX_IRQHandler[WEAK]
EXPORTUSB_LP_CAN_RX0_IRQHandler [WEAK]
EXPORTCAN_RX1_IRQHandler[WEAK]
EXPORTCAN_SCE_IRQHandler[WEAK]
EXPORTEXTI9_5_IRQHandler[WEAK]
EXPORTTIM1_BRK_IRQHandler[WEAK]
EXPORTTIM1_UP_IRQHandler[WEAK]
EXPORTTIM1_TRG_COM_IRQHandler[WEAK]
EXPORTTIM1_CC_IRQHandler[WEAK]
EXPORTTIM2_IRQHandler[WEAK]
EXPORTTIM3_IRQHandler[WEAK]
EXPORTTIM4_IRQHandler[WEAK]
EXPORTI2C1_EV_IRQHandler[WEAK]
EXPORTI2C1_ER_IRQHandler[WEAK]
EXPORTI2C2_EV_IRQHandler[WEAK]
EXPORTI2C2_ER_IRQHandler[WEAK]
EXPORTSPI1_IRQHandler[WEAK]
EXPORTSPI2_IRQHandler[WEAK]
EXPORTUSART1_IRQHandler[WEAK]
EXPORTUSART2_IRQHandler[WEAK]
EXPORTUSART3_IRQHandler[WEAK]
EXPORTEXTI15_10_IRQHandler[WEAK]
EXPORTRTCAlarm_IRQHandler[WEAK]
EXPORTUSBWakeUp_IRQHandler[WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMAChannel1_IRQHandler
DMAChannel2_IRQHandler
DMAChannel3_IRQHandler
DMAChannel4_IRQHandler
DMAChannel5_IRQHandler
DMAChannel6_IRQHandler
DMAChannel7_IRQHandler
ADC_IRQHandler
USB_HP_CAN_TX_IRQHandler
USB_LP_CAN_RX0_IRQHandler
CAN_RX1_IRQHandler
CAN_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler

B.

ENDP
// 字对齐(32位)
ALIGN
; User Initial Stack & Heap
//这里我们没有定义__MICROLIB,走下面分支
IF:DEF:__MICROLIB
//导出栈顶,堆头和堆尾地址
EXPORT__initial_sp
EXPORT__heap_base
EXPORT__heap_limit

ELSE


IMPORT __use_two_region_memory
//一个小函数,我们前面提到在__main中会调用他来初始化堆栈
EXPORT__user_initial_stackheap
__user_initial_stackheap
//传入这些关于堆栈的地址,作为参数
LDRR0, = Heap_Mem
LDRR1, =(Stack_Mem + Stack_Size)
LDRR2, = (Heap_Mem +Heap_Size)
LDRR3, = Stack_Mem
//调回__mian中调用__user_initial_stackheap的地方去
BXLR

ALIGN

ENDIF
//汇编结束
END

    推荐阅读