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

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

[复制链接]
STMCU小助手 发布时间:2021-12-27 18:00
STM32H7的启动过程分析: q# T0 b. y) T1 v
启动文件

6 I0 P) }) ^8 N不同编译器对应的启动文件不同,在MDK下,以startup_stm32h743xx.s为例,这是一个汇编文件,启动文件中主要做了如下事情:1 F3 ]2 C+ \/ Z
设置堆栈指针 SP = __initial_sp。/ Y% y9 h- Y! F/ s! O2 \
设置 PC 指针 = Reset_Handler。
1 D- p  s7 Y6 @- y设置中断向量表。
* u+ W3 M8 w, M& G) Y配置系统时钟。
% D/ N( N+ e8 P  ]3 `" I+ O, s7 v6 c配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。
2 D$ p9 R! X5 @跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。
: Q% V# h; s5 Z8 t6 L* L- X1 uCortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为用主堆栈 MSP。% h# [, x0 w( T, @5 k
堆栈指针
9 L, H6 ^8 F$ ~6 x* G通用寄存器组* I2 T; g3 t7 A, }) m. t% @
Cortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以用。* }# s) ~8 Z7 I% A
主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
0 \  e3 n6 `7 f进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。' f6 s5 a! ^) K( K
另外以下两点要注意:5 x/ L- V' y4 K% a) B3 W
大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。
9 F" x$ j, X# z! x& v+ B4 {$ l+ yR13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。
  q4 h. h. C. s) m6 c3 [' a. |& K6 s
Cortex-M7/M4/M3 向下生长的满栈0 S: ^- ~% Y* i6 R3 e
PUSH 入栈操作:SP 先自减 4,再存入新的数值
  S0 ?" h# o8 u! d. LPOP 出栈操作:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4
, \' {3 V2 i8 D% B: J2 S
4 t0 [4 J7 G& Y+ V. W分析启动过程
  c9 _7 t3 M+ R/ @硬件上电后,会触发硬件复位,复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000)
6 ]7 q* G; y0 s/ i1 Y. h
5 S1 u5 k" M0 A) T将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。+ w. v8 W; O" U$ U4 Y
将 0x08000004 位置存放的向量地址装入 PC 程序计数器。
3 B( x( h0 u& ]# B( oCPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。为啥,因为在启动文件中,最先做的两件事情是; Q0 B: I( s1 c8 b
设置堆栈指针 SP = __initial_sp& E" z& J+ M# P2 Q1 Z1 P* k$ n
设置 PC 指针 = Reset_Handler, s9 K* K1 b# C& x% [
复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。) f9 V9 U9 ~6 R0 B6 p

