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

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

[复制链接]
STMCU小助手 发布时间:2021-12-27 18:00
STM32H7的启动过程分析) s" v- h" ]" E# U
启动文件
* k0 j  \( J: T3 h- ^. d' L' a
不同编译器对应的启动文件不同,在MDK下,以startup_stm32h743xx.s为例,这是一个汇编文件,启动文件中主要做了如下事情:' @0 z/ h! h( k! T! P0 |, p+ x! i
设置堆栈指针 SP = __initial_sp。- ~; M9 x) ~# K7 u
设置 PC 指针 = Reset_Handler。. `7 K8 V2 t; k/ Z7 d6 f
设置中断向量表。: ]) ?9 ~: h% F  o# L- K6 R8 c' `
配置系统时钟。
0 M3 X, v1 w4 j4 h, p/ Y% S( p1 k配置外部 SRAM/SDRAM 用于程序变量等数据存储(这是可选的)。/ T# U0 m  e9 y1 t+ z
跳转到 C 库中的 __main ,最终会调用用户程序的 main()函数。
6 J( `- N4 ^. ]& a* ?1 \Cortex-M 内核处理器复位后,处于线程模式,指令权限是特权级别(最高级别),堆栈设置为用主堆栈 MSP。6 e9 _" H; C9 h  d, w  X- X$ ^  |
堆栈指针$ m" y7 r6 l7 o; Z
通用寄存器组
. j/ R$ N+ Z6 p1 f8 PCortex – M7/M4/M3 处理器拥有 R0-R15 的通用寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以用。: Q. I& I4 I! B1 j) S
主堆栈指针(MSP):这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
3 q0 n) m/ U9 V* h" x: U进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。5 }$ k3 j: o/ b3 e/ u( j) A
另外以下两点要注意:% F  X2 A, Z9 g. _; s
大多数情况下的应用,只需使用指针 MSP,而 PSP 多用于 RTOS 中。
; V& Z8 ?2 M3 y9 U7 A1 ?6 MR13 的最低两位被硬线连接到 0,并且总是读出 0,这意味着堆栈总是 4 字节对齐的。; F6 F/ x8 T/ _2 X
6 G% M$ {! y$ h- a+ D% R; }+ P
Cortex-M7/M4/M3 向下生长的满栈
; Y; v4 _: K+ i0 |7 u' d) fPUSH 入栈操作:SP 先自减 4,再存入新的数值; F( V, F5 P* y9 m6 K0 r8 Q( ~/ F
POP 出栈操作:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4) S3 i" E% X; ~- U9 q

2 d0 j1 U! s9 n) _分析启动过程
* l  b, G/ {- \+ u- o$ x硬件上电后,会触发硬件复位,复位之后,CPU 内的时序逻辑电路首先完成如下两个工作(程序代码下载到内部 flash 为例,flash首地址 0x0800 0000)% o+ ~6 j0 D5 E" F
5 f2 b0 j. {/ F( Y
将 0x08000000 位置存放的堆栈栈顶地址存放到 SP 中(MSP)。
1 a9 T4 ], j5 x$ O+ @将 0x08000004 位置存放的向量地址装入 PC 程序计数器。
. Y, k# A) d  j  QCPU 从 PC 寄存器指向的物理地址取出第 1 条指令开始执行程序,也就是开始执行复位中断服务程序 Reset_Handler。为啥,因为在启动文件中,最先做的两件事情是
( x  U% R5 E; i3 H1 q) E) i& f设置堆栈指针 SP = __initial_sp! n, b# M3 ^  S3 ]; l2 r4 S
设置 PC 指针 = Reset_Handler
& o1 A& V2 `4 s复位中断服务程序会调用SystemInit()函数来配置系统时钟、配置FMC总线上的外部SRAM/SDRAM,然后跳转到 C 库中__main 函数。由 C 库中的__main 函数完成用户程序的初始化工作(比如:变量赋初值等),最后由__main 函数调用用户写的 main()函数开始执行 C 程序。0 _% j$ ~( {$ J# L7 O% l
9 Y- e8 R. C" L8 p3 X; g7 T
代码分析
, {9 I/ ^  r1 H8 {3 m3 j8 d* H①、开辟栈(stack)空间,用于局部变量、函数调用、函数的参数等
! b4 c" Q$ ]- Q) b5 q! Y3 V4 r- d% j) a% q- s) l5 c1 R4 N2 m0 [
  1. //类似宏定义,这是个伪指令,定义栈大小,这里是以字节为单位
    1 J1 e7 ^0 \7 f; o, O
  2. Stack_Size      EQU     0x00001000   6 ?: ?" I( \  ^5 o% Z

  3. 5 E9 v$ Y! u6 h# `. H0 Q/ \8 K% \
  4. /*
    ( _1 p. A5 u7 _4 k' ?! N: C
  5. 开辟一段数据空间可读可写,段名 STACK,按照 8 字节对齐。 ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。 ARER 后面的关键字表示这个段的属性。
    6 e: b9 h' h8 W: u4 \# M
  6. STACK :表示这个段的名字,可以任意命名。7 c3 s7 T7 e! v: b% `
  7. NOINIT:表示此数据段不需要填入初始数据。
    ) `3 I/ c. j2 L
  8. READWRITE:表示此段可读可写。6 ?% ]2 r6 z& `+ \
  9. ALIGN=3 :表示首地址按照 2 的 3 次方对齐,也就是按照 8 字节对齐(地址对 8 求余数等于 0)。
    / a$ ^5 @5 J/ ?+ R$ [
  10. */  M4 ]( E" b9 Z% a# Z5 H+ z" N, x& a
  11.                 AREA    STACK, NOINIT, READWRITE, ALIGN=39 G# e% i/ \; r' D9 V0 I
  12. + B- ?: L2 }' L# Q+ s' y2 Z+ t; |
  13. //SPACE 这行指令告诉汇编器给 STACK 段分配 0x00001000 字节的连续内存空间。7 f: d- z- W/ I2 B/ {% X- G
  14. Stack_Mem       SPACE   Stack_Size: d/ V& m6 U$ P, D/ Y( W& ^

  15. ) x; m. m  O; _! o3 X" y4 {2 Y+ m
  16. /*4 G) j$ U7 Q9 M  ]# d
  17. __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。 __initial_sp 只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于 C 语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C 语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。  f- |: v& D2 j# o( t% U. r0 C
  18. */& n$ c; W; t1 L6 ~8 B
  19. __initial_sp
复制代码

9 ^1 T5 a& a* {0 |0 L9 J9 x# m4 B①、开辟堆(heap)空间,主要用于动态内存分配,也就是说用 malloc,calloc, realloc 等函数
( ?$ t) K" g: _+ k  s* @分配的变量空间是在堆上2 T. A5 a( r; B1 J1 a7 ^

' V/ f) ?8 ~( x9 r4 v
  1. //定义堆空间大小
    * l4 E- u! \& v
  2. Heap_Size       EQU     0x0000800/ n1 g& v! y% {8 x' E9 F6 C& X
  3. 9 p  a1 C; H, g8 U3 X7 M
  4. //分配一段名字为HEAP、可读可写、不需要初值、8字节对齐的数据空间
    " q. H# L# S% p& C( J% V/ k& n
  5.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    , O- y$ J$ ?! \; E

  6. & Z9 C+ T/ g% U- h+ P8 ]! A' G( D& R
  7. //__heap_base 表示堆的开始地址。
    8 ]' `5 K- t4 L3 ]$ |
  8. __heap_base
    3 [& q' H& [7 E- c  Q) }/ N+ P
  9. - C4 m/ Q+ `* B( Y6 S0 V# P
  10. //SPACE 这行指令告诉汇编器给 HEAP段分配 0x0000800字节的连续内存空间。* l% x2 l4 M  f; ^+ H1 I
  11. Heap_Mem        SPACE   Heap_Size: M, W8 r, P3 m; D. R" U

  12. 6 I+ }' c# Q7 Q: ]& d+ F/ b  ~
  13. __heap_limit 表示堆的结束地址
    9 s8 B3 V6 m( J4 l% g6 k0 u* z6 f# W
  14. __heap_limit
复制代码

$ s; L4 ]1 r1 ^# M. g' ?③、生成属性设置、定义RESET代码段: s6 L" v* |* a7 U

, E; e" d( k; ^3 q4 K1 K- k" x
  1. //PRESERVE8 指定当前文件保持堆栈八字节对齐。
    ; Q2 c% g. Q% K( E
  2.                 PRESERVE8
    8 v! e7 [2 B' \" D7 q
  3. $ u, h* ]# D- ~0 I
  4. //THUMB 表示后面的指令是 THUMB 指令集 ,CM7 采用的是 THUMB - 2 指令集
    7 s/ w* v/ y6 a4 R/ z0 P% b/ }7 @
  5.                 THUMB
    1 |2 c. L( t1 L$ Y/ K

  6. % B/ a; T4 F/ q1 E
  7. //:AREA 定义一块代码段,只读,段名字是 RESET。 READONLY 表示只读,缺省就表示代码段了。
    1 V7 Q+ X; W( C) |& u
  8. ; Vector Table Mapped to Address 0 at Reset9 z7 B- j: T6 @0 n1 |# s3 Z" ^
  9.                 AREA    RESET, DATA, READONLY3 K1 \1 W8 ]7 p0 a5 q
  10. - R# n% @8 z) n% o/ H
  11. //3 行 EXPORT 语句将 3 个标号申明为可被外部引用, 主要提供给链接器用于连接库文件或其他文件。
    + v7 R$ z3 x2 I3 O$ K
  12.                 EXPORT  __Vectors. s  m* `4 N3 y) b5 {
  13.                 EXPORT  __Vectors_End
    , U8 [( A) ~+ _" Z9 X! f& w. e
  14.                 EXPORT  __Vectors_Size
复制代码
, w( L1 w" T5 V4 K% P8 W7 h
④、中断向量变定义/ f( f! o2 t1 K- g6 Q

  b2 D  @3 t* E
  1. __Vectors       DCD     __initial_sp                      ; Top of Stack
    - A5 j$ h# U, p: r) q( k2 ^/ b
  2.                 DCD     Reset_Handler                     ; Reset Handler
    & x, R7 W# N. \0 U- n/ t2 o; h
  3.                 DCD     NMI_Handler                       ; NMI Handler8 U4 @0 x* l) K# k, f& a: ~
  4.                 DCD     HardFault_Handler                 ; Hard Fault Handler; A0 Y2 V4 A1 q( G0 P' E
  5. /*  省略部分代码 */                              
    # |* [/ @3 D- C: a: z
  6.                 DCD     0                                 ; Reserved                                    
    9 [- F5 V0 H, }2 Y. I6 K1 E
  7.                 DCD     WAKEUP_PIN_IRQHandler             ; Interrupt for all 6 wake-up pins
    ! d- ~8 e6 q) A- m6 s5 J
  8. & x' {' z: X& t1 w5 u

  9. * W) S* y' Z% K4 R1 f! T
  10. __Vectors_End
    5 w, N& _! R5 _1 o4 t
  11. 7 i4 N) x. M0 j6 }) }$ P9 R
  12. //定义向量表大小, U2 x" R2 N8 f0 U8 ]1 ^
  13. __Vectors_Size  EQU  __Vectors_End - __Vectors
复制代码
; Z+ H& N, W( R9 R. p) Z! Q  M! F
上面的这段代码是建立中断向量表,中断向量表定位在代码段的最前面。具体的物理地址由链接器的配置参数(IROM1 的地址)决定。如果程序在 Flash 运行,则中断向量表的起始地址是0x08000000。以 MDK 为例,就是如下配置选项:
0 ~3 {" h0 w8 n. G) O2 r$ D3 r
5 h9 j4 b% l9 {( C. D7 f9 M. D
20191124212435739.png

. C$ X3 _- Q; y8 O9 @* N
' y* d6 \' M% A+ U, @/ mDCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序& F& B  J5 H8 Z. w3 D

! m  V$ {: b7 a) T⑤、代码段定义、Reset_Handler过程处理" t0 \6 F3 u5 ~2 i0 ?9 Z) F1 _

% M$ O5 n  r. @5 `4 d  F5 j
  1.                 AREA    |.text|, CODE, READONLY2 b8 c% d& X& @
  2. 5 y2 P1 Q! R! v  w
  3. ; Reset handler
    ; a; N! ?! R$ F
  4. Reset_Handler    PROC! m+ ?1 _/ j4 L. u; z# G: l
  5.                  EXPORT  Reset_Handler                    [WEAK]
      j; }4 P, S: F8 Z* ?" c; w8 m* C
  6.         IMPORT  SystemInit* @0 R( V+ `+ l" y
  7.         IMPORT  __main* B. v) R" k4 o; O+ X" b9 F- s

  8. ) A, S! F* N/ H2 a. i3 h2 v2 r
  9.                  LDR     R0, =SystemInit  y+ _. Z! D& ]" d) }2 C, Y
  10.                  BLX     R0
    . N# A6 z1 [+ \2 G$ K
  11.                  LDR     R0, =__main7 f% _( H; ?; b8 v
  12.                  BX      R0
    $ |1 y) M  c5 U" D: x1 M: U6 q
  13.                  ENDP
复制代码
; n8 |0 L0 S9 W( K3 J. L9 O
AREA 定义一块代码段,只读,段名字是 .text 。 READONLY 表示只读  {3 n% h1 i6 c: E6 a/ J
利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。" {) F  }9 a& w. l
WEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个声明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证 C 函数的名字和向量表中的名字一致即可。
  U4 x( f$ A% q8 e' BIMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
. G) X& E( _0 U, U8 oSystemInit 函数在文件 system_stm32h7xx.c 里面,主要实现 RCC 相关寄存器复位和中断向量表位置设置。
" n/ W  D/ k- k__main 标号表示 C/C++标准实时库函数里的一个初始化子程序__main 的入口地址。该程序的一个主要作用是初始化堆栈(跳转__user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到 C 程序中的 main 函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规,并且不能更改。( E; r+ R2 t/ }7 I  e5 E

' a6 Q+ g0 h# c" [6 D$ J⑥、缺省中断服务函数定义
  1. ; Dummy Exception Handlers (infinite loops which can be modified)
    2 B  ~$ C" P/ N  B5 e2 ~
  2. 1 s' [3 e) g- s: Y4 u4 M" e' O
  3. NMI_Handler     PROC7 L1 O8 P4 c  d& A0 p3 d4 }
  4.                 EXPORT  NMI_Handler                      [WEAK]
    0 d0 ~1 n, A  i% |, m" R5 I9 o4 c; f
  5.                 B       .      //死循环
    1 p0 X+ r) z( K/ X8 ?
  6.                 ENDP  `5 d0 P6 j, L0 A- v1 p% r! u1 k
  7. ...省略
    , N% }" l+ S2 l% g4 P" W
  8.                 EXPORT  PendSV_Handler                    [WEAK]- b  h" C  y& n& v5 o. E9 z
  9.                 B       .
    - F7 d: `' a$ Q& e# a: o% |
  10.                 ENDP( x! s$ X6 s& A# x( O. v0 v
  11. SysTick_Handler PROC9 v' Z  u5 H, r6 C  k
  12.                 EXPORT  SysTick_Handler                   [WEAK]
    : h/ G0 G- K; o* s  }
  13.                 B       .: h' Q; ]. `4 D% f5 |3 s8 E
  14.                 ENDP                                    
    : j# B6 k) |. y: ~" Z/ r

  15. ) ?3 j! g8 k* |% N9 i
  16. Default_Handler PROC                                      
    * @" _+ [4 ~# _+ w
  17. 5 e; e2 W8 D  D7 y9 g
  18.                 EXPORT  WWDG_IRQHandler                   [WEAK]                                       4 _# o9 t# y7 a( n3 u, B! C
  19.                 EXPORT  PVD_AVD_IRQHandler                [WEAK]                        
    6 o; i  w, [0 D5 e; w6 @# C8 [
  20. ...省略                                                                                 , T$ E  m8 ^2 E- Q
  21.                 EXPORT  WAKEUP_PIN_IRQHandler             [WEAK]
    ; Y+ t7 n8 r0 M
  22. 3 F  V; F4 ]/ S3 g9 a  V

  23. : t3 _; y5 a! b1 D  i
  24. WWDG_IRQHandler                                                         
    " l' b& e/ S, W- T
  25. ...省略       j- w/ g) R. ^' ]1 {4 M
  26. WAKEUP_PIN_IRQHandler. q' e  ^) j3 A7 L; g6 f/ f

  27. 8 Z5 i' U! I: b' V) e1 v
  28.                 B       .# W) l  z! s& R) V$ J0 [

  29. 5 n  Z6 g7 y) R
  30.                 ENDP( _2 N2 K* u. H# |' L
  31. ' @: }4 I0 E# F& E
  32.                 ALIGN
复制代码
' x- |0 K- [8 s# [# ~3 v/ S
这里全部的中断服务函数都是用[WEAK]来声明,假如没有在其他文件中定义同名的中断服务函数,来了中断,就会进入到这里。; g$ X9 f4 I7 L; ~5 ~

. D' ?' W( }- f7 D0 X  v0 H⑦、堆和栈的初始化
+ H3 F4 C1 _, y5 @" h5 O
; g( v( T  c2 F4 b: ]+ L3 q
  1. ;*******************************************************************************
    $ J& o: a* @0 X% t
  2. ; User Stack and Heap initialization" D" ]- b- g4 `: [2 B) j' S# K
  3. ;*******************************************************************************- M5 h& q( p4 s0 j
  4. //假如定义了MICROLIB,这里类似于if...else...: l. }+ g8 ^' x2 f, m$ a5 j/ t
  5.                  IF      :DEF:__MICROLIB
      l2 ?6 Z- l5 S& I4 k' i

  6. + }& ^6 G) E4 l5 h  c  S
  7.                  EXPORT  __initial_sp
    4 M3 }) z  A) B9 \' M
  8.                  EXPORT  __heap_base
    , @& ^9 z( K( G" M: |
  9.                  EXPORT  __heap_limit' n) a7 C* x/ o. o) J, w

  10. 8 a0 P0 W' v$ l  W1 r( h: a0 {
  11.                  ELSE
    % t: J! W% q7 d% M$ M" q) ?

  12.   G4 x, X+ E3 M. {
  13.                  IMPORT  __use_two_region_memory: H- y( x6 o- [" T$ k
  14.                  EXPORT  __user_initial_stackheap% A9 H4 m4 Y3 f) J% J: T5 w
  15. 1 k. _1 ?$ K' a; b* Y7 p, s; n
  16. //__user_initial_stackheap 将由__main 函数进行调用。                 
    % R& J7 l9 y8 f4 d8 s
  17. __user_initial_stackheap* \. Y6 Z6 p  ^
  18. 9 \- Q, _2 Q. d$ k) R5 \8 x
  19.                  LDR     R0, =  Heap_Mem
    ! o3 R6 _& I: w: t' J: n2 J2 _  @
  20.                  LDR     R1, =(Stack_Mem + Stack_Size), e$ Y2 W1 g" P9 L/ I
  21.                  LDR     R2, = (Heap_Mem +  Heap_Size)/ G7 {! _9 M+ Y7 E
  22.                  LDR     R3, = Stack_Mem0 N* h7 i8 H  d
  23.                  BX      LR/ V% Q5 W( V1 a# A
  24. ; O% }9 @6 |4 A4 y# a+ B1 e
  25.                  ALIGN
    # _& u' s/ [3 _' [% O" S

  26. . A; I, S) E. w# P5 A* `
  27.                  ENDIF  q2 Z6 j9 n. |1 b
  28. 7 p9 k! I: B, W
  29.                  END
复制代码
Boot的启动模式不同于以往的M3、M4内核的ST MCU,H7的boot引脚只有一个,但是H7 专门配套了两个 option bytes 选项字节配置,如此以来就可以方便设置各种存储器地址了。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9J2Qpbcf-1574601496448)(./1574512528932.png)]% f1 e2 D+ f, D# J
BOOT_ADD0 和 BOOT_ADD1 对应 32 位地址到高 16 位,这点要特别注意。 通过这两个选项字节,所有 0x0000 0000 到 0x3FFF 0000 的存储器地址都可以设置,包括:$ X7 r1 @) V+ G* A4 ?

