前言
+ S' S' X" v+ r$ Q! N6 |众所周知,使用 STM32 时,当需要使用 System Memory 中的 Bootloader 进行代码升级的时候,需要将 BOOT0 脚拉高,复位后才能进入 Bootloader 程序,使用 Flash Loader Demonstrator 等工具进行串口烧写升级。这就需要在 BOOT0 这个引脚上留出按键或者是跳线脚。但是,STM32F09x 具有一项新的特性,使不必使用 BOOT 脚而进行串口升级成为可能。我们来共同探讨一下。7 r. b! v# _1 {" r/ [
问题# q3 _, m( F) a+ X, n) u5 R, g9 ]) A
某客户在其产品的设计中,使用了 STM32F091RCT6,产品在出厂后将来可能由于功能的升级需要升级代码。由于外观的需要,客户不希望留一个用于升级的按键或是跳线槽在外边。希望能够是通过接收串口命令来实现启动升级,又希望能够直接使用 System Memory 中的 Bootloader 进行代码升级。
/ _- L1 |# {) Z6 X! L
( b. n z) Y7 W: `# c. B6 w9 k# g: {调研* v" f9 v* p% m( @/ w0 j$ w* z
1.认识一下 STMF09x 和 STM32F04x 在 Boot Configuration 中的新特性
d; e8 }% v6 P打开参考手册 RM0091,翻到 Boot Configuration 那一节,我们可以看到 Table 3 中对 Boot Mode 进行了描述,如下:
8 A+ B5 K5 N. H5 ]) U/ I& J$ I; U
/ S, ]. m+ F0 Q5 h+ d
) u6 s, L# r; B0 d7 P, }5 x6 @2 V从表格中,我们先来看一下第 1 条注释:
5 W; o) L/ {' j ^' N1 [- A“Grey options are available on STM32F04x and STM32F09x devices only.”
, q% K$ L, y, ]" ], D2 w意思是说,灰色部分对 STM32F04x 和 STM32F09x 是有效的。也就是说,除了 BOOT1 是在 Option Byte 中的 nBOOT1 配置之外,STM32F04x 和 STM32F09x 甚至可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,只要将 Option Byte 中的BOOT_SEL 位配置为 0。这使得纯粹使用软件来实现进入 System Memory 中的 Bootloader 来进行串口升级成为可能。
P: N0 k. X3 B
: P+ w: T4 ^3 U( I# e2.方案设计$ i, \% r- p% |: Y5 I
既然 STM32F04x 和 STM32F09x 甚至可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,那么基本的思路就是:) w1 t& q& ?- `- S
3 C! p3 R7 p6 {) z( O% i9 m
1) 在用户代码中,设置进入串口升级的条件(比如说使用复合按键,或者接收到串口发来的一串命令,等等);一旦条件成立,就将 Option Byte 中关于 BOOT 的配置设置为:BOOT_SEL=0(BOOT0 信号由 nBOOT0 选项位定义),nBOOT1=1 & nBOOT0=0。然后进行复位,重载 Option Byte,这个时候系统就会进入 System Memory 中去运行Bootloader。. i W. }8 P/ C9 U' ~
7 F2 g4 O9 F9 t4 L# ?3 {
2) 此时,打开 Flash Loader Demonstrator,可以进行下载,在下载之前我们要设置在烧写完程序后从 Bootloader 跳转到用户代码,因为我们需要在用户代码中去将 BOOT 的配置修改为从 Main Flash memory 启动。如果不这么做,那么每次复位后都将从 System memory 启动。
8 G: q: O: k U' i9 s* \2 ^5 [" \# |: G1 r
3) 从第二步可以看到,我们还需要在用户代码的最前面加入判断,如果 BOOT 的配置不是从 Main Flash memory 启动的话,必须对 Option Byte 进行改写,将 BOOT 的配置设置为从 Main Flash memory 启动:BOOT_SEL=0(BOOT0 信号由 nBOOT0 选项位定义),nBOOT1=1 & nBOOT0=1。
" o5 T# ?$ L; ]1 R# H+ b" C _ 从这个思路出发,我们可以得到需要设计的流程图。
% V: T& U/ i9 T# ^: D; c/ k" S( K8 } 主程序的流程图如下:4 C( v! e1 C* ?
4 j+ N3 B9 a7 r+ l
+ N8 J$ J' i) K: r# V2 T! @. \7 h o6 b
( u9 e/ e, @- _. R 当进入串口升级的条件成立时,其流程如下:
$ d# t \1 V0 S4 y9 O T2 s" F
' x- Q* Q2 Q5 b, O
: d3 G8 h m/ b% p2 p
6 ^9 @) ^" o4 W) J$ e. P- e% r 实验
" `; q0 U$ T1 p6 a2 { 1.设计目标6 r# e. @' k( R4 R4 h4 Z8 i
使用 NUCLEO-F091RC 来实现此实验。空片进行第一次烧写时使用 SWD 进行烧写,之后所有的.hex 文件再次烧写都将使用Flash Loader Demonstrator 进行下载。不使用 BOOT0 脚。由于只是实验,这里用按下 USER 按键来代表进入串口烧写的条件,用户代码运行 LD2 闪烁的功能。
1 }7 y8 S$ t2 Y$ ^1 a* X o% T# G3 X
2.程序设计 P! h" |* O1 a; U5 G0 J
main()主程序如下:; B5 _& R( U# F
- int main(void)+ K& c2 g5 c' F" C
- { G& s! `: C) i* K, |& l
- /*!APB2ENR |= RCC_APB2ENR_SYSCFGEN;
s! o; c; u) z4 b9 o4 b4 K - SYSCFG->CFGR1 = (uint32_t)0x00000000;3 t# }3 b8 K/ D; S" E
- /* 此处调用子程序,用来判断 BOOT 的配置是否从 Main Flash memory 启动,如果不是,将% Y" S' K& ^! t- l3 D1 B4 @. |
- Option Bytes 中的 BOOT 配置设置为从 Main Flash memory 启动 */3 g( l6 ^0 l0 l2 O- s
- BOOTCONF_User();5 W2 Z1 p; u! B" Q+ ~6 w
- /* 配置 LED 灯 LD2 的初始化 */
4 u0 F4 B/ }5 K! x7 z - STM_NUCLEO_LD2Init();6 W9 ^' o$ b) A7 ~
- /* 配置 USER 按键的初始化,设置为中断口 */
9 i" A- p3 p" v4 z" v5 l2 g0 r - STM_EVAL_PBInit(BUTTON_USER, BUTTON_MODE_EXTI);! q9 T0 f+ E" R, X" N; ?5 h
- /* 主循环进行闪灯,随时等待按键中断以启动串口烧写 */
1 P' G, X! F: r9 a% c9 D- w; Q5 { - while (1)9 a* p: ^# N# W% w8 c
- {
( h7 ]. E& J& `0 a1 M3 Y! V% a - /* Toggle LD2 */8 S! Z5 M$ } b$ Q& t' Z* s
- STM_NUCLEO_LD2Toggle();. T. Z [" {1 q& s6 L0 Q0 E" R5 O; `
- /* Inset delay */
7 N8 t, D2 S1 D) l: W - Delay(0xAFFFF);5 s9 k J, r6 f! O
- }
8 x# W5 E [, y1 k8 f% t( w+ @ - }
复制代码
6 N2 H3 t6 u' E BOOTCONF_User()就是我们思路中的第 3)步,也是我们所关心的,我们来仔细看代码:0 R. U7 e" S/ n9 R% ]' k
- void BOOTCONF_User(void)2 Y6 T b7 }& v1 o+ ~
- {5 h+ r. |$ i) a
- FLASH_Status status = FLASH_COMPLETE;3 i) B7 C! b* ]6 j& f" N" g
- /* 定义一个 8 个半字的数组用来存放 option bytes 的数据 */
+ C8 V6 A' b2 O$ ] - uint16_t ob[8];
. U) @/ w' Q0 i- W) n3 c" E - /* 读取 Option Bytes 的数据 *// P7 g, K! r, h& U8 z
- ob[0] = *(uint16_t*)(0x1FFFF800); //RDP; ]6 {) e+ [3 u {+ X7 c+ s" q; \: N, ?
- ob[1] = *(uint16_t*)(0x1FFFF802); //USER# M( {0 {% Q4 i2 A' ?9 W7 } ~" n
- ob[2] = *(uint16_t*)(0x1FFFF804); //DATA0
5 S9 H2 F) Q3 m9 [1 J - ob[3] = *(uint16_t*)(0x1FFFF806); //DATA1' e+ [9 S0 l* Q6 e
- ob[4] = *(uint16_t*)(0x1FFFF808); //WRP0! E; n& @1 h1 J9 w$ Y3 d
- ob[5] = *(uint16_t*)(0x1FFFF80A); //WRP14 j, l6 s% h( T# F) n4 ^( B
- ob[6] = *(uint16_t*)(0x1FFFF80C); //WRP2# _( V6 p) S+ S, U" Y
- ob[7] = *(uint16_t*)(0x1FFFF80E); //WPR35 x9 C; x5 ^; g. |7 I$ _: l5 Q
- /* 判断 BOOT 配置是否正确并更新 */6 o& V) Q( q% g R7 `3 @
- /* 当从串口烧写完代码之后,从 Bootloader 跳转到用户代码,我们需要将 BOOT 配置设置回来,
, I- T* X3 X( m - 修改为从 Main Flash memory 启动;平时已经是此设置的时候,就不需要修改了 */* p7 T+ G4 ]; ]
- if((ob[1] & 0x0098) != 0x0018) //判断是否是“BOOT_SEL=0 且 nBOOT1=1 且 nBOOT0=1”, 否则更新: H9 v0 D; I$ @2 Z4 G' }* W: s6 }
- {
: V% }& @+ M; G, [ - /* 先解锁 */
$ I3 `$ o, K$ D* f6 z - FLASH_Unlock();
0 ^4 x3 a* P9 M M; u. B - FLASH_OB_Unlock();
# g, {2 h3 U$ e3 _- c5 ^5 x - /* 再擦除,擦除完成后对 option bytes 进行修改更新*/
# l8 J# o* D& F3 f/ Z% J3 |8 g - /* Wait for last operation to be completed */
1 H. v& B+ k: G1 K - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);& [* L: |! Z) t& ]$ w& L6 K
- if(status == FLASH_COMPLETE)7 F+ h2 M9 A+ \7 H/ N4 m z2 p
- {0 B& ~& X: ^8 Y% q! |# {+ Y' a7 L
- /* 擦除 */3 ` d; u! O& S2 Q1 |
- /* If the previous operation is completed, proceed to erase the option bytes */
9 W) U. x+ E: Q) `+ \& X' n - FLASH->CR |= FLASH_CR_OPTER;
% r! T5 J; v1 b; @) i! I/ p - FLASH->CR |= FLASH_CR_STRT;9 L, d' p3 w6 J
- /* Wait for last operation to be completed */
6 M. W! `3 @4 b - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);- i( F5 G" A7 W- N
- /* If the erase operation is completed, disable the OPTER Bit */2 e- _, y# w- t0 B9 v
- FLASH->CR &= ~FLASH_CR_OPTER;
9 t6 K9 r$ u, `; R. v- v1 C+ y4 q - /* 更新 option Bytes */! r4 f2 v( C9 B" |# F+ {6 S3 z
- /* ob[1]对应的是 USER,里边包含了 BOOT_SEL, nBOOT1, nBOOT0,所以我们对 ob[1]中的这三个位
0 K( l3 O& X. s- s- A2 w - 进行修改 */! Y$ ?# }! q; [. M4 V
- ob[1] = (ob[1] & 0x6767) | 0x8018;
' E) [7 Q1 c. R7 g* x' L9 [+ F - 0 J' l1 w. g) p
- /* 写回 option bytes */& z, I A8 _7 D
- /* Enable the Option Bytes Programming operation */
4 P/ M4 G6 m& K& G( G5 C6 z - FLASH->CR |= FLASH_CR_OPTPG;2 N) U' J/ L* G. H
- /* Restore the last read protection Option Byte value */
' |! l K! N: L3 V1 C3 T - OB->RDP = ob[0];' y& C# n/ k% D! C- P
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
! Q4 \5 l! f4 ?3 K% z - if(status == FLASH_TIMEOUT)% c1 s b' l0 g" N! P V
- while(1) ; //ERROR. Z( I: \, b% g5 x, M
- FLASH->CR &= ~FLASH_CR_OPTPG;4 U8 s& r) [4 V `. h
- FLASH->CR |= FLASH_CR_OPTPG;7 o- p1 q8 ]* u |2 L7 R
- OB->USER = ob[1];+ q- R: C' f& `
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
" B8 ?8 l. A" J5 K - if(status == FLASH_TIMEOUT)
$ P2 e @" I" P: Q5 E; }+ k9 h9 x" w - while(1) ; //ERROR! C T1 n$ {% N9 F
- FLASH->CR &= ~FLASH_CR_OPTPG;/ E; @3 t6 h) h% T* T1 C7 N2 i
- FLASH->CR |= FLASH_CR_OPTPG;
' I9 {' a: s% |# C - OB->DATA0 = ob[2];
) Y# N" a$ X, e - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
4 g6 G W) `* [- n# z4 B0 L - if(status == FLASH_TIMEOUT)
7 y: Q0 F* ]! B4 v8 A - while(1) ; //ERROR( a& \6 S/ G% T9 m2 }
- FLASH->CR &= ~FLASH_CR_OPTPG;, ?; }: Q% d' p$ j1 [& `! n3 v! n/ m
- FLASH->CR |= FLASH_CR_OPTPG;) i( H3 o# j# F( x
- OB->DATA1 = ob[3];7 T% B( a( n" {( ]6 N; Y
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
1 E1 }( e2 D3 Q% p% L - if(status == FLASH_TIMEOUT)' {" ]7 F Q) x2 ]& o" n
- while(1) ; //ERROR
. L5 m1 T6 s: g- @ - FLASH->CR &= ~FLASH_CR_OPTPG;0 L2 C1 }) _. @! r
- FLASH->CR |= FLASH_CR_OPTPG;# u& h- U5 o, S+ z2 ]0 b
- OB->WRP0 = ob[4];
8 |/ R# O E; f9 S7 D- m3 K! f - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);+ Y3 f7 Z+ L( T8 q* w( I7 E
- if(status == FLASH_TIMEOUT)" r2 |% f5 s5 x' F& _9 t* ^& r! t
- while(1) ; //ERROR
( X |& X# [2 |$ P, ~% W& `* O - FLASH->CR &= ~FLASH_CR_OPTPG;
/ f. y# B4 S- p7 O - FLASH->CR |= FLASH_CR_OPTPG;2 W% @ ~& Y& F u
- OB->WRP1 = ob[5];
4 @! B+ x) n: d4 i1 K4 N7 G - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
) \; R0 o7 `/ d; b* ^ - if(status == FLASH_TIMEOUT)2 E/ j9 d6 t' X1 w4 B! v- `0 R
- while(1) ; //ERROR. S# w9 N C2 N/ o4 M5 E: c
- FLASH->CR &= ~FLASH_CR_OPTPG;- ]& ~) S( b! {9 I& }5 n
- FLASH->CR |= FLASH_CR_OPTPG;, P/ f% v* S! g* Y+ S8 U
- OB->WRP2 = ob[6];
' U _& c) W4 n- ?$ o9 {& J - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);' S! V! J3 c. T4 K% G
- if(status == FLASH_TIMEOUT)
, k* f( R9 l5 j+ ~, A - while(1) ; //ERROR$ A% x7 _! q. h+ A' N* }' o3 `
- FLASH->CR &= ~FLASH_CR_OPTPG;
2 k) \7 ?% D( J: j4 R* U8 G - FLASH->CR |= FLASH_CR_OPTPG;
- a& {" X' E; K! d \! Z | - OB->WRP3 = ob[7];
6 J6 t8 B. {$ [. R# {, f - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);4 Y% {$ y6 I! p8 I1 s: W
- if(status == FLASH_TIMEOUT)0 f6 c, e8 r) a7 W" V
- while(1) ; //ERROR; A! I. t) G5 n2 i( ~9 r. a
- FLASH->CR &= ~FLASH_CR_OPTPG;6 u k9 Q8 C8 x, Y5 b! c5 e0 P! O/ K
- }1 V7 g8 T6 ~- T T+ F8 F: A
- else
3 m. @# M: |1 [ A+ [ - {
& }% t2 j8 _; ^+ M: ]7 B6 s - if (status != FLASH_TIMEOUT)$ I' u1 N$ z9 N) Z
- {7 H! O6 D2 }/ P$ U/ R( r7 D8 A/ ~
- /* Disable the OPTPG Bit */
1 C7 Y9 t1 R& m" |: e! S - FLASH->CR &= ~FLASH_CR_OPTPG;; |- k c7 g1 l8 f& n2 Y6 @' f
- }7 {' }' g- l2 X. C* f4 t1 t
- }. N2 @( H, v( ?4 a, [) J
- /* 写完,重新锁止 */& `7 }5 ? H* g8 s4 g: I' ]
- FLASH_OB_Lock();
+ u1 j2 c) T7 T! Q& C - FLASH_Lock();* \7 i* w7 I' k6 S& @2 {
- /* 验证 option bytes 数据被正确写入 */ l8 L* t% l# N- G
- if((*(uint16_t*)(0x1FFFF800)) != ob[0])1 s3 L! R: v+ J; K
- while(1) ; //ERROR
; W9 Q4 [& X- O$ I A7 Z - if((*(uint16_t*)(0x1FFFF802)) != ob[1])! t# T! b; L( O4 b' A" n
- while(1) ; //ERROR: _. I; j# ^$ q8 _ O( X$ @7 g
- if((*(uint16_t*)(0x1FFFF804)) != ob[2])" w/ V0 X5 s- s2 Y" u# C" d$ ~& o
- while(1) ; //ERROR
! O. f" l/ L1 t - if((*(uint16_t*)(0x1FFFF806)) != ob[3])+ }, G$ {' p) Q& Q
- while(1) ; //ERROR) V2 ?9 v) K4 v# u
- if((*(uint16_t*)(0x1FFFF808)) != ob[4]); r) }" w8 _6 G
- while(1) ; //ERROR/ a! s( R, F n6 }0 k% n; P
- if((*(uint16_t*)(0x1FFFF80A)) != ob[5]): ]& ?& R1 t! H' m! u9 m
- while(1) ; //ERROR7 E4 l5 C5 A Q" {3 }$ f. W0 d# }
- if((*(uint16_t*)(0x1FFFF80C)) != ob[6])/ X; ?# ^( _* e* V. c$ g) t$ t+ j2 L
- while(1) ; //ERROR: k9 F* }# N+ E6 N6 ^3 y' G0 V
- if((*(uint16_t*)(0x1FFFF80E)) != ob[7])% P6 ^! e- p# c* Q" |2 t4 }0 N
- while(1) ; //ERROR, w/ _3 d2 n3 t I; v* I& ~; w
0 n# |: z5 x& ?& [- /* 将 FLASH_CR 的 OBL_LAUNCH 位置 1,产生一个可以重新载入 option bytes 的复位
( L& }6 U, ]7 Q6 M4 r - 其目的在于避免此复位和 Power On 的复位以外的其他复位产生时不能正常工作。
0 ~2 U7 E) i% Y7 L) V! H7 z( S - 例如,使用串口烧写完代码,从 Bootloader 跳入用户代码,此时如果按下 RESET 按键
9 i7 X9 z- |6 C - 产生复位,则又重新进入 System Memory 中。所以加此复位,以避免此现象产生。 */
' t* a5 L' F* f. D; m - FLASH_OB_Launch();8 ?6 p# Z) E0 @* C0 a
- }
9 I$ p4 { I" y6 i. A |0 I - }
复制代码
1 j, W( T6 o- T1 V, Q) F我们再来看按键中断的程序:6 ^4 F) e) Q' Q2 K* Z
- void EXTI4_15_IRQHandler(void)/ R( S, P. e) K8 D, N7 @2 Y
- {7 N/ h; [( G0 B# k0 }
- if(EXTI_GetITStatus(EXTI_Line13) != RESET)
N: C- `2 e3 k4 P8 ^$ Z9 ? - { ~: X9 q, y+ o6 k# t
- EXTI_ClearITPendingBit(USER_BUTTON_EXTI_LINE); m: o2 ~, t- ?- W
- BOOTCONF_System();
! n( l" C0 [6 n' p7 s; G, _ - FLASH_OB_Launch();8 ]/ Z8 m1 P& R: q
- }0 o, M( `2 w: K/ K3 s0 K4 P
- }
复制代码 e6 V, l3 R; H" v0 X* z
这个代码很简单,就是清除一下中断标志位,然后将 Option Bytes 中的 BOOT 配置设置为从 System memory 启动,然后再调用 FLASH_OB_Launch()来产生一个可以重新载入 option bytes 的复位,这个复位将导致复位后进入的是 System memory,而不是 Main Flash memory 了。# S% G; r& z5 b- B! D
其中关键的代码是 BOOTCONF_System(),我们来看一下:
( ]7 N3 _1 k/ S& J( |! L- void BOOTCONF_System(void)2 r* ]5 a5 ^3 N( J, A' X& ^
- {
* k9 e. T9 o- I( _4 S - FLASH_Status status = FLASH_COMPLETE;
: K) i) H' ^7 ^0 c - uint16_t ob[8];7 Q, r( j! C Q7 I6 k
- /* Get opiton bytes data */0 y5 N% G! [+ `
- ob[0] = *(uint16_t*)(0x1FFFF800); //RDP
- }. ~1 h+ h2 n! G4 G - ob[1] = *(uint16_t*)(0x1FFFF802); //USER9 P) Z' {) j+ P0 [ \: |$ ?+ X
- ob[2] = *(uint16_t*)(0x1FFFF804); //DATA00 k! H3 q2 k' ^, A$ ~ x' U
- ob[3] = *(uint16_t*)(0x1FFFF806); //DATA1
8 A/ i7 \! v4 x& [! C8 W. T/ O - ob[4] = *(uint16_t*)(0x1FFFF808); //WRP0. D$ D# z: [: F* _! p" F
- ob[5] = *(uint16_t*)(0x1FFFF80A); //WRP1
! |: m& ~ h* Z - ob[6] = *(uint16_t*)(0x1FFFF80C); //WRP2
+ F4 y3 T7 H* L- B6 H - ob[7] = *(uint16_t*)(0x1FFFF80E); //WPR3. A- F* q8 t) C9 X/ A# z" L
- /* Unlock */5 r, |! \7 r+ h5 g$ R
- FLASH_Unlock();
5 |0 B) |1 c; }8 J2 m5 y - FLASH_OB_Unlock();
& M2 P* G. H6 K3 k" \ - /* Erase and update Option bytes*/
* ~) w8 P. a' U% z - /* Wait for last operation to be completed */
! M: [( W7 Q% o( }4 G - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
: u/ \4 l, E0 a; C+ a - if(status == FLASH_COMPLETE)
# c1 p6 q, P! G: N, H; R) S* g - {2 j% Z( n5 s) w" l/ N
- /* If the previous operation is completed, proceed to erase the option bytes */
' f6 }3 l3 i! n* F7 [ - FLASH->CR |= FLASH_CR_OPTER;8 T: u' `; b. }- J
- FLASH->CR |= FLASH_CR_STRT;
4 s# U5 _4 }8 |- a* w - /* Wait for last operation to be completed */
, C% K, Y+ d# U0 @' K - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
% I6 h4 W Y7 e$ t) C, _2 r6 P4 s' B/ \3 g - /* If the erase operation is completed, disable the OPTER Bit */
, t9 W6 n) d0 @( H" r - FLASH->CR &= ~FLASH_CR_OPTER;. v( ]1 L! _' S) E
- /* Update option bytes */
, A0 U# v2 z( Y: W1 \ - ob[1] = (ob[1] & 0xFFF7) | 0x0800;7 ~- _6 J/ x$ H U
- /* Enable the Option Bytes Programming operation */
" e0 R9 \1 V% C4 ]. a1 F1 ~4 [ - FLASH->CR |= FLASH_CR_OPTPG;
9 j1 c2 e& I/ ~( ~- Y; w) Z - /* Restore the last read protection Option Byte value */3 [3 O/ d, Y) e
- OB->RDP = ob[0];
+ {% l$ Y' U* ]* p" P v9 K, ^ - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
, O. w/ d6 D: _9 Y9 U5 f, L - if(status == FLASH_TIMEOUT)
3 i% P# [) r4 C: G5 Z4 s0 @3 S - while(1) ; //ERROR/ l- B6 h6 D) e& f Z: O; n; C& x
- FLASH->CR &= ~FLASH_CR_OPTPG;& y9 y: x3 Q0 |# v* Q5 N
- FLASH->CR |= FLASH_CR_OPTPG;! @. R/ f' d0 j4 i: n5 I
- OB->USER = ob[1];
: i( x6 t! l0 v) N0 Z - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);' K8 j* e: i% ^5 f6 S- x( Q% x3 r
- if(status == FLASH_TIMEOUT)
" m4 R# m" }0 Q/ J - while(1) ; //ERROR
$ W' }8 u( F% K% i" k ` - FLASH->CR &= ~FLASH_CR_OPTPG;2 V5 K2 X Z2 X- v# C7 H
- FLASH->CR |= FLASH_CR_OPTPG;
' Y; @$ y, K5 A' d - OB->DATA0 = ob[2];0 g0 l# O! C5 `5 `
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);
- B& o- b1 K# {, e/ q - if(status == FLASH_TIMEOUT)
: }3 m5 y: @/ B( V0 d - while(1) ; //ERROR
# R9 @/ ^9 b" O) G+ W( U7 T - FLASH->CR &= ~FLASH_CR_OPTPG;
5 N" p% C1 W& Q: E( J6 E4 R8 y! a - FLASH->CR |= FLASH_CR_OPTPG;
3 o! j" w) |# V$ k. q$ Y: V - OB->DATA1 = ob[3];
7 `- y+ Y; m* u; h3 a6 r6 o' ? - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);+ {% {5 L& Q; ?; z) B- Y. c
- if(status == FLASH_TIMEOUT)! v0 o' _' f: i8 Z: c2 P- v2 [" v
- while(1) ; //ERROR
1 m3 C( \, U4 J4 I% E" n: w - FLASH->CR &= ~FLASH_CR_OPTPG;# U3 R& C6 a) e- n: n
- FLASH->CR |= FLASH_CR_OPTPG;
+ z( o: o% E3 U$ W4 x - OB->WRP0 = ob[4];) C+ K f5 Q; l& Y$ B
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);9 e c+ j4 \ Q' D; V) ]
- if(status == FLASH_TIMEOUT)
( q2 S# m) x8 Y# j - while(1) ; //ERROR
0 k+ o; z" B. }7 c$ E - FLASH->CR &= ~FLASH_CR_OPTPG;1 e3 v3 W- s1 v8 e9 \0 K9 q
- FLASH->CR |= FLASH_CR_OPTPG;/ h: t6 e0 S9 s( h4 c7 m% x+ _
- OB->WRP1 = ob[5];- W" R* M% p( M* R/ x/ |9 d7 B5 m
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);+ O( ~- b5 i. c5 G
- if(status == FLASH_TIMEOUT) S' |. w& C* z
- while(1) ; //ERROR
Y8 n( ~1 U1 P y/ w! R: c: | - FLASH->CR &= ~FLASH_CR_OPTPG;0 U3 z; _+ W1 G
- FLASH->CR |= FLASH_CR_OPTPG;/ {+ _+ k4 G% W- |2 h9 G. N' \
- OB->WRP2 = ob[6];
# a" ?2 x# y$ o6 v1 X. o - status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);( M$ ?6 q* v& E5 x( u& z
- if(status == FLASH_TIMEOUT)8 n; D9 M S# z7 k5 t4 l
- while(1) ; //ERROR' N+ W2 \$ b$ ?, `& N: Q! y7 T
- FLASH->CR &= ~FLASH_CR_OPTPG;
( Q7 X4 _) `. v" o# C# Z - FLASH->CR |= FLASH_CR_OPTPG;
8 c7 Y( O6 U5 P: U% {% f2 T; _ - OB->WRP3 = ob[7];% e3 Y% R: L ?
- status = FLASH_WaitForLastOperation(FLASH_ER_PRG_TIMEOUT);- J2 x6 o4 v# C/ l4 z
- if(status == FLASH_TIMEOUT)
, }! x, U L! i7 s - while(1) ; //ERROR
) l# O/ r/ O t3 @! O - FLASH->CR &= ~FLASH_CR_OPTPG;! j3 E9 @4 E, x
- }
9 ~4 Y! l$ L: M: m - else! v; G" e$ j! \; H! C8 M+ h
- {
' k" ]8 A2 v# e& b& b - if (status != FLASH_TIMEOUT)
! {. d- `/ D& J$ f/ f - {* {" O8 v% _6 [ q8 j2 c
- /* Disable the OPTPG Bit */; K$ c! c5 c4 W; T5 D
- FLASH->CR &= ~FLASH_CR_OPTPG;$ X' c, u8 K: p0 V; A& L4 h, f) S
- }
. u+ f9 u v. X! R8 O# y - }
) ~! m0 C: ?6 d1 {/ {/ x - /* Lock */
7 [% A' b% s. ~1 Z* v - FLASH_OB_Lock();
* N( h8 }2 g; K! \ - FLASH_Lock();
. ^( d, Y+ Z& b4 z+ V) ?: V- w' S - /* Verify */" Y) ], M) H$ Z1 D: L: M; E
- if((*(uint16_t*)(0x1FFFF800)) != ob[0])
; k1 w+ i' G' i+ F) }4 _# Y4 A" T - while(1) ; //ERROR2 m8 k6 R3 @' g1 {" U
- if((*(uint16_t*)(0x1FFFF802)) != ob[1])2 {2 i: v! h8 v2 C7 c9 f
- while(1) ; //ERROR
# @2 `$ [4 j* h- w - if((*(uint16_t*)(0x1FFFF804)) != ob[2])
' O0 V% q2 q, x2 T* O - while(1) ; //ERROR& s2 @* j% n' ?) x
- if((*(uint16_t*)(0x1FFFF806)) != ob[3]) W9 @& |/ @& m$ Y3 X0 r9 M
- while(1) ; //ERROR
" J- [0 P' L% x9 ~! d/ r2 ^/ g - if((*(uint16_t*)(0x1FFFF808)) != ob[4])5 S4 j+ l( g0 l
- while(1) ; //ERROR" K7 P. Q2 ~3 a& j$ X' t6 c
- if((*(uint16_t*)(0x1FFFF80A)) != ob[5])
2 b- d) v _! J; B6 ~( y5 u - while(1) ; //ERROR
& Z# d+ s! g; o' [+ T! J - if((*(uint16_t*)(0x1FFFF80C)) != ob[6]). ]6 U; q% ?. P4 G$ F1 m6 y
- while(1) ; //ERROR6 s- j: f! c7 l( K' c2 k" P7 m- U( V
- if((*(uint16_t*)(0x1FFFF80E)) != ob[7])7 A4 { N$ u- d. k7 X% B f
- while(1) ; //ERROR& y& P2 G& E+ m3 n7 o8 o' B0 T
- }
复制代码 ( B5 L' k- ?5 x( s5 ~1 h/ ]9 W
我们可以看到,其实其整个过程与 BOOTCONF_User()非常地像,唯独有两个区别:6 {8 T, r( u! U7 B( G8 g# T
1 C& R- ]' l) n) \
1) 没有判断当前的 BOOT 配置,并且在更新 USER 时只是将 nBOOT0 清零而已。因为在用户代码的最开始,我们已经保证了,BOOT 的配置一定是从 Main Flash memory 启动,所以要切换到从 System memory 启动,仅需要将nBOOT0 清零。
( w2 ?- U% }# ~1 q' t* }- V/ R0 I
2) Option Bytes 更新并验证后,并没有调用 FLASH_OB_Launch()。因为我们把它放在中断程序里头了。0 I& T; B) g7 T! E: P! H0 U5 F
" f# o+ F6 E7 x/ I j
程序要点到这里就已经很清楚了。此程序仅为实验用,所以对于出错处理和代码优化,可根据实际应用自行处理。6 h/ a5 C6 ]) m8 x
2 {9 I9 M' ~5 s; w M5 d" a
3.实验步骤
5 J, [& M# l/ w3 l: ^4 R 下面我们来进行实验:
7 J7 _. Q) R; v. o% K3 @) x1) 我们将程序编译生成的的.hex(我们将其命名为 1.hex)通过 SWD 接口,使用 STM32 ST-LINK Utility 烧写到
0 P6 P! Z- b N0 B# c NUCLEO_F091RC 后,可以看到 STM32F091RC 正在欢快地运行 1.hex,闪烁着 LD2。关闭 STM32 ST-LINK
, p4 c J7 h0 Y) [# m4 R Utility。, s9 \ Z l4 Z5 V- V5 @& M* K
注:1.hex 是慢闪烁现象
/ c. z# ~0 T8 l0 L: B2 I0 C# X# I# g1 P/ ~$ Z" ~% {" b; P
( `8 u9 c0 z$ l/ U
. `: h* H. R: B& F2 U9 o2 d6 c2) 回到程序中,将 main()函数中主循环的“Delay(0xAFFFF);”修改为“Delay(0xFFFF);”,重新编译生成.hex 文件,我们将其命名为 2.hex。
; m* ~, _9 X6 ~# v3 {% V- } 注:2.hex 是快闪烁现象
# D1 u1 t6 ]$ ~6 c. c0 _" S5 _0 Y# P3) 现在我们要开始进行串口升级了。按下 USER 按键,发现 LD2 不再闪烁了,程序已经进入 System memory 运行Bootloader 了。
+ \8 a+ ?- ]2 ]: c6 Z- C4) 打开 Flash Loader Demonstrator,做以下的配置:+ X& y2 K. n0 V; E/ w( f/ ~
4 @' a+ N: A! W( w9 V- A
# x( S% R+ ?' g- \
6 ^ h! J6 g. }; z5) 点“Next”进入下一个界面,再点“Next ”再进入下一个界面,再点“Next ”进入下一个界面,我们在这个页面中选择下载代码“Download to device”,导入刚才编译生成的 2.hex,选中“Jump to the user program ”,意思就是通过串口烧写完毕后,直接跳到用户代码去运行。如图:
* A; R% i; r+ ], c! A- S) i$ Z0 n9 [5 B& y R- x
2 Q/ B" A* T" ? ]1 M. e4 ]# I
( n% o5 L) P2 X# O3 _
6) 点击“Next”进行代码烧写。烧写成功后,我们就可以看到 LD2 更加欢快地闪烁了,证明我们的烧写是成功的。按下 RESET 按键,也能正常运行代码;断电后再上电,也正常运行。
, Y7 K+ x6 J# M7 q `' \( O; C% u0 M* W3 B3 l
. j _) ]4 A, x& _# g1 R8 M
5 g, t2 |" f1 \& U0 v7 @9 w 到这里,我们的实验成功了,我们可以重复 3)~6)步骤来反复烧写 1.hex 和 2.hex,体验这个串口升级的方便性,再也不用去拔跳线插跳线了。
- O. ^% U: X/ o, L, A! `
" g: O% i6 ?9 @, i1 |, f7 T 结论
* a# V' V3 G2 o6 Z; D 由于 STM32F04x 和 STM32F09x 可以将 BOOT0 通过 Option Byte 中的 nBOOT0 来配置,这个新特性决定了我们可以很方便地实现不使用 BOOT 脚就直接从 System memory 中的 Bootloader 进行代码升级。非常地方便。
/ |* p. u, B2 {9 M8 c$ r" t5 W5 l! r- c9 y- j
处理 ]: Y8 m1 M2 c2 S
此实验证明了“STM32F04x 和 STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码”的可行性。但只是实验,客户可自行处理错误情况和代码优化。此外,此实验用按下 USER 按键来激活串口烧写,客户可自行根据应用来修改激活条件,比如从串口接到到一串固定的数据作为激活条件。
. g4 P7 W1 @7 n/ @ z5 D, y( M7 W5 O8 D1 p! m% X' o
后续+ }- N" d: t) b
其实我们在看参考手册时,还看到这么一句话:6 k2 {% Y; h% ~' R- K D7 b
“For STM32F04x and STM32F09x devices, see also Empty check description.”
3 y% F/ Q$ H$ k 也就是说,空片也是可以通过串口进行烧写的,我们后续继续讨论。& ~ p. T6 j, v) H$ L p% W
$ r4 @( ^# k7 z% \' B. I8 j. i
|