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

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

[复制链接]
STMCU小助手 发布时间:2021-12-27 18:00
STM32H7的启动过程分析3 O" V+ J* E4 ~1 V+ ^( D( {
启动文件
+ }" Y4 b2 R; r( f( S' D
不同编译器对应的启动文件不同,在MDK下,以startup_stm32h743xx.s为例,这是一个汇编文件,启动文件中主要做了如下事情:
2 `& k( |' j, ~8 s: Z0 m设置堆栈指针 SP = __initial_sp。
' x9 l$ @, S& S& s7 s设置 PC 指针 = Reset_Handler。
. }! h+ J0 H4 Y+ w$ r设置中断向量表。$ M& T6 b  @1 Z% V8 J
配置系统时钟。
( q9 n. }0 P' |1 U" d! W配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。
# B6 e0 T0 G% h& I9 n跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。
' t% C6 e4 N# f  M0 JCortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为用主堆栈 MSP。$ s* t; {5 s9 E7 l& M1 {
堆栈指针
) J4 D5 N$ J+ w. {% e通用寄存器组
; _1 E  W) Z$ t  g- ECortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以用。
, o9 x+ d! J" R7 X4 n* z8 l7 L主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
4 y) A. M8 ~5 g# R$ c4 \" R进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。
" u: W  W4 T" V  l' |另外以下两点要注意:
" S& ?. f2 V6 |7 H大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。
" b2 e6 X& f" r* U; Q" F2 AR13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。9 w$ w0 C4 Q; A! b

- Q  r3 ^9 U( YCortex-M7/M4/M3 向下生长的满栈
6 X6 E1 m3 A/ L9 ~* P; Q+ ~PUSH 入栈操作:SP 先自减 4,再存入新的数值
2 X# W& w. A% ~& V, Y5 N0 hPOP 出栈操作:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4  q/ {' ~( e0 }2 m8 @5 Z1 L  o

) R) q3 i0 k; G5 k: T分析启动过程
+ I+ u8 {1 E3 n. Q硬件上电后,会触发硬件复位,复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000)
' A" b3 Y6 L$ y7 k, F* W# ]9 I3 s8 M
将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。2 S  P$ p9 @4 g! y# @
将 0x08000004 位置存放的向量地址装入 PC 程序计数器。2 `1 N1 @+ N3 [9 Q
CPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。为啥,因为在启动文件中,最先做的两件事情是6 j- F! ~6 J/ b' |" t8 ]7 k6 f! d( }
设置堆栈指针 SP = __initial_sp  K; o" H5 k. a" ~& a% F& E3 @1 g
设置 PC 指针 = Reset_Handler. w, N+ }7 K, ~7 u( G
复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。
1 f$ ~! A8 G9 G6 J
, L* S5 `) p9 f代码分析$ |$ @, `" {  J7 c0 H  i
①、开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等, i9 y% @/ y+ Q  C0 ~0 F
' W8 g: O& o5 U7 w# q) w) ^
  1. //类似宏定义,这是个伪指令,定义栈大小,这里是以字节为单位  C' w) R$ i2 ?
  2. Stack_Size      EQU     0x00001000   / T$ L+ S. p. b  w" F0 A* d) r
  3. * E" j4 J% W" I- H
  4. /*
    ! L2 [/ V( U7 d$ t' D. ~* F
  5. 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。 ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。 ARER 后面的关键字表示这个段的属性。
    , W* L$ `  M5 V7 c4 ]& c
  6. STACK :表示这个段的名字,可以任意命名。
      m/ v. e7 V4 ^8 L6 b6 f
  7. NOINIT:表示此数据段不需要填入初始数据。
    " h8 b, i  N1 G6 L# `9 h7 [. a
  8. READWRITE:表示此段可读可写。
    9 \* [: _5 x  t8 C2 z. T
  9. ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。
    " f) C) n8 G* a9 r4 n+ M( ?
  10. */8 D7 h0 W: y* \; ]0 h/ H5 \
  11.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3
    ( T! s& q) O$ l4 N! j& Y* e

  12. ' o4 }# R9 D0 e% v( X
  13. //SPACE 这行指令告诉汇编器给 STACK 段分配 0x00001000 字节的连续内存空间。
    * s# ~9 s7 r5 B  t3 u! S' [
  14. Stack_Mem       SPACE   Stack_Size
    4 t* K8 n. {- z" e! Q0 a/ T

  15. * M1 L& A6 |# [4 g4 B8 N
  16. /*
    ; H5 V) U% }( W& P
  17. __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。 __initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
    $ v$ W' y: U) a" ^8 ~) i2 N" e9 p3 h
  18. */) I6 O) I- G4 l0 a. F* m) I
  19. __initial_sp
