This is the 9th day of my participation in Gwen Challenge
The source of the startup file is as follows:
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;栈初始化
Stack_Size EQU 0x00000400 ;EQU定义栈空间的大小 1024字节,栈空间由编译器自动分配变量所占内存,该汇编语句等效于#define Stack_Size 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个名为STACK的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
Stack_Mem SPACE Stack_Size ;SPACE分配连续的栈存储空间,大小为0x00000400(1K),把首地址赋给Stack_Mem
__initial_sp ;初始化堆栈指针,指向堆栈顶。
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
;堆初始化
Heap_Size EQU 0x00000200 ;EQU定义堆空间的大小,堆空间由人为手动分配变量所占内存,该汇编语句等效于#define Heap_Size 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;定义一个名为HEAP的内存单元,它是可读写的,对齐方式为2^3=8字节对齐
__heap_base ;堆起始地址
Heap_Mem SPACE Heap_Size ;SPACE分配连续的堆存储空间,大小为0x00000200(512Bytes),把首地址赋给Heap_Mem
__heap_limit ;堆终止地址,与__heap_base配合限制堆的大小
PRESERVE8 ;告诉编译器以8字节对齐,命令指定当前文件保持栈的八字节对齐
THUMB ;告诉编译器使用THUMB指令集,THUMB 必须位于使用新语法的任何Thumb代码之前;定义复位段(中断向量表),并初始化
;中断向量表定义
; Vector Table Mapped to Address 0 at Reset ;中断向量表默认从地址0开始,如果使用了向量表重定位,则从重定位地址开始。假设STM32从FLASH启动,则此中断向量表起始地址即为0x8000000。
AREA RESET, DATA, READONLY ;定义一个名为RESET的数据段,它是只读类型的。
EXPORT __Vectors ;声明全局变量_Vectors,该标号可在其他文件中使用,表示中断向量表入口地址
EXPORT __Vectors_End ;向量表终止地址
EXPORT __Vectors_Size ;向量表空间大小
__Vectors DCD __initial_sp ; Top of Stack Top of Stack 第一个表项是栈顶地址;该处物理地址值即为 __Vetors 标号所表示的值;该地址中存储__initial_sp所表示的地址值,
;大小为一个字(32bit)
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 总线错误中断,一般发生在数据访问异常,比如fsmc访问不当
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 挂起异常,此处可以用作RTOS的上下文切换异常,这是被推荐使用的,因为Cortex-M4会在异常发生时自动保存R0-R3,R12,R13(堆栈指针SP),R14(链接地址,也叫返回地址LR,在异常返回时使用),R15(程序计数器PC,为当前应用程序+4)和中断完成时自动恢复,我们只需保存R4-R11,大大减少了中断响应和上下文切换的时间。
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 ;向量表结束标志
__Vectors_Size EQU __Vectors_End - __Vectors ;得到向量表的大小 共76个中断向量,304个字节也就是0x130个字节
;地址重映射及中断向量表的转移
AREA |.text|, CODE, READONLY ;定义一个代码段,可读,段名字是.text 段名若以数字开头,则该段名需用"|"括起来,如|1_test|。 |.text| 表示由 C 编译程序产生的代码段,或用于以某种方式与 C 库关联的代码段。
;定义只读数据段,实际上是在CODE区,如果在FLASH区起动,则 中断向量起始地址为0X8000000
;复位向量处理函数
; Reset handler
Reset_Handler PROC ;标记一个函数的开始;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
EXPORT Reset_Handler [WEAK] ;此处[WEAK]表示弱定义,优先执行其他文件的定义
IMPORT __main ;伪指令用于通知编译器要使用的标号在其他的源文件中定义
IMPORT SystemInit
LDR R0, =SystemInit ; 装载寄存器指令
BLX R0 ; 带链接的跳转,切换指令集。将处理器的工作状态由ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。
LDR R0, =__main ; 切换指令集,main函数不返回
BX R0 ;BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
ENDP
;虚构的异常处理程序(可以修改的无限循环)
; 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 . ; 点“.”,它表示跳转到当前的指令地址处(即当前的 PC 值),也就是进入到当前的死循环中了。
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
; 输出异常向量表标号,方便外部实现异常的具体功能 , [WEAK] 是弱定义的意思,如果外部定义了,优先执行外部定义,否则执行下面的函数定义
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 ; 默认是字对齐方式,也说明了代码是4字节对齐的
;*******************************************************************************
; User Stack and Heap initialization 用户堆栈初始化
;*******************************************************************************
IF :DEF:__MICROLIB ;判断是否使用DEF:__MICROLIB(micro lib)在keil中选中 option--target--USE MicroLIB
EXPORT __initial_sp ;使用的话则将栈顶地址,堆始末地址赋予全局属性使外部程序可以使用
EXPORT __heap_base
EXPORT __heap_limit
ELSE ;如果使用默认C库运行时
IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory
EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap,这样外部程序也可调用此标号,进行堆栈和堆的赋值,在__main函数执行过程中调用
__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 ;LR连接寄存器,也就是R14,用于函数或者子程序调用返回地址的保存。 等同于 mov pc,lr 即跳转到lr中存放的地址处
ALIGN ;设置对齐方式,在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐
ENDIF
END
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
Copy the code
Let’s start with a line-by-line analysis of the key code
Stack initialization:
To understand these three lines of code, first understand the meaning of these assembly instructions
1, the EQU
EQU defines the size of the stack space, which is automatically allocated by the compiler to memory occupied by variables. This assembly statement is equivalent to #define Stack_Size 0x00000400. This is where the stack size is first defined as 0x400, or 1024 bytes.
2, the AREA
So the second line defines a memory cell called STACK, which is read-write and aligned with 2^3=8 bytes
3, SPACE
The third line of code means that I’m going to initialize my STACK to zero.
The last line of __initial_sp means to initialize the stack pointer and point to the top of the stack.
Heap initialization:
1. EQU defines the size of the heap space (512 bytes). The heap space is allocated manually by variables
Define a memory unit called HEAP, which is read-write and aligned to 2^3=8 bytes
3. Set the start heap address
4, SPACE allocate contiguous heap storage SPACE 0x00000200(512Bytes) and assign the first address to Heap_Mem
5, the end of the heap address, in conjunction with __heap_base to limit the size of the heap
6. Tells the compiler to align the file with 8 bytes, and specifies that the current file keeps the stack aligned with 8 bytes
7, tell the compiler that the following code starts using the THUMB instruction set and that THUMB must precede any THUMB code using the new syntax. Define the reset segment (interrupt vector table) and initialize it.
Interrupt direction scale definition:
By default, the interrupt routing table starts at address 0. If routing table relocation is used, the relocation address starts. If STM32 is started from FLASH, the start address of the interrupt direction table is 0x8000000.
1. Define a data segment named RESET that is read-only.
2. Declare the global variable _Vectors, which can be used in other files to indicate the entry address of the interrupt vector table
3. End address of the vector table
4. Space size of vector scale
Interrupt vector table entry address:
The first entry is the address at the Top of the Stack. The physical address value is the value represented by the __Vetors label. This address stores the address value represented by __initial_sp.
Assign addresses to each interrupt vector in turn, and the assigned addresses are as follows:
__Vectors_End indicates the end of the vector table.
Address remapping and interrupt transfer to table:
Define a code, can read, name is. The text if section name begin with Numbers, is the segment name must be enclosed “|”, such as | 1 _test |. |. Text | said the code generated by the C compiler, or used in some way associated with C library of code. Define the read-only data segment, which is actually in the CODE area. If started in the FLASH area, the interrupt vector starts at address 0X8000000.
Reset vector processing functions:
The use of PROC, ENDP this pair of pseudo-instructions to segment the program, equivalent to the C language {} role.
If the Reset_Handler function is defined in the program, the user-defined Reset_Handler function will be executed. If the user does not define the Reset_Handler function, the user-defined Reset_Handler function will be executed. If the user does not define the Reset_Handler function, the user-defined Reset_Handler function will be executed. Execute the current function. Weak definition means that when two functions have the same name, the user-defined function is executed first.
This is to prevent the user to enable the interrupt without interrupting the service function, resulting in a program crash. If interrupts are enabled and the user does not define the interrupt service function, the default interrupt is entered, and the default interrupt is an infinite loop.
Use IMPORT to declare the main() and SystemInit() functions.
Using the LDR command, load the address of the function SystemInit into register R0.
Jump to the SystemInit() function with the BLX instruction.
Next, load the main() address with the LDR instruction.
Jump to main() with the BX instruction.
Imaginary exception handlers:
Output abnormal direction table label:
Weak definitions are also used here; if external definitions are defined, the external definitions take precedence, otherwise the following function definitions are executed.
Implement weakly defined functions:
Implementation of each interrupt function, but here the interrupt function is empty function, no specific implementation, the user needs to achieve in the code.
User stack initialization:
Use if and else to determine whether the microlibrary was selected
If the USE MicroLIB option is selected in keil, the if statement is executed, otherwise the else statement is executed, using the default C library.
IF :DEF:__MICROLIB ; DEF:__MICROLIB (micro lib) select option--target--USE MicroLIB EXPORT __initial_sp; If used, give global attributes to the top of the stack and the start and end of the heap so that external programs can use EXPORT __heap_base EXPORT __heap_limit ELSE; If you use the default C library runtime IMPORT __use_two_region_memory; Define the global label __use_two_region_memory EXPORT __user_initial_stackheap; Declare the global label __user_initial_stackheap so that external programs can also call this label, perform stack and heap assignments, and call __user_initial_stackheap during execution of the __main function; The label __user_initial_stackheap, representing the user stack initializer entry LDR R0, = Heap_Mem; Save heap start address LDR R1, =(Stack_Mem + Stack_Size); LDR R2, = (Heap_Mem + Heap_Size); Save heap size LDR R3, = Stack_Mem; Save the top pointer to the stack BX LR; The LR connection register, also known as R14, is used to save the return address of a function or subroutine call. Equivalent to mov PC, LR jumps to the address stored in LR ALIGN; Set the alignment. By default, ELF (Executable connection file) code segments and data segments are word-aligned to ENDIFCopy the code