本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。
_; F" a- Z8 W/ \7 e2 J# u `- G
1.在线升级知识
0 X# d, ~4 y) |( }3 p2 E什么是BootLoader?$ n4 c6 E3 P2 Q
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.. Z+ u$ V( W7 e/ T# K) G6 d
1 ]8 b& X* S9 `% s2 ?& U
STM32中的程序在哪儿?
8 p6 G2 i$ \: `" M, V/ ]正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码& x; g# {2 S( w0 h, Q
: f5 H1 g. S; U; m( P; N- u
! c R; @; ~/ C4 @9 Y- F
% S* }* b# f- |$ `接下来就可以进入正题了.! h9 w1 y! r+ }/ u( Y
5 W" H3 I" Z* C0 M& j- G0 \1 p
进行分区( h2 z4 m* Y# n: B; u- G% A* c, t
既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:$ q7 ~$ H: r4 ^* R, t7 U- ^
1 A I3 u4 S9 J$ A+ J4 ~
( [$ W1 V7 R2 _# v/ p' l
7 U) m# F4 r i4 q0 s0 o9 Q以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:6 c. C) _& K2 C& @7 v5 F' J; x6 v) I
BootLoader区存放启动代码 P& L8 d8 i& x' k* Q' t; P
App1区存放应用代码8 ]. k3 I: a+ Y3 T
App2区存放暂存的升级代码
7 Z8 V; k, x$ {$ A. M" Q6 z$ p) k7 O8 c. G" x9 |/ L! [6 I" `. q
% R1 |; s3 [7 e' s4 {7 R
6 G( P/ M1 N& h, v# A! w总体流程图
! x9 M# S3 { {- b8 m( o先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.8 o3 I. ]! n+ \1 }
然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
5 u% T2 T3 o5 ^7 L/ ]2 k9 Q在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:" i, P, |8 Y* M% p; R
q2 b9 B* s) b3 M5 t% S |: J
4 l) j7 `) S5 a: I* {
! n' Z/ T- G( p1 n0 b5 @! j
x' i1 V# P; x: x: V2. BootLoader的编写, A! Q3 F- R3 z% Z. @) E/ w3 t4 j3 l
本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。% a) r/ l$ Y2 K& e1 _8 O& @$ f) X
2 b! A3 _8 z/ w7 X- w
流程图分析+ F9 w9 _& K. G* T
以我例程的BootLoader为例:
- k t8 H: q& I5 s! u3 a) V0 P# P. S k- U
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示, R7 G. f* }- O# R7 `- c, c
/ M6 Y: i( J9 ?8 X
9 M: j! x3 U* n: `. k4 F a0 o {) c- m
+ n; u, D4 O; d' Z程序编写和分析
5 W* s, C3 h8 O9 Z% |所需STM32的资源有:
4 `( |5 r/ @% w4 s& b+ z发送USART数据和printf重定向
X, U' U& z5 V2 S/ I, hFlash的读写
4 o2 I/ R/ Q" _6 d6 J. r* H" o, b程序跳转指令,可以参考如下代码:- _# p7 a6 i% W) I( g- c9 w
$ [2 U1 [$ n/ [4 b' W0 j5 r5 ~" Q* @
- /* 采用汇编设置栈的值 */4 X# Z( g/ l/ t" L! |/ J5 n
- __asm void MSR_MSP (uint32_t ulAddr)
, C" ? B5 ^- M1 Y7 x1 B. H# d1 m - {
2 p9 u5 F% N. a! p* C - MSR MSP, r0 //设置Main Stack的值: J" ?/ B2 |$ R5 m5 l
- BX r140 ?+ P( ?9 P% l! l: n
- }
- y2 O" d; Y! S3 @) r3 q( S3 \
/ b ]; Q5 ?" P W/ h: x' F1 T- /* 程序跳转函数 */6 B& s+ h3 ~5 W0 w( Y: _
- typedef void (*Jump_Fun)(void);0 x. N* g+ H- N( l& r
- void IAP_ExecuteApp (uint32_t App_Addr)
# H% L F) J4 O! Y* U4 w - {
, y* Z9 D1 `, I0 f, l- |! M8 p - Jump_Fun JumpToApp;- J1 R _) h6 ^: Y2 I+ }7 q
- / \; q6 h% ]. Z5 v
- if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
; J; g1 k( j* O# Q7 x* j - {. b# g, ]1 o0 G6 j6 E! k& T
- JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址); q% s; c V2 j2 E& e; H" @8 Z
- MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
$ a2 `( t: {) T" S - JumpToApp(); //跳转到APP.& @$ `3 ]4 n! n% k) c
- }
6 Q9 @* c3 Y l) M9 G' \% H - }
复制代码
% d2 P- A" t4 p在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr); a" C9 e+ P+ n$ W) T- z
其他的代码请参考BootLoader源代码
b+ r8 ]1 [6 R3. APP的编写
c! U- f( V: g本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
3 p/ y8 | _& K8 d! `0 U, F! _5 G. s" Q% C" [- e. W$ t
流程图分析+ i v1 @6 P3 {/ f+ o+ }
以我例程的App1为例:
, k; X5 g+ b* z5 F& M4 {% }# v* n2 H) e! L/ ^+ H/ A
5 ~7 s2 Q. [8 O, H
9 w+ E2 `3 t" s
先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;" L6 @' n( V0 P+ b
打印版本信息, 方便查看不同的App版本;8 s. g7 {3 ] E7 i
本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:0 d. m }% j1 T4 E
9 l8 [3 s' A8 r) \' [
程序编写和分析+ k2 V y4 J1 a- Q3 n" X; G4 P
所需STM32的资源有:2 a8 `) z6 d! J) C9 ~- t
发送USART数据和printf重定向
" ^; q3 G% _% g7 |0 v2 i4 b4 z& HFlash的读写0 M/ b1 ^' G2 f: }. ]5 p O
串口的DMA收发
3 L1 b( ~% m! f& uYModem协议相关* h, {- ~+ n0 g" B* L4 R" I
Ymodem协议& C8 u) ^' c# L( y9 ~* S& o
百度百科[Ymodem协议]
# y, B) G2 n T具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
+ j. r9 z; Z6 r, g7 i9 cYmodem协议相关介绍可参考我的这篇教程 YModem介绍1 R3 L3 J8 w6 z6 C2 H3 y) K
3 h Z% d+ D, S代码分析
; M( t2 k, l; `8 Z/ b. d6 N代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
& Y" s* @6 Q: _$ L; V' T- V2 `2 o# ]
' ~: r. x( W- B' ^4 ?5 J后面放了我的源代码, 详情请参考我的源代码.( s& t" p" Z {
; Q/ j) P4 n! I+ `. A8 N% i
主函数添加修改向量表的指令
0 | U6 Q' E; q1 d
, N8 s. @" P) S+ u8 b
* Q# k7 O9 Q$ P7 B" g7 }' H
! ]8 C# v) Z( W+ B
# _6 _9 h4 @) u3 O2 d7 u4 r& w0 f打印版本信息以及跳转指令' V+ Z/ t! W/ @; Y+ Y% w% \. l- J
0 N/ y- T9 z" ^; e9 p; `
' ?/ d1 `- k. ^
5 I9 P. P8 @6 G; Z! pYModem相关的文件接收部分2 Z# ~# W7 t: H: }- A, B5 o; ~/ b( d
- /**
" H/ X: W9 R( w, \$ G$ |2 { - * @bieaf YModem升级
7 ]/ B9 p1 ?% F2 x* {! b - *2 g9 f+ t1 s9 O
- * @param none( Q3 o7 Q; e1 d' Y: n8 l
- * @return none; v& Y, J' w; _( Z+ t
- */( e( r9 e* h6 S/ D5 y, H% c9 D
- void ymodem_fun(void)
0 G& P v ]2 ^1 V# ? y - {0 t7 R7 I" K& r7 D/ n3 r
- int i;" L5 T s$ E) V. O; W! W
- if(Get_state()==TO_START)
3 y A- E' g/ D5 |( M - {1 x# h( J# Q; |4 r9 j0 Z
- send_command(CCC);
# O5 u, h! H% H( r, V5 d6 @ - HAL_Delay(1000);
, E7 V) o7 b5 ?" p* ]+ p - }
/ U5 u9 f3 j5 `$ C+ p - if(Rx_Flag) // Receive flag
0 j# V! Y4 ~0 `9 a% x& A - {* t* A d. Q5 d0 F$ U8 j7 Y2 V+ r7 W
- Rx_Flag=0; // clean flag! G: |& F; \% d* y
- 4 {9 x9 N6 ~ F+ o$ h- \& x2 S! Z
- /* 拷贝 */ ~, T2 L- ^( ~+ l; W
- temp_len = Rx_Len;
1 G% {2 a" b9 z. C - for(i = 0; i < temp_len; i++)
( S1 A) \/ n3 ~/ t2 C' u1 L" U - {
! R4 ^+ D% V; `2 s! \ - temp_buf[i] = Rx_Buf[i];0 t( n/ E. V8 }6 I! D
- }
! o7 m1 M2 {: }4 V, s2 R; I) @ - # k$ `4 W' e5 X/ z+ M% q
- switch(temp_buf[0])
' i- |. s6 v% ]) i - {7 I* M8 f& R. V9 r5 f4 u( J6 j
- case SOH:///<数据包开始6 X: H. d0 \5 g8 A" g
- {
2 {7 ]" t1 r8 i# z% R) c! s - static unsigned char data_state = 0;
( R' t+ b2 v9 G3 B. G - static unsigned int app2_size = 0;
3 K3 i: f3 ?, i, {" h& f; L# B - if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
$ N( S1 ~! O J' n - {& G) y& w2 v- D& s
- if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始) F0 g J6 a K! ?& z
- {
7 X2 V0 X C: j4 N# C1 _ - printf("> Receive start...\r\n");
r5 y& O# Q6 K4 } d4 r4 T - * I" b. d+ s0 v g0 f
- Set_state(TO_RECEIVE_DATA);9 y ]0 f! c4 X& r) x
- data_state = 0x01;$ }4 W6 v2 D7 V
- send_command(ACK);! Y ]: _5 g: `& B. T$ E2 v/ X
- send_command(CCC);, h( w' v; w* a0 ?7 @4 M3 v
( o, Y0 D3 Q+ x9 P8 y5 X- /* 擦除App2 */; K1 _; T4 U0 ^0 S5 ^0 v. s
- Erase_page(Application_2_Addr, 40);# b0 j3 \( n$ z% m z, Z& l
- }
2 p# G9 R: ]) n( `+ E k. J - else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
: {1 @2 x! A* t, X - {7 h) h5 m' m2 y" h9 x% U Y
- printf("> Receive end...\r\n");
9 H) e2 ?: C& T3 @ - 2 z1 Z5 G) G; _: K
- Set_Update_Down();; B; T5 \ L- A( ?8 Y, o5 f' F
- Set_state(TO_START);
0 T# W$ N$ e8 l1 C0 U( t& W( ~ - send_command(ACK);/ _' C3 _$ w" z, p; b2 I. U$ H/ z
- HAL_NVIC_SystemReset();
# ]/ U% l, N- C7 Z - }) T* R. ^: E5 Y4 M- s! r& H
- else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
! g7 C, f* i2 ^& o! j - {
9 G: C0 q6 M8 N4 V" I# H. c5 L - printf("> Receive data bag:%d byte\r\n",data_state * 128);) V, d% Z' U$ h/ o: U; a
" @) E5 A# b' @" s) x/ r- /* 烧录程序 */1 Q% _# o/ s- O' t0 W) L2 d: S
- WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
- I# A+ @/ h" D- `- a - data_state++;
: i! d4 u, U9 w2 U q$ `# L
) q. ?/ Q7 k7 h& `- send_command(ACK); _% B" H# [) Q' b) Z3 h3 H) B* T
- }
0 X& } u4 ~* ?) E3 u8 @+ b - }
7 t7 H' q% ?* O - else# \3 X+ P" C' S* {) e6 y5 E
- {
+ X& E$ O; G5 x; ` - printf("> Notpass crc\r\n");
" l3 l' ?. w' I# a2 V& Z: p - }
1 l+ E% _' d% h2 w* } - 8 A+ o3 V6 r' |1 W" v) d; r
- }break;
% z2 [: B4 ?% P! T* t - case EOT://数据包开始
5 p' M1 l& {2 h4 h; B2 Q - {/ F0 l2 ^4 b6 f+ W+ C6 J4 `3 O
- if(Get_state()==TO_RECEIVE_DATA)0 V r3 u! U* w9 ^& W" x) Y
- {& U3 d1 K1 c/ ~/ w! N; d1 Y
- printf("> Receive EOT1...\r\n");
( _% B1 a8 g5 m; a - 9 G2 O( }, R' m9 s
- Set_state(TO_RECEIVE_EOT2);
: D0 a* v; s" T5 h/ E2 a9 v - send_command(NACK);
+ Z+ C7 `+ J0 H/ r! N - }
/ A/ u) N4 Z+ Y M/ H( Q - else if(Get_state()==TO_RECEIVE_EOT2)1 f& u. _; t! p" |1 N z
- {9 S, F& ^. H; T: q* m, f9 s- j
- printf("> Receive EOT2...\r\n");
( ]" w O5 [! O3 Q! U- ~9 t9 O/ R" D
/ [9 ]. j( C* m! u: e/ s- Set_state(TO_RECEIVE_END);
/ Y: V& A9 s- a% F - send_command(ACK);$ [' W0 ~# ~% K) B
- send_command(CCC);
( I( G9 j' S; g' \/ z$ M# [ - }5 u. g. E, i8 H! r6 j$ |
- else5 C+ ~' q7 i- |0 B0 z/ m
- {
* o/ ?% Z* \1 M% T% A/ c2 X" } i) H$ O - printf("> Receive EOT, But error...\r\n");
% E4 y1 M& U4 H: C - }
# [/ h5 a' W3 C, q& z+ o7 Q, y - }break;
7 t8 A6 h0 W h! Y, \: q3 e/ w - }. ~- y8 b! S7 j4 I
- }2 j4 Y! j7 L& b4 q
- }
复制代码
1 K! {! T1 ^" ?" ?8 M其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.
* `& U4 q2 R1 i( p- p! B A# _" k1 b/ h7 a" k( M; K9 E& @
4. 整体测试, j v$ p+ }5 U( v# L+ A Y' R. t. ?* X, P
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
: B" U q* u0 ?- [" I3 p: |, q8 w5 [/ N" N; w, R% B) h9 _. q
代码的下载8 d2 ^% `+ u* C. F( D1 L% N5 ]8 c
由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。! {. Q, w% f* B, T6 b
/ n; k, _* t! P+ F
: H" N( {7 D# G7 r) [3 E9 O; a5 \3 f1 u& O! e2 _/ W' ~
BootLoader的下载& j2 b& i% `$ [6 \ Q7 e
BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置1 f3 Y8 d( M0 X9 r; h* A
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
* E% M3 P# z# n3 R: ~1 w: m; J2 h, O# @5 A7 w" P) V% m
# q! k N$ q. q* V
% @3 @' d2 Q# r: T5 `) C烧录代码
) F1 `" ]; j5 `/ W运行, 通过串口1打印输出, 会看到以下打印消息. B- S$ Y# R# s2 ~" b9 p0 A4 `
说明BootLoader已经成功运行
* x+ Q V/ j5 I4 _) w- L( Y3 b4 d. o0 x9 l/ f( v
- t" a* D9 V* I' v4 s
2 |2 s0 S7 B& f* e7 ~
App1的下载# p5 ^" i$ ]' z
App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
8 X& C* v1 t: E7 T+ H同时也要修改擦除方式为Erase Sectors, 见下图
) |2 U6 U1 C2 Y( t& J7 Y! e f% \# o. _7 t6 j |9 `) G
w& |" s8 l+ m/ f
! h% e6 k6 o0 L/ h$ r: ?1 [
* B7 m, V u6 G6 F
, G. c" w! a% s# }4 U烧录代码: Q8 f% G& Q. X, Y4 `
运行, 通过串口1打印输出, 会看到以下打印消息2 g6 @, b& D( @1 o) K. D2 o
说明BootLoader已经成功跳转到版本号为0.0.1的App1
' H* G' A: V5 Y0 b, d$ ^8 s) ]$ @9 f4 Z! `# f
1 X0 o; U& u' Q# U( w& j
. N- Q b, E* b$ y0 [: y生成App2的.bin文件5 p- ~# t B2 v T
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
1 Z5 B1 l3 I5 o: ^& J, S1 V/ l0 k
$ F" h+ S9 r2 h, M) G生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件' X" ~: t9 R( t$ X) j6 y1 v
! n! v6 C8 Q ^4 i u% N
' \3 C; l6 ]9 f$ [
+ C! [( g" D8 l1 V/ r% g# F) k
使用Xshell进行文件传输
5 G' A: N& e+ r) V. N W打开Xshell6 s% [' o1 v( _8 h" O
代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的( U- i. ], Z4 _7 [
所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息; j2 I0 n( t9 n! l! u7 ?. L
) {, Q; j8 l, g4 O
# c- f1 |! J7 b6 |( g6 S
: V8 Y4 S! ?' _你会看到App的版本成功升级到0.0.2了.( L3 D" a" Y( h% k" V$ K
如果你到了这一步.9 P+ z2 K/ G7 U" k6 x8 J7 i, c
那么恭喜你! 你已经能够使用在线升级了!) J$ y' d7 t5 i- y( T
6 i; a/ D% l3 O1 Z" y2 |# |( c5. 总结9 `2 g/ e; C: S
通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。' x7 |0 m9 N( @+ u; U! N+ U
2 g$ N$ d X, n+ ~
: h) Z0 `9 _- v: n+ Q( l7 \1 j D6 p
转载自:混说Linux5 _! k2 f" ^2 J$ ?4 Y/ R( B4 L
% k* T1 ?4 d' d
7 _& W' y# C! ^8 u" u/ H |