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

【经验分享】STM32F0单片机快速入门三 MCU启动过程

[复制链接]
STMCU小助手 发布时间:2021-11-20 23:00
1.MCU 代码如何启动
- U* c0 b3 c: Y1 R首先我们需要澄清一个问题,什么是 Startup Code,什么是 Bootloader?因为总看到有同学混用这两个概念。$ d) Z. w/ H* Z, |8 Y0 j

9 H; A+ `' }$ x* w( l/ YBootloader 可以译为引导程序。早期的单片机是没有 Bootloader 这种概念的。如大家熟悉的 MCS51,最初芯片内是不能存储代码的,需要外挂EPROM,就是下面这种带个小玻璃窗的存储器。擦除 EPROM 中的代码需要用紫外线照射几分钟才行。  \7 I! I+ _& U2 H: A
3 O% E" w; ~# H
20200207173108336.jpg
3 F0 K9 J/ N- D4 r$ Q2 Y7 O; L! F
6 X% ~! }9 U+ a; A+ e9 G5 J7 w6 W
后来出现了 Flash 这种可电擦写的存储器,并集成在了单片机内部。但出厂的时候单片机的程序存储区仍然是空白的,没有任何代码。用户编译程序后,下载到单片机后才能运行。那么在产品发给用户后,如果发现有Bug怎么办呢?就得用编程器把新代码重新下载一次。这实在是有点儿麻烦,特别是如果客户距离很远的话。于是有聪明的程序猿想了一个办法,写一小段特殊的代码放在程序里,这段代码可以通过一定方式,比如用按键触发进入运行,它可以通过串口(早期的 PC 串口是标配)接收新的代码并写入Flash,从而在没有硬件编程器的情况下也能完成代码的更新。
7 _/ E  {. c- o! O9 z- o" B2 V# x& _; e: Q* P
程序猿们也是现代历史前进的重要推动力啊!: N1 ?  y' X7 _" x+ O7 w: C1 Y5 w
1 x. z' Z  e, ~! t2 O
后来,有芯片厂商把这种代码在出厂时就固化在芯片里,极大的方便了代码下载和程序更新。STM32F030内部就固化了Bootloader。当我们把一个引脚 BOOT0 拉高的同时,重新给芯片上电或复位,就会触发Boootloader进入运行。此时我们通过单片机的串口就可以把新程序发送给单片机,发送完后把 BOOT0 拉低,再复位单片机,新程序就会运行起来。3 _2 H2 N* r- _+ Q5 a
' o  P/ W" ?( v+ q1 A- @
Startup Code 可以译为启动代码。单片机上电或复位后最先执行的一段代码。一般主要会完成堆栈指针的设置,复位向量的获取和加载,然后初始化变量,最后跳转到用户代码。在详细看启动代码之前,我们先看一下 STM32F030 的内存映射。5 J1 m5 u+ I! }! C2 L1 p
  O$ |$ k6 I( \1 R; K" A- i" D
2.STM32F030内存映射(Memory Map)2 _, ^& n( }5 E9 U2 ]
下面是 STM32F030 的内存映射,其它芯片会因为 Flash,SRAM 空间大小不同而略有不同。
" l( V& K) ]: s9 {! y3 n: o6 p1 b% A
20200207173223663.png
# x  P4 X1 v5 A

1 `! t% U3 E1 \5 m# h因为是32位机,所以可寻址从 0x0000_0000 到 0xFFFF_FFFF 的总共 4G 空间。
' ?& q6 H5 _/ X0 _' B4 v
, U* t. e* X2 B2 K" M) z8 {这是采用32位机的好处,地址空间足够用。不像8位或16位机,很容易出现地址空间不够用,动不动就需要用 Page 来间接寻址。+ u. v# Y' H" [; e
" x% u5 K4 S4 G2 r) B) b# L
我们从低地址到高地址逐段看一下:7 G- Y# }" e8 c/ ]: u# @6 d4 Y

+ O. {/ O6 n! p' Q/ k+ ?0x0000 0000 Virtual memory
( p2 F, x% T' C$ C3 {, E1 F1 B) S" z: ^
这段地址空间,会因为不同的 BOOT 模式而映射到不同的物理内存。9 F& e/ m* v" d5 p6 z
. _& h8 o) i% J3 e# i3 {$ |
当芯片复位,或从 Standby 低功耗模式唤醒时:8 R  G8 j0 J! s/ O
+ F/ v$ S  q$ o- C2 i
如果引脚 BOOT0 是被拉低的,将映射到 Flash memory。这是最常用的代码运行模式;) i- h! U6 ^8 T* X' h( B& @

( V- z- T/ s# y0 g2 p如果引脚 BOOT0 是被拉高的,且nBOOT1为 1 ,将映射到 System memory。进入bootloader模式;9 R1 |# {' G/ Q" S. F: N" ~6 `
8 ?, y4 T& E' A# b+ ?
如果引脚 BOOT0 是被拉高的,且nBOOT1为 0 ,将映射到 SRAM。
% q! [7 t( F- [6 v6 d" G5 K, M
$ X* a8 |1 p% F: T* o注:nBOOT1 为Flash寄存器中的一位,用户何以设置。
% ^6 f5 R) M% |8 B. s+ |3 A5 @! e
0 N. E; O) K/ C3 B; E( d0x0800 0000 Flash memory
; y( T  r. ]/ o存放用户代码
3 d2 M0 i4 P# j! {# n3 W5 \6 w! A
8 H8 z$ G6 |; X6 A0x1FFF EC00 System memory
; i2 G9 J: j! j存放 bootloader, 片内集成温度传感器的校正数据,和片内集成电压参考的校正数据
* `0 M8 g; W% w, s" H0 t; g6 ]这些代码和数据是在工厂固化好的。- W- V# j4 U; E) L# |6 K
: [, ]' |. [$ w2 f5 g
0x2000 0000 SRAM
- @% |; T7 A5 R& ^存放用户变量,堆(Heap)和栈(Stack)。也可以把代码加载到 SRAM 运行。/ y; ]) Z, X6 [! i, ^5 S. X
$ T2 h" }+ V# f6 C. p
0x4000 0000 Pheriperals. f. `' p7 Q/ P1 o9 J& D) j" s+ {9 Z
芯片集成的外设,如 USART, SPI, GPIO等的寄存器地址在这一区域。
4 P) ~; L6 B  ~0 o7 d: |3 T9 o
8 h7 r2 @5 \: _! |7 ?) t! w5 E0xE000 0000 Cortex-M0 internal pheriperals
  b1 z  k: l! vM0内核的外设映射到此区域。如 systick (System Tick),NVIC,Debug Registers。这些寄存器在芯片手册里是查不到的,需要到 ARM 的手册里查找。
4 |( g8 B* Z# [3 c1 i- L) Z7 B7 X. g5 _* D' O7 p5 H
3. 启动代码(Startup Code)
  }* ?& q& E, p5 `我们还是以下面这个最简单的GPIO翻转代码为例:
3 M7 x/ _+ a8 ?" O3 jSTM32Cube_FW_F0_V1.11.0\Projects\STM32F030R8-Nucleo\
6 @7 X( W2 X" e2 QExamples\GPIO\GPIO_IOToggle\MDK-ARM\Project.uvprojx
& o, ~. w- H) v7 e; A把此工程下载到单片机后,用调试器观察下面两个地址的内容:
9 c# X- K+ ^5 M6 n/ m1 F我们会发现0x0000_0000开始的区域, 和0x0800_0000开始的区域,内容完全相同。这说明Flash 区的内容映射到了 0x0000_0000起始的这一段地址区域。
* e/ U- ?% f; u0 {8 P& h" B, Y/ Z. {8 Y0 u4 A0 y: w+ F6 Z: }
20200207173410739.png
0 [: X1 M% I2 n+ {0 P3 t
! s' e3 S8 q& ^" v5 j9 x
注意STM32F030使用的是小端模式(Litlle Edian)。
" K; v' Z" t/ ^& @& b& U" c# m( c3 z) \2 D+ p; H
不同于 MCS51 在 0x0000 放的是复位向量,STM32F030 还有其它 ARM 芯片在零地址存放的是初始堆栈指针地址。
, `+ g- M0 y% O  E  N; @7 g1 L6 m9 D% a# N4 N
0x0000 0000: (0x2000 0428) 初始堆栈指针
* C9 O; ?4 ^9 ]# \
7 n9 ^- H& r8 X5 @, `0x0000 0004: (0x0800 00C9) 复位向量,上电或复位后最先加载入PC
+ i( ~* e" M1 a1 U+ R, y( L9 a$ K3 x! V) A& E* I
注:单片机上电或复位后,堆栈指针初始化和 PC 初始值的加载总是从地址 0x0000_0000,0x0000_0004获取。在上面这种用户模式下,实际是从 Flash 区的 0x0800_0000,0x0800_0004 获取的。$ J( c" C0 @' {0 @& U
; a; r- J! q% _2 r* {. Y) x# S, b8 L, \
我们可以通过调试器观察一下芯片复位后 M0 内核的寄存器:
' m& o  Q0 t9 G, {
7 a5 }/ ?6 n7 J8 F# {1 g3 k) ^5 c
20200207173413306.png
; p7 s- ?: s& S5 z

7 p7 ^7 T+ r5 W; z2 d$ h9 a细心的同学这时可能发现了一个问题。
! c$ Y- y' B% c1 R) U5 i$ T& J8 F3 T
堆栈指针 SP 的内容和前面存储器中的内容是对的上的。但是 PC 里的内容好像对不上啊?PC 里的值是 0x0800_00C8,存储器里明明是 0x0800_00C9 啊!
- j# ~+ Z5 j3 e' u( C+ w# q
' v) t' [! Y' x/ q这里牵涉到了 ARM 体系里的两种工作状态 ARM 和 Thumb。ARM 状态下执行32位指令,Thumb状态下执行16位指令。那么如何在这两者之间切换呢,一个方法就是靠跳转地址的最低位(Bit0), 当 Bit0 设为 1 时进入 Thumb 状态,当 Bit0 设为 0 时进入 ARM 状态。! T3 Z6 O! @% [+ Z) J: n& I$ G

& r9 w  M. F. T3 T: M6 |; n/ j* \对于单片机来说,16位的 Thumb 指令就足够了,而且16位指令比32位指令能节省存储器空间。所以 M0 内核只支持 Thumb 指令。
! B3 V% t9 v' O4 R$ P
! ^/ o4 ?4 z: a; N, w  Y' \1 @到这里我们就可以理解复位向量为什么是 0x0800_00C9 了。
# D+ f8 ~3 z* K1 W
* {6 h/ s) ~$ ?# J接下来我们来看复位向量 0x0800_00C8 指向的第一条指令:
! G5 [  {, U, m2 L) x) A/ S2 c- w# ?# i+ ]
20200207173440317.png

2 q4 {( s5 \9 W6 B7 p4 p, N) z" K: v* u. \/ ?! o2 z
7 {0 i7 N  K6 `; T* ]

