startup_stm32f10x_hd.s:
1. 开辟栈空间
Stack_Size EQU 0x00000400 ;
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
开辟大小为0x00000400(1kb)的栈,名为STACK,NOINIT即不初始化,READWRITE表可读可写,ALIGN=3表8(2^3)字节对齐
EQU: 宏定义的伪指令,类似于c语言中的#define
AREA: 告诉汇编器开辟一个新的代码段或者数据段
SPACE: 用于分配一个一定大小的内存空间,以字节为单位,这里指定的大小为Stack_size
__initial_sp: 这是一个标号,它紧接在SPACE之后,表栈的开始地址。注意,栈的开始地址是栈顶,ARM是满降栈的,也就是栈由高向低生长。
2. 开辟堆空间
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
堆是用于内存动态分配malloc的,这里开辟大小为0x00000200(512字节)的堆,名字为HEAP,不初始化,可读可写,8字节对齐。__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址。堆是由低向高生长的。
PRESERVE8表指定当前文件的堆栈按照8字节对齐,THUMB表示接下来的指令兼容THUMB指令。THUMB是ARM老的16bit的指令集,现在Cortex-M系列的ARM都使用32bit的THUMB-2指令集,它兼容16bit和32bit的指令。
3. 定义向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
“AREA RESET, DATA, READONLY”是定义一个名为RESET的数据段,只读。
“__Vectors”、”__Vectors_End”、”__Vectors_Size”用EXPORT声明的标号,使其具有全局属性,可供外部文件调用。
这个数据段充当一张异常向量表。__Vectors为向量表起始地址,__Vectors_End为向量表的结束地址。两个相减即可算出向量表的大小。当内核去响应一个异常后,对应的异常服务函数就会得到执行。异常向量表则是决定这些服务程序的入口地址。异常向量表其实是一个32位整型数组,每个数组元素代表一种异常,数组元素的值等于服务程序的入口地址。异常向量表的起始地址可以通过NVIC(嵌套向量中断控制器)设置的,NVIC对应该设置地址功能的位复位值为0,所以地址0必须是一张中断向量表。
由手册存储器映像图获知,STM32的0地址处是用来映射的。
STM32启动的方式如下:
(1)用户闪存存储器: 用户代码烧录在这里,STM32正常启动时就是从这里启动
(2)系统存储器: 实现ISP下载功能。ISP(in-system programming)意为在系统编程。烧录程序时不需要烧录器,PC机通过串口把BIN/HEX文件直接烧录到单片机内部FLASH中
(3)内嵌SRAM: 实现调试器调试功能用
当选择从用户闪存存储器(flash)启动时候,0x0地址就会把flash的起始地址映射于此,flash的起始地址是0x08000000,中断向量表就放在这个地址。当用户选择从内嵌SRAM启动时候,0x0地址就会把SRAM的起始地址映射于此,SRAM的起始地址是0x20000000。
注意,一般我们都是选择从flash启动的。
中断向量表我的内容如下:
__Vectors DCD __initial_sp ; Top of Stack DCD表分配一个或者多个以字为单位的内存
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
__Vectors_Size EQU __Vectors_End - __Vectors
在《STM32中文参考手册_V10.pdf》的第9章,有对这些中断的描述的表格:(部分截图)
向量表(一般)从flash的0地址处开始放置,以4字节为一单位,地址0存放的是栈顶地址,0x04放的是复位处理函数的地址,以此类推。代码中的”*_Handler”是对应中断的中断服务函数,也就是中断处理程序的地址。
4. 复位处理程序
复位处理程序是系统上电/复位后第一个执行的:
AREA |.text|, CODE, READONLY ; Reset handlerReset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT SystemInitLDR R0, =SystemInitBLX R0
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0 ;BL表示跳转到R0地址,跳转前将下一条指令地址保存在LR
LDR R0, =__main ;LDR表示从存储器加载一个字到寄存器
BX R0 ;BX表示跳转到R0地址不用返回
ENDP
调用了SystemInit函数用于初始化系统时钟、然后调用__main函数,最终调用到程序员编写的main函数。
特别注意[WEAK]声明,它表示弱定义:如果外部文件优先定义了该标号则首先引用外部文件定义的标号,反之就引用此处用[WEAK]声明的标号。
IMPORT表示该标号来自外部文件,跟c语言关键字的extern类似。这里声明__main和SystemInit这两个标号表明均来自外部文件。
SystemInit在system_stm32f10x.c文件中定义并实现,是ST公司为我们写好的,其主要作用是配置系统时钟为72MHz。__main函数不等于main函数,它实现的是初始化用户堆栈,在函数最后才是去调用main函数进入c语言运行环境。
5. 中断服务函数
启动文件中已经帮我们写好所有的中断服务函数了,只不过除了复位中断处理函数,其它的都是不限循环。
NMI_Handler PROC ;系统异常
EXPORT NMI_Handler [WEAK]
B . ;B表跳转到一个标号,"."表无限循环
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
...
真正的中断服务函数需要我们在外部的.c文件中实现,这里只是占位,是把中断和与之对应中断服务函数绑定了。当我们开启某个中断后,没有写对应的中断服务函数或者函数名有误,当中断来临时程序还是跳转到启动文件预先写好的中断服务函数中,在这个函数中无限循环。
6. 堆栈初始化
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB ;这个宏在KEIL里定义
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
ALIGH
判断是否定义了__MICROLIB,这个宏是在KEIL里面可配置的,
选中它表示使用c库的备选库,里面有一个__mian函数。若定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆的起始地址)、__heap_limit(堆的结束地址)为外部文件可调用的变量,即可供外部c库中的__main调用,由__main初始化堆栈,否则需要用户自己实现__user_initial_stackheap函数初始化堆栈。
启动文件相当于嵌入式Linux中的BootLoader,只不过这里简单多了。感觉大概了解就可以,需要深究时再深究。
startup_stm32f10x_hd.s:
1. 开辟栈空间
Stack_Size EQU 0x00000400 ;
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
开辟大小为0x00000400(1kb)的栈,名为STACK,NOINIT即不初始化,READWRITE表可读可写,ALIGN=3表8(2^3)字节对齐
EQU: 宏定义的伪指令,类似于c语言中的#define
AREA: 告诉汇编器开辟一个新的代码段或者数据段
SPACE: 用于分配一个一定大小的内存空间,以字节为单位,这里指定的大小为Stack_size
__initial_sp: 这是一个标号,它紧接在SPACE之后,表栈的开始地址。注意,栈的开始地址是栈顶,ARM是满降栈的,也就是栈由高向低生长。
2. 开辟堆空间
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
堆是用于内存动态分配malloc的,这里开辟大小为0x00000200(512字节)的堆,名字为HEAP,不初始化,可读可写,8字节对齐。__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址。堆是由低向高生长的。
PRESERVE8表指定当前文件的堆栈按照8字节对齐,THUMB表示接下来的指令兼容THUMB指令。THUMB是ARM老的16bit的指令集,现在Cortex-M系列的ARM都使用32bit的THUMB-2指令集,它兼容16bit和32bit的指令。
3. 定义向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
“AREA RESET, DATA, READONLY”是定义一个名为RESET的数据段,只读。
“__Vectors”、”__Vectors_End”、”__Vectors_Size”用EXPORT声明的标号,使其具有全局属性,可供外部文件调用。
这个数据段充当一张异常向量表。__Vectors为向量表起始地址,__Vectors_End为向量表的结束地址。两个相减即可算出向量表的大小。当内核去响应一个异常后,对应的异常服务函数就会得到执行。异常向量表则是决定这些服务程序的入口地址。异常向量表其实是一个32位整型数组,每个数组元素代表一种异常,数组元素的值等于服务程序的入口地址。异常向量表的起始地址可以通过NVIC(嵌套向量中断控制器)设置的,NVIC对应该设置地址功能的位复位值为0,所以地址0必须是一张中断向量表。
由手册存储器映像图获知,STM32的0地址处是用来映射的。
STM32启动的方式如下:
(1)用户闪存存储器: 用户代码烧录在这里,STM32正常启动时就是从这里启动
(2)系统存储器: 实现ISP下载功能。ISP(in-system programming)意为在系统编程。烧录程序时不需要烧录器,PC机通过串口把BIN/HEX文件直接烧录到单片机内部FLASH中
(3)内嵌SRAM: 实现调试器调试功能用
当选择从用户闪存存储器(flash)启动时候,0x0地址就会把flash的起始地址映射于此,flash的起始地址是0x08000000,中断向量表就放在这个地址。当用户选择从内嵌SRAM启动时候,0x0地址就会把SRAM的起始地址映射于此,SRAM的起始地址是0x20000000。
注意,一般我们都是选择从flash启动的。
中断向量表我的内容如下:
__Vectors DCD __initial_sp ; Top of Stack DCD表分配一个或者多个以字为单位的内存
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
__Vectors_Size EQU __Vectors_End - __Vectors
在《STM32中文参考手册_V10.pdf》的第9章,有对这些中断的描述的表格:(部分截图)
向量表(一般)从flash的0地址处开始放置,以4字节为一单位,地址0存放的是栈顶地址,0x04放的是复位处理函数的地址,以此类推。代码中的”*_Handler”是对应中断的中断服务函数,也就是中断处理程序的地址。
4. 复位处理程序
复位处理程序是系统上电/复位后第一个执行的:
AREA |.text|, CODE, READONLY ; Reset handlerReset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT SystemInitLDR R0, =SystemInitBLX R0
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0 ;BL表示跳转到R0地址,跳转前将下一条指令地址保存在LR
LDR R0, =__main ;LDR表示从存储器加载一个字到寄存器
BX R0 ;BX表示跳转到R0地址不用返回
ENDP
调用了SystemInit函数用于初始化系统时钟、然后调用__main函数,最终调用到程序员编写的main函数。
特别注意[WEAK]声明,它表示弱定义:如果外部文件优先定义了该标号则首先引用外部文件定义的标号,反之就引用此处用[WEAK]声明的标号。
IMPORT表示该标号来自外部文件,跟c语言关键字的extern类似。这里声明__main和SystemInit这两个标号表明均来自外部文件。
SystemInit在system_stm32f10x.c文件中定义并实现,是ST公司为我们写好的,其主要作用是配置系统时钟为72MHz。__main函数不等于main函数,它实现的是初始化用户堆栈,在函数最后才是去调用main函数进入c语言运行环境。
5. 中断服务函数
启动文件中已经帮我们写好所有的中断服务函数了,只不过除了复位中断处理函数,其它的都是不限循环。
NMI_Handler PROC ;系统异常
EXPORT NMI_Handler [WEAK]
B . ;B表跳转到一个标号,"."表无限循环
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
...
真正的中断服务函数需要我们在外部的.c文件中实现,这里只是占位,是把中断和与之对应中断服务函数绑定了。当我们开启某个中断后,没有写对应的中断服务函数或者函数名有误,当中断来临时程序还是跳转到启动文件预先写好的中断服务函数中,在这个函数中无限循环。
6. 堆栈初始化
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB ;这个宏在KEIL里定义
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
ALIGH
判断是否定义了__MICROLIB,这个宏是在KEIL里面可配置的,
选中它表示使用c库的备选库,里面有一个__mian函数。若定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆的起始地址)、__heap_limit(堆的结束地址)为外部文件可调用的变量,即可供外部c库中的__main调用,由__main初始化堆栈,否则需要用户自己实现__user_initial_stackheap函数初始化堆栈。
启动文件相当于嵌入式Linux中的BootLoader,只不过这里简单多了。感觉大概了解就可以,需要深究时再深究。
举报