复制代码
- H6 C  B0 Y' X" \- _& J
①、开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数
5 l  \; }# Y+ d( N8 B) t分配的变量空间是在堆上* l! {0 g. ]" S- p
0 O. }' u4 E; S, m1 a2 j
  1. //定义堆空间大小
    9 @2 p' k5 S( U" n
  2. Heap_Size       EQU     0x0000800! a4 J' h2 @( P- B( h: z

  3. 0 X" O; Z/ f! I; W) Z1 ]. f
  4. //分配一段名字为HEAP、可读可写、不需要初值、8字节对齐的数据空间/ G7 W& w4 R: n9 D1 k1 L) F& n' D1 ^
  5.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
      V' w, y. v' x$ I5 n3 K2 \

  6. % K* |% F% V- c8 P
  7. //__heap_base 表示堆的开始地址。
    8 A8 ~9 H% U6 n; e2 x
  8. __heap_base# c- P/ C! T1 E4 z& S6 E

  9. 1 a% w9 V1 \& ~6 E! @3 f
  10. //SPACE 这行指令告诉汇编器给 HEAP段分配 0x0000800字节的连续内存空间。: W6 V( p' W1 R" l
  11. Heap_Mem        SPACE   Heap_Size& a4 F5 L" A, @; h
  12. 5 X: p, @" w9 C, j, A( D7 F
  13. __heap_limit 表示堆的结束地址
    " i4 d; K& N! I9 @  H
  14. __heap_limit
复制代码

