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

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

[复制链接]
STMCU小助手 发布时间:2021-12-27 18:00
STM32H7的启动过程分析' i/ ?: u' Q. }- c" B: T' s9 w
启动文件
* Q6 T6 V. v! l5 K
不同编译器对应的启动文件不同,在MDK下,以startup_stm32h743xx.s为例,这是一个汇编文件,启动文件中主要做了如下事情:8 J- o3 Q" H8 O8 m4 m# W1 y
设置堆栈指针 SP = __initial_sp。  S8 Z" J8 Q! C) _! G
设置 PC 指针 = Reset_Handler。. k  w0 X$ g5 @1 \& Q) J
设置中断向量表。: k4 n) M9 v4 a, j  g& H/ t8 P
配置系统时钟。
8 [! ?$ H* G% m/ L7 L! O配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。5 W* r4 e8 Q8 A# V
跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。  l2 @9 @# r. Q2 M4 |* S/ o0 W
Cortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为用主堆栈 MSP。* t( {, q& W; q0 z& Q
堆栈指针$ c( S) N% C2 y
通用寄存器组
( c, L- T% K6 G. Q* v6 sCortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以用。
7 B# [' J/ k9 w9 J/ }主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。- o* B* u$ n5 u" y/ h' P
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。$ C6 {4 D; g; B7 q2 l
另外以下两点要注意:
5 W! @( @. _$ `0 ]/ W大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。+ v2 w# }" }5 s- e- O1 R0 M' T
R13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。! y; c  B( |; b
- Y% S5 {" z2 s6 y& I3 S
Cortex-M7/M4/M3 向下生长的满栈" X+ {' d9 F2 c! r1 {
PUSH 入栈操作:SP 先自减 4,再存入新的数值. Z1 ]; n* g" d) f
POP 出栈操作:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4
  G9 Y) }4 ^2 q/ {8 n0 u* J8 i+ S5 F4 w* U, t: P! U2 n
分析启动过程+ Y- c% U0 r: \& d5 Z* Z6 I+ P
硬件上电后,会触发硬件复位,复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000): G8 |# Z, Y  s1 H1 G+ f( B3 S% }

: a, O2 X6 R- V- |将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。7 @3 ~( V' d1 I* [! u( j$ w
将 0x08000004 位置存放的向量地址装入 PC 程序计数器。
# z) \" `' j7 Q$ N" G- HCPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。为啥,因为在启动文件中,最先做的两件事情是3 @  m' _6 M1 g2 r/ o
设置堆栈指针 SP = __initial_sp5 c! U& ]8 d2 l0 R. G
设置 PC 指针 = Reset_Handler# L+ r" j7 w$ D/ N7 z
复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。
& N' w6 T) g$ I5 e' m1 \; s3 n# R! O5 U+ X% a
代码分析
; |& Y; ^# ]: Q: T0 i! H  G①、开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等
. K% H% G" H% q: w, i1 W" C( U4 n8 v8 R  }
  1. //类似宏定义,这是个伪指令,定义栈大小,这里是以字节为单位
    . }# V1 @, U0 S8 j6 e$ ]" i: F
  2. Stack_Size      EQU     0x00001000   , v9 W( B9 b' j

  3. : D% f9 q6 K2 H) c
  4. /*
    ( }  E3 h! C' o* p* {) C5 t
  5. 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。 ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。 ARER 后面的关键字表示这个段的属性。
    ) Z, n. @4 ]' h* \2 |( _) z
  6. STACK :表示这个段的名字,可以任意命名。# J6 P" b4 r& z9 P, I: L7 ^
  7. NOINIT:表示此数据段不需要填入初始数据。' ~. e$ L+ u2 _! `% a9 b$ T
  8. READWRITE:表示此段可读可写。" G. M  ~8 ~/ P: {5 N* P7 ^" f8 X
  9. ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。
    # ]- W. e+ w" P$ n2 @
  10. */
    % M1 @. j. H8 e2 U$ k; P% g- P6 a
  11.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3" H9 o+ l2 W+ E7 c+ H) `) Z0 l

  12. 3 T% d  j- t+ |1 A( ?! m
  13. //SPACE 这行指令告诉汇编器给 STACK 段分配 0x00001000 字节的连续内存空间。
    ' K0 o7 Z* v6 `. V8 _/ @
  14. Stack_Mem       SPACE   Stack_Size
    ; N1 m% D/ |* D9 }" ?+ F# b

  15. # T9 J; N# k4 F( o
  16. /*5 v; M: N# }. s
  17. __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。 __initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
    ! `9 J* H) E( S- l6 Q; Q% f0 b# z* Q- \
  18. */9 b9 h+ W% g" n/ S5 d- J0 z. J
  19. __initial_sp
复制代码
0 x, e% x9 d. |; h  w0 W" m
①、开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数
0 B: k+ W7 g/ h3 W& ?" m分配的变量空间是在堆上
+ n5 N  v3 f( W1 G" F6 t: I6 L' F2 Q) E$ ^6 F( V7 l
  1. //定义堆空间大小: y2 G' S' H* l4 U3 S) ^- r
  2. Heap_Size       EQU     0x0000800
    8 V8 e( s2 x: X  |7 o

  3. 7 k$ a5 i( l  k$ A8 {9 R
  4. //分配一段名字为HEAP、可读可写、不需要初值、8字节对齐的数据空间# q- r& d, Z+ O. K' h* X6 Y% `
  5.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=33 i  a2 P( _/ ^8 e  g! ^* r- Y
  6. ' P9 @  u- K# C( G/ Q+ C
  7. //__heap_base 表示堆的开始地址。
    5 R7 G5 m5 g, d4 j
  8. __heap_base1 p% ?( u1 P: T" w+ A2 N

  9. + X+ w  i* U  e
  10. //SPACE 这行指令告诉汇编器给 HEAP段分配 0x0000800字节的连续内存空间。" M- m0 X: b# q& M# Z2 ^) H3 V
  11. Heap_Mem        SPACE   Heap_Size
    ) Y$ }  g3 J8 w9 Z# _
  12. 6 V" R" O/ d. e
  13. __heap_limit 表示堆的结束地址
    # E- D4 V* R* m) s
  14. __heap_limit
复制代码
- V9 w+ F! S) l  f5 J2 d1 r
③、生成属性设置、定义RESET代码段
, e6 X4 b* L' W
: n, U+ `: _+ s, ]
  1. //PRESERVE8 指定当前文件保持堆栈八字节对齐。
    3 ]* ^6 C, `0 _( M( Y
  2.                 PRESERVE8
    4 D( M$ U+ k- Q! H4 V( c9 H

  3. 0 ?' m: U& g: ~0 f* s7 b" Y
  4. //THUMB 表示后面的指令是 THUMB 指令集 ,CM7 采用的是 THUMB - 2 指令集
    3 `9 t/ T) H' X8 }" d
  5.                 THUMB! t0 A6 e/ V2 h* G
  6. - a8 y% G7 `# a4 n% }
  7. //:AREA 定义一块代码段,只读,段名字是 RESET。 READONLY 表示只读,缺省就表示代码段了。
      n: l( x% p% A. i" B5 M
  8. ; Vector Table Mapped to Address 0 at Reset
    , |7 F/ X0 f% M7 z
  9.                 AREA    RESET, DATA, READONLY4 y/ S) o4 Z# p

  10. 3 m; h  i8 E+ P
  11. //3 行 EXPORT 语句将 3 个标号申明为可被外部引用, 主要提供给链接器用于连接库文件或其他文件。) n: i0 i5 H8 F: o8 X
  12.                 EXPORT  __Vectors
    4 l9 f+ ]( P1 l
  13.                 EXPORT  __Vectors_End
    ( y% l/ E* e# Q" i& k
  14.                 EXPORT  __Vectors_Size
复制代码

6 y% k9 p3 ]8 W3 E/ N④、中断向量变定义9 c+ \4 B2 `1 T, y# k
5 K" ^' B. r! k
  1. __Vectors       DCD     __initial_sp                      ; Top of Stack5 {! }2 _1 |, \" x. e, L/ w9 O$ ~! h
  2.                 DCD     Reset_Handler                     ; Reset Handler. \8 A, M: h" V
  3.                 DCD     NMI_Handler                       ; NMI Handler1 F  s0 j2 t/ Q  B$ P( n# p4 [
  4.                 DCD     HardFault_Handler                 ; Hard Fault Handler
    ) M* p: _: `6 u' d6 x
  5. /*  省略部分代码 */                              
    1 H# ~7 d& x/ i& n- \
  6.                 DCD     0                                 ; Reserved                                    ; e: H: j' t; s, F1 {" Z
  7.                 DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins 6 p2 c& f+ M; i& W
  8. . b, k- Y: f. [* ]* N. U  g

  9. ; x2 z' m& q& d
  10. __Vectors_End( l$ r: O( c5 k% ]  L% i9 ^
  11. 4 W- I8 B5 w# B; P
  12. //定义向量表大小
    4 s  g( f9 G# E
  13. __Vectors_Size  EQU  __Vectors_End - __Vectors
复制代码
$ f8 f* }" [9 c' M' B. U* r* ?
上面的这段代码是建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。如果程序在 Flash 运行,则中断向量表的起始地址是0x08000000。以 MDK 为例,就是如下配置选项:
9 L. H/ S( `7 [( \% \8 v2 @
* ^; [" M/ D% M6 F: H
20191124212435739.png

9 H3 E) ?! ~1 e
; K2 @8 x% x/ q" I7 nDCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序
8 `2 E; S- k$ Z9 G$ Z8 N5 Q2 z( J: S- s7 M. F4 C
⑤、代码段定义、Reset_Handler过程处理
! A$ V. ?/ l/ M' F" W/ l2 r1 K/ S2 Y1 d* L
  1.                 AREA    |.text|, CODE, READONLY
    2 X' X7 o7 C7 r2 d

  2. 5 O- J$ O! }% F) I- s
  3. ; Reset handler
    " A8 o7 \7 [) S0 q  d& _) L
  4. Reset_Handler    PROC
    : x  z. e5 G1 L" \6 }3 o/ j
  5.                  EXPORT  Reset_Handler                    [WEAK]" ^% N: q4 K) N) M2 N  {3 b
  6.         IMPORT  SystemInit- `+ m& W, d$ b2 L9 V
  7.         IMPORT  __main. F9 H1 x' s. o! s
  8. ; V) T5 W8 I" d5 ?2 f/ o. I
  9.                  LDR     R0, =SystemInit: d4 k- }# q4 p! `
  10.                  BLX     R0. O% ?  z9 X7 t! v+ h7 R( B) a3 E0 f
  11.                  LDR     R0, =__main% w! I8 k' K/ F0 H' f  L" ?# f
  12.                  BX      R0
    % w! L' n& o; D3 B. ]! k8 }4 f
  13.                  ENDP
复制代码

8 ?$ m% Z+ Y, X. T  f/ Q6 iAREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读% |% N" Y! f& Q8 ]& ~
利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
, H& y0 S2 O! p5 E2 B# f- IWEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。
/ P7 @* ~* C! I, T" D* Y& UIMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
, P9 l9 ~+ A, |SystemInit 函数在文件 system_stm32h7xx.c 里面,主要实现 RCC 相关寄存器复位和中断向量表位置设置。
6 m: j: m7 Q# K4 W__main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到 C 程序中的 main 函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。
# o+ h+ t% c/ p) L7 X2 b
+ w7 j9 k7 y3 [+ Q⑥、缺省中断服务函数定义
  1. ; Dummy Exception Handlers (infinite loops which can be modified)  A2 Q; k) F; ]& V/ U2 D

  2. ; w$ K7 X) @7 n3 J" G) m
  3. NMI_Handler     PROC
    1 s) d4 y% q. Z3 B. ?1 K
  4.                 EXPORT  NMI_Handler                      [WEAK]
    ! v7 ~3 }  K1 U' h+ l
  5.                 B       .      //死循环
    - y9 W6 L1 G8 g! P* V
  6.                 ENDP
    6 X3 A" C: }4 y
  7. ...省略
    . Z' z( z  G& m$ O7 a
  8.                 EXPORT  PendSV_Handler                    [WEAK]5 k- f  B6 A0 ?5 @9 h3 N
  9.                 B       .. @- g" l, f; k9 P1 Y
  10.                 ENDP
    8 ]* {% |, C# N
  11. SysTick_Handler PROC0 X$ M6 q! R$ c( g
  12.                 EXPORT  SysTick_Handler                   [WEAK]
    ( ^7 q. V' N! O5 @
  13.                 B       .$ ?9 {* \7 b4 a4 z, N- S. |! R4 R8 c
  14.                 ENDP                                    
    * H, `' Y2 c$ U8 [/ b' `, J

  15. 0 q% n: e; @; p/ b3 J3 h
  16. Default_Handler PROC                                      # Y1 h6 T# @. `9 v6 i
  17. 1 n' h2 P. a1 L/ W1 ^& \" y7 C
  18.                 EXPORT  WWDG_IRQHandler                   [WEAK]                                       
    % d* q; {) @5 W# }$ \- H0 F
  19.                 EXPORT  PVD_AVD_IRQHandler                [WEAK]                        
    3 D# |3 }4 ^6 x
  20. ...省略                                                                                 
    - n+ A! d5 j; G1 U
  21.                 EXPORT  WAKEUP_PIN_IRQHandler             [WEAK] . @& z! w" T) O; Y) o
  22. 8 F6 c5 R4 x# \; e
  23. 6 c- R7 U8 D4 i  X
  24. WWDG_IRQHandler                                                          # T% M! X# H; p9 C- \9 d, O) r# U
  25. ...省略     : b. R0 q- k7 M' T3 ]7 t6 X
  26. WAKEUP_PIN_IRQHandler
    0 y1 H# c0 i1 j1 o# |  i# L" x

  27. , O( V7 M) m5 x$ `" B9 u  ^8 I
  28.                 B       .
    $ b6 p1 k! I* F+ e$ ?

  29. ( {6 {/ ?5 x: d
  30.                 ENDP
    1 @1 I+ E: C6 p0 `5 h9 `5 O
  31. 8 H9 o4 ?8 A% X. p# d- W1 L6 c
  32.                 ALIGN
复制代码

% U7 k; s. C9 w& o. I) _这里全部的中断服务函数都是用[WEAK]来声明,假如没有在其他文件中定义同名的中断服务函数,来了中断,就会进入到这里。3 P1 N, s8 o: a- U

0 L) j& Q: z, }) c# E2 P⑦、堆和栈的初始化
8 w2 c, u- p5 X8 e3 l( R
& F# n( y* F9 P' f
  1. ;******************************************************************************** q5 }% G7 T. A! j
  2. ; User Stack and Heap initialization/ r. b6 g# j9 ?
  3. ;*******************************************************************************# b" m, t$ v0 v* I0 D
  4. //假如定义了MICROLIB,这里类似于if...else...
    + y3 F) S+ J4 D+ \6 K0 z' z
  5.                  IF      :DEF:__MICROLIB
    " z" X- s3 p# m3 e; l1 W' w4 S0 u

  6. " a& w) b3 z/ `# \+ ^
  7.                  EXPORT  __initial_sp
    / x9 R- y/ J9 ^/ v9 G0 H
  8.                  EXPORT  __heap_base
    2 `+ J  A- b% v4 J
  9.                  EXPORT  __heap_limit( X. Y' k& Y2 H8 @

  10. . n( b8 @# O, N, V9 H: L2 P2 C
  11.                  ELSE5 c! q2 r: h/ q% t# M" ?

  12. 1 E6 K* h  r6 s+ d: L9 E
  13.                  IMPORT  __use_two_region_memory4 C3 Q7 Z" X5 c
  14.                  EXPORT  __user_initial_stackheap
    ( P* ^* d2 D8 t( x1 c7 F

  15. 5 T3 c9 e" V' z' |
  16. //__user_initial_stackheap 将由__main 函数进行调用。                 9 ]! t  a8 f' v
  17. __user_initial_stackheap$ K, v2 W1 x7 r  A

  18. # g  u$ l) _) ]1 e0 X2 N) Y
  19.                  LDR     R0, =  Heap_Mem$ h0 A5 X2 W6 E) l+ W5 N- s; G* K
  20.                  LDR     R1, =(Stack_Mem + Stack_Size)- o* Y1 m7 d# w" A
  21.                  LDR     R2, = (Heap_Mem +  Heap_Size)
    # U, h% o% e1 P6 Q) D3 w
  22.                  LDR     R3, = Stack_Mem
    % Y" e' V% _' M+ C' T8 m7 Y+ B4 M6 B
  23.                  BX      LR; d' ^  A1 Q+ p1 u

  24. - K# |0 C7 o' Z" H  R
  25.                  ALIGN* f' v9 t. c+ f" C! |) o1 T
  26.   H6 ^4 A) q7 ?* C* k
  27.                  ENDIF4 G" m8 q+ t! ^- f# o" J
  28. $ D8 S" R. i# j0 u9 @$ S$ {6 _
  29.                  END
复制代码
Boot的启动模式不同于以往的M3、M4内核的ST MCU,H7的boot引脚只有一个,但是H7 专门配套了两个 option bytes 选项字节配置,如此以来就可以方便设置各种存储器地址了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9J2Qpbcf-1574601496448)(./1574512528932.png)]
) B# n. r5 s3 D5 A. S* Z% e! xBOOT_ADD0 和 BOOT_ADD1 对应 32 位地址到高 16 位,这点要特别注意。 通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:
& H- x( v$ c, @' R7 p3 u1 t: y
4 U% M1 x1 U, D' ]# z; r, C所有 Flash 地址空间。5 J5 T! E1 M+ ?' _1 Q% B
所有 RAM 地址空间,ITCM,DTCM 和 SRAM。
2 f1 v2 ~6 V# m5 `5 ?设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0,或 BOOT_ADD1 所设置的地址进行启动。
3 `) Z  M6 s! L1 o使用 BOOT 功能,注意以下几个问题:8 U5 t9 {# B2 N1 w& b& K. C  y
如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址 0x08000000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。0 S6 Y4 N4 m2 c9 r! g; G+ P
当 Flash 的保护级别被配置为级别 2 之后, 只能从 Flash 自举。 如果 BOOT_ADD0/BOOT_ADD1选项字节中自举地址被配置为位于存储器范围之外或属于 RAM 地址范围,则系统只能从地址 0x0800 0000 上的 Flash 开始执行
6 J! G; A5 ~5 F, ~# m4 M& ~* o( A" L! I

/ l1 @# ]2 c6 q8 u3 A! G9 @9 S: B, `) l- y  d" x& W
收藏 评论0 发布时间:2021-12-27 18:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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