你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的启动过程分析

[复制链接]
STMCU小助手 发布时间:2021-12-27 18:00
STM32H7的启动过程分析: a' m% g/ K9 @( U6 h( o: ]4 f
启动文件

% n" v8 Z5 u& m" a0 m不同编译器对应的启动文件不同,在MDK下,以startup_stm32h743xx.s为例,这是一个汇编文件,启动文件中主要做了如下事情:
/ C. x4 t9 c4 O( ~# y- F设置堆栈指针 SP = __initial_sp。+ e6 k6 s. T% X1 ~) L3 g$ w3 E7 [
设置 PC 指针 = Reset_Handler。7 s1 \. }' T: @2 b, N8 c0 y
设置中断向量表。
. r! z# @+ u, N9 ^' c8 h* E配置系统时钟。: w7 O0 W' W' H3 \+ j: ]4 }( x
配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。% v* B/ |- c1 g( L; E& c" @
跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。
1 w8 ?  Y; s) d  A' q( x' G( uCortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为用主堆栈 MSP。0 I* D2 A8 A' p5 ~
堆栈指针0 t, R. G0 D8 h+ V  A1 b. h
通用寄存器组8 e) X! e( B2 W' J
Cortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以用。
9 \$ _- k9 X! O1 ?$ Z主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。( q7 {. w2 B1 n- S9 ], r! {; \. ?6 D
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。: B0 I% H, f+ b# I% b: Y
另外以下两点要注意:
0 Y7 j9 L5 a4 U% t( b4 a7 H大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。6 m- A- ]* |) W1 b
R13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。
5 ?! D7 O: T/ m/ M4 \6 v) _# s) s. |& i4 C+ D
Cortex-M7/M4/M3 向下生长的满栈
" d' |, b6 G' i5 ~PUSH 入栈操作:SP 先自减 4,再存入新的数值
) W$ U2 t! h2 L* I' Y& y. E$ ZPOP 出栈操作:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4
* d  G0 P7 [: m! O; I" d1 R1 K
# T, ?# T- P6 X% Q分析启动过程1 |. Y- _" a) q- w/ P7 R9 K% ?
硬件上电后,会触发硬件复位,复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000)
7 _  c- l6 L( }6 F% _7 P9 @8 G$ {8 n" M- X5 m" H
将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。$ c7 c% j- R; K+ d9 b2 b  w
将 0x08000004 位置存放的向量地址装入 PC 程序计数器。
2 q4 M* p+ o# C: j! L0 _6 oCPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。为啥,因为在启动文件中,最先做的两件事情是
1 ]) S, P1 V6 L4 ]3 x设置堆栈指针 SP = __initial_sp
3 i: n7 L  K* Q# @! X: _" a- V设置 PC 指针 = Reset_Handler, l$ Z- u& m0 y3 C" n4 w" y
复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。
& `0 _2 S( K# G3 \& q# j$ f
9 D; S4 K' v8 B3 {# {# _! S7 {0 H代码分析7 d' U' H5 Z- X0 u. R( M+ x' z9 C& i
①、开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等. m( a* |8 _: Z# k; o
" t( s6 a; `6 t; g' D- z2 T- R
  1. //类似宏定义,这是个伪指令,定义栈大小,这里是以字节为单位; z6 z0 C# s9 `/ Y" X) ^
  2. Stack_Size      EQU     0x00001000   ) F% _9 \! l0 ?9 P
  3. * `* }: ^5 H; z5 Q1 d* _3 _
  4. /*  p0 d- O+ X$ A! p  H# t5 a
  5. 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。 ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。 ARER 后面的关键字表示这个段的属性。/ X; ~5 }2 m% S+ G% ~0 G/ t$ ^
  6. STACK :表示这个段的名字,可以任意命名。# w) M4 F, _) o/ m: C
  7. NOINIT:表示此数据段不需要填入初始数据。- d3 ]* n9 F8 g! i( Y
  8. READWRITE:表示此段可读可写。
    ) P/ [2 O2 q8 o5 r/ m
  9. ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。
    6 V$ ?; t5 E& T3 T8 L: u  a
  10. */) ]$ M$ N. F4 v2 u  m4 @. b
  11.                 AREA    STACK, NOINIT, READWRITE, ALIGN=38 y7 F# y) r4 x8 U7 S9 k2 g$ r
  12. % f! N! Q; J, ?; _/ p+ ?
  13. //SPACE 这行指令告诉汇编器给 STACK 段分配 0x00001000 字节的连续内存空间。
    ( s3 [( l/ s- F
  14. Stack_Mem       SPACE   Stack_Size
    1 K7 E9 ~, L! o! @' J6 n) K

  15. ; O" k& L7 q' B; r
  16. /*
    , |6 S7 E9 g2 e' f+ P: c0 T# T
  17. __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。 __initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
    " m1 z, s7 h4 ]( @7 Q! I1 H
  18. */) X8 x- Z$ `# T- `! U
  19. __initial_sp