8 N$ Y8 T# {& F4 a③、生成属性设置、定义RESET代码段
& ^$ a7 q( _6 [& Y3 q5 C5 O+ N- U9 _
  1. //PRESERVE8 指定当前文件保持堆栈八字节对齐。
    ( x) y3 H5 Z: A& H0 s5 y
  2.                 PRESERVE8
    ' o# J: `: d0 \' g; U3 T5 [2 d: l
  3. * ?, W/ I4 u( `# R1 w; Z  q
  4. //THUMB 表示后面的指令是 THUMB 指令集 ,CM7 采用的是 THUMB - 2 指令集
    # c* v3 y* [' r) @0 `, z, G. }
  5.                 THUMB+ r' z1 G, T2 j9 W' `
  6. " Z- i) g- X: M9 A1 ^
  7. //:AREA 定义一块代码段,只读,段名字是 RESET。 READONLY 表示只读,缺省就表示代码段了。
    3 |. N6 i2 n, b0 P% h6 x# x! W
  8. ; Vector Table Mapped to Address 0 at Reset
    3 J+ J5 y& S2 l5 A: X( Z3 W! m
  9.                 AREA    RESET, DATA, READONLY4 ?1 k0 j/ I/ |) ?4 c

  10. % V" `' N. I1 R" T( V+ N! ~& V3 j
  11. //3 行 EXPORT 语句将 3 个标号申明为可被外部引用, 主要提供给链接器用于连接库文件或其他文件。
    7 r4 G# U! E5 o. j1 x
  12.                 EXPORT  __Vectors
    ( [3 i; `, e! N9 b
  13.                 EXPORT  __Vectors_End
    # O6 |5 `/ |" M, w4 O$ Z. O
  14.                 EXPORT  __Vectors_Size
复制代码
2 N6 c9 z- d# q% E/ a. D
④、中断向量变定义9 X: Q3 ]' E; S/ k" J
/ y& b: f  P4 e5 X9 r( F
  1. __Vectors       DCD     __initial_sp                      ; Top of Stack
    + M0 Y% {. j/ i+ e* s! Z& i  v7 t
  2.                 DCD     Reset_Handler                     ; Reset Handler
    1 u( G# T8 b1 _* M8 O, S
  3.                 DCD     NMI_Handler                       ; NMI Handler2 v- ?: p1 K' D5 {0 q8 n5 u& J
  4.                 DCD     HardFault_Handler                 ; Hard Fault Handler: U. s: e! X, x9 v* ^1 L
  5. /*  省略部分代码 */                              
    ! @9 a, k( L6 k$ ~% k: k3 }* g# P8 }
  6.                 DCD     0                                 ; Reserved                                    
    4 |6 F! [7 e- B
  7.                 DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins   d- E6 p6 D& C3 u0 n8 b- Y
  8. 7 i( ~% i' I: I5 s6 p3 I" g
  9. : u$ X+ J( P8 Q& i
  10. __Vectors_End
    , f: a# E- U; z  L0 b% m

  11. : h# Z' Q0 a( @( d2 p& Q- a
  12. //定义向量表大小
    7 x) @; c( T- d
  13. __Vectors_Size  EQU  __Vectors_End - __Vectors
复制代码

, i+ O: m' V3 D# m  y5 r上面的这段代码是建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。如果程序在 Flash 运行,则中断向量表的起始地址是0x08000000。以 MDK 为例,就是如下配置选项:* j/ x& Q! t; D* s( l. K2 K
2 v( H2 U* s% t' y! X2 Z
20191124212435739.png

1 d* g  h+ J0 @7 h9 R: L% G- [. r2 U' t2 ~- Z7 K, ?
DCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序5 v' }3 b/ N, J
) u9 Q1 p0 O  O6 R% |, `
⑤、代码段定义、Reset_Handler过程处理
. a4 Z" D, x2 y9 J
$ U% f& w' f, g8 M; h/ s
  1.                 AREA    |.text|, CODE, READONLY8 c; ]* t( V) b$ C: b+ T
  2. $ q0 y+ m" ?1 D' U' f5 y* b
  3. ; Reset handler
    1 U9 W- G. E, k3 c( Z
  4. Reset_Handler    PROC, R4 N$ c  ^5 s6 d+ s# e" b; T; O
  5.                  EXPORT  Reset_Handler                    [WEAK]& ?, V# o6 O. z5 B; i, g9 t
  6.         IMPORT  SystemInit
    2 m7 Z9 w" t: h
  7.         IMPORT  __main& d; |# W$ Z6 B8 K$ G! o4 r
  8.   s) [% e5 H  C$ O: p+ F! r
  9.                  LDR     R0, =SystemInit
    ( [0 e& N' @# i7 e. C" g; K8 O
  10.                  BLX     R0  f2 J# r; _+ o/ ]4 {( }
  11.                  LDR     R0, =__main) W1 n% A' D2 I& W
  12.                  BX      R0
    7 a1 M( S3 r, I% }8 }! {; y9 @
  13.                  ENDP
复制代码
1 h7 L4 c4 D, n9 f# u
AREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读1 Y3 j( V- i1 ~2 f$ Z4 i
利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
4 U7 r3 d. D( X9 h" @& W& uWEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。: [, N$ U- D5 S$ t6 i
IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。: r9 O* K$ K) |
SystemInit 函数在文件 system_stm32h7xx.c 里面,主要实现 RCC 相关寄存器复位和中断向量表位置设置。
6 C% `8 a3 D9 K$ A/ B__main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到 C 程序中的 main 函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。
  Y& `1 D8 [. O+ X7 |8 l! U0 `$ d0 \5 p: S0 s7 k9 u
⑥、缺省中断服务函数定义
  1. ; Dummy Exception Handlers (infinite loops which can be modified)
    " M. v4 X$ \2 a; _

  2. # _- X  G' {; n, w! v' i/ f
  3. NMI_Handler     PROC% Y' N% N' ]+ n  e% D1 u1 Q* r8 P
  4.                 EXPORT  NMI_Handler                      [WEAK]
    + C3 G3 |: }5 V' x" i
  5.                 B       .      //死循环" d$ H" O$ K% Z! _1 V4 [% R
  6.                 ENDP, s' n( {" j/ E7 R0 \3 z4 d
  7. ...省略; ^& t; ]% Y3 ]: f  T% Q- P& n. ?
  8.                 EXPORT  PendSV_Handler                    [WEAK]* L9 K6 g, r) j3 g7 C9 H
  9.                 B       .
    & B* k7 y- _% u5 }8 t+ e
  10.                 ENDP
    7 Y% R' ~, B. A$ q' N, h
  11. SysTick_Handler PROC2 v. l8 C0 P( W6 w( ?/ y4 p
  12.                 EXPORT  SysTick_Handler                   [WEAK]
    8 c6 t4 @" }8 U* ]+ _- e
  13.                 B       .
    ! w" @: [4 M( X! S" F+ z: F
  14.                 ENDP                                    
    8 `; a) p/ {/ p' n
  15. # [9 R% v, d3 z4 z3 J& W( C/ K
  16. Default_Handler PROC                                      7 `* k3 c0 [3 t3 [- \: f

  17. % O* Q* e$ d9 G0 W# d/ G  {' f8 J
  18.                 EXPORT  WWDG_IRQHandler                   [WEAK]                                       : u1 ^- [" x& _, B* h
  19.                 EXPORT  PVD_AVD_IRQHandler                [WEAK]                        
    : e1 U8 N# L/ z1 ~* D
  20. ...省略                                                                                 
    0 A) ~7 m  C  T' M  ?
  21.                 EXPORT  WAKEUP_PIN_IRQHandler             [WEAK] 1 o- @' _' F/ j" d

  22. % v" i9 z2 f: l/ M, C: [9 u

  23. 4 i( ^3 |+ @% N8 {6 s: s9 Y
  24. WWDG_IRQHandler                                                         
    4 b' n; X1 L2 }. i& Z( N
  25. ...省略     
    % f" @7 J( f' D
  26. WAKEUP_PIN_IRQHandler, |" \0 T8 {2 \# w
  27. ( l& `' n; C/ Y) c6 g! Q5 @, z
  28.                 B       .8 I# U' |$ A- O& N2 c7 |

  29. . t5 ^2 Q! l& X$ t( A+ @
  30.                 ENDP+ W$ F1 ?2 U$ N7 g) ~7 S
  31. 6 }. K1 B4 c, R; F* F% e$ L* c1 v/ e
  32.                 ALIGN
复制代码

% B) c: V* Q+ E6 H, `这里全部的中断服务函数都是用[WEAK]来声明,假如没有在其他文件中定义同名的中断服务函数,来了中断,就会进入到这里。. I( z; u% W$ P1 ?
7 p0 D1 V! ]$ T0 d. J
⑦、堆和栈的初始化
5 _# c* {; A0 @" W& ~# r1 R* h+ N' G( a/ u- b
  1. ;*******************************************************************************( f, X# B+ M8 R
  2. ; User Stack and Heap initialization( a0 V: V5 @, ?
  3. ;*******************************************************************************
    4 r/ j. G3 V0 \% C- r
  4. //假如定义了MICROLIB,这里类似于if...else...
    & H' ]- S7 F' J  k
  5.                  IF      :DEF:__MICROLIB
    + ~. G, V/ J$ f$ b3 n

  6. / r: y: R& H0 T0 S
  7.                  EXPORT  __initial_sp
    , I" {; j: Q% X
  8.                  EXPORT  __heap_base
    ; A3 ?4 |- D7 `- [: |# \
  9.                  EXPORT  __heap_limit
    / W1 n  Q& E3 K  @

  10. ( N0 |& I& X! N
  11.                  ELSE
    , F( W. E# i3 Q
  12. . t/ F7 Z; o) F" s- }- R
  13.                  IMPORT  __use_two_region_memory
    - O% {8 r/ f# y. U) u" e
  14.                  EXPORT  __user_initial_stackheap
    ; m, n9 U! X* C8 P1 x) z6 i4 O% W

  15. 1 ~3 s- B. S  M8 C+ f! w
  16. //__user_initial_stackheap 将由__main 函数进行调用。                 / Y5 p" o3 \; U; x5 c5 G! g
  17. __user_initial_stackheap
    : w) u9 g+ B+ ?& H
  18. - |$ o* z1 P3 s
  19.                  LDR     R0, =  Heap_Mem$ n4 ]2 X- s2 }( @
  20.                  LDR     R1, =(Stack_Mem + Stack_Size)
    - I9 t7 U* U2 G6 G+ ~- |  y
  21.                  LDR     R2, = (Heap_Mem +  Heap_Size)
    * D) m/ k, m- Z5 V/ L8 \) g, {
  22.                  LDR     R3, = Stack_Mem
    0 W! {3 g, z( M# g
  23.                  BX      LR
    - Q# E" X$ C% F, r/ b) j" c

  24. # J( B4 G5 L! H+ c7 M% F% ]
  25.                  ALIGN
    9 h6 m, C$ Y2 e& R3 D. I+ F) L

  26. 1 J( F. t5 c5 `: r
  27.                  ENDIF/ z3 h6 E" h/ @7 F0 e# P
  28. ; `5 v& N$ ^$ Q
  29.                  END
复制代码
Boot的启动模式不同于以往的M3、M4内核的ST MCU,H7的boot引脚只有一个,但是H7 专门配套了两个 option bytes 选项字节配置,如此以来就可以方便设置各种存储器地址了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9J2Qpbcf-1574601496448)(./1574512528932.png)]
3 Q. H; r6 |% |4 C; Q9 f, R& VBOOT_ADD0 和 BOOT_ADD1 对应 32 位地址到高 16 位,这点要特别注意。 通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:$ z4 K. P; ^% R+ B9 j

6 X- Y  F1 K% b# ?: W& u% D所有 Flash 地址空间。
" A& D' p1 V, \; n& y所有 RAM 地址空间,ITCM,DTCM 和 SRAM。
$ q% ^( o6 G" V设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0,或 BOOT_ADD1 所设置的地址进行启动。  Y  q# u2 |% y9 T
使用 BOOT 功能,注意以下几个问题:  e: q: |0 a1 t7 l& f" Y6 {
如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址 0x08000000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。4 i0 N. \2 V8 b% ^- v. F# V
当 Flash 的保护级别被配置为级别 2 之后, 只能从 Flash 自举。 如果 BOOT_ADD0/BOOT_ADD1选项字节中自举地址被配置为位于存储器范围之外或属于 RAM 地址范围,则系统只能从地址 0x0800 0000 上的 Flash 开始执行
, i- M' Q4 u) t# u: [: l4 T, `2 W6 A0 P
6 E# y0 z6 {' B4 `0 P
/ F/ t  f( r( n  G4 V
收藏 评论0 发布时间:2021-12-27 18:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版