查看原文
其他

STM32启动过程--启动文件--分析

点击上方「嵌入式大杂烩」,选择「置顶公众号」第一时间阅读编程笔记!

原文:https://www.cnblogs.com/amanlikethis/p/3719529.html

一、概述

1、说明

每一款芯片的启动文件都值得去研究,因为它可是你的程序跑的最初一段路,不可以不知道。通过了解启动文件,我们可以体会到处理器的架构、指令集、中断向量安排等内容,是非常值得玩味的。

STM32作为一款高端 Cortex-M3系列单片机,有必要了解它的启动文件。打好基础,为以后优化程序,写出高质量的代码最准备。

本文以一个实际测试代码--START_TEST为例进行阐述。

2、整体过程概括

STM32整个启动过程是指从上电开始,一直到运行到 main函数之间的这段过程,步骤为(以使用微库为例):

①上电后硬件设置SP、PC

②设置系统时钟

③软件设置SP

④加载.data、.bss,并初始化栈区

⑤跳转到C文件的main函数

3、整个启动过程涉及的代码

启动过程涉及的文件不仅包含 startup_stm32f10x_hd.s,还涉及到了MDK自带的连接库文件 entry.oentry2.oentry5.oentry7.o等(从生成的 map文件可以看出来)。

二、程序在Flash上的存储结构

在真正讲解启动过程之前,先要讲解程序下载到 Flash上的结构和程序运行时(执行到main函数)时的SRAM数据结构。程序在用户Flash上的结构如下图所示。下图是通过阅读hex文件和在MDK下调试综合提炼出来的。

  1. MSP初始值        编译器生成,主堆栈的初始值


  2. 异常向量表        不多说


  3. 外部中断向量表      不多说


  4. 代码段          存放代码


  5. 初始化数据段       .data


  6. 未初始化数据段      .bss

加载数据段和初始化栈的参数

加载数据段和初始化栈的参数分别有4个,这里只讲解加载数据段的参数,至于初始化栈的参数类似。

  1. 0x0800 033c  Flash上的数据段(初始化数据段和未初始化数据段)起始地址


  2. 0x2000 0000  加载到SRAM上的目的地址


  3. 0x0000 000c  数据段的总大小


  4. 0x0800 02f4  调用函数_scatterload_copy

需要说明的是初始化栈的函数-- 0x08000304与加载数据段的函数不一样,为 _scatterload_zeroinit,它的目的就是将栈空间清零。

三、数据在SRAM上的结构

程序运行时(执行到main函数)时的SRAM数据结构

四、详细过程分析

有了以上的基础,现在详细分析启动过程。

1、上电后硬件设置SP、PC

刚上电复位后,硬件会自动根据向量表偏移地址找到向量表,向量表偏移地址的定义如下:

调试现象如下:

看看我们的向量表内容(通过J-Flash打开hex文件)

硬件这时自动从0x0800 0000位置处读取数据赋给栈指针SP,然后自动从0x0800 0004位置处读取数据赋给PC,完成复位,结果为:

  1. SP = 0x0200 0810

  2. PC = 0x0800 0145

2、设置系统时钟

上一步中令 PC=0x08000145的地址没有对齐,硬件自动对齐到 0x08000144,执行 SystemInit函数初始化系统时钟。

3、软件设置SP

  1.   LDR R0,=__main

  2.   BX   R0

执行上两条之类,跳转到 __main程序段运行,注意不是main函数, ___main的地址是0x0800 0130。

