本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。
5 p% R$ e0 s1 o) u+ T! Y1 a3 D1 y# F% Q6 _5 h( O
1.在线升级知识
' ]# E) t- B9 F0 r什么是BootLoader?' F9 e$ C# f) _7 G3 s& D( m
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.) S! o6 {7 E! O6 v8 ?& @2 V. m
& d. [9 a9 Q# S2 K
STM32中的程序在哪儿?
9 W8 P$ W9 s* \$ R2 ~9 I正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码) t' p5 N+ I# h5 h: c( r. p
8 k T t0 N% H, B+ m
7 r0 m1 S! Z( E' d5 S0 Y& y* w; k9 I' y# L2 D
接下来就可以进入正题了.
! x A! L5 W f. e( c, t
! J, J2 f9 {, c: d7 q5 @进行分区
) H& ] s* C. ^* c7 m: Z9 J既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
7 R$ ]& ]! f" E8 P% e q7 a
$ l) }$ u% C) g: L
# z# `( t8 z* s4 O7 J) U
' t. s' `2 U) x; b0 x+ |以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:
$ l; z! i1 |0 c6 ?2 s: FBootLoader区存放启动代码
& Q R3 O2 f$ z+ Z0 AApp1区存放应用代码* H) x: Y4 j1 P+ G1 D/ p5 T
App2区存放暂存的升级代码
: @( P/ u5 z# J& e
9 b+ N/ b* R' a% h+ _! _
. @' ]( f, Z4 w* F1 \
6 r( g: z2 r& o' c% B总体流程图- w) Q# d* x7 W' @2 e- L
先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.3 |9 P* v2 W$ [; v5 V7 q: I) A# `
然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.: z, o- }) L1 Z6 P0 Z* y
在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:8 g& b k' G& Q" U. ]; g
) X S! S( R+ a6 ^% [* T
+ o, Q% L7 ~% B" @2 t' K$ y
0 j5 I1 E+ d8 f6 M8 N4 e, m, D v& X1 @
2. BootLoader的编写" x% K3 f' r) B$ f: f' ]
本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。# U- j; {1 s+ ]$ f' B; a
J6 I0 {. V3 |! j E7 T流程图分析+ ~. g" F4 W/ B8 V1 T1 d0 n
以我例程的BootLoader为例:
0 w- Z0 s8 l: `' F6 {
; y0 i9 Q" G, e) w& @我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示
) [. H `9 l0 B7 ~ u
# B( n8 n: @. T( \
/ C$ t/ ^% b& O' _0 j
" e" \7 ~& o3 [3 b3 R6 _- w. M( K! n) @7 ]+ o4 B6 Q
程序编写和分析
5 [$ v% E8 _& ?- }. |4 `. C所需STM32的资源有:: f$ D% L2 @2 E; T/ ~
发送USART数据和printf重定向
0 e1 `. `* m% i8 K& n/ |5 M: R9 `3 r7 U* dFlash的读写
0 r1 ~1 n0 l% N2 ]8 o程序跳转指令,可以参考如下代码:
& h% i$ x, L& u. D& \+ V9 O" `5 h, ~
- /* 采用汇编设置栈的值 */" j) ?7 |5 t, |+ J/ \9 n2 @1 ^
- __asm void MSR_MSP (uint32_t ulAddr)7 D1 d+ k) |+ H- d* U. J4 g! n
- {+ B* N4 n, X* x" s) D" R- D6 o8 L
- MSR MSP, r0 //设置Main Stack的值1 d# I2 p- X- I7 z" f5 v
- BX r14
; K8 h9 b8 [" }0 v2 s4 s8 M X - }
6 i. ^/ Y/ T- A9 U# j - 9 A; a$ ]+ m0 \" B# w
- /* 程序跳转函数 */
* F1 h. F5 U; L8 ~) d( p, _2 ~' K - typedef void (*Jump_Fun)(void);9 _# o( g E6 a4 C
- void IAP_ExecuteApp (uint32_t App_Addr)
. k8 o$ _# V. s& R. _. Z - {' S! W) Q; r1 F- u! X
- Jump_Fun JumpToApp;# i6 U3 l) R9 w0 j
- ; Z" | K% Z+ { \0 V
- if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.8 v5 D& r' S1 Z" a# B) Y
- {
3 O& z, P% v: }% G0 Y6 M9 H K - JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)* N. }( g1 ?$ v" N
- MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)/ d2 |( f# U5 e4 U4 q
- JumpToApp(); //跳转到APP.
6 y+ `+ E, o9 F# ^ H N' u - }
9 o6 f/ H! l" n0 D0 |; _ - }
复制代码
9 `& W I) L* q9 f+ A3 z在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);- a: n' ^( {2 [3 G) Y7 C
其他的代码请参考BootLoader源代码
. G ~- A" i) U% R2 g$ n3. APP的编写8 ]$ T/ j+ H7 k( _/ k: X2 v
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
7 _; D" o: e) ?/ a: M
: @" o9 }5 d$ |( Q+ [流程图分析( e" h: C! \: Z* w* u/ M
以我例程的App1为例:
% a0 R- W' n: h7 I3 }; v
0 ]0 P" k6 I# ?, q" ]
* ~9 _: p+ t q$ A0 d
: k# H. }: B- |/ d5 x( T先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;8 j2 h8 O% `! K% @7 J j/ J
打印版本信息, 方便查看不同的App版本;
`& u5 l5 f' S8 ^; c# u$ o: q& K本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
/ _: S/ _5 L: { k$ W8 q- x7 a
, V* d: @' T9 E6 S8 a程序编写和分析
" K" B0 f( I! L5 C# |7 x所需STM32的资源有:
, a3 S& _3 M* ]6 ^发送USART数据和printf重定向
$ T' _4 h+ X4 N. {: W/ d5 {, _% yFlash的读写1 J3 y% ~/ a, A
串口的DMA收发
$ G+ c6 h$ G& {# s0 _YModem协议相关% w, x, }" G9 F' ]& w, ?
Ymodem协议8 Z2 ]- W$ f$ C& T! q) j
百度百科[Ymodem协议]
) ~- ]3 @, x) q2 z, Q具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
- | v7 j+ Y& _( sYmodem协议相关介绍可参考我的这篇教程 YModem介绍
6 }! y( }4 w( k( V$ h8 z }) ^2 D+ x8 ]! p+ |3 @4 R! {8 d4 w
代码分析 x) c" z, }3 G5 D
代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
1 \( w/ u* E0 S; Q7 V' J7 `( o
- J2 T3 l5 v9 B' a. H后面放了我的源代码, 详情请参考我的源代码.( k! t7 K/ \7 s y' Z9 A4 k- @
, f# W: H, I( [+ \& Q+ {2 T8 ?) V. {- K
主函数添加修改向量表的指令 y+ |4 R3 W, \. C" X
2 i4 T6 x! R- N5 l/ F
* L/ G) M" y M0 d. k
9 w- o0 P# \: c3 H+ d9 n5 k8 R1 B
0 i! k1 O1 N6 O9 p4 ^ [4 I打印版本信息以及跳转指令5 N8 w2 o5 A8 H$ t4 a8 P
7 q t/ v# b2 p
, q- l& V* |: ], \" G" T j
7 {1 T. r1 ?1 @: X) A
YModem相关的文件接收部分. t1 `( ]0 b! }! ]- ^
- /**# @# i1 U% e- c+ w
- * @bieaf YModem升级
2 p' W" S2 A' d& c' y' T2 M - *
8 \! o- C- i' a4 I - * @param none
3 N/ D2 A* ~7 ?5 v* k! R - * @return none
* g' k! t# i4 F - */) y" i; Q3 `, Q# x- ]
- void ymodem_fun(void)
& T! x8 s3 l8 e: s+ U. y - {+ b+ ]) o+ G: Z
- int i;
9 ?% K- X% P3 Q% ^- c7 b, S, o - if(Get_state()==TO_START)0 e8 c$ E' ~: R4 N. F5 E! ]
- {
% b. n7 A5 I% I: Q& [. h$ | - send_command(CCC);
6 V4 X4 o& n4 e6 X5 k4 q - HAL_Delay(1000);# Q$ I3 X" H& A; I6 B+ k) U
- }- m% C" B% k, Z3 j$ T" r
- if(Rx_Flag) // Receive flag
5 j, e% o5 Y3 X6 r+ z8 |; a - {, ^& k8 C! b* b( p+ @. O; W5 E
- Rx_Flag=0; // clean flag/ n& o- Y* I% b% ]
4 R( g( C. g k- /* 拷贝 */) {2 W1 C! u' m& }. U5 H" C9 d
- temp_len = Rx_Len;- Y% q& y9 R' ?( g
- for(i = 0; i < temp_len; i++)
% v. ~ `& h5 v5 X2 u4 ` - {! M1 ?" h9 |/ b! `7 U/ Y
- temp_buf[i] = Rx_Buf[i];
0 n O# s( V9 _/ W* T3 Z8 o+ Z( x - }
* h" R. V) l/ |/ p' `$ }' B) Z - $ a% i4 Q. @6 ]# ]. i
- switch(temp_buf[0]); d3 E: y8 X( b
- {8 v( w( V4 ^$ U2 q& k* x
- case SOH:///<数据包开始+ C" c% d/ C/ D' G- y- R7 A3 }
- {! B5 K7 v+ n7 I" F+ m& _
- static unsigned char data_state = 0;6 s( W6 N7 K" ^! ^ D1 V- z
- static unsigned int app2_size = 0;
+ S% Q% G8 S1 I7 d - if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
8 Y: N5 w; l6 {; T& X - { k! \: I N. M7 e
- if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
1 F$ q G6 w" o4 K+ C - {
' y$ N2 n8 A& C - printf("> Receive start...\r\n");- i9 c' b* H/ d3 T- D$ J
) i# B1 e/ S; Y1 M" }8 \- Set_state(TO_RECEIVE_DATA);
0 `: X9 c. d. Q. Y. H, q - data_state = 0x01;& g" w! z; l7 j, s* v" X7 j+ j
- send_command(ACK);( {0 V2 v, q( z9 L
- send_command(CCC);
; s2 \& a3 Y5 Q7 `* i - 2 Q7 C* Y, A3 }. X6 y0 Q/ C$ h" f
- /* 擦除App2 */% s r7 |9 e! |. F2 y
- Erase_page(Application_2_Addr, 40);% j" N" x, H% c% i; X2 y
- }
2 W, S6 r# ]/ d1 l0 s" D - else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
% X/ q% C/ ?2 W - {: `, P+ ~+ x& E" v8 u: X
- printf("> Receive end...\r\n");* K, X/ x/ A& p
) {* p4 u8 s; U- Set_Update_Down();' M& o. Z/ |: }2 c* J3 F; P
- Set_state(TO_START);8 I1 y: S4 Q' s! c# _
- send_command(ACK);5 t' C Y7 p- R0 y& b
- HAL_NVIC_SystemReset();
3 e# X% M" L, B$ a3 Z* Y( y& m/ e - }
" b$ O& Q1 l. Q - else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
: ?0 a- l9 k2 e0 z% }3 @8 d G2 d - {* r9 V8 @* N0 E: H
- printf("> Receive data bag:%d byte\r\n",data_state * 128);
. c, K4 X# Q7 V* D3 U# G
2 A. ^* G' y" f( Z" X- /* 烧录程序 */' G# L2 C. i3 f' d7 F" Z; y
- WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
/ g3 K; n6 p" z; _0 O% _$ ` - data_state++;
( Y2 k( c, L9 h8 c6 Y5 p
0 z H$ X L4 j6 J7 Z$ G$ h- send_command(ACK);
% m3 X g N4 { - }
4 B1 r1 v/ T5 T* W9 f - }# L7 ~9 g& @( H# s1 y8 K% x
- else
* ~/ c" f1 j$ \4 e$ | j3 n3 m- J8 x8 a - {! b% E+ K$ I" o
- printf("> Notpass crc\r\n");! v+ f3 H; i7 H4 O# i. x
- }
! u; d o. ]* w8 F: M
" y+ t i& s1 z b4 K8 s0 S- }break;
7 n! q+ H8 W2 k5 K- h: I; A" P - case EOT://数据包开始
+ R) m; z1 X9 c5 e1 s3 k0 @' _ - {2 a W; D6 l( u
- if(Get_state()==TO_RECEIVE_DATA). d1 G' W {- b3 E5 L- q
- {( x& A, m9 L/ t4 _+ H
- printf("> Receive EOT1...\r\n"); Z4 C) y% c- g" u5 ]4 R
" y$ p# b- c/ R5 M8 o* \6 q1 J- Set_state(TO_RECEIVE_EOT2);
6 o6 V& i/ S# K - send_command(NACK);
7 I- r$ ~" @+ e - }
0 N' Q7 }# h5 z( G* A - else if(Get_state()==TO_RECEIVE_EOT2)
& h/ j. _, s1 j- A: S2 X - {+ R( k" l1 l% I
- printf("> Receive EOT2...\r\n");" D1 u# @% u2 z8 L
9 s; T1 }+ K* j; M( w9 q/ Y! \' L- Set_state(TO_RECEIVE_END);
" j. Z# u/ w* Q8 m" ?6 H/ e - send_command(ACK);9 r1 c8 S% \- L/ X. z* k1 d |
- send_command(CCC);
8 ~$ S' H' [6 D8 ]. z4 l0 b. H - }+ n' G! h& f/ y, ` M
- else k& b: h G" r _7 \
- {) J+ J( P* i# }
- printf("> Receive EOT, But error...\r\n");4 k+ U9 O" [% D! L5 `5 ? S& M
- }) C j; f; z8 W7 X* o" h
- }break;
( z% {* [; [' j7 r1 { - }; ^" R0 k4 E! F! u! f
- }
1 [! x( d6 D# G' u& ?+ w. A - }
复制代码
. M& ~6 B( E. D+ c% I7 q其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接./ _$ i# L' E! Z) g# e
9 P! v) x1 u5 Z
4. 整体测试
! w+ R: g9 n9 h7 [本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。6 ~" ^ x9 e. z3 L V. E, W
& G3 }, B/ U M. G9 n
代码的下载 }9 N& ~1 |' n) c& G+ ?
由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。$ a# ~% G5 d1 i J/ ~7 c
9 K( U/ C9 L* V1 L" p
8 Z# e1 \& f6 t* s, e9 A7 E8 q, N
% I* K, T5 I6 |' PBootLoader的下载
; p1 K9 B; S d# T7 fBootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置; Q3 n" f/ P; b- Y- }( g+ U
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
/ k; K( s/ r+ M$ d0 a6 v6 m# [6 Y0 |, H2 G* G; Y4 k3 @
# Z% i& E3 t" d# m0 K
m1 X2 J4 v! \" A: `烧录代码) o3 E- n, {1 k0 X3 C4 q9 ]
运行, 通过串口1打印输出, 会看到以下打印消息
: @9 l3 J! Q- t说明BootLoader已经成功运行6 O1 j7 [! A' B1 r3 k/ f% n, r# e" n( y
# W( ~2 `% `& Z
" s+ s# A) O/ X, p0 W$ D
( F6 p% k2 p, T, S }
App1的下载
" D! \, D) \- \. b) l+ jApp1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
# l: A! j. X0 U3 X* ~0 k同时也要修改擦除方式为Erase Sectors, 见下图
7 d5 t; Y. N, }: L0 N+ K0 S8 g% O+ |
7 v+ A( X8 i4 o1 `6 w' U: v# J- ~1 B) M
# x$ E! _& f0 `
8 P* }3 t0 L0 X _( o+ P
烧录代码
" e0 M; D$ s% {2 u/ a+ u运行, 通过串口1打印输出, 会看到以下打印消息
) C; A6 y* L+ I! ~说明BootLoader已经成功跳转到版本号为0.0.1的App1 N0 f/ N) ]- b6 E
: B$ C/ w7 {0 \2 \- z3 M1 j7 T. L; ~
' P. _! F0 H/ R2 I% |4 J# K) ]) ?: L9 X, O) m, d6 S
生成App2的.bin文件$ g! e' F( p6 H) n7 w7 X
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
( r8 |8 ?! z4 O H! d8 `
& K/ y Q2 G& d; _; c3 t生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件6 R1 D! K5 f5 e
/ Q/ q2 y+ E5 t# W5 Z; O J; b
1 }5 O/ b: b& C7 Z# a
6 E p' N3 c3 _, D! f! g使用Xshell进行文件传输
5 b8 \1 T! [% K/ y! N打开Xshell
/ `7 b' w# e6 u! N8 D$ h! a- t代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
6 ` Q0 d0 t3 T( U; `' ^所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
+ r( e( N" P7 Z+ X+ ?
! o- A- n% f h! D
8 j4 F$ K/ o" E9 f
5 ~% O/ F* P; H: M* J& [4 V7 x你会看到App的版本成功升级到0.0.2了.7 m0 Q2 g3 y: ]+ e
如果你到了这一步.
# c" H' A+ x+ x! {# J- f- V那么恭喜你! 你已经能够使用在线升级了!0 F' d$ v! C$ B( Y* ~% y
4 c9 a C+ r! H* S4 t( g5. 总结
2 w0 X5 [4 g: R" T' ^通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。
/ R- G: l2 F0 q" l+ c. B: y5 ~
9 i' G N( r0 t& b: o
/ H4 r/ ^; y, f+ F% ?% t1 E/ ^9 N! N* I$ k( q9 z
转载自:混说Linux
5 P. O1 z. k6 E4 r. s* j/ Z- P
$ s' [, W) c+ S# T0 e) y
$ }# f4 h* h/ Z* V3 W; a |