. j" s' i6 y: Q6 F8 j代码分析
6 n( a' K1 a2 u, z  Y& N. k6 h①、开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等" J1 ^, j& N1 ~2 K% ?1 e1 Q! X
* k2 ^& g" D) ^) d
  1. //类似宏定义,这是个伪指令,定义栈大小,这里是以字节为单位- w  {# ?4 I8 t; l+ R
  2. Stack_Size      EQU     0x00001000   8 v, p; K/ {8 c
  3. ( y, g. X2 z  r4 U* w( C! G- q
  4. /*0 z! x0 c0 a. R, Q
  5. 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。 ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。 ARER 后面的关键字表示这个段的属性。9 r" q# g" P1 _
  6. STACK :表示这个段的名字,可以任意命名。
    & l  B$ k! z: g8 _" i
  7. NOINIT:表示此数据段不需要填入初始数据。! z+ M9 r7 o% q3 O# X7 X& i
  8. READWRITE:表示此段可读可写。  _3 S2 M2 d0 _  c  m' n, B
  9. ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。
    8 `$ q# h( d! v0 Y: p
  10. */0 B+ P/ o' w0 l+ A+ ~! G
  11.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
    ( Y$ r: l( o, [) A* n5 U
  12. . _& q8 S* V, u# y( b4 U
  13. //SPACE 这行指令告诉汇编器给 STACK 段分配 0x00001000 字节的连续内存空间。3 o! e! S. Q+ Q3 y$ n1 J- G
  14. Stack_Mem       SPACE   Stack_Size% D# j; U" [# w5 ?! r

  15. ' ?- g0 |, J4 `# j" ?* s% c; R3 ~
  16. /*
    2 r# M7 H, a; I" i6 a  L
  17. __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。 __initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。7 m1 u9 D+ J. T
  18. */
    . S) _# o8 ?! z7 P; c: b
  19. __initial_sp
复制代码
) m: K' [: G* [$ ^
①、开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数
1 U4 G& U9 h8 e5 J分配的变量空间是在堆上
. [: L( a* a7 L4 V/ C" k5 e3 u' [7 L' w, |8 m! b7 Y
  1. //定义堆空间大小& t; v; ?' S9 j( j- r
  2. Heap_Size       EQU     0x0000800
    5 a8 h$ q' Q+ d  ]" [0 G
  3. ( o8 O! u' v  ^. D
  4. //分配一段名字为HEAP、可读可写、不需要初值、8字节对齐的数据空间
    , y; d' ]6 B- e# h4 ~
  5.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3* l+ n1 Y6 {! }0 V; K

  6. ) B" W# E+ V- G, X. p: X) I! j
  7. //__heap_base 表示堆的开始地址。
    6 T. O, \5 e& A6 C" s3 C  _
  8. __heap_base
    , D9 v$ [2 X; q6 Q8 ]9 H1 @
  9. # C- G. j% K. ^: [& {
  10. //SPACE 这行指令告诉汇编器给 HEAP段分配 0x0000800字节的连续内存空间。9 d. w2 U' {7 }' V$ [: K
  11. Heap_Mem        SPACE   Heap_Size
    $ m) `7 O/ j* c3 V
  12. : S9 \* C3 |  f6 [5 M* s
  13. __heap_limit 表示堆的结束地址/ U; f$ a9 p1 W+ m/ W# c2 w; K
  14. __heap_limit
复制代码
: P  e" m! g+ ~/ e  v+ |
③、生成属性设置、定义RESET代码段! p+ q3 n! p& V1 x. C: q3 C2 l. i
8 C- E/ v- A* _
  1. //PRESERVE8 指定当前文件保持堆栈八字节对齐。7 A9 e* X! R8 G5 _
  2.                 PRESERVE8
    ) O$ ~5 y8 R5 [0 ]7 u
  3. 7 N' o$ p' v+ k; U( j. k
  4. //THUMB 表示后面的指令是 THUMB 指令集 ,CM7 采用的是 THUMB - 2 指令集9 }) n. O( |1 k; i: _9 I
  5.                 THUMB& P2 g9 `! l4 Z! |' u+ v
  6. 1 D$ v. s4 X& t, y
  7. //:AREA 定义一块代码段,只读,段名字是 RESET。 READONLY 表示只读,缺省就表示代码段了。/ _+ w" e. @$ r6 ]
  8. ; Vector Table Mapped to Address 0 at Reset
    , f) }+ ^# ?+ t
  9.                 AREA    RESET, DATA, READONLY
    5 r3 v) h/ s- u$ n! h
  10. ( a$ j, J7 U4 L2 ?9 W" s; g$ t5 F: N
  11. //3 行 EXPORT 语句将 3 个标号申明为可被外部引用, 主要提供给链接器用于连接库文件或其他文件。/ U4 a) K% s  l
  12.                 EXPORT  __Vectors
    ! P2 P$ C& s4 Y5 v+ ^* f" M' [
  13.                 EXPORT  __Vectors_End' {2 H) M; P9 E9 K/ }7 [# r+ ^
  14.                 EXPORT  __Vectors_Size
复制代码
8 B; ?- r- j. {2 U, X
④、中断向量变定义0 O3 ?7 x( I/ y' u# Q
/ v& a# O: y# n* N" h2 |& M5 ~, {+ L
  1. __Vectors       DCD     __initial_sp                      ; Top of Stack& [% q5 o" G8 L! _. Z7 f3 U: B
  2.                 DCD     Reset_Handler                     ; Reset Handler) l; H( t" `* Q7 Z; N* [- s6 V7 A# P
  3.                 DCD     NMI_Handler                       ; NMI Handler
    , p& E3 f6 g: X2 d, ]/ o/ p0 ~$ ]
  4.                 DCD     HardFault_Handler                 ; Hard Fault Handler$ z6 D3 \6 {' q4 h9 l
  5. /*  省略部分代码 */                              5 w$ Z9 i6 J7 \3 d
  6.                 DCD     0                                 ; Reserved                                    
    1 @+ _+ ~% O& I$ _; p2 f
  7.                 DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins , H. u; C" W/ ^6 O  K$ p

  8. ' x! {6 j2 x: u1 Q, q5 R( V# v

  9. # k1 |  q7 k0 w0 _9 ]
  10. __Vectors_End8 N* ?3 K4 L1 G% T( a0 G# Y2 U

  11. " Q6 e3 A7 h  e; M* Y# J4 G
  12. //定义向量表大小; K9 ], I7 o; y- Z5 L, [
  13. __Vectors_Size  EQU  __Vectors_End - __Vectors
复制代码

9 m- o: w0 ^2 r- z& \2 A上面的这段代码是建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。如果程序在 Flash 运行,则中断向量表的起始地址是0x08000000。以 MDK 为例,就是如下配置选项:
7 H7 a- ^$ ~7 W% k- b% h, {3 q( O
20191124212435739.png
0 @4 F& X+ d) ~; ^. z: Q

/ x/ [* D$ e0 I4 t' oDCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序% b9 G1 K2 s" C0 s% S2 k

* k0 B3 v# i: d! }: k, j0 z⑤、代码段定义、Reset_Handler过程处理# n& x# p, H6 u5 R4 z$ C0 r8 s
( R& x6 @, z2 i' h
  1.                 AREA    |.text|, CODE, READONLY" }  l- h9 V' U6 q

  2. 7 |: {2 H' D' Q
  3. ; Reset handler
    # \$ |$ U" Q* |. {* M
  4. Reset_Handler    PROC
    ( n  F6 q3 }7 L8 l& U& H, o
  5.                  EXPORT  Reset_Handler                    [WEAK]
    * s+ y& l; u. n
  6.         IMPORT  SystemInit
    9 H+ f" O! |+ q5 U2 w$ s
  7.         IMPORT  __main
    ; K6 c1 T/ x: p  L# C2 z. W
  8. / q* d* T1 Y9 c$ @- V0 b% N
  9.                  LDR     R0, =SystemInit
    , j; P- `( R6 Q. Y0 H$ _8 V; p* u
  10.                  BLX     R0! V( m2 m! O" }4 m  ?
  11.                  LDR     R0, =__main; _2 r/ W; |3 K. c% q
  12.                  BX      R0
    & J: {! z$ @' Q; x& p
  13.                  ENDP
复制代码
  j# p+ B0 z% V& {  ~1 k$ h. n
AREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读
5 j8 S$ F+ K8 Y: _8 V, F% o1 ?利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
* l0 c8 H$ g" U/ b4 }( i5 rWEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。
) P: W% K0 a' Z5 K6 S' D/ G8 M6 ]IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。5 n$ F  A  @' k) d, H
SystemInit 函数在文件 system_stm32h7xx.c 里面,主要实现 RCC 相关寄存器复位和中断向量表位置设置。) r3 {. [  e9 @* K+ s3 `
__main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到 C 程序中的 main 函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。% }$ K6 D8 T9 d
. P1 y$ _2 n5 p, L$ J+ k# S
⑥、缺省中断服务函数定义
  1. ; Dummy Exception Handlers (infinite loops which can be modified)
    . y- x" e  E) n! C) Z
  2. . }9 n- ]/ s" C5 q8 r7 ~
  3. NMI_Handler     PROC* V" V4 d5 G7 E
  4.                 EXPORT  NMI_Handler                      [WEAK]$ {  z  @- f- k# c2 D
  5.                 B       .      //死循环- @, p- U& E& ]* t' z$ `+ R% ~$ o2 m
  6.                 ENDP6 N1 ~! q# p& b
  7. ...省略) {: f) w! i3 u2 |2 G
  8.                 EXPORT  PendSV_Handler                    [WEAK], b# w! q. z3 ~& [5 Q
  9.                 B       .1 J. w- G9 G, i
  10.                 ENDP
    & z3 p) ]  p7 t$ l  b3 _6 Z
  11. SysTick_Handler PROC
    % D) y5 N! y0 e2 r+ L, i
  12.                 EXPORT  SysTick_Handler                   [WEAK]. d' m0 q) y+ Q6 X$ n; L9 }( j
  13.                 B       .) h% M9 r' X9 K
  14.                 ENDP                                    
    # S9 W" q# M, ]4 n* L

  15. + n# {4 T1 D0 n( _8 d& x' D8 U, [
  16. Default_Handler PROC                                      
    ( u4 `1 p6 B" o

  17. 1 E, c  }; i0 F: \
  18.                 EXPORT  WWDG_IRQHandler                   [WEAK]                                       4 a; `! f. B1 c- Y
  19.                 EXPORT  PVD_AVD_IRQHandler                [WEAK]                        
    8 _$ R+ e6 l1 p0 H; {& q/ Y
  20. ...省略                                                                                 * ]3 X4 |2 n# \' m: f0 y, N
  21.                 EXPORT  WAKEUP_PIN_IRQHandler             [WEAK]
    & ]+ |$ @* j; J6 r  h% c
  22. * u5 S& V" _& Y7 B; a' H( G

  23. ) D& q- G9 R9 Q& K5 \
  24. WWDG_IRQHandler                                                         
    5 c! b9 F0 _9 W0 b  H% R& P
  25. ...省略     
    , r& c. R2 N5 s. z
  26. WAKEUP_PIN_IRQHandler
      J: b, M* W) Z7 l
  27. 7 ~) Y4 w6 }4 S' j" K/ k
  28.                 B       .
    - D/ Y7 Z% M" q5 D: O2 f0 J( S5 B
  29. 7 h2 j, u! A9 e- O8 c( ~
  30.                 ENDP
    ) p9 l) ]. K: |: M; M& _
  31. " c+ ?8 X! h7 }; f/ u
  32.                 ALIGN
