本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。2 Q2 T' T9 T7 N
+ P# O/ b! G4 g- C- B+ i5 q
1.在线升级知识) u$ k2 \" h# [( k, G$ x! S( r% P( y3 O
什么是BootLoader?
. {# u) @$ X2 Y5 s, I" NBootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.
0 a& e9 X9 l5 ]8 P9 C
- w% r( }/ ~3 h, {% l# ]5 USTM32中的程序在哪儿?
# i5 n: Y, L9 L* w P正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码, s' j# w5 z! E; X P: G1 e' U! L
1 j; d" G7 _ m* P8 }7 n
9 S5 x2 ~! [" j$ ^" t D+ J' b* @3 c. V
接下来就可以进入正题了.( @; e2 y. }4 q$ _" J6 M8 S
1 c4 n4 E0 A7 ]5 a# f$ p: s
进行分区
3 F% L' A; ~. f7 W, l既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:. @, b4 Y1 G0 i4 V8 S
! H- X: C+ y' V9 e, m$ p
8 a h) _5 g$ W, a* }
' y3 K6 A4 T* U以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:% b: g: s7 J' E3 f9 I6 U
BootLoader区存放启动代码
, E# U. ?- V% ^- ^App1区存放应用代码
% H+ Z H' `7 z+ A; YApp2区存放暂存的升级代码
! {( L; V, B1 b6 E2 Z9 r$ b% {; a7 y+ O( p3 O9 ]
: ~ r8 y; |+ D# o9 w+ ]" i' M
. P3 V6 m" T* I$ n" }总体流程图: W9 d9 e# h; c
先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
2 D# q6 @3 K5 g% c然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
4 ]% B8 ^3 }* y+ m$ L! n; h在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:! I. W+ b" B/ |4 S; ~
K, t# d. Z& e8 c/ o: C
! ?2 g# L6 [9 G) d2 T0 @$ l4 q; E4 Q7 Z- x8 u5 u
! S1 i {5 L7 H) V& z2. BootLoader的编写
% p: n! c9 W" [+ d4 P本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。4 |! f5 t3 j( N" T) ~; |1 l1 v0 o
' F& c1 Q7 U8 T! L# C- f
流程图分析+ a' `) {5 u4 U
以我例程的BootLoader为例:
* C0 C7 N l5 A1 {
% g) h: Z; g5 W2 ]& g& |我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示& s9 x9 b8 Z# c4 E& ~
. }; B. R A( @3 y. s4 Z
/ q- C; k( U( c+ \. B) u
& G8 c6 B; B! t! ~$ r4 p/ _! `# G6 }* M; E/ i
程序编写和分析
1 }; y5 [6 v7 W) Q _所需STM32的资源有:
' @$ S! Z" Z2 X发送USART数据和printf重定向) ~6 J4 f/ I% s0 @7 [
Flash的读写
0 k0 |) n% _: M; s5 }程序跳转指令,可以参考如下代码:
( E* M. P! [! U- }: D8 a6 c8 Y6 V; q& j& Q. b7 |4 h. T
- /* 采用汇编设置栈的值 */
$ b: N7 E5 ?# M; F! T6 z1 n3 A5 o - __asm void MSR_MSP (uint32_t ulAddr)
) v- N% m+ q" h2 L/ A - {' E0 o |+ M4 Z
- MSR MSP, r0 //设置Main Stack的值
! q& U+ [( T: [+ w - BX r14) c! s: M2 ]: [" m+ e9 O
- }
& P% E0 {6 y. c) T
- p' } R3 v" c& B6 V- /* 程序跳转函数 */
. @8 V7 N2 M7 q - typedef void (*Jump_Fun)(void);
3 }4 L! u4 W v( o# H - void IAP_ExecuteApp (uint32_t App_Addr)
# f* H( [. }4 \ |' v) M3 J - {) w K7 Y# j, T& e3 H7 P
- Jump_Fun JumpToApp;
$ e+ z7 p7 p! a# u% C4 f
+ C. B+ m p, r g, }% Z( v/ D+ M- if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.' W D" y# ]" b( E p" h& l
- {% N1 U7 h- |9 @" Q9 V2 e
- JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)
3 ?$ `& J4 b5 [* P. q. i4 w - MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
" J2 p- B6 O- w9 Q; y - JumpToApp(); //跳转到APP.
3 O- e% i. ?0 V& x1 r - }
4 D* I3 I% y* B/ @( K - }
复制代码 , a" q4 x; @- g% M# l4 P
在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);& S) r0 \; g' p, p, N- g
其他的代码请参考BootLoader源代码5 t/ g3 P/ T* ~
3. APP的编写
/ F6 W) R n1 f7 C. r2 d本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
4 M, v5 S3 D" n% C2 R$ `- e+ Z/ g" o# e0 B0 x" r, i% J
流程图分析
* [) I2 i p1 Q以我例程的App1为例:6 u- ^9 H0 D& M( O
: f, O( C" M5 K5 J X- r
3 x- d8 M3 M: u! t$ ?
$ I2 t5 j& o' y$ g1 E: a9 q
先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;# G) f# z- K/ K% f+ `
打印版本信息, 方便查看不同的App版本;: V9 k: C) y' Y$ ]
本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
. o1 F0 z& J4 V! _: v8 b A1 u7 @
3 L8 F: u3 O- G- G- d程序编写和分析" ?8 P c( p- D/ q# V1 j
所需STM32的资源有:3 N! s$ |0 l d& r9 G
发送USART数据和printf重定向+ ]2 A# d( }' q. H$ ?! {
Flash的读写
: W! y" Y. [: w9 T8 c# j串口的DMA收发! W% v- q Z v
YModem协议相关
, o; i, k8 I5 ~% w, FYmodem协议1 A6 K' [/ O# S- Y
百度百科[Ymodem协议]% _# N' y P) a' g+ B
具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
1 }0 [* s3 M" ]Ymodem协议相关介绍可参考我的这篇教程 YModem介绍
z* n; L/ f, k8 T1 Y8 U7 p, s5 F8 L4 K2 g8 ]! k2 s* V* I7 ~
代码分析9 }$ I+ f. g h7 a, X
代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明/ B2 Z/ ~- O- D) q8 b9 L
# }& K# G$ O3 {8 }1 F, n
后面放了我的源代码, 详情请参考我的源代码.
- y/ R' r( Z" s, V5 h: i0 [8 T$ \4 O7 x
主函数添加修改向量表的指令
0 a* l- P! _ z n3 j0 V; M) _" m7 N( a
$ ]2 n: y9 q/ g2 h: ^3 {
! X J8 ~1 _% P U; p2 f- r8 G
3 x1 d0 z) V4 b: R打印版本信息以及跳转指令' d0 m' b' |# d7 @, K0 d
' s" ` E! G% c$ m( Q. ^
" A5 C/ @& A1 P9 o$ R) a W% e. J, e1 l- \! f7 W0 U- G
YModem相关的文件接收部分, a1 p% Q7 b6 k
- /**
( D: B& P( u& {; N - * @bieaf YModem升级; r# i( n* Z+ u8 ]; Y ]
- *
/ x/ P4 b* d- m7 N$ ?( M' P - * @param none) _" e4 ^' h& f# B' a' a+ Q% V
- * @return none
1 P& V, Z9 {# q* r - */! P) C% l1 _ Y2 I1 O3 V2 G
- void ymodem_fun(void)
1 m" Y- r- b# n( A2 \# V - {5 `% O i3 K& D. s. c3 l6 i
- int i;# H* U! o- x1 {9 S, z& ?
- if(Get_state()==TO_START)7 O! {/ k$ d$ h: u5 R: o! J/ f
- {
0 ?1 P5 T6 f+ X6 E, \5 @- b - send_command(CCC);
2 {/ ~' h3 w# C! K, i0 b9 z6 o - HAL_Delay(1000);. w$ x" K) ~% M$ q% t
- }
2 W/ Z: u7 l9 [! A3 I Q - if(Rx_Flag) // Receive flag
: P6 q8 c: y$ y0 n& c B4 \. x+ | - {
6 L9 @- R: @9 R3 n9 C9 n - Rx_Flag=0; // clean flag
4 k7 A" w5 o3 N3 a. K
3 ^8 R, J1 F' a3 D- N4 U- /* 拷贝 */
% T I/ \, t0 a - temp_len = Rx_Len;. Q1 x! s$ n$ i2 `$ {# X
- for(i = 0; i < temp_len; i++)2 Y9 k: w" Y _/ E- h
- {
9 }. ^2 U' R, y% B9 H7 \6 }* s - temp_buf[i] = Rx_Buf[i];
" i: p. `2 ^8 `. p/ {% w8 K4 _8 R - }# ~' ?8 u" I3 l+ \' }* C: R
0 O" h4 S9 O5 E( M4 n- switch(temp_buf[0])- \" i5 X5 ^2 E- u* f
- {' S. t% l& o0 N- `; V6 @3 x
- case SOH:///<数据包开始
]) C% C& z( N - {3 C: ^, {. [- Y
- static unsigned char data_state = 0;
2 I% u" v( V% z - static unsigned int app2_size = 0;! `- Z' w; Q7 G9 W1 t
- if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验4 f ]. Q( x3 p$ v
- {
- P F- m1 p4 H: y - if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
# X$ ~! P8 @& m5 y: E1 L: A - {2 q% |) d% |: d6 `
- printf("> Receive start...\r\n");
% P2 [! ~1 p' {* C - : a/ j# I' z1 k6 |2 I# S6 ^
- Set_state(TO_RECEIVE_DATA);/ b" m# ]! b M$ t6 N2 H
- data_state = 0x01;
- t5 p: X3 X: \% N - send_command(ACK);8 g) g8 w, d, D0 j' D/ _! d! k
- send_command(CCC);. a' t# r% G" ]2 \
- 9 m9 e8 C: H9 e8 o, n) H
- /* 擦除App2 */ q4 E8 @. i9 P) i; ]
- Erase_page(Application_2_Addr, 40);+ B- m7 g s8 L% w1 U
- }6 n: a6 U r: j) F' e+ Z
- else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
8 G- u6 @9 \" L3 v - {# G0 x/ L% p( j6 l" H
- printf("> Receive end...\r\n");/ H& E! Y. q3 K' k0 o5 q1 M* s- Y
- . e: x3 q5 g% K' A9 c# h8 ~
- Set_Update_Down();3 E8 P p3 o+ u \3 O1 a4 |
- Set_state(TO_START);
3 T% d* _% |9 h+ Z; i6 C- I - send_command(ACK);9 N' d3 D |% r
- HAL_NVIC_SystemReset();
8 w- R' @2 Z0 p& p- N6 J* b5 P - }
/ E2 B- H' ~) d. N1 C. O( g - else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据6 Q' S. x7 \2 W. N& E0 ^
- {! p% ]( [. |0 q: T# s8 Q
- printf("> Receive data bag:%d byte\r\n",data_state * 128);
, T' P b1 T. ~9 t1 h) t
" c4 W9 O# B- O6 }5 M- /* 烧录程序 */
5 i9 F* a$ X9 M6 M/ n. j' S - WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);4 X9 i- l- W* h5 |
- data_state++;
* ^7 X& p, F5 a - - i) K" A6 L) @( t( ^$ G7 g
- send_command(ACK);* d$ n7 ~% n K* N# m3 {
- }5 L1 L, D+ l% i8 B$ a [
- }
+ A2 b+ l5 Q0 ?0 m9 g' W - else) V; N( f4 {- w3 A
- {& e5 k2 b$ M" N
- printf("> Notpass crc\r\n"); u6 }# s; n2 s9 v
- }
. m( e8 f! H! e" o8 @, _! g
& O: {, u. [! F1 S# J- }break;3 W9 h7 T4 I9 r% V$ D) }/ G
- case EOT://数据包开始5 M6 ^9 L! L) F6 P8 L1 I; v N3 Q( E
- {
) D( P1 ~. C; y& N+ d# ~ - if(Get_state()==TO_RECEIVE_DATA)+ H( f, O1 {' D0 ]
- {
7 K8 D3 N4 z- Y4 g - printf("> Receive EOT1...\r\n");, D2 l. J( r3 f3 E/ ^
0 r; S+ C7 Q" L2 R- Set_state(TO_RECEIVE_EOT2);
* Y \. C9 r5 Z* C5 b; ^$ v - send_command(NACK);
4 H# C$ F2 O$ [* i; a, H - }
/ i- W/ l3 n3 i7 p0 v6 }2 h - else if(Get_state()==TO_RECEIVE_EOT2)* @# n1 v' p4 O. [
- {# ?3 s# x' T- R6 I: C" @
- printf("> Receive EOT2...\r\n");
+ g6 z8 P! O) Z6 \3 c, m' ?' I - ) P2 f" L% U+ m* v$ W/ I
- Set_state(TO_RECEIVE_END);9 _9 c+ {& Z s W8 Y6 V+ c, G0 _" Z
- send_command(ACK);
: c( u8 l4 C4 ^5 _9 [% R0 M - send_command(CCC);
7 }$ H9 D. F& r& s. F - }: M* F O4 {6 S& z
- else) B" Y. O0 T' O' r2 Y
- {
1 g1 `+ M. J; I3 f$ m- N2 I - printf("> Receive EOT, But error...\r\n");
- i8 ~3 x2 o: y2 i - }
$ c' H9 [2 m9 @8 N/ |7 ^ - }break;$ R: l) f$ R) F
- }/ M) s" [1 x* X1 j3 b% n
- }4 i3 }. N1 Q1 | {) f' L# |( T, T- o% r
- }
复制代码 , ]0 E8 t) `6 M* S" C
其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.
! F% p: _; |- O7 t% k9 y4 M7 @5 @$ O6 e& S3 d
4. 整体测试9 j* X6 l2 ^/ N0 [- ?4 k8 f
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
5 e; d& z2 [% M1 E/ J. k# S" t5 d$ P6 {3 s) {/ y2 O- ?/ y
代码的下载
# ~6 c, Y! ~, j7 }: l9 ?由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。0 K, x- A' k8 E* i B4 X! H6 |
. g* j# ?7 @3 n) V- V8 L( ]
2 d1 C: r* R( v6 T2 i9 a* D% ], e. m" w# A( e
BootLoader的下载' O% R" ?. _1 f' s
BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置" B, E7 `, {: G! b1 F
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
8 ^2 [( [& G9 d+ v& D; O: G) b B5 V; c( m$ v, i* n
6 D" F( s8 r9 K' l4 L0 _) ^* ` P- g& q9 O0 B9 K
烧录代码( B e6 }) g4 g' y6 K
运行, 通过串口1打印输出, 会看到以下打印消息$ b8 U0 q f( E" B. x
说明BootLoader已经成功运行" |: S o& ~7 h6 Y
, M0 c6 q% B& z
) O' U) S( S! w M) w& ~8 A8 A3 A# T
App1的下载# [/ J3 n: m [; D' S9 R
App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
3 _0 F( W) Q7 h0 T+ D# |0 ~8 i4 ?同时也要修改擦除方式为Erase Sectors, 见下图
5 @* [3 j! o9 e2 m" R
( W; V4 ?4 W& u5 f! Z3 Z
L4 r% R Y4 q, \1 m
% Z) G7 `* s2 R
; @7 \- a$ \5 K* ^0 Q2 c" H4 K2 i* ?' R* N3 U' i
烧录代码6 I; \' u# N2 G7 l1 N$ E/ G, ^
运行, 通过串口1打印输出, 会看到以下打印消息
/ Q+ }- y& w& u说明BootLoader已经成功跳转到版本号为0.0.1的App1
u5 X# P6 ~% L8 T/ t. f5 I2 M4 u+ G+ q' u8 o ]6 D% {' L
, E" i" k7 \7 v8 G" L! {' ?( q$ A& j
生成App2的.bin文件
V& i$ l3 Y% A修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
4 x4 g% e) d* Y; m
) O/ v( \) p; `8 N" K生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
9 B( ^; F6 m) h
* ]3 p- P. R: S% O4 Y+ h
! _! M" U: P* T( i. F3 q
8 R5 E* W. d% N+ ^: h- y使用Xshell进行文件传输
. K ?7 J' n/ ]; J2 Y2 D9 b打开Xshell4 Q6 D, y) u" D/ A6 ?$ G9 Y+ d
代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的1 p& O+ f# l+ r% W9 k" p: E& E
所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息. N- j+ R% ?- M" l5 i. J1 i# q
8 t0 x) {2 U1 i* n6 X, M
) I' c# B6 |$ z0 ~2 N+ O# P1 B
$ m. A# z$ x3 I你会看到App的版本成功升级到0.0.2了. @0 j4 \+ ?$ ^
如果你到了这一步. t! f/ K5 H9 z. B3 U, |
那么恭喜你! 你已经能够使用在线升级了!
6 F2 S% \. U8 w' i9 [
5 @! f- e0 Y& M+ ~0 A5. 总结( p, l0 s/ H% @' x( M. }! ^
通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。, B3 @; Z# O9 h Q! ^
% s H. i* @" [
& L- D/ j" @3 M7 P+ D
& h4 W m0 t* }3 ]9 {9 M* x# R
转载自:混说Linux
@) F0 O* q0 S3 Q0 u
( U; t. R: ^2 e8 t8 _( R2 G; J: I. O: t; V
|