目录:

一、STM32启动代码

二、STM32与ARM启动代码比较

1、中断向量表的定义

2、中断函数的跳转实现

三、STM32启动代码汇编详解

1、Stack栈

2、Heap 堆

3、向量表

4、复位程序

5、中断服务程序

6、用户堆栈初始化
-------------------------------------
编译见:Keil编译软件的使用汇总之四、RAM和ROM

--------------------------------------------------------------------------------------------------
一、STM32启动代码
编译器:keil MDK4.1
进入到嵌入式领域,main函数之前还有一段启动代码!
究竟在main函数之前,发生了什么?如果你觉得已经明白了这个过程,那么请试着回答这个问题:程序是存储到FLASH中的,运行时static变量地址是指向RAM,那么这些static变量的初始值是如何映射到RAM中的?
 我们以STM32F10x的启动代码为例,先看看其完整的源码:
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler\
                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
UsageFault_Handler\
                PROC
                EXPORT  UsageFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler                [WEAK]
                B       .
                ENDP
DebugMon_Handler\
                PROC
                EXPORT  DebugMon_Handler           [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler             [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP
Default_Handler PROC
                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                EXPORT  TAMPER_IRQHandler          [WEAK]
                EXPORT  RTC_IRQHandler             [WEAK]
                EXPORT  FLASH_IRQHandler           [WEAK]
                EXPORT  RCC_IRQHandler             [WEAK]
                EXPORT  EXTI0_IRQHandler           [WEAK]
                EXPORT  EXTI1_IRQHandler           [WEAK]
                EXPORT  EXTI2_IRQHandler           [WEAK]
                EXPORT  EXTI3_IRQHandler           [WEAK]
                EXPORT  EXTI4_IRQHandler           [WEAK]
                EXPORT  DMA1_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel4_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel5_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel6_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel7_IRQHandler   [WEAK]
                EXPORT  ADC1_2_IRQHandler          [WEAK]
                EXPORT  USB_HP_CAN1_TX_IRQHandler  [WEAK]
                EXPORT  USB_LP_CAN1_RX0_IRQHandler [WEAK]
                EXPORT  CAN1_RX1_IRQHandler        [WEAK]
                EXPORT  CAN1_SCE_IRQHandler        [WEAK]
                EXPORT  EXTI9_5_IRQHandler         [WEAK]
                EXPORT  TIM1_BRK_IRQHandler        [WEAK]
                EXPORT  TIM1_UP_IRQHandler         [WEAK]
                EXPORT  TIM1_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM1_CC_IRQHandler         [WEAK]
                EXPORT  TIM2_IRQHandler            [WEAK]
                EXPORT  TIM3_IRQHandler            [WEAK]
                EXPORT  TIM4_IRQHandler            [WEAK]
                EXPORT  I2C1_EV_IRQHandler         [WEAK]
                EXPORT  I2C1_ER_IRQHandler         [WEAK]
                EXPORT  I2C2_EV_IRQHandler         [WEAK]
                EXPORT  I2C2_ER_IRQHandler         [WEAK]
                EXPORT  SPI1_IRQHandler            [WEAK]
                EXPORT  SPI2_IRQHandler            [WEAK]
                EXPORT  USART1_IRQHandler          [WEAK]
                EXPORT  USART2_IRQHandler          [WEAK]
                EXPORT  USART3_IRQHandler          [WEAK]
                EXPORT  EXTI15_10_IRQHandler       [WEAK]
                EXPORT  RTCAlarm_IRQHandler        [WEAK]
                EXPORT  USBWakeUp_IRQHandler       [WEAK]
                EXPORT  TIM8_BRK_IRQHandler        [WEAK]
                EXPORT  TIM8_UP_IRQHandler         [WEAK]
                EXPORT  TIM8_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM8_CC_IRQHandler         [WEAK]
                EXPORT  ADC3_IRQHandler            [WEAK]
                EXPORT  FSMC_IRQHandler            [WEAK]
                EXPORT  SDIO_IRQHandler            [WEAK]
                EXPORT  TIM5_IRQHandler            [WEAK]
                EXPORT  SPI3_IRQHandler            [WEAK]
                EXPORT  UART4_IRQHandler           [WEAK]
                EXPORT  UART5_IRQHandler           [WEAK]
                EXPORT  TIM6_IRQHandler            [WEAK]
                EXPORT  TIM7_IRQHandler            [WEAK]
                EXPORT  DMA2_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_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
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
                B       .
                ENDP
                ALIGN
     一些旁枝末节和本文的主题无关,我们先不要去理会,只需要知道这个启动代码是设置向量表,然后跳转到__main函数。跳转具体到代码段部分如下:
; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit  //读取内存中的数据到寄存器R0
                BLX     R0 
//从ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM 状态切换到Thumb 状态
//该指令同时将PC的当前内容保存到寄存器R14
                LDR     R0, =__main  //读取内存中的数据到寄存器R0
                BX       R0 //跳转到指令中所指定的目标地址
                ENDP

当大家看到__main函数时,估计应该有不少人认为这个是main函数的别名或是编译之后的名字,否则在启动代码中再也无法找到和main相关的字眼了。可事实是,__main和main是完全两个不同的函数!如果这还不足以让你诧异,那么再告诉你另一个事实:你无法找到__main代码,因为这个是编译器自动创建的!

如果你对此还半信半疑,可以查看MDK的文档,会发现有这么一句说明:It is automatically created by the linker when it sees a definition of main()。简单点来说,当编译器发现定义了main函数,那么就会自动创建__main。

__main函数的出身我们基本搞清楚了,那么现在的问题是,它和main又有什么关系呢?其实__main主要做这么两件事:初始化C/C++所需的资源,调用main函数。初始化先暂时不说,但“调用main函数”这个功能能够让我们解决为什么之前的启动代码调用的是__main,最后却能转到main函数的疑惑。

初始化C/C++所需的资源,如果脱离了具体情况,实在很难解释清楚,还是先看看编译出来的汇编代码片段:

凡是以__rt开头的,都是用来初始化C/C++运行库的;而以__scatterload开头,则是根据离散文件的定义,将代码中的变量映射到相应的内存位置。而回答本文开头的问题,关键就在于__scatterload_copy函数!

我们在STM32F10x平台举个简单的例子,首先要明白一点是,该平台的的flash地址以0x08000000为起始,主要是存储代码;而SRAM是以0x20000000为起始,也就是内存。然后C/C++有这么一行代码:

static int g_iVal = 12;

当我们程序开始跑起来的时候,通过IDE发现,g_iVal被映射到内存地址0x20000000,数值为一个随机数0xFFFFBE00,而不是代码中设置的12,如图:

我们让程序继续往下执行,当执行完毕__scatterload_copy之后,我们发现g_iVal这时候已经变成我们所需要的初始值了:

接下来就是C/C++库的初始化,最后就是进入到main函数,而此时已经是万事俱备。

--------------------------------------------------------------------------------------------------

二、STM32与ARM启动代码比较

通常的启动代码结构:

1、中断向量表的定义

Ø         ARM

ARM代码在这块的代码为跳转语句,因为指令长度的限制,4个字节也就能放个跳转语就差不多了。通常两种实现方式:

1.       B   Reset_Handler

2.       LDR PC, Reset_Handler

其实都是一个意思,跳转到真正实现Reset_Handler功能的地方去。ARM中断向量在这里总共有8条(复位、未定义、SWI、指令、数据异常、预留、IRQ、FIQ),具体的当前中断类型,在IRQ或FIQ的中断实现里面判断,之后再转到对应的中断处理函数里面。

注意,仔细看,想一想,这里的中断向量处存放的是机器指令码。然而,STM在中断向量处存放的是实现中断功能的入口地址,而不是指令功能码。

Ø         STM

正如上面所说,STM中断向量处存放的是目标地址。但是要注意的是,第一条中断向量存放的堆栈的地址,真正的传统意义上的中断向量从第二条开始。除此之外,STM的中断向量表很长,它不像ARM由IRQ或FIQ进行判断后再处理,而是将所有的中断处理函数入口地址全列在这里:

__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

; External Interrupts

DCD     WWDG_IRQHandler      ; Window Watchdog

DCD     PVD_IRQHandler       ; PVD through EXTI Line detect

DCD     TAMPER_IRQHandler    ; Tamper

…………….

---------------------------------

2、中断函数的跳转实现

这块功能的实现依赖于编译器、链接器的功能,实现方法各不相同。

Ø         ARM

CODE32

AREA    Startup,CODE,READONLY

Vectors

LDR     PC, ResetAddr

LDR     PC, UndefinedAddr

LDR     PC, SWI_Addr

LDR     PC, PrefetchAddr

LDR     PC, DataAbortAddr

B            .

LDR       PC, IRQ_Addr ;跳转至标号IRQ_Addr处

LDR     PC, FIQ_Addr

ResetAddr              DCD     Reset

UndefinedAddr      DCD     Undefined

SWI_Addr               DCD     SoftwareInterrupt

PrefetchAddr         DCD     PrefetchAbort

DataAbortAddr      DCD     DataAbort

Nouse                    DCD     0

IRQ_Addr              DCD     IRQ_Handler ; IRQ_Addr定义为IRQ_Handler地址

FIQ_Addr               DCD     FIQ_Handler

; IRQ_Handler在这里定义

IRQ_Handler

SUB SP, SP, #4

STMFD SP!, {R8-R9}

LDR R9, =INTOFFSET

LDR R9, [R9]

LDR       R8, =HandleEINT0

ADD R8, R8,R9,LSL #2

LDR R8, [R8]

STR R8, [SP,#8]

LDMFD SP!,{R8-R9,PC}

注意上面的HandleEINT0标号,它是中断函数的入口首地址,加上当前中断编号的偏移值INTOFFSET。具体对应到哪里呢?看下面:

;这是定义(或者说预留)一个段指定位置开始的内存空间.

MAP (0x33FFBF00)

SysRstVector    #     4

UdfInsVector     #     4

SwiSvcVector    #     4

InsAbtVector     #     4

DatAbtVector     #     4

ReservedVector       #     4

IrqSvcVector      #     4

FiqSvcVector     #     4

HandleEINT0        #   4

HandleEINT1          #   4

HandleEINT2          #   4

HandleEINT3          #   4

HandleEINT4_7     #   4

….

实际上这里也可以理解为定义一个结构体变量,各个标号对应结构体的域,跟C语言不同的是,这里定义的结构体变量可以指定它在内存空间中的地址。

好了,如果当前来了一个IRQ类型的EINT3中断,按照上面的代码应该是跳转至以HandleEINT3这个域存储的值为地址处。那么HandleEINT3这个域里存储的值是什么呢?

下面的代码即可在C语言中定义了。

#define _ISR_STARTADDRESS   0x33FFBF00

#define pISR_EINT3     (*(unsigned *)(_ISR_STARTADDRESS+0x2c))

pISR_EINT3 = (unsigned int)EINT3_Handler;

static void __irq EINT3_Handler(void)

{

}

Ø         STM32

STM32中断处理实现跟ARM不一样。来看代码:

启动代码处的中断向量表(我们以EXTI0为例):

__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

; External Interrupts

DCD     WWDG_IRQHandler        ; Window Watchdog

DCD     PVD_IRQHandler           ; PVD through EXTI Line detect

DCD     TAMPER_IRQHandler       ; Tamper

DCD     RTC_IRQHandler           ; RTC

DCD     FLASH_IRQHandler         ; Flash

DCD     RCC_IRQHandler           ; RCC

DCD     EXTI0_IRQHandler      ; EXTI Line 0 中断发生时跳转至EXT0_IRQHandler地址处。@@@记住这条代码,下面以此处为例@@@

….

Default_Handler PROC

EXPORT  WWDG_IRQHandler           [WEAK]

EXPORT  PVD_IRQHandler            [WEAK]

EXPORT  TAMPER_IRQHandler         [WEAK]

EXPORT  RTC_IRQHandler            [WEAK]

EXPORT  FLASH_IRQHandler          [WEAK]

EXPORT  RCC_IRQHandler            [WEAK]

EXPORT  EXTI0_IRQHandler          [WEAK]

…..

WWDG_IRQHandler

PVD_IRQHandler

TAMPER_IRQHandler

RTC_IRQHandler

FLASH_IRQHandler

RCC_IRQHandler

EXTI0_IRQHandler

EXTI1_IRQHandler

….

B   .

ENDP

这段是啥意思呢?这里是定义各个中断向量的处理函数处,所有列出来的中断向量处理函数地址一致,功能也是一致:原地跳转。

既然所有的中断处理函数功能一致,那它是如何跳转至用户定义在C语言中的中断处理函数的呢?答案是,如果用户没有在用户代码(C语言)中定义对应向量的中断处理函数,则实际起作用的真正的中断处理函数即为上面列出的原地跳转功能处。

它是如何实现的? 注意到在声明导出处理函数后面的[WEAK]了吗?它的功能由链接器实现:如果在别处也定义该标号(函数),在链接时用别处的地址。如果没有其它定方定义,则以此处地址进行链接。

可能不太好理解,实际上是启动代码已经预定义了中断处理函数,它的功能很简单,就是原地跳转。只不过这块预定义的中断处理函数是否真正起作用,要看你是否在别处重定义了相同标号的中断处理函数。如果你已经重定义了,则以你重定义的中断处理函数为准。

以EXTI0中断为列,假设用户在自已的代码中配置好了EXTI0的中断,并且重定义了下面的EXTI0_IRQHandler函数,则链接器会以此函数地址进行链接。

void EXTI0_IRQHandler()

{

}

也就是在上面启动代码的@@@标注处(DCD  EXTI0_IRQHandler),会以用户重定义的EXTI0_IRQHandler()函数地址填入。

--------------------------------------------------------------------------------------------------

三、STM32启动代码汇编详解

代码详解可搜索百度云盘“零死角玩转STM32—F429”,相关指令说明见“ARM单片机汇编指令使用一和ARM单片机汇编指令使用二”。
1、Stack栈
1 Stack_Size EQU 0x00000400
2
3 AREA STACK, NOINIT, READWRITE, ALIGN=3
4 Stack_Mem SPACE Stack_Size
5 __initial_sp
开辟栈的大小为 0X00000400(1KB),名字为 STACK, NOINIT 即不初始化,可读可写, 8(2^3)字节对齐。
栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。
如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬 fault 的时候,这时你就要考虑下是不是栈不够大,溢出了。
EQU:宏定义的伪指令,相当于等于,类似与 C 中的 define。
AREA:告诉汇编器汇编一个新的代码段或者数据段。 STACK 表示段名,这个可以任意命名; NOINIT 表示不初始化; READWRITE 表示可读可写, ALIGN=3,表示按照 2^3对齐,即 8 字节对齐。
SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。
标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。
----------------------------------------------
2、Heap 堆
1 Heap_Size EQU 0x00000200
2
3 AREA HEAP, NOINIT, READWRITE, ALIGN=3
4 __heap_base
5 Heap_Mem SPACE Heap_Size
6 __heap_limit
开辟堆的大小为 0X00000200(512 字节),名字为 HEAP, NOINIT 即不初始化,可读可写, 8(2^3)字节对齐。 __heap_base 表示对的起始地址, __heap_limit 表示堆的结束地址。堆是由低向高生长的, 跟栈的生长方向相反。
堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆上面。这个在 STM32里面用的比较少。
1 PRESERVE8
2 THUMB
PRESERVE8: 指定当前文件的堆栈按照 8 字节对齐。
THUMB: 表示后面指令兼容 THUMB 指令。 THUBM 是 ARM 以前的指令集, 16bit,现在 Cortex-M 系列的都使用 THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。  
----------------------------------------------

3、向量表
1 AREA RESET, DATA, READONLY
2 EXPORT __Vectors
3 EXPORT __Vectors_End
4 EXPORT __Vectors_Size

定义一个数据段,名字为 RESET,可读。并声明__Vectors、 __Vectors_End 和__Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。
EXPORT: 声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。
当内核响应了一个发生的异常后,对应的异常服务进程(ESR)就会执行。 为了决定 ESR的入口地址, 内核使用了向量表查表机制。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP(SP指针) 的初值。
表格 11 F429 向量表
编 号 优 先 级 优先级
类型
名称 说明 地址
- - - 保留(实际存的是 MSP 地址) 0X0000 0000
-3 固定 Reset 复位 0X0000 0004
-2 固定 NMI 不可屏蔽中断。 RCC 时钟安全系统
(CSS) 连接到 NMI 向量
0X0000 0008
-1 固定 HardFault 所有类型的错误 0X0000 000C
0 可编程 MemManage 存储器管理 0X0000 0010
1 可编程 BusFault 预取指失败,存储器访问失败 0X0000 0014
2 可编程 UsageFault 未定义的指令或非法状态 0X0000 0018
- - - 保留 0X0000 001C-
0X0000 002B
3 可编程 SVCall 通过 SWI 指令调用的系统服务 0X0000 002C
4 可编程 Debug Monitor 调试监控器 0X0000 0030
- - - 保留 0X0000 0034
5 可编程 PendSV 可挂起的系统服务 0X0000 0038
6 可编程 SysTick 系统嘀嗒定时器 0X0000 003C
0 7 可编程 - 窗口看门狗中断 0X0000 0040
1 8 可编程 PVD 连接 EXTI 线的可编程电压检测中断 0X0000 0044
2 9 可编程 TAMP_STAMP 连接 EXTI 线的入侵和时间戳中断 0X0000 0048
中间部分省略,详情请参考 STM32F4xx 中文参考手册》第十章-中断和事件-向量表部分
84 91 可编程 SPI4 SPI4 全局中断 0X0000 0190
85 92 可编程 SPI5 SPI5 全局中断 0X0000 0194
86 93 可编程 SPI6 SPI6 全局中断 0X0000 0198
87 94 可编程 SAI1 SAI1 全局中断 0X0000 019C
88 95 可编程 LTDC LTDC 全局中断 0X0000 01A0
89 96 可编程 LTDC_ER LTDC_ER 全局中断 0X0000 01A4
90 97 可编程 DMA2D DMA2D 全局中断 0X0000 01A8

代码 12 向量表
1 __Vectors DCD __initial_sp ;栈顶地址
2 DCD Reset_Handler ;0X04 复位程序地址

3 DCD NMI_Handler
4 DCD HardFault_Handler
5 DCD MemManage_Handler
6 DCD BusFault_Handler
7 DCD UsageFault_Handler
8 DCD 0 ; 0 表示保留
9 DCD 0
10 DCD 0
11 DCD 0
12 DCD SVC_Handler
13 DCD DebugMon_Handler
14 DCD 0
15 DCD PendSV_Handler
16 DCD SysTick_Handler
17
18
19 ;外部中断开始
20 DCD WWDG_IRQHandler
21 DCD PVD_IRQHandler
22 DCD TAMP_STAMP_IRQHandler
23
24 ;限于篇幅,中间代码省略
25 DCD LTDC_IRQHandler
26 DCD LTDC_ER_IRQHandler
27 DCD DMA2D_IRQHandler
28 __Vectors_End
1 __Vectors_Size EQU __Vectors_End - __Vectors
__Vectors 为向量表起始地址, __Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。
向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址, 0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。
DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR(异常服务进程) 的入口地址初始化它们。
----------------------------------------------
4、复位程序
1 AREA |.text|, CODE, READONLY
定义一个名称为.text 的代码段,可读。
1 Reset_Handler PROC  ;复位子程序
2 EXPORT Reset_Handler [WEAK]
3 IMPORT SystemInit  ;外部定义的文件system_stm32f4xx.c
4 IMPORT __main  ;IMPORT类似于EXTERN
5 ;C 语言中的函数名就是一个地址
6 LDR R0, =SystemInit  ;=SystemInit地址所指地址处的内容传送到R0
7 BLX R0  ;跳转到R0,cpsr→PC
8 LDR R0, =__main  ;外部文件__main地址所指地址处的内容传送到R0
9 BX R0  ;跳转到R0
10 ENDP  ;子程序结束
复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数_mian,最终调用 main 函数去到 C 的世界。
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit 和__main 这两个函数均来自外部的文件。
SystemInit()是一个标准的库函数,在 system_stm32f4xx.c 这个库文件总定义。主要作用是配置系统时钟,这里调用这个函数之后, F429 的系统时钟配被配置为 180M。
__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,最终调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们 C 文件里面的 main,如果是调皮的用户就可以修改主函数的名称,然后在这里面 IMPORT 你写的主函数名称即可。
1 Reset_Handler PROC
2 EXPORT Reset_Handler [WEAK]
3 IMPORT SystemInit
4 IMPORT user_main
5
6 LDR R0, =SystemInit
7 BLX R0
8 LDR R0, =user_main
9 BX R0
10 ENDP
这个时候你在 C 文件里面写的主函数名称就不是 main 了,而是 user_main 了。
LDR、 BLX、 BX 是 CM4 内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到,具体作用见下表: 
指令名称 作用
LDR 从存储器中加载字到一个寄存器中
BL 跳转到由寄存器/标号给出的地址,并把跳转前的下条指令地址保存到 LR
BLX 跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要
把跳转前的下条指令地址保存到 LR
BX 跳转到由寄存器/标号给出的地址,不用返回

----------------------------------------------

5、中断服务程序
在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。
如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无限循环,即程序就死在这里。
1 NMI_Handler PROC ;系统异常
2 EXPORT NMI_Handler [WEAK]

3 B .
4 ENDP
5
6 ;限于篇幅,中间代码省略
7 SysTick_Handler PROC
8 EXPORT SysTick_Handler [WEAK]
9 B .
10 ENDP
11
12 Default_Handler PROC ;外部中断
13 EXPORT WWDG_IRQHandler [WEAK]
14 EXPORT PVD_IRQHandler [WEAK]
15 EXPORT TAMP_STAMP_IRQHandler [WEAK]
16
17 ;限于篇幅,中间代码省略
18 LTDC_IRQHandler
19 LTDC_ER_IRQHandler
20 DMA2D_IRQHandler
21 B .
22 ENDP
B:跳转到一个标号;这里跳转到一个‘.’,即表示无限循环
----------------------------------------------
6、用户堆栈初始化
1 ALIGN
ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。
1 ;用户栈和堆初始化
2 IF :DEF:__MICROLIB
3
4 EXPORT __initial_sp
5 EXPORT __heap_base
6 EXPORT __heap_limit
7
8 ELSE
9
10 IMPORT __use_two_region_memory
11 EXPORT __user_initial_stackheap
12
13 __user_initial_stackheap
14
15 LDR R0, = Heap_Mem
16 LDR R1, =(Stack_Mem + Stack_Size)
17 LDR R2, = (Heap_Mem + Heap_Size)
18 LDR R3, = Stack_Mem
19 BX LR  ;跳转到LR
20
21 ALIGN
22
23 ENDIF
24 END
判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、 __heap_limit
(堆结束地址)全局属性,可供外部文件调用。
如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的 C 库,然后初始化用户堆栈大小,这部分有 C 库函数__main 来完成,当初始化完堆栈之后,就调用 main函数去到 C 的世界。 

IF,ELSE,ENDIF:汇编的条件分支语句,跟 C 语言的 if ,else 类似
END:文件结束

--------------------------------------------------------------------------------------------------

Main函数之前都发生了什么相关推荐

  1. STM32系列单片机在进入main函数前都在干些什么?

    在刚开始学习单片机的时候,一直以为程序启动后就直接进入到了main函数,但是随着学习的深入才发现,程序在进入main函数前其实还要干好多事情.现在就来分析一下,STM32系列单片机程序在进入main函 ...

  2. java main函数_都知道Java程序的入口方法是main,那你知道为什么是main方法吗?

    我们都知道Java的入口方法是main函数,下面这段代码就是Java中非常经典的Hello World代码: 我们通过Java提供的工具进行编译执行: 通过上面的代码我们知道Java入口方法的几个要求 ...

  3. iOS程序main函数之前发生了什么

    文章转载自 http://blog.sunnyxx.com/2014/08/30/objc-pre-main/,侵权必删除 前言 一个iOS app的main()函数位于main.m中,这是我们熟知的 ...

  4. iOS 程序 main 函数之前发生了什么

    一个 iOS App 的 main 函数位于 main.m 中,这是我们熟知的程序入口.但对 objc 了解更多之后发现,程序在进入我们的 main 函数前已经执行了很多代码,比如熟知的 + load ...

  5. Linux C: 为什么C都必须有一个main函数

    gcc的编译过程分为三步: 第一步 将 *.c 文件分别通过编译器解析成汇编语言    *.s     . 第二步将  *.s 文件分别通过汇编器生产目标文件  *.o      . 第三步将 c.o ...

  6. char怎么比较_C语言的 main 函数到底怎么写才是对的?

    相信各位对C语言中的 main 函数大家都再熟悉不过了,这是你学习C语言首先就要学习的东西,但是我看过很多人写的代码包括我的一些读者在main函数的写法方面版本很多,今天就跟大家聊一聊main函数到底 ...

  7. C++程序设计基础(8)main函数

    注:读<程序员面试笔记>笔记总结 1.知识点 (2)main函数的形式 1 //first type 2 int main() 3 //second type 4 int main(int ...

  8. WPF中如何重新定义Main函数

    相信大家都知道,Main函数一直都作为程序的入口点,而在开发WPF项目的时候,有些初始化的操作则是想放在Main中去执行,那么当时想试试如果重新写一个Main函数后,程序会不会执行,不过结果很遗憾. ...

  9. C语言main函数的参数

    在我们之前编写的C程序中,main函数是没有参数的,但是在实际开发中,main函数一般都需要参数,没有参数的情况极少. main函数的参数是从命令提示符下执行程序的时候传入,例如ls 命令. 在以上示 ...

最新文章

  1. html 二叉树模式,重建二叉树.html
  2. matlab 最后一列,求大神帮我解释一下matlab最后几行是什么意思
  3. 日活4000万,占据22%手游时长份额的竟然是……(文末有彩蛋)
  4. python中调用c库
  5. 法语语言考试C1,法语考试大比拼:专八与Dalf C1,哪个更难?
  6. ubuntu下载百度网盘文件油猴+aria2
  7. Hadoop之MapTask工作机制
  8. php data类型转换,【原】超简单类型转换(DataTable
  9. 【ArcGIS遇上Python】ArcGIS Python获取Shapefile矢量数据字段名称
  10. Java SE 11(18.9)中的API更新
  11. jsp 页面实现增减行
  12. python学了真的很有用吗-python学了真的很有用吗?当然!赶紧学,不学后悔!
  13. github上开源的UVCCamera为什么不能正常运行
  14. Windows 11旗舰版
  15. iso文件连接服务器访问路由器硬盘,多种系统下访问路由器硬盘的方法 | 192路由网...
  16. 树莓派安装系统和系统备份还原
  17. RuntimeError: mat1 dim 1 must match mat2 dim 0
  18. L1-6 吉老师的回归 (15 分)
  19. 【易语言界面开发系列教程之(EX_UI使用系列教程 ——1-8节)】
  20. 常用的激活函数sigmoid,relu,tanh

热门文章

  1. 实战破解某高校校园网密码——你的账号安全吗?
  2. I want to go hone——变形的Dijkstra
  3. Uniapp+SpringBoot即时通讯聊天安卓APP源码
  4. Boboniu Chats with Du(贪心 + 前缀和)
  5. eclipse ganymede下安装Jbosstools
  6. Vue实现侧边导航栏于Tab页关联
  7. 让5G成为小儿科的人工智能大生意
  8. ZZULI新生周赛(8)部分题题解
  9. EXCEL VBA创建sheet/工作簿
  10. 登录注册模块设计构思