复制代码
& s: \5 Y# ?5 n. L7 g
这里全部的中断服务函数都是用[WEAK]来声明,假如没有在其他文件中定义同名的中断服务函数,来了中断,就会进入到这里。
; S5 Z* }& |' `7 E
6 B; A4 L, ]1 b⑦、堆和栈的初始化& V4 L/ p0 R; K* u' G# E4 d; C7 m
' H8 \+ P  p# }' X- f& e  D
  1. ;*******************************************************************************
    & U2 E# x8 q7 f8 Z  L5 D& r
  2. ; User Stack and Heap initialization8 b# C. T* B' X! S. `- o  ~
  3. ;*******************************************************************************
    3 t3 S+ o" X4 h6 E
  4. //假如定义了MICROLIB,这里类似于if...else...# P0 W0 ]. `) X+ C
  5.                  IF      :DEF:__MICROLIB5 E  E+ i/ R5 ?

  6. , v# N% _, G* I! i' u
  7.                  EXPORT  __initial_sp
    + a+ _$ D( k- q
  8.                  EXPORT  __heap_base
    ( i5 C) W4 v9 c& b* V9 C
  9.                  EXPORT  __heap_limit
      N. ?/ F  R: d* |" d1 L- s& `2 g

  10. . T+ v4 I7 ~; |1 d7 X3 l7 `
  11.                  ELSE
    6 Q  a) h, h4 a( Q0 D

  12. - _5 e1 f( L; B7 B
  13.                  IMPORT  __use_two_region_memory. L1 o- o( m: U7 k
  14.                  EXPORT  __user_initial_stackheap
    * A7 E& e# O+ Q) V* O# F5 L

  15. ( m+ x/ }) [4 M; K3 z
  16. //__user_initial_stackheap 将由__main 函数进行调用。                 
    % z7 x( l* L" I1 e$ g
  17. __user_initial_stackheap8 O* Z0 P, |* B. `+ w$ ~

  18. 5 ~9 F% D; D7 l7 p: P  \
  19.                  LDR     R0, =  Heap_Mem* O+ z. n& P; o$ @
  20.                  LDR     R1, =(Stack_Mem + Stack_Size)
    7 t2 M" l8 B+ D# Y1 ?& h
  21.                  LDR     R2, = (Heap_Mem +  Heap_Size)4 W0 ]8 g5 M3 F/ T3 z0 K' c: M
  22.                  LDR     R3, = Stack_Mem% W, g7 \) C7 P: [3 b
  23.                  BX      LR. T7 R' S4 H2 u3 F+ c+ K+ j

  24. 6 G, u" e  S9 \* Q0 b% L
  25.                  ALIGN5 J9 K$ f1 e' I$ N  u( Q( u7 @

  26. 0 e* a8 h  t7 K' e
  27.                  ENDIF; x  D) u% b3 J7 c5 |" N( b
  28. ( C; M- w$ q3 C( [% N
  29.                  END
复制代码
Boot的启动模式不同于以往的M3、M4内核的ST MCU,H7的boot引脚只有一个,但是H7 专门配套了两个 option bytes 选项字节配置,如此以来就可以方便设置各种存储器地址了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9J2Qpbcf-1574601496448)(./1574512528932.png)]4 [2 h+ M# i7 X  |  Q+ u
BOOT_ADD0 和 BOOT_ADD1 对应 32 位地址到高 16 位,这点要特别注意。 通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:
* p  K; m2 `- e7 ?4 d. r# X) n, r7 B5 {: k# c# V
所有 Flash 地址空间。) }' Z: q; W. v4 o# ^
所有 RAM 地址空间,ITCM,DTCM 和 SRAM。
1 ]5 d4 c2 M  W2 `2 N设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0,或 BOOT_ADD1 所设置的地址进行启动。8 p. j' U  m) }
使用 BOOT 功能,注意以下几个问题:1 Q0 X) o& l# U* C: B( f
如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址 0x08000000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。7 V0 e. Z( ]0 h6 C
当 Flash 的保护级别被配置为级别 2 之后, 只能从 Flash 自举。 如果 BOOT_ADD0/BOOT_ADD1选项字节中自举地址被配置为位于存储器范围之外或属于 RAM 地址范围,则系统只能从地址 0x0800 0000 上的 Flash 开始执行/ x( {  \) L- R; J( s8 ^' j1 G
! d) j1 i, @+ E6 {! @4 B* m

8 K+ u9 v# ^7 M9 X, b) K2 S; \1 l. f1 p8 I" Z6 E% t; H; \
赞 收藏 评论0 发布时间:2021-12-27 18:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版