stm32专题三十八:在SRAM中调试代码
1、启动文件简介
启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:
(1)初始化堆栈指针 MSP=_initial_sp
(2)初始化 PC 指针=Reset_Handler
(3)初始化中断向量表
(4)配置系统时钟
(5)调用 C 库函数_main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界
2、STM32的启动流程
下面这段话引用自《CM3 权威指南 CnR2》—复位序列, CM4 的复位序列跟 CM3 一样。
在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:
(1)从地址 0x0000,0000(FLASH 的地址 0x08000000,因为STM32设计的Flash起始地址是在0x0800 0000开始的)处取出 MSP 的初始值。
(2)从地址 0x0000,0004(FLASH 的地址 0x08000004,因为STM32设计的Flash起始地址是在0x0800 0000开始的)处取出 PC 的初始值——这个值是复位向量, LSB 必须是1, 然后从这个值所对应的地址处取值。
请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。 在CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。 向量表中的数值是 32位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们刚刚分析的 Reset_Handler 这个函数。
初始化 MSP 和 PC 的一个范例
因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加1。举例 来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。
向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,上 图中使用 0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行(即去到 C 的世界) 。在此之前初始化 MSP 是必需的,因为可能第 1条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。
现在,程序就进入了我们熟悉的 C 世界,现在我们也应该明白 main 并不是系统执行的第一个程序了。
4、STM32 的启动方式
上面讲到STM32在离开复位状态后的工作过程如下,见下图:
(1) 从地址 0x00000000(FLASH 的首地址 0x08000000)处取出栈指针 MSP 的初始值,该值就是栈顶的地址。
(2) 从地址 0x00000004(FLASH 的首地址 0x08000004) 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令。
(1)从地址 0x00000000 处取出栈指针 MSP 的初始值,该值就是栈顶的地址;
(2)从地址 0x00000004 处取出程序指针 PC 的初始值,该值指向复位后应执行的第一条指令;
上述过程由内核自动设置运行环境并执行主体程序,因此它被称为自举过程。
这个实际上和启动文件是相对应的:
虽然内核是固定访问 0x00000000 和 0x00000004 地址的,但实际上这两个地址可以被重映射到其它地址空间,因为STM32设计的Flash起始地址是在0x0800 0000位置开始的。以 STM32F429 为例,根据芯片引出的 BOOT0 及 BOOT1 引脚的电平情况,这两个地址可以被映射到内部 FLASH、内部 SRAM 以及系统存储器中,不同的映射配置下表。
BOOT1 |
BOOT0 |
映射到的存储器 |
0x00000000地址映射到 |
0x00000004地址映射到 |
x |
0 |
内部 FLASH |
0x08000000 |
0x08000004 |
1 |
1 |
内部 SRAM |
0x20000000 |
0x20000004 |
0 |
1 |
系统存储器 |
0x1FFFB000 |
0x1FFFB004 |
内核在离开复位状态后会从映射的地址中取值给栈指针 MSP 及程序指针 PC,然后执行指令,我们一般以存储器的类型来区分自举过程,例如内部 FLASH 启动方式、内部SRAM 启动方式以及系统存储器启动方式。
(1) 内部 FLASH 启动方式
当芯片上电后采样到 BOOT0 引脚为低电平时, 0x00000000 和 0x00000004 地址被映射到内部 FLASH 的首地址 0x08000000 和 0x08000004。因此,内核离开复位状态后,读取内部 FLASH 的 0x08000000 地址空间存储的内容,赋值给栈指针 MSP,作为栈顶地址,再读取内部 FLASH 的0x08000004 地址空间存储的内容,赋值给程序指针PC,作为将要执行的第一条指令所在的地址。具备这两个条件后,内核就可以开始从PC 指向的地址中读取指令执行了。
(2) 内部 SRAM 启动方式
类似地,当芯片上电后采样到 BOOT0 和 BOOT1 引脚均为高电平时, 0x00000000和 0x00000004 地址被映射到内部 SRAM 的首地址 0x20000000 和 0x20000004,内核从SRAM 空间获取内容进行自举。
在实际应用中,由启动文件 starttup_stm32f429_439xx.s 决 定 了 0x00000000 和0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到内部 FLASH 还是内部 SRAM。
(3) 系统存储器启动方式
当芯片上电后采样到 BOOT0 引脚为高电平, BOOT1 为低电平时,内核将从系统存储器的 0x1FFFF000 及 0x1FFFF004 获取 MSP 及 PC 值进行自举。系统存储器是一段特殊的空间,用户不能访问, ST 公司在芯片出厂前就在系统存储器中固化了一段代码。
因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为 ISP 提供支持(In System Program),如检测 USART1/2、 CAN2 及 USB 通讯接口传输过来的信息,并根据这些信息更新自己内部 FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为 ISP 启动方式。
(4)在 RAM 中调试代码的优点
(5)在 RAM 中调试代码的缺点
4.1、内部 FLASH 的启动过程
下面我们以最常规的内部 FLASH 启动方式来分析自举过程,主要理解 MSP 和 PC 内容是怎样被存储到 0x08000000 和 0x08000004 这两个地址的。
如下图所示,这是 STM32F4 默认的启动文件的代码,启动文件的开头定义了一个大小为 0x400 的栈空间,且栈顶的地址使用标号“__initial_sp”来表示;在图下方定义了一个名为“ Reset_Handler”的子程序,它就是我们总是提到的在芯片启动后第一个执行的代码。
在汇编语法中,程序的名字和标号都包含它所在的地址,因此,我们的目标是把“ __initial_sp”和“ Reset_Handler”赋值到 0x08000000 和 0x08000004 地址空间存储,这样内核自举的时候就可以获得栈顶地址以及第一条要执行的指令了。在启动代码的中间部分,使用了汇编关键字“ DCD” 把“ __initial_sp”和“ Reset_Handler”定义到了最前面的地址空间。
启动代码中存储的 MSP 及 PC 指针内容
在启动文件中把设置栈顶及首条指令地址到了最前面的地址空间,但这并没有指定绝对地址,各种内容的绝对地址是由链接器根据分散加载文件(*.sct)分配的,STM32F429IGT6 型号的默认分散加载文件配置见代码清单。
; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00100000 { ; load region size_region ER_IROM1 0x08000000 0x00100000 { ; load address = execution address * .o (RESET, +First) * (InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 UNINIT 0x00030000 { ; RW data .ANY (+RW +ZI) } }
分散加载文件把加载区和执行区的首地址都设置为 0x08000000,正好是内部 FLASH的首地址,因此汇编文件中定义的栈顶及首条指令地址会被存储到 0x08000000 和0x08000004 的地址空间。
类似地,如果我们修改分散加载文件,把加载区和执行区的首地址设置为内部 SRAM的首地址 0x20000000,那么栈顶和首条指令地址将会被存储到 0x20000000 和 0x20000004的地址空间了。
为了进一步消除疑虑,我们可以查看反汇编代码及 map 文件信息来了解各个地址空间存储的内容,见图 52-3,这是多彩流水灯工程编译后的信息,它的启动文件及分散加载文件都按默认配置。其中反汇编代码是使用 fromelf 工具从 axf 文件生成的。
从反汇编代码及 map 文件查看存储器的内容
从反汇编代码可了解到,这个工程的 0x08000000 地址存储的值为 0x20000400,0x08000004 地址存储的值为 0x080001C1,查看 map 文件,这两个值正好是栈顶地址__initial_sp 以及首条指令 Reset_Handler 的地址。下载器会根据 axf 文件(bin、 hex 类似)存储相应的内容到内部 FLASH 中。
由此可知, BOOT0 为低电平时,内核复位后,从 0x08000000 读取到栈顶地址为0x20000400,了解到子程序的栈空间范围,再从 0x08000004 读取到第一条指令的存储地址为 0x080001C1,于是跳转到该地址执行代码,即从 ResetHandler 开始运行,运行SystemInit、 __main(包含分散加载代码),最后跳转到 C 语言的 main 函数。
对比在内部 FLASH 中运行代码的过程,可了解到若希望在内部 SRAM 中调试代码,需要设置启动方式为从内部 SRAM 启动,修改分散加载文件控制代码空间到内部 SRAM地址以及把生成程序下载到芯片的内部 SRAM 中。
4.2、内部SDRAM的启动过程
一般情况下,我们在 MDK 中编写工程应用后,调试时都是把程序下载到芯片的内部FLASH 运行测试的,代码的 CODE 及 RW-data 的内容被写入到内部 FLASH 中存储。但在某些应用场合下却不希望或不能修改内部 FLASH 的内容,这时就可以使用 RAM 调试功能了,它的本质是把原来存储在内部 FLASH 的代码(CODE 及 RW-data 的内容)改为存储到SRAM 中(内部 SRAM 或外部 SDRAM 均可),芯片复位后从 SRAM 中加载代码并运行。把代码下载到 RAM 中调试有如下优点:
(1)下载程序非常快。 RAM 存储器的写入速度比在内部 FLASH 中要快得多,且没有擦除过程,因此在 RAM 上调试程序时程序几乎是秒下的,对于需要频繁改动代码的调试过程,能节约很多时间,省去了烦人的擦除与写入 FLASH 过程。另外,STM32 的内部 FLASH 可擦除次数为 1 万次,虽然一般的调试过程都不会擦除这么多次导致 FLASH 失效,但这确实也是一个考虑使用 RAM 的因素。
(2)不改写内部 FLASH 的原有程序。
(3)对于内部 FLASH 被锁定的芯片,可以把解锁程序下载到 RAM 上,进行解锁。相对地,把代码下载到 RAM 中调试有如下缺点:
(4)存储在 RAM 上的程序掉电后会丢失,不能像 FLASH 那样保存。
(5)若使用 STM32 的内部 SRAM 存储程序,程序的执行速度与在 FLASH 上执行速度无异,但 SRAM 空间较小。
(6)若使用外部扩展的 SDRAM 存储程序,程序空间非常大,但 STM32 读取SDRAM 的速度比读取内部 FLASH 慢,这会导致程序总执行时间增加,因此在SDRAM 中调试的程序无法完美仿真在内部 FLASH 运行时的环境。另外,由于STM32 无法直接从 SDRAM 中启动且应用程序复制到 SDRAM 的过程比较复杂(下载程序前需要使 STM32 能正常控制 SDRAM),所以在很少会在 STM32 的SDRAM 中调试程序。
5、STM32的启动文件
(1)STM32F1的启动文件
startup_stm32f10x_ld.s: 小容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_ld_vl.s:小容量超值型的STM32F100xx startup_stm32f10x_md.s:中容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_md_vl.s: 中容量超值型的STM32F100xx startup_stm32f10x_hd.s :大容量的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_hd_vl.s :大容量超值型的STM32F100xx startup_stm32f10x_xl.s: 超大容量FLASH在512K到1024K字节的STM32F101xx,STM32F102xx,STM32F103xx startup_stm32f10x_cl.s: 互联型的STM32F105xx,STM32F107xx
ld:小容量产品,flash<64K md:中容量产品,flash = 64k or flash = 128k hd:大容量产品,flash > 128k xl:超大容量产品,flash > 512k && flash < 1024k cl:互联型产品,stm32f105/107系列 vl:超值型产品,stm32f100系列
(2)STM32F4的启动文件
startup_stm32f40_41xxx.s startup_stm32f40xx.s startup_stm32f401xx.s startup_stm32f410xx.s startup_stm32f411xe.s startup_stm32f412xg.s startup_stm32f413_423xx.s startup_stm32f427_437xx.s startup_stm32f427x.s startup_stm32f429_439xx.s startup_stm32f446xx.s startup_stm32f469_479xx.s
以startup_stm32f429_439xx.s为例进行讲解:
启动文件中用到的汇编指令:
;******************** (C) COPYRIGHT 2016 STMicroelectronics ******************** ;* File Name : startup_stm32f429_439xx.s ;* Author : MCD Application Team ;* @version : V1.8.0 ;* @date : 09-November-2016 ;* Description : STM32F429xx/439xx devices vector table for MDK-ARM toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Configure the system clock and the external SRAM/SDRAM mounted ;* on STM324x9I-EVAL boards to be used as data memory ;* (optional, to be enabled by user) ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the CortexM4 processor is in Thread mode, ;* priority is Privileged, and the Stack is set to Main. ;* <<< Use Configuration Wizard in Context Menu >>> ;******************************************************************************* ; ; Licensed under MCD-ST Liberty SW License Agreement V2, (the "License"); ; You may not use this file except in compliance with the License. ; You may obtain a copy of the License at: ; ; http://www.st.com/software_license_agreement_liberty_v2 ; ; Unless required by applicable law or agreed to in writing, software ; distributed under the License is distributed on an "AS IS" BASIS, ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ; See the License for the specific language governing permissions and ; limitations under the License. ; ;******************************************************************************* ; 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 ;开辟1KB的栈 AREA STACK, NOINIT, READWRITE, ALIGN=3 ;栈名为STACK, NOINIT即不初始化, READWRITE表示可读可写, 3表示8( 2^3)字节对齐 Stack_Mem SPACE Stack_Size __initial_sp ;栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。 ;如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬 fault 的时候,这时你就要考虑下是不是栈不够大,溢出了。 ;EQU:宏定义的伪指令,相当于等于,类似与 C 中的 define。 ;AREA:告诉汇编器汇编一个新的代码段或者数据段。 STACK 表示段名,这个可以任意命名; NOINIT 表示不初始化; READWRITE 表示可读可写, ALIGN=3,表示按照 2^3对齐,即 8 字节对齐。 ;SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。 ;标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。 ;/*************堆****************/ ; <h> Heap Configuration ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Heap_Size EQU 0x00000200 ;开辟512字节的堆 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base ;起始 Heap_Mem SPACE Heap_Size __heap_limit ;结束 PRESERVE8 ;当前文件的堆按照8字节对齐 THUMB ;表示后面指令兼容THUMB指令 ;开辟堆的大小为 0X00000200( 512 字节),名字为 HEAP, NOINIT 即不初始化,可读可写, 8( 2^3)字节对齐。 __heap_base 表示对的起始地址, __heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反。 ;堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆上面。这个在 STM32里面用的比较少。 ;PRESERVE8: 指定当前文件的堆栈按照 8 字节对齐。 ;THUMB: 表示后面指令兼容 THUMB 指令。 THUBM 是 ARM 以前的指令集, 16bit,现在 Cortex-M 系列的都使用 THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超级。 ;/*******************向量表********************/ ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors ;向量表起始地址 EXPORT __Vectors_End ;向量表结束地址 EXPORT __Vectors_Size ;记录向量表的大小 ;定义一个数据段,名字为 RESET,可读。并声明 __Vectors、 __Vectors_End 和__Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。 ;EXPORT: 声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。 ;当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了―向量表查表机制‖。这里使用一张向量表。向量表其实是一个WORD( 32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值。 ;各个中断对应的中断函数的地址 ;__Vectors :向量表的起始地址 __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 ;0表示保留 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 SysTick定时器中断函数 ;外部中断开始 ; External Interrupts DCD WWDG_IRQHandler ; Window WatchDog DCD PVD_IRQHandler ; PVD through EXTI Line detection DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line DCD FLASH_IRQHandler ; FLASH DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line0 DCD EXTI1_IRQHandler ; EXTI Line1 DCD EXTI2_IRQHandler ; EXTI Line2 DCD EXTI3_IRQHandler ; EXTI Line3 DCD EXTI4_IRQHandler ; EXTI Line4 DCD DMA1_Stream0_IRQHandler ; DMA1 Stream 0 DCD DMA1_Stream1_IRQHandler ; DMA1 Stream 1 DCD DMA1_Stream2_IRQHandler ; DMA1 Stream 2 DCD DMA1_Stream3_IRQHandler ; DMA1 Stream 3 DCD DMA1_Stream4_IRQHandler ; DMA1 Stream 4 DCD DMA1_Stream5_IRQHandler ; DMA1 Stream 5 DCD DMA1_Stream6_IRQHandler ; DMA1 Stream 6 DCD ADC_IRQHandler ; ADC1, ADC2 and ADC3s DCD CAN1_TX_IRQHandler ; CAN1 TX DCD CAN1_RX0_IRQHandler ; CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; External Line[9:5]s DCD TIM1_BRK_TIM9_IRQHandler ; TIM1 Break and TIM9 DCD TIM1_UP_TIM10_IRQHandler ; TIM1 Update and TIM10 DCD TIM1_TRG_COM_TIM11_IRQHandler ; TIM1 Trigger and Commutation and TIM11 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 ; External Line[15:10]s DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line DCD TIM8_BRK_TIM12_IRQHandler ; TIM8 Break and TIM12 DCD TIM8_UP_TIM13_IRQHandler ; TIM8 Update and TIM13 DCD TIM8_TRG_COM_TIM14_IRQHandler ; TIM8 Trigger and Commutation and TIM14 DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare DCD DMA1_Stream7_IRQHandler ; DMA1 Stream7 DCD FMC_IRQHandler ; FMC DCD SDIO_IRQHandler ; SDIO DCD TIM5_IRQHandler ; TIM5 DCD SPI3_IRQHandler ; SPI3 DCD UART4_IRQHandler ; UART4 DCD UART5_IRQHandler ; UART5 DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors DCD TIM7_IRQHandler ; TIM7 DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0 DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1 DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2 DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3 DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4 DCD ETH_IRQHandler ; Ethernet DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line DCD CAN2_TX_IRQHandler ; CAN2 TX DCD CAN2_RX0_IRQHandler ; CAN2 RX0 DCD CAN2_RX1_IRQHandler ; CAN2 RX1 DCD CAN2_SCE_IRQHandler ; CAN2 SCE DCD OTG_FS_IRQHandler ; USB OTG FS DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5 DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6 DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7 DCD USART6_IRQHandler ; USART6 DCD I2C3_EV_IRQHandler ; I2C3 event DCD I2C3_ER_IRQHandler ; I2C3 error DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI DCD OTG_HS_IRQHandler ; USB OTG HS DCD DCMI_IRQHandler ; DCMI DCD CRYP_IRQHandler ; CRYP crypto DCD HASH_RNG_IRQHandler ; Hash and Rng DCD FPU_IRQHandler ; FPU DCD UART7_IRQHandler ; UART7 DCD UART8_IRQHandler ; UART8 DCD SPI4_IRQHandler ; SPI4 DCD SPI5_IRQHandler ; SPI5 DCD SPI6_IRQHandler ; SPI6 DCD SAI1_IRQHandler ; SAI1 DCD LTDC_IRQHandler ; LTDC DCD LTDC_ER_IRQHandler ; LTDC error DCD DMA2D_IRQHandler ; DMA2D __Vectors_End ;向量表结束地址 __Vectors_Size EQU __Vectors_End - __Vectors ;计算向量表大小 ;Vectors 为向量表起始地址, __Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。 ;向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址, 0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。 ;DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。 ;/****************复位程序***************/ AREA |.text|, CODE, READONLY ;定义一个名称为.text 的代码段,可读 ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit ;外部文件 IMPORT __main ;外部文件 LDR R0, =SystemInit ;调用SystemInit()函数配置系统时钟 BLX R0 LDR R0, =__main ;初始化用户栈,并在函数的最后调用main函数进入C的世界 BX R0 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 你写的主函数名称即可。 ; /*************中断复位函数,异常常处理函数***************************/ ; 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 TAMP_STAMP_IRQHandler [WEAK] EXPORT RTC_WKUP_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_Stream0_IRQHandler [WEAK] EXPORT DMA1_Stream1_IRQHandler [WEAK] EXPORT DMA1_Stream2_IRQHandler [WEAK] EXPORT DMA1_Stream3_IRQHandler [WEAK] EXPORT DMA1_Stream4_IRQHandler [WEAK] EXPORT DMA1_Stream5_IRQHandler [WEAK] EXPORT DMA1_Stream6_IRQHandler [WEAK] EXPORT ADC_IRQHandler [WEAK] EXPORT CAN1_TX_IRQHandler [WEAK] EXPORT CAN1_RX0_IRQHandler [WEAK] EXPORT CAN1_RX1_IRQHandler [WEAK] EXPORT CAN1_SCE_IRQHandler [WEAK] EXPORT EXTI9_5_IRQHandler [WEAK] EXPORT TIM1_BRK_TIM9_IRQHandler [WEAK] EXPORT TIM1_UP_TIM10_IRQHandler [WEAK] EXPORT TIM1_TRG_COM_TIM11_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 RTC_Alarm_IRQHandler [WEAK] EXPORT OTG_FS_WKUP_IRQHandler [WEAK] EXPORT TIM8_BRK_TIM12_IRQHandler [WEAK] EXPORT TIM8_UP_TIM13_IRQHandler [WEAK] EXPORT TIM8_TRG_COM_TIM14_IRQHandler [WEAK] EXPORT TIM8_CC_IRQHandler [WEAK] EXPORT DMA1_Stream7_IRQHandler [WEAK] EXPORT FMC_IRQHandler [WEAK] EXPORT SDIO_IRQHandler [WEAK] EXPORT TIM5_IRQHandler [WEAK] EXPORT SPI3_IRQHandler [WEAK] EXPORT UART4_IRQHandler [WEAK] EXPORT UART5_IRQHandler [WEAK] EXPORT TIM6_DAC_IRQHandler [WEAK] EXPORT TIM7_IRQHandler [WEAK] EXPORT DMA2_Stream0_IRQHandler [WEAK] EXPORT DMA2_Stream1_IRQHandler [WEAK] EXPORT DMA2_Stream2_IRQHandler [WEAK] EXPORT DMA2_Stream3_IRQHandler [WEAK] EXPORT DMA2_Stream4_IRQHandler [WEAK] EXPORT ETH_IRQHandler [WEAK] EXPORT ETH_WKUP_IRQHandler [WEAK] EXPORT CAN2_TX_IRQHandler [WEAK] EXPORT CAN2_RX0_IRQHandler [WEAK] EXPORT CAN2_RX1_IRQHandler [WEAK] EXPORT CAN2_SCE_IRQHandler [WEAK] EXPORT OTG_FS_IRQHandler [WEAK] EXPORT DMA2_Stream5_IRQHandler [WEAK] EXPORT DMA2_Stream6_IRQHandler [WEAK] EXPORT DMA2_Stream7_IRQHandler [WEAK] EXPORT USART6_IRQHandler [WEAK] EXPORT I2C3_EV_IRQHandler [WEAK] EXPORT I2C3_ER_IRQHandler [WEAK] EXPORT OTG_HS_EP1_OUT_IRQHandler [WEAK] EXPORT OTG_HS_EP1_IN_IRQHandler [WEAK] EXPORT OTG_HS_WKUP_IRQHandler [WEAK] EXPORT OTG_HS_IRQHandler [WEAK] EXPORT DCMI_IRQHandler [WEAK] EXPORT CRYP_IRQHandler [WEAK] EXPORT HASH_RNG_IRQHandler [WEAK] EXPORT FPU_IRQHandler [WEAK] EXPORT UART7_IRQHandler [WEAK] EXPORT UART8_IRQHandler [WEAK] EXPORT SPI4_IRQHandler [WEAK] EXPORT SPI5_IRQHandler [WEAK] EXPORT SPI6_IRQHandler [WEAK] EXPORT SAI1_IRQHandler [WEAK] EXPORT LTDC_IRQHandler [WEAK] EXPORT LTDC_ER_IRQHandler [WEAK] EXPORT DMA2D_IRQHandler [WEAK] WWDG_IRQHandler PVD_IRQHandler TAMP_STAMP_IRQHandler RTC_WKUP_IRQHandler FLASH_IRQHandler RCC_IRQHandler EXTI0_IRQHandler EXTI1_IRQHandler EXTI2_IRQHandler EXTI3_IRQHandler EXTI4_IRQHandler DMA1_Stream0_IRQHandler DMA1_Stream1_IRQHandler DMA1_Stream2_IRQHandler DMA1_Stream3_IRQHandler DMA1_Stream4_IRQHandler DMA1_Stream5_IRQHandler DMA1_Stream6_IRQHandler ADC_IRQHandler CAN1_TX_IRQHandler CAN1_RX0_IRQHandler CAN1_RX1_IRQHandler CAN1_SCE_IRQHandler EXTI9_5_IRQHandler TIM1_BRK_TIM9_IRQHandler TIM1_UP_TIM10_IRQHandler TIM1_TRG_COM_TIM11_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 RTC_Alarm_IRQHandler OTG_FS_WKUP_IRQHandler TIM8_BRK_TIM12_IRQHandler TIM8_UP_TIM13_IRQHandler TIM8_TRG_COM_TIM14_IRQHandler TIM8_CC_IRQHandler DMA1_Stream7_IRQHandler FMC_IRQHandler SDIO_IRQHandler TIM5_IRQHandler SPI3_IRQHandler UART4_IRQHandler UART5_IRQHandler TIM6_DAC_IRQHandler TIM7_IRQHandler DMA2_Stream0_IRQHandler DMA2_Stream1_IRQHandler DMA2_Stream2_IRQHandler DMA2_Stream3_IRQHandler DMA2_Stream4_IRQHandler ETH_IRQHandler ETH_WKUP_IRQHandler CAN2_TX_IRQHandler CAN2_RX0_IRQHandler CAN2_RX1_IRQHandler CAN2_SCE_IRQHandler OTG_FS_IRQHandler DMA2_Stream5_IRQHandler DMA2_Stream6_IRQHandler DMA2_Stream7_IRQHandler USART6_IRQHandler I2C3_EV_IRQHandler I2C3_ER_IRQHandler OTG_HS_EP1_OUT_IRQHandler OTG_HS_EP1_IN_IRQHandler OTG_HS_WKUP_IRQHandler OTG_HS_IRQHandler DCMI_IRQHandler CRYP_IRQHandler HASH_RNG_IRQHandler FPU_IRQHandler UART7_IRQHandler UART8_IRQHandler SPI4_IRQHandler SPI5_IRQHandler SPI6_IRQHandler SAI1_IRQHandler LTDC_IRQHandler LTDC_ER_IRQHandler DMA2D_IRQHandler B . ;跳转到一个标号。这里跳转到一个‘ .’,即表示无线循环 ENDP ;在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。 ;如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
;/****************用户堆与栈的初始化*************/ ALIGN ;对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。 ;******************************************************************************* ; 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 ;判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、 __heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的 C 库,然后初始化用户堆栈大小,这部分有 C 库函数__main 来完成,当初始化完堆栈之后,就调用 main函数去到 C 的世界。 ;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****