STM32启动代码原理分析

STM32启动代码原理分析(底层技术); 简述 ARM Cortex-M系列MCU的启动代码(使用汇编语言编程则不需要)主要做3件事情: 初始化并正确放置异常/中断矢量表; 分散加载; 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)。 Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口矢量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口矢量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断矢量表的位置而起始地址是可变化的。 源码分析 基于STM32F103C6T6的启动文件startup_stm32f103x6.s的简要说明如下: ;******************** (C) COPYRIGHT 2017 STMicroelectronics ******************** ;* File Name : startup_stm32f103x6.s ;* Author : MCD Application Team ;* Description : STM32F103x6 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 clock system ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the Cortex-M3 processor is in Thread mode, ;* priority is Privileged, and the Stack is set to Main. ;****************************************************************************** ;* @attention ;* ;* Copyright (c) 2017 STMicroelectronics. ;* All rights reserved. ;* ;* This software component is licensed by ST under BSD 3-Clause license, ;* the "License"; You may not use this file except in compliance with the ;* License. You may obtain a copy of the License at: ;* opensource.org/licenses/BSD-3-Clause ;* ;****************************************************************************** ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; Stack Configuration ; Stack Size (in Bytes) ; Stack_Size EQU 0x400 ;声明栈的大小为0x400字节 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size ;开辟一段大小为Stack_Size的内存空间作为栈 __initial_sp ;标号__initial_sp,表示栈空间顶地址。 ; Heap Configuration ; Heap Size (in Bytes) ; Heap_Size EQU 0x200 ;声明栈的大小为0x200字节 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base ;标号__heap_base,表示堆空间起始地址。 Heap_Mem SPACE Heap_Size ;开辟一段大小为Heap_Size的内存空间作为堆。 __heap_limit ;标号__heap_limit,表示堆空间结束地址。 ;-------------------------------------------- ;第一部分: ;启动代码最重要的工作是把异常中断向量表放到正确的Flash地址上 ;把向量表定义为只读数据段,并导出向量表标号(Symbol),让链接器识别此标号并根据分散加载文件正确的放置向量表 ;__Vectors标号需要与分散加载文件合起来看,才会明白其真正的功能 ;-------------------------------------------- PRESERVE8 ;告诉编译器以8字节对齐。 THUMB ;告诉编译器使用THUMB指令集。 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY ;声明权限为“READONLY”的名称为“RESET”的数据段 ;(假设STM32从FLASH启动,则此中断矢量表起始地址即为0x8000000) EXPORT __Vectors ;将标号__Vectors声明为全局标号,这样外部文档就可以使用这个标号。 EXPORT __Vectors_End EXPORT __Vectors_Size ;标号__Vectors,表示中断矢量表入口地址 ;创建中断矢量表 ;--------------------------------------- ;第二部分: ;__initial_sp ; 1、栈顶指针地址,此语法跟MDK编译器的底层相关,是ARMCC编译器才能识别的语法 ; GCC与IAR的底层编译器ICCARM编译器不能识别; ; 2、__initial_sp 是一个链接器Image Symbol; ; 3、此处__initial_sp相当于是顶地址,或者此处直接把顶地址写到此处也行(如:0x20004000); ; 4、__initial_sp具体是多少,在此种写法下,是由分散加载文件决定的,下文会有详细论述; ; ;Reset_Handler: ; 1、Reset_Handler函数地址,此处相当于把Reset_Handler函数地址赋值给PC,即调用Reset_Handler函数; ; 2、此处也可以是其他函数,只是把复位函数放于此处最符合实际应用场景。 ; 重要关键节点: ; 1、绝大多数cortex-M微控制器(M0、M3、M4都是这样)复位后先进入厂商BOOTROM,此时所有用户行为均无法介入处理器; ; 2、厂商BOOTROM(有些厂商会有其他名称来称呼此功能) 主要负责处理一些芯片最初级初始化、 ; 加密以及一些对MCU的差异化设置等工作: ; 3、BOOTROM顺利完成后,MCu控制权会交给用户,即启动代码; ; 4、启动代码(运行汇编语言则不需要此启动代码),最重要的工作在于设置MSP (主堆栈指针)以及PC(程序计数器)的值; ; 5、Cortex-M微控制器会默认把0x00000000地址里面的值设置为MSP的值,0x00000004地址里面的值设置为PC的值; ; 6、5中的默认地址可以通过修改Cortex-M中的VTOR寄存器来重新映射,比如改到0x20000000地址或其他; ; ;关于Exception(异常) 与Interrupt (中断) 的区别说明 ; 1、Exception(异常)与Interrupt(中断)是不同的,是两个不同的概念,很多人会混淆两者, ; 把他们都按照中断来看待,这是错误的; ; 2、Exception(异常)是向量表的前16个向量,其优先级为负数,高于所有中断,而且不可调整优先级也不可关闭, ; 可以打断正常程序与Interrupt(中断) 的运行: ; 3、从第16个向量以后才是Interrupt(中断),可以设置优先级,不用时可以关闭,但优先级永远低于Exception(异常); ; 4、从这里大家就可以理解Pendsy Handler与svsTick Handler为什么会被用于嵌入式操作系统, ; 因为其优先级高于所有中断,可以确保操作系统拥有高于普通用户程序执行的超级权限, ; SVC_Handler有时也会用于操作系统,原理相同; ; 5、用户应用程序应尽量避免使用Exception(异常); ;--------------------------------------- __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 操作系统会用到的心跳定时器异常向量(没有操作系统时可以用作普通定时器中断) ;--------------------------------------- ;第三部分: ; 1、这里开始是中断向量表; ; 2、各个向量的顺序是芯片设计的时候就定义好的,不能更改; ;--------------------------------------- ; 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_2 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 0 ; Reserved DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD 0 ; Reserved DCD 0 ; Reserved DCD SPI1_IRQHandler ; SPI1 DCD 0 ; Reserved DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD 0 ; Reserved DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors ;--------------------------------------- ;第四部分: ;这部分开始可以称作Reset Handler实体,芯片上电后,经过BOOTROM后进入的用户可控最开始处的地方; ; 1、如果想让Mcu正常使用c语言,务必在此处调用 main函数; ; 2、__main() 不是main() 两者有着本质性的区别; ; 3、__main()是c Library中的函数,Kei1开发环境中自带的c Library中的函数; ; 4、main()是被 __main()调用的,__main()工作完成后最后一步就是调用main(); ; 5、__main()被调用之前,可以根据需要插入一个或多个其他功能函数; ;--------------------------------------- AREA |.text|, CODE, READONLY ;定义只读的代码段 ; Reset handler routine ;复位中断服务程序,PROC…ENDP结构表示程序的开始和结束。 Reset_Handler PROC EXPORT Reset_Handler [WEAK] ;声明复位中断矢量Reset_Handler为全局属性 ;这样外部文档就可以调用此复位中断服务。 IMPORT __main ;声明__main标号。 IMPORT SystemInit ;声明SystemInit标号。 LDR R0, =SystemInit ;跳转SystemInit地址执行 BLX R0 LDR R0, =__main ;跳转__main地址执行 BX R0 ENDP ; Dummy Exception Handlers (infinite loops which can be modified) ;--------------------------------------------------------------- ;第五部分: ; 1、[weak]指定了一个这个函数为"弱函数”; ; 2、这些中断服务函教定义成弱函数的意义是,当中断出现时,需要有一个中断服务函数予以响应,但真实的 ; 用户程序往往只会使用一部分中断,甚至不使用中断,所以以下这些函数给出了异常/中断服务函数的 ; 默认实现,很简单,默认实现就是死循环汇编中的"B."语句,相当于while(1);因为不知道用户是否会 ; 用到多少中断,但这些服务函数又很重要,所以就把这些函数都"实现"并声明为弱函数; ; 3、弱函数的意思是如果用户定义了同样名称的另一个函数,那么默认实现的弱函数就会被覆盖,比如 ; HardFault_Handler异常在下面有一个默认的实现,但这种默认的实现不能满足我的需要的时候,我可以 ; 再重新定义一个HardEault Handler函数这个新定义的HardFault_Handler函数会覆盖原有的被声明 ; 为[WEAK]的弱函数; ; 4、有很多种适合使用弱函数的场合,默认的异常/中断服务函数只是一种应用场景; ; 5、在C语言中声明弱函数是在函数后加“__attribute((weak))”; ;---------------------------------------------------------------- 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 I2C1_EV_IRQHandler [WEAK] EXPORT I2C1_ER_IRQHandler [WEAK] EXPORT SPI1_IRQHandler [WEAK] EXPORT USART1_IRQHandler [WEAK] EXPORT USART2_IRQHandler [WEAK] EXPORT EXTI15_10_IRQHandler [WEAK] EXPORT RTC_Alarm_IRQHandler [WEAK] EXPORT USBWakeUp_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 I2C1_EV_IRQHandler I2C1_ER_IRQHandler SPI1_IRQHandler USART1_IRQHandler USART2_IRQHandler EXTI15_10_IRQHandler RTC_Alarm_IRQHandler USBWakeUp_IRQHandler B . ENDP ALIGN ;******************************************************************************* ; User Stack and Heap initialization ;******************************************************************************* ;IF…ELSE…ENDIF结构,判断是否使用DEF:__MICROLIB(此处为不使用)。 ;若使用DEF:__MICROLIB,则将__initial_sp,__heap_base,__heap_limit ;亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。 IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __use_two_region_memory ;定义全局标号__use_two_region_memory。 EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap, ;这样外程序也可调用此标号 __user_initial_stackheap ;标号__user_initial_stackheap,表示用户堆栈初始化程序入口 ;分别保存栈顶指针和栈大小,堆始地址和堆大小至R0,R1,R2,R3寄存器。 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;程序完毕 ;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE***** 部分解释 以上便是STM32的启动代码的完整解析,接下来对几个小地方做解释: ...