, F* m% j0 ]9 N; ]单片机将要执行的第一条指令 0x4804,这是什么意思呢?  e3 O: R' |4 p0 P" z, u7 e+ a

! `: D. p) `$ b. h先说结论:它就是下图中,单片机复位后光标指向的这条指令:
$ K( A' e' }: p) f/ x4 B5 y7 R" a
LDR     R0, =SystemInit; S4 p+ U' t9 ^3 Z0 d/ U
: I9 U! h  ~# V) N
20200207173500363.png
/ r  i: ^8 V6 K" n* l' W
, y9 E5 O) b2 ~
4 z% `6 M! G( x$ S- x. ]
% g# }' n0 _; s6 d- t& h( h6 s# D& N
在这里详细解释一下 0x4804 这条指令:8 L2 S. J9 w* S( |

$ r3 i  d  o3 q3 v它对应的机器码是 0100100000000100
* }: C6 |3 v$ k2 y* E, d  M6 q
" a% h$ o5 n; m1 H. s' p6 Q% ?; p+ iBit15 to Bit11 (01001)为LDR(literal)指令,既从PC偏移地址取数据送至寄存器Rt。# Y2 B5 N; I6 @+ j" {4 J
4 y5 v& g  S+ O4 S! A( C/ U4 P
Bit10 to Bit8  (000)表明目的寄存器Rt为 R0
! W+ A) \7 b% L4 u
& ~* d6 y6 ?- mBit7 to Bit0  (00000100)表明相对于 PC 的偏移量为 0b10000,既0x10。2 i/ u! u7 a% c. F# F! Y

; {. s& p8 |7 d/ S& _2 o' ^注意PC的值是当前地址+4。
/ f, l( y3 X; b! t9 N9 h1 h( A- s! @) a) }, l
那么从 0x080000C8 + 0x4 + 0x10 = 0x080000DC 取出数据 0x0800092D 送至寄存器 R0。此地址是 SystemInit( )函数的地址。下一条语句 BLX R0 就是调用此系统初始化函数。
2 `) w, a% U9 @3 n' x6 T" C
8 ?" ~) T" c0 t# Y3 bSystemInit( ) 这个函数在 system_stm32f0xx.c 这个文件里,主要完成系统时钟的初始化。可以点进去看一下具体的内容。
6 Z( ^6 A3 s* ]$ \' ~2 ]1 t* {+ _- e5 h. m
20200207173508532.png
! b7 E& c8 D: h7 ~/ @

7 X" g3 k: l2 @2 P: p5 K9 a5 X6 c( s: u0 n! R
0 W3 |/ T* d. Y; q& Q) G
函数 SystemInit( ) 执行完之后,程序跳转回来,取得 __main( ) 函数的地址,跳转到 __main() 函数执行。需要注意,这个函数不是我们用户代码里的 main( ) 函数。
6 O# O& T) ]: ~
7 }' y2 `. }9 B. o9 C! C( K" a__main() 函数是 Keil 的库提供的,我们看不到代码,它主要完成变量的初始化。这里不用太纠结,如果想进一步深究可以看一下 ARM Compiler User Guide 的 Reset and initialization 这一节。
5 R/ v& T* l. R
* E* l/ f8 k$ g( d2 O__main() 函数执行完,基本工作就做完了,这才跳转到用户代码的 main( ) 函数。  M8 U- z8 T0 F7 m0 A. |

) j; j! b- N/ Y3 Y; k- w& f
6 k$ \. @- P" K4 K) J& m$ p1 }* i
2 ^1 W/ j, }$ o, Y' b
20200723225010732.png
收藏 评论0 发布时间:2021-11-20 23:00

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版