2 W) A, R* f5 F4 j  L5 q所有 Flash 地址空间。
! Z( B; h& e. ]2 A1 s: D0 t所有 RAM 地址空间,ITCM,DTCM 和 SRAM。" ^# e" E2 @( G8 l
设置了选项字节后,掉电不会丢失,下次上电或者复位后,会根据 BOOT 引脚状态从 BOOT_ADD0,或 BOOT_ADD1 所设置的地址进行启动。3 P- C/ A0 C' V3 J; U: T% O  o
使用 BOOT 功能,注意以下几个问题:: D9 H$ _# w) g2 @1 ?+ A- G
如果用户不慎,设置的地址范围不在有效的存储器地址,那么 BOOT = 0 时,会从 Flash 首地址 0x08000000 启动,BOOT = 1 时,会从 ITCM 首地址 0x0000 0000 启动。
1 o  A  Y" U% i; M1 L* ~当 Flash 的保护级别被配置为级别 2 之后, 只能从 Flash 自举。 如果 BOOT_ADD0/BOOT_ADD1选项字节中自举地址被配置为位于存储器范围之外或属于 RAM 地址范围,则系统只能从地址 0x0800 0000 上的 Flash 开始执行
( M9 t& f' d+ U' j
% `9 v! d+ b3 z* W; j/ O. O( G* B" b8 C9 Z0 L' c

: l6 r6 P1 `" S) q' }, l5 v3 f" |
收藏 评论0 发布时间:2021-12-27 18:00

举报

0个回答

所属标签

相似分享

官网相关资源

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