前言
使用STM32单片机时候,必须导入对应型号的启动文件。这个笔记在于对于启动文件的内容的分析,基于基本的stm32F103型号的单片机的启动文件stm32f10x.hd.s进行进行分析。具体的参考资料和分析参考《STM32F10X-中文参考手册》中的第九章-中断和事件:表 55 其他 STM32F10xxx 产品(小容量、中容量和大容量)的向量表; MDK 中的帮助手册—ARM Development Tools:用来查询ARM 的汇编指令和编译器相关的指令。
启动文件简介
启动文件由ARM的汇编语言编写,是系统上位复位所执行的第一个程序,存在的意义在对单片机进行一定的初始化工作,主要的工作如下:
- 初始化堆栈指针 SP=_initial_sp
- 初始化PC指针 Reset_Handler
- 初始化中断向量表
- 配置系统的时钟
- 调用 C 库函数 _main 初始化用户堆栈
在阅读启动文件的时候,必须要知道其使用的ARM的汇编语言。不过也不多,具体的细节可以参考《CM3 权威指南 CnR2》第四章:指令集以及 MDK->Help->Uvision Help
为了方便分析我将使用的ARM汇编语言整理成下表IF
[tr]指令名称作用[/tr]
| EQU | 给变量取一个符号名字,类似C语言中的define |
| AREA | 汇编一个新的代码段或者数据段 |
| SPACE | 分配内存空间 |
| PRESERVE8 | 当前的文件的堆栈必须按照8字节进行对齐 |
| EXPORT | 声明一个标号为全局变量,可以被外部的文件引用 |
| DCD | 以字为单位分配内存,要求4字节对齐,并对分配的内存进行初始化 |
| PROC | 定义子程序,和ENDP成对的出现 |
| WEAK | 弱定义,如果外部声明一个标号,则优先使用外部文件定义的标号, 如果外部没有定义也不会出错,注意:不是ARM的指令而是汇编器keil本身的指令 |
| IMPORT | 声明标号来自外部文件,和C语言中的EXTERN可以类似比较 |
| B | 跳转到另一个标号 |
| ALIGN | 编译器对指令或者数据的存放的地址进行对齐,缺省表示4字节对齐,这个同样是汇编器的指令 |
| END | 文件结束 |
| IF ELSE ENDIF | 汇编的判断语句 |
有以上的汇编的基础这时候进行阅读启动文件就可以比较清楚明白代码的含义
启动文件分析
为了比较清楚的分析启动文件,我对其进行分块的分析,
栈的初始化
栈的初始化
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SACE Stack_Size
__initial_sp
定义Stack_Size的长度为1KB,对栈命名为STACK NOINIT表示不初始化,可读可写,对其方式为2^3字节对齐。
这里补充一下栈的作用,一般程序中的全局变量和局部变量以及函数的开销自动在栈上分配的。必须注意的是栈的大小不能超过内部的SRAM的大小。如果编写的程序的变量过多的话,并且出现一些奇怪的错误,这个时候软件排除bug后,错误依然存在,就要考虑这个栈是不够大。__initial_sp表示栈的结束地址,即栈顶地址;在ARM中栈的由高向低地址生长的。
堆的初始化
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
定义堆的尺寸为512字节,名字为HEAP,NOINIT不初始化,可读可写,8字节对齐标准。__heap_base表示堆的起始地址, __heap_limit表示堆的结束地址,在ARM中堆是由低地址向高地址生长的。
PRESERVE8
THUMB
PRESERVE8表示堆栈都是按照8字节对齐,THUMB表示后面指令兼容THUMB指令也是早期的16位的ARM芯片指令。
向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
分配一个数据段命名为RESET只读。声明_Vectors,__Vectors_End,__Vectors_Size为全局属性可以被外部的文件调用。
这里引用一个说明:
当内核响应了一个发生的异常后,对应的异常服务例程 (ESR) 就会执行。为了决定 ESR 的入口地址,内核使用了“向量表查表机制”。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值
好的这个说明说明了向量表的作用,查看《STM32的中文参考手册》第九章的中断和事件部分,可以得到其规定的向量表,下为截取的一部分的由来下面分析启动代码来使用。
__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
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
向量表从 FLASH 的 0 地址开始放置 ,在上面引用中已经说明来复位时,通用NVIC 寄存器中一个重定位寄存器置位为0,所以在FLASH中的零地址必须要有一张向量表。
复位函数
通过上面的代码,可以知道上电复位函数Reset_Handler是单片机最早实现的函数,在启动文件中,
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ;
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声
明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit
和 __main 这两个函数均来自外部的文件。SystemInit() 是一个标准的库函数system_stm32f10x.c 这个库文件总定义。主要作用是配置系统时钟,这里调用这个函数之后,单片机的系统时钟配被配置为 72M。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。
LDR、 BLX、 BX 是 CM4 内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到。
中断服务程序
在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。
如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
从底层这个角度理解中断才能算是比较理解中断
用户堆栈初始化
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
首先判断是否定义了 __MICROLIB ,如果定义了这个宏则赋予标号 __initial_sp(栈顶地址)__heap_base(堆起始地址)、 ——_heap_limit(堆结束地址)全局属性,可供外部文件调用。有关这个宏我们在 KEIL 里面配置,具体见图使用微库 。然后堆栈的初始化就由 C 库函数 _main 来完成 。一般是不需要勾选
目前大概分析这里应该够用,后面有bug在更新,反向flag
前言
使用STM32单片机时候,必须导入对应型号的启动文件。这个笔记在于对于启动文件的内容的分析,基于基本的stm32F103型号的单片机的启动文件stm32f10x.hd.s进行进行分析。具体的参考资料和分析参考《STM32F10X-中文参考手册》中的第九章-中断和事件:表 55 其他 STM32F10xxx 产品(小容量、中容量和大容量)的向量表; MDK 中的帮助手册—ARM Development Tools:用来查询ARM 的汇编指令和编译器相关的指令。
启动文件简介
启动文件由ARM的汇编语言编写,是系统上位复位所执行的第一个程序,存在的意义在对单片机进行一定的初始化工作,主要的工作如下:
- 初始化堆栈指针 SP=_initial_sp
- 初始化PC指针 Reset_Handler
- 初始化中断向量表
- 配置系统的时钟
- 调用 C 库函数 _main 初始化用户堆栈
在阅读启动文件的时候,必须要知道其使用的ARM的汇编语言。不过也不多,具体的细节可以参考《CM3 权威指南 CnR2》第四章:指令集以及 MDK->Help->Uvision Help
为了方便分析我将使用的ARM汇编语言整理成下表IF
[tr]指令名称作用[/tr]
| EQU | 给变量取一个符号名字,类似C语言中的define |
| AREA | 汇编一个新的代码段或者数据段 |
| SPACE | 分配内存空间 |
| PRESERVE8 | 当前的文件的堆栈必须按照8字节进行对齐 |
| EXPORT | 声明一个标号为全局变量,可以被外部的文件引用 |
| DCD | 以字为单位分配内存,要求4字节对齐,并对分配的内存进行初始化 |
| PROC | 定义子程序,和ENDP成对的出现 |
| WEAK | 弱定义,如果外部声明一个标号,则优先使用外部文件定义的标号, 如果外部没有定义也不会出错,注意:不是ARM的指令而是汇编器keil本身的指令 |
| IMPORT | 声明标号来自外部文件,和C语言中的EXTERN可以类似比较 |
| B | 跳转到另一个标号 |
| ALIGN | 编译器对指令或者数据的存放的地址进行对齐,缺省表示4字节对齐,这个同样是汇编器的指令 |
| END | 文件结束 |
| IF ELSE ENDIF | 汇编的判断语句 |
有以上的汇编的基础这时候进行阅读启动文件就可以比较清楚明白代码的含义
启动文件分析
为了比较清楚的分析启动文件,我对其进行分块的分析,
栈的初始化
栈的初始化
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SACE Stack_Size
__initial_sp
定义Stack_Size的长度为1KB,对栈命名为STACK NOINIT表示不初始化,可读可写,对其方式为2^3字节对齐。
这里补充一下栈的作用,一般程序中的全局变量和局部变量以及函数的开销自动在栈上分配的。必须注意的是栈的大小不能超过内部的SRAM的大小。如果编写的程序的变量过多的话,并且出现一些奇怪的错误,这个时候软件排除bug后,错误依然存在,就要考虑这个栈是不够大。__initial_sp表示栈的结束地址,即栈顶地址;在ARM中栈的由高向低地址生长的。
堆的初始化
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
定义堆的尺寸为512字节,名字为HEAP,NOINIT不初始化,可读可写,8字节对齐标准。__heap_base表示堆的起始地址, __heap_limit表示堆的结束地址,在ARM中堆是由低地址向高地址生长的。
PRESERVE8
THUMB
PRESERVE8表示堆栈都是按照8字节对齐,THUMB表示后面指令兼容THUMB指令也是早期的16位的ARM芯片指令。
向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
分配一个数据段命名为RESET只读。声明_Vectors,__Vectors_End,__Vectors_Size为全局属性可以被外部的文件调用。
这里引用一个说明:
当内核响应了一个发生的异常后,对应的异常服务例程 (ESR) 就会执行。为了决定 ESR 的入口地址,内核使用了“向量表查表机制”。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值
好的这个说明说明了向量表的作用,查看《STM32的中文参考手册》第九章的中断和事件部分,可以得到其规定的向量表,下为截取的一部分的由来下面分析启动代码来使用。
__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
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
向量表从 FLASH 的 0 地址开始放置 ,在上面引用中已经说明来复位时,通用NVIC 寄存器中一个重定位寄存器置位为0,所以在FLASH中的零地址必须要有一张向量表。
复位函数
通过上面的代码,可以知道上电复位函数Reset_Handler是单片机最早实现的函数,在启动文件中,
Reset_Handler PROC
EXPORT Reset_Handler [WEAK] ;
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声
明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit
和 __main 这两个函数均来自外部的文件。SystemInit() 是一个标准的库函数system_stm32f10x.c 这个库文件总定义。主要作用是配置系统时钟,这里调用这个函数之后,单片机的系统时钟配被配置为 72M。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。
LDR、 BLX、 BX 是 CM4 内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到。
中断服务程序
在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。
如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
从底层这个角度理解中断才能算是比较理解中断
用户堆栈初始化
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
首先判断是否定义了 __MICROLIB ,如果定义了这个宏则赋予标号 __initial_sp(栈顶地址)__heap_base(堆起始地址)、 ——_heap_limit(堆结束地址)全局属性,可供外部文件调用。有关这个宏我们在 KEIL 里面配置,具体见图使用微库 。然后堆栈的初始化就由 C 库函数 _main 来完成 。一般是不需要勾选
目前大概分析这里应该够用,后面有bug在更新,反向flag
举报