复制代码

3 c9 |  |  o% ^4 A①、开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数2 c/ `( x$ S/ o" f% @
分配的变量空间是在堆上
. P" s, d8 O9 Y/ O6 }
2 k8 `$ I  l% Y
  1. //定义堆空间大小
      p* S* y2 S6 F( c( B% ~
  2. Heap_Size       EQU     0x0000800
    0 L) ~9 e3 w" z; p1 ]# L" U4 Z
  3. . L7 m- H& \4 S9 g* M7 l  Z2 _
  4. //分配一段名字为HEAP、可读可写、不需要初值、8字节对齐的数据空间6 F/ Y5 z* k& O8 e
  5.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3  h3 v8 c) \7 Z. H0 ~" X
  6. ! ]' q/ X- j# U8 ~* m, h8 [8 L( J
  7. //__heap_base 表示堆的开始地址。/ N( h3 i/ M3 Y
  8. __heap_base0 ?$ ?0 ?2 |) i. h5 R3 A& O
  9.   e2 {) p* t0 k, Q+ w7 k; v
  10. //SPACE 这行指令告诉汇编器给 HEAP段分配 0x0000800字节的连续内存空间。6 w# p7 j8 G& c. N% _
  11. Heap_Mem        SPACE   Heap_Size
    + U) ^4 ~* U( c' ]: }0 q2 D2 m

  12. # w) h& ^/ h: Q- S
  13. __heap_limit 表示堆的结束地址
    / K3 [5 }, `5 w6 m% V" b
  14. __heap_limit
复制代码
; ^& C% B1 y) L0 W. {
③、生成属性设置、定义RESET代码段! Y1 e" Z( i4 C( s5 u, l7 `3 G
/ F! N' F8 {1 G
  1. //PRESERVE8 指定当前文件保持堆栈八字节对齐。4 l" V/ B! q2 K
  2.                 PRESERVE86 a. R( ~1 B6 c) p) W. @0 g8 b1 J

  3. . S! p' t4 g( @
  4. //THUMB 表示后面的指令是 THUMB 指令集 ,CM7 采用的是 THUMB - 2 指令集
    * Q! C, A1 X, C# c1 N- z
  5.                 THUMB
    / F2 k3 y% f, d

  6. ) k: S  x9 C4 Y, ]# ]9 ?8 x2 I
  7. //:AREA 定义一块代码段,只读,段名字是 RESET。 READONLY 表示只读,缺省就表示代码段了。
    ! Q& K& ~7 b9 R& X+ R' W
  8. ; Vector Table Mapped to Address 0 at Reset
    6 S7 L! k! y* O1 L* {- J' q
  9.                 AREA    RESET, DATA, READONLY3 \4 X/ D$ ~5 \8 o5 S

  10. . `" G4 A. n1 f$ t+ C" f# ~% U
  11. //3 行 EXPORT 语句将 3 个标号申明为可被外部引用, 主要提供给链接器用于连接库文件或其他文件。4 b6 \9 B: z! t0 L1 r  f7 w
  12.                 EXPORT  __Vectors" t. O9 c' a/ E/ ]- B: C
  13.                 EXPORT  __Vectors_End
    " l8 Q, N; U2 N: ]
  14.                 EXPORT  __Vectors_Size
复制代码

. O1 e9 ^) i3 f4 W$ a④、中断向量变定义4 Y! E8 h& l1 U6 j; @! g
4 G8 t1 m1 [/ s
  1. __Vectors       DCD     __initial_sp                      ; Top of Stack
    $ `5 ]2 k; {) M& @0 Q
  2.                 DCD     Reset_Handler                     ; Reset Handler; T6 h4 u0 L% {" c
  3.                 DCD     NMI_Handler                       ; NMI Handler
    - v1 G: i1 W4 F% J! m" v
  4.                 DCD     HardFault_Handler                 ; Hard Fault Handler
      I/ s4 O1 f- M, D! S7 r
  5. /*  省略部分代码 */                              
    ! c+ k, J/ r" G/ F8 d7 b
  6.                 DCD     0                                 ; Reserved                                    3 s6 o0 a. j% n" D3 h- c
  7.                 DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins
    9 ^/ h1 s" Z" V! n( e- E
  8. - x8 S  L4 i& C. Z/ h
  9. & \! c% n- C# h! v4 T2 u4 D" W
  10. __Vectors_End8 R( t( l& k' [& a9 d8 ?& O( p

  11. 3 ]; T! b: p6 O2 Y
  12. //定义向量表大小
    ; h5 Z1 s; v2 e8 {
  13. __Vectors_Size  EQU  __Vectors_End - __Vectors
复制代码
2 F  E. m0 N+ a* k: O: l
上面的这段代码是建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。如果程序在 Flash 运行,则中断向量表的起始地址是0x08000000。以 MDK 为例,就是如下配置选项:
& N+ [& o& w1 [) K( X% e: m1 P+ W- k' n6 a
20191124212435739.png
3 j) s& a' X5 L% x7 n

  a2 H/ ~  c: }' K) zDCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序
2 s2 r9 c7 O. R2 r. |! p. ~1 H5 H" J, e5 h& @: {
⑤、代码段定义、Reset_Handler过程处理/ F/ r, ]/ k+ u) w; @. [3 _7 I

/ L0 F: o% ?1 i2 k0 |$ a
  1.                 AREA    |.text|, CODE, READONLY
    7 }( o& {/ r* Q6 P: g2 G

  2. ; {  M5 g# ?2 Q% _2 m9 l* r. R0 `
  3. ; Reset handler, j  ?7 A4 k) J: W+ ?! L
  4. Reset_Handler    PROC3 }2 @3 v( H: |, J  O% |
  5.                  EXPORT  Reset_Handler                    [WEAK]
    # B8 w  y# y* p/ X$ q: L! N
  6.         IMPORT  SystemInit% C0 J& u/ d# `
  7.         IMPORT  __main+ K0 f9 X7 Z0 g: a9 c, i2 x: W

  8. 8 m5 d% ^& C' Q/ @
  9.                  LDR     R0, =SystemInit7 B& ]- H2 X* n1 A/ e
  10.                  BLX     R0
    - L" M5 K* J% ~; _
  11.                  LDR     R0, =__main
    0 ]; a5 O2 d3 R1 P) Q* Z$ ~
  12.                  BX      R01 S. A( `) U# Y
  13.                  ENDP
复制代码
- G2 S( U! U3 ]0 C
AREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读! g! A7 M9 w' r* |1 l/ B
利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
& G) W0 }: K& R/ BWEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。
. E' @/ o8 \9 N' H% P# j! qIMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。9 D  y6 [3 t" _# g
SystemInit 函数在文件 system_stm32h7xx.c 里面,主要实现 RCC 相关寄存器复位和中断向量表位置设置。& u3 e4 ^  M4 y) e: `, h6 ]
__main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到 C 程序中的 main 函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。
6 C  b, ~! i0 S/ E# Y; S. w
- T  \* Z% |6 H) `6 ]$ g⑥、缺省中断服务函数定义
  1. ; Dummy Exception Handlers (infinite loops which can be modified)- I2 ?- m$ D5 m! M
  2. % n. i! \8 ?; v1 @, P( q
  3. NMI_Handler     PROC3 G3 L, D5 \- a
  4.                 EXPORT  NMI_Handler                      [WEAK]9 r- }* F: l$ L0 c. [/ c
  5.                 B       .      //死循环$ Y( {# R, M# ]- Z9 P( ^
  6.                 ENDP$ V+ R; g" N" l; i
  7. ...省略
    3 t9 o2 x9 J+ q' f7 d' z3 p2 R
  8.                 EXPORT  PendSV_Handler                    [WEAK]) n' y- e, f/ j9 D) m2 N+ v
  9.                 B       .$ u* t" y$ a5 D& P2 _
  10.                 ENDP( N+ S& \& g$ [3 X
  11. SysTick_Handler PROC* r+ Y- }6 Y7 t, n7 i
  12.                 EXPORT  SysTick_Handler                   [WEAK]
    7 H) q$ V6 B' I  r1 E
  13.                 B       .
    # {8 ~. _5 f" x. @& y
  14.                 ENDP                                    
    1 k6 N; R0 J9 r; J$ u+ s' h
  15. $ p" ~( p" B5 A
  16. Default_Handler PROC                                      * I8 y" f) |0 D( V+ o

  17. 3 m: `6 R2 t* F0 r: `* p+ F
  18.                 EXPORT  WWDG_IRQHandler                   [WEAK]                                       8 k& g, o. ~3 p" w2 k1 m# X
  19.                 EXPORT  PVD_AVD_IRQHandler                [WEAK]                        
    ! }( |- X/ U( W2 u+ X) _9 Y) K4 [
  20. ...省略                                                                                 
    / C7 q1 z6 e0 J9 s
  21.                 EXPORT  WAKEUP_PIN_IRQHandler             [WEAK] ; j/ g# r6 f" p1 l2 w4 F

  22. ' S9 p- v! s+ x

  23. 8 j' Y: F: J: K, h
  24. WWDG_IRQHandler                                                            R  I4 H% x; K. ~1 l
  25. ...省略     & [6 Y- g: ^3 B2 L
  26. WAKEUP_PIN_IRQHandler) \! D* X7 t/ ^# \( O7 n1 V

  27. 7 T: O% g  l1 H. {$ p( \  j# \- i
  28.                 B       .& d; H: t3 J# E1 j2 e( z& k7 |8 m

  29. 4 n9 y: p( j/ A9 @+ G
  30.                 ENDP
    : B9 F0 @" [% m: B
  31. 0 C. @5 |+ e5 v% x6 ^# Y5 i, g
  32.                 ALIGN
复制代码
* K* b3 ^4 p0 ~' V
这里全部的中断服务函数都是用[WEAK]来声明,假如没有在其他文件中定义同名的中断服务函数,来了中断,就会进入到这里。
% q9 @% t4 o7 ~8 s
3 R: {3 [! O& j5 F) ^+ _; U+ f) K⑦、堆和栈的初始化
' y! [- I4 u6 H
1 D, A/ f) o, E7 o
  1. ;*******************************************************************************
    / V( f  f# w" E: u0 J# w2 x
  2. ; User Stack and Heap initialization+ v  @# S- x, a* A6 N
  3. ;*******************************************************************************
    , R; y! V9 _2 s2 N; Q$ q
  4. //假如定义了MICROLIB,这里类似于if...else...- r- |, X( A7 E. J
  5.                  IF      :DEF:__MICROLIB
    1 D& Y9 v+ X4 w; @- O0 G" D

  6. ' p3 s% M( H$ I8 d
  7.                  EXPORT  __initial_sp4 l) m. P, A! j3 E" j0 u9 g
  8.                  EXPORT  __heap_base
      q* G- \5 L, C5 a
  9.                  EXPORT  __heap_limit# L6 |/ @! {& z$ ?' w" Z4 B

  10. ( Q9 K, d0 \: `1 g# s- q1 I1 p3 ]8 b
  11.                  ELSE
    4 G9 K. O7 |5 _9 @- R
  12. & H( {9 t) b; G  Z- M. X; |
  13.                  IMPORT  __use_two_region_memory3 |/ Y& M' o- W4 T1 r
  14.                  EXPORT  __user_initial_stackheap
    ) }- @* q) V& ]

  15. , @: }( N0 }4 y2 P! j6 H) b; K
  16. //__user_initial_stackheap 将由__main 函数进行调用。                 
    ) B* ^% ?" y7 p" u, k, R
  17. __user_initial_stackheap0 t9 S7 W! i6 G; U

  18. * g( d2 B4 G  z# ]2 m
  19.                  LDR     R0, =  Heap_Mem
    ' o. \7 C' S. @7 ~. m8 F
  20.                  LDR     R1, =(Stack_Mem + Stack_Size)
    5 J# L& j' M& s' U! A9 {$ }
  21.                  LDR     R2, = (Heap_Mem +  Heap_Size)8 t, ~7 ~* m& Z8 u8 X  J
  22.                  LDR     R3, = Stack_Mem3 h8 M9 U! F: n
  23.                  BX      LR
    5 o% y$ u  P/ y
  24. / m' G4 Y) W7 E- v7 `5 G3 ^
  25.                  ALIGN0 J! U  d6 c- b/ @/ V: C
  26. & b' ^' Z( l& l5 p0 @9 L: ~
  27.                  ENDIF
    . P1 t3 p+ A6 `6 e3 r) S4 j% `
  28. # _. E' s  Q% C
  29.                  END
复制代码
Boot的启动模式不同于以往的M3、M4内核的ST MCU,H7的boot引脚只有一个,但是H7 专门配套了两个 option bytes 选项字节配置,如此以来就可以方便设置各种存储器地址了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9J2Qpbcf-1574601496448)(./1574512528932.png)]
5 Q/ f: x$ x: m, mBOOT_ADD0 和 BOOT_ADD1 对应 32 位地址到高 16 位,这点要特别注意。 通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:
. c; ^2 e* H% {4 x0 B, m) f: x6 \+ c
所有 Flash 地址空间。  T, y* O' t0 S& h
所有 RAM 地址空间,ITCM,DTCM 和 SRAM。6 P9 B& g) ~/ E  Y" C5 i
设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0,或 BOOT_ADD1 所设置的地址进行启动。! l/ O6 p& q6 V  g# K" p
使用 BOOT 功能,注意以下几个问题:
- d+ A! [# ]. i$ t1 Q% _  f: |如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址 0x08000000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。3 c" A, y1 A( x2 f9 n& w0 u# }
当 Flash 的保护级别被配置为级别 2 之后, 只能从 Flash 自举。 如果 BOOT_ADD0/BOOT_ADD1选项字节中自举地址被配置为位于存储器范围之外或属于 RAM 地址范围,则系统只能从地址 0x0800 0000 上的 Flash 开始执行1 ]! x$ w7 o. ]2 J- Q. @
) @2 Y1 P' P+ M4 Y. x
0 T  e! F5 V5 N( c$ W
0 M+ M3 v/ o4 L- E% d
收藏 评论0 发布时间:2021-12-27 18:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版