February 25, 2023 · 6 min · Rancho

STM32的启动过程

STM32单片机是如何从上电运行到main()函数的; 用三张图片基本就能理解了: 参考: https://blog.csdn.net/weixin_39118482/article/details/79508747?spm=1001.2014.3001.5502 https://www.modb.pro/db/548699 https://www.cnblogs.com/yucloud/p/stm32_SystemInit_to_main.html 1、函数的调用过程: 2、启动流程1(使用标准库,不使用Microlib) 如下图: 3、启动流程2(使用Microlib) microlib 是缺省 C 库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行。 microlib 进行了高度优化以使代码变得很小。它的功能比缺省 C 库少,并且根本不具备某些 ISOC 特性。某些库函数的运行速度也比较慢,例如, memcpy() 。 microlib与缺省C库之间的主要差异是: microlib不符合ISO C库标准。不支持某些ISO特性,并且其他特性具有的功能也较少; microlib不符合IEEE 754二进制浮点算法标准; microlib进行了高度优化以使代码变得很小; 无法对区域设置进行配置。缺省C区域设置是唯一可用的区域设置; 不能将main()声明为使用参数,并且不能返回内容; 不支持stdio,但未缓冲的stdin、stdout和stderr除外; microlib对C99函数提供有限的支持; microlib不支持操作系统函数; microlib不支持与位置无关的代码; microlib不提供互斥锁来防止非线程安全的代码; microlib不支持宽字符或多字节字符串; 与stdlib不同,microlib不支持可选择的单或双区内存模型。microlib只提供双区内存模型,即单独的堆栈和堆区。 启动流程如下图: 4、其他 关于stm32的启动文件: https://www.fan-pengfei.top/2023/02/25/STM32%E5%90%AF%E5%8A%A8%E4%BB%A3%E7%A0%81%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/#more