可以看到指令LDR.W sp,[pc,#12],结果SP=0x2000 0810。

4、加载.data、.bss,并初始化栈区

  1. BL.W __scatterload_rt2

进入 __scatterload_rt2代码段。

  1. __scatterload_rt2:

  2. 0x08000168 4C06 LDR r4,[pc,#24] ; @0x08000184

  3. 0x0800016A 4D07 LDR r5,[pc,#28] ; @0x08000188

  4. 0x0800016C E006 B 0x0800017C

  5. 0x0800016E 68E0 LDR r0,[r4,#0x0C]

  6. 0x08000170 F0400301 ORR r3,r0,#0x01

  7. 0x08000174 E8940007 LDM r4,{r0-r2}

  8. 0x08000178 4798 BLX r3

  9. 0x0800017A 3410 ADDS r4,r4,#0x10

  10. 0x0800017C 42AC CMP r4,r5

  11. 0x0800017E D3F6 BCC 0x0800016E

  12. 0x08000180 F7FFFFDA BL.W _main_init (0x08000138)

这段代码是个循环 BCC0x0800016e,实际运行时候循环了两次。第一次运行的时候,读取“加载数据段的函数 _scatterload_copy”的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取“初始化栈的函数 _scatterload_zeroinit”的地址并跳转到该函数处运行。 相应的代码如下:

  1. 0x0800016E 68E0 LDR r0,[r4,#0x0C]

  2. 0x08000170 F0400301 ORR r3,r0,#0x01

  3. 0x08000174

  4. 0x08000178 4798 BLX r3

当然执行这两个函数的时候,还需要传入参数。至于参数,我们在“加载数据段和初始化栈的参数”环节已经阐述过了。当这两个函数都执行完后,结果就是“数据在SRAM上的结构”所展示的图。最后,也把事实加载和初始化的两个函数代码奉上如下:

  1. __scatterload_copy:

  2. 0x080002F4 E002 B 0x080002FC

  3. 0x080002F6 C808 LDM r0!,{r3}

  4. 0x080002F8 1F12 SUBS r2,r2,#4

  5. 0x080002FA C108 STM r1!,{r3}

  6. 0x080002FC 2A00 CMP r2,#0x00

  7. 0x080002FE D1FA BNE 0x080002F6

  8. 0x08000300 4770 BX lr

  9. __scatterload_null:

  10. 0x08000302 4770 BX lr

  11. __scatterload_zeroinit:

  12. 0x08000304 2000 MOVS r0,#0x00

  13. 0x08000306 E001 B 0x0800030C

  14. 0x08000308 C101 STM r1!,{r0}

  15. 0x0800030A 1F12 SUBS r2,r2,#4

  16. 0x0800030C 2A00 CMP r2,#0x00

  17. 0x0800030E D1FB BNE 0x08000308

  18. 0x08000310 4770 BX lr

5、跳转到C文件的main函数

  1. _main_init:

  2. 0x08000138 4800 LDR r0,[pc,#0] ; @0x0800013C

  3. 0x0800013A 4700 BX r0

五、异常向量与中断向量表

  1. ; Vector Table Mapped to Address 0 at Reset

  2. AREA RESET, DATA, READONLY

  3. EXPORT __Vectors

  4. EXPORT __Vectors_End

  5. EXPORT __Vectors_Size


  6. __Vectors DCD __initial_sp ; Top of Stack

  7. DCD Reset_Handler ; Reset Handler

  8. DCD NMI_Handler ; NMI Handler

  9. DCD HardFault_Handler ; Hard Fault Handler

  10. DCD MemManage_Handler ; MPU Fault Handler

  11. DCD BusFault_Handler ; Bus Fault Handler

  12. DCD UsageFault_Handler ; Usage Fault Handler

  13. DCD 0 ; Reserved

  14. DCD 0 ; Reserved

  15. DCD 0 ; Reserved

  16. DCD 0 ; Reserved

  17. DCD SVC_Handler ; SVCall Handler

  18. DCD DebugMon_Handler ; Debug Monitor Handler

  19. DCD 0 ; Reserved

  20. DCD PendSV_Handler ; PendSV Handler

  21. DCD SysTick_Handler ; SysTick Handler


  22. ; External Interrupts

  23. DCD WWDG_IRQHandler ; Window Watchdog

  24. DCD PVD_IRQHandler ; PVD through EXTI Line detect

  25. DCD TAMPER_IRQHandler ; Tamper

  26. DCD RTC_IRQHandler ; RTC

  27. DCD FLASH_IRQHandler ; Flash

  28. DCD RCC_IRQHandler ; RCC

  29. DCD EXTI0_IRQHandler ; EXTI Line 0

  30. DCD EXTI1_IRQHandler ; EXTI Line 1

  31. DCD EXTI2_IRQHandler ; EXTI Line 2

  32. DCD EXTI3_IRQHandler ; EXTI Line 3

  33. DCD EXTI4_IRQHandler ; EXTI Line 4

  34. DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1

  35. DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2

  36. DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3

  37. DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4

  38. DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5

  39. DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6

  40. DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7

  41. DCD ADC1_2_IRQHandler ; ADC1 & ADC2

  42. DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX

  43. DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0

  44. DCD CAN1_RX1_IRQHandler ; CAN1 RX1

  45. DCD CAN1_SCE_IRQHandler ; CAN1 SCE

  46. DCD EXTI9_5_IRQHandler ; EXTI Line 9..5

  47. DCD TIM1_BRK_IRQHandler ; TIM1 Break

  48. DCD TIM1_UP_IRQHandler ; TIM1 Update

  49. DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation

  50. DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare

  51. DCD TIM2_IRQHandler ; TIM2

  52. DCD TIM3_IRQHandler ; TIM3

  53. DCD TIM4_IRQHandler ; TIM4

  54. DCD I2C1_EV_IRQHandler ; I2C1 Event

  55. DCD I2C1_ER_IRQHandler ; I2C1 Error

  56. DCD I2C2_EV_IRQHandler ; I2C2 Event

  57. DCD I2C2_ER_IRQHandler ; I2C2 Error

  58. DCD SPI1_IRQHandler ; SPI1

  59. DCD SPI2_IRQHandler ; SPI2

  60. DCD USART1_IRQHandler ; USART1

  61. DCD USART2_IRQHandler ; USART2

  62. DCD USART3_IRQHandler ; USART3

  63. DCD EXTI15_10_IRQHandler ; EXTI Line 15..10

  64. DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line

  65. DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend

  66. DCD TIM8_BRK_IRQHandler ; TIM8 Break

  67. DCD TIM8_UP_IRQHandler ; TIM8 Update

  68. DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation

  69. DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare

  70. DCD ADC3_IRQHandler ; ADC3

  71. DCD FSMC_IRQHandler ; FSMC

  72. DCD SDIO_IRQHandler ; SDIO

  73. DCD TIM5_IRQHandler ; TIM5

  74. DCD SPI3_IRQHandler ; SPI3

  75. DCD UART4_IRQHandler ; UART4

  76. DCD UART5_IRQHandler ; UART5

  77. DCD TIM6_IRQHandler ; TIM6

  78. DCD TIM7_IRQHandler ; TIM7

  79. DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1

  80. DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2

  81. DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3

  82. DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5

  83. __Vectors_End

这段代码就是定义异常向量表,在之前有一个“J-Flash打开hex文件”的图片跟这个表格是一一对应的。编译器根据我们定义的函数 Reset_HandlerNMI_Handler等,在连接程序阶段将这个向量表填入这些函数的地址。

  1. startup_stm32f10x_hd.s内容:


  2. NMI_Handler PROC

  3. EXPORT NMI_Handler [WEAK]

  4. B .

  5. ENDP



  6. stm32f10x_it.c中内容:

  7. void NMI_Handler(void)

  8. {

  9. }

在启动汇编文件中已经定义了函数 NMI_Handler,但是使用了“弱”,它允许我们再重新定义一个 NMI_Handler函数,程序在编译的时候会将汇编文件中的弱函数“覆盖掉”--两个函数的代码在连接后都存在,只是在中断向量表中的地址填入的是我们重新定义函数的地址。

六、使用微库与不使用微库的区别

使用微库就意味着我们不想使用MDK提供的库函数,而想用自己定义的库函数,比如说printf函数。那么这一点是怎样实现的呢?我们以printf函数为例进行说明。

1、不使用微库而使用系统库

在连接程序时,肯定会把系统中包含printf函数的库拿来调用参与连接,即代码段有系统库的参与。

在启动过程中,不使用微库而使用系统库在初始化栈的时候,还需要初始化堆(猜测系统库需要用到堆),而使用微库则是不需要的。

  1. IF :DEF:__MICROLIB


  2. EXPORT __initial_sp

  3. EXPORT __heap_base

  4. EXPORT __heap_limit


  5. ELSE


  6. IMPORT __use_two_region_memory

  7. EXPORT __user_initial_stackheap


  8. __user_initial_stackheap


  9. LDR R0, = Heap_Mem

  10. LDR R1, =(Stack_Mem + Stack_Size)

  11. LDR R2, = (Heap_Mem + Heap_Size)

  12. LDR R3, = Stack_Mem

  13. BX LR


  14. ALIGN


  15. ENDIF

另外,在执行 __main函数的过程中,不仅需要完成“使用微库”情况下的所有工作,额外的工作还需要进行库的初始化,才能使用系统库(这一部分我还没有深入探讨)。附上 __main函数的内容:

  1. __main:

  2. 0x08000130 F000F802 BL.W __scatterload_rt2_thumb_only (0x08000138)

  3. 0x08000134 F000F83C BL.W __rt_entry_sh (0x080001B0)

  4. __scatterload_rt2_thumb_only:

  5. 0x08000138 A00A ADR r0,{pc}+4 ; @0x08000164

  6. 0x0800013A E8900C00 LDM r0,{r10-r11}

  7. 0x0800013E 4482 ADD r10,r10,r0

  8. 0x08000140 4483 ADD r11,r11,r0

  9. 0x08000142 F1AA0701 SUB r7,r10,#0x01

  10. __scatterload_null:

  11. 0x08000146 45DA CMP r10,r11

  12. 0x08000148 D101 BNE 0x0800014E

  13. 0x0800014A F000F831 BL.W __rt_entry_sh (0x080001B0)

  14. 0x0800014E F2AF0E09 ADR.W lr,{pc}-0x07 ; @0x08000147

  15. 0x08000152 E8BA000F LDM r10!,{r0-r3}

  16. 0x08000156 F0130F01 TST r3,#0x01

  17. 0x0800015A BF18 IT NE

  18. 0x0800015C 1AFB SUBNE r3,r7,r3

  19. 0x0800015E F0430301 ORR r3,r3,#0x01

  20. 0x08000162 4718 BX r3

  21. 0x08000164 0298 LSLS r0,r3,#10

  22. 0x08000166 0000 MOVS r0,r0

  23. 0x08000168 02B8 LSLS r0,r7,#10

  24. 0x0800016A 0000 MOVS r0,r0

  25. __scatterload_copy:

  26. 0x0800016C 3A10 SUBS r2,r2,#0x10

  27. 0x0800016E BF24 ITT CS

  28. 0x08000170 C878 LDMCS r0!,{r3-r6}

  29. 0x08000172 C178 STMCS r1!,{r3-r6}

  30. 0x08000174 D8FA BHI __scatterload_copy (0x0800016C)

  31. 0x08000176 0752 LSLS r2,r2,#29

  32. 0x08000178 BF24 ITT CS

  33. 0x0800017A C830 LDMCS r0!,{r4-r5}

  34. 0x0800017C C130 STMCS r1!,{r4-r5}

  35. 0x0800017E BF44 ITT MI

  36. 0x08000180 6804 LDRMI r4,[r0,#0x00]

  37. 0x08000182 600C STRMI r4,[r1,#0x00]

  38. 0x08000184 4770 BX lr

  39. 0x08000186 0000 MOVS r0,r0

  40. __scatterload_zeroinit:

  41. 0x08000188 2300 MOVS r3,#0x00

  42. 0x0800018A 2400 MOVS r4,#0x00

  43. 0x0800018C 2500 MOVS r5,#0x00

  44. 0x0800018E 2600 MOVS r6,#0x00

  45. 0x08000190 3A10 SUBS r2,r2,#0x10

  46. 0x08000192 BF28 IT CS

  47. 0x08000194 C178 STMCS r1!,{r3-r6}

  48. 0x08000196 D8FB BHI 0x08000190

  49. 0x08000198 0752 LSLS r2,r2,#29

  50. 0x0800019A BF28 IT CS

  51. 0x0800019C C130 STMCS r1!,{r4-r5}

  52. 0x0800019E BF48 IT MI

  53. 0x080001A0 600B STRMI r3,[r1,#0x00]

  54. 0x080001A2 4770 BX lr

  55. __rt_lib_init:

  56. 0x080001A4 B51F PUSH {r0-r4,lr}

  57. 0x080001A6 F3AF8000 NOP.W

  58. __rt_lib_init_user_alloc_1:

  59. 0x080001AA BD1F POP {r0-r4,pc}

  60. __rt_lib_shutdown:

  61. 0x080001AC B510 PUSH {r4,lr}

  62. __rt_lib_shutdown_user_alloc_1:

  63. 0x080001AE BD10 POP {r4,pc}

  64. __rt_entry_sh:

  65. 0x080001B0 F000F82F BL.W __user_setup_stackheap (0x08000212)

  66. 0x080001B4 4611 MOV r1,r2

  67. __rt_entry_postsh_1:

  68. 0x080001B6 F7FFFFF5 BL.W __rt_lib_init (0x080001A4)

  69. __rt_entry_postli_1:

  70. 0x080001BA F000F919 BL.W main (0x080003F0)

2、使用微库而不使用系统库

在程序连接时,不会把包含printf函数的库连接到终极目标文件中,而使用我们定义的库。

启动时需要完成的工作就是之前论述的步骤1、2、3、4、5,相比使用系统库,启动过程步骤更少。




我的博客

zhengnianli.github.io

猜你喜欢

【强烈推荐】一个炫酷的博客网站

是否要从单片机转嵌入式Linux?

智能桌面天气预报系统(终)

一位大牛的单片机笔记

STM32两种printf函数重定向方法

【RT-Thread】启动问题

省电子设计竞赛一等奖作品分享(三)





    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存