February 25, 2023 · 1 min · Rancho

分散加载文档

分散加载(scatter)文档是一个文本文档,它可以用来描述ARM连接器生成映像文档时所需要的信息; 参考:https://blog.csdn.net/KXue0703/article/details/114018759 一、基础知识 为了充分理解分散加载文档的魅力,需要对工程编译后的内容有详细的了解。 Keil 编译后的内容如下所示: Code:为程序代码部分; RO-Data:表示程序定义的常量及 const 型数据; RW-Data:表示已经初始化的静态变量,变量有初值; ZI-Data:表示未初始化的静态变量,变量无初值; 当 Keil 工程编译完成后,查看其 map 文档,可得到结果如下程序清单: Code (inc. data) RO Data RW Data ZI Data Debug 4194 230 714 16 1640 72715 Grand Totals 4194 230 714 16 1640 72715 ELF Image Totals 4194 230 714 16 0 0 ROM Totals Total RO Size (Code + RO Data) 4908 (4.79kB) Total RW Size (RW Data + ZI Data) 1656 (1.62kB) Total ROM Size (Code + RO Data + RW Data) 4924 (4.81kB) 由map文档可以看出: ROM Size = Code+RO-Data+RW-Data = 4.81kB; RAM Size = RW-Data+ZI-Data = 1.62kB; ...

February 25, 2023 · 5 min · Rancho