本文主要讲解在线升级IAP的基础知识, 主要是针对IAP从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识。
+ V9 N D0 O. k7 h4 W' R2 i$ z" {* k
1.在线升级知识 r+ y0 S$ O; k7 R7 X. b b8 v
什么是BootLoader? E# ^* {3 j; L' r8 C8 b, @# c
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序,App也是一个程序,BootLoader程序是用于启动App程序的.
& e Y t% c! L1 V
' P8 R3 q# j/ d2 z+ ySTM32中的程序在哪儿?9 O* ]2 T4 l3 j9 j( p/ q
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码$ l4 r s: L8 g; ?2 x
5 Q8 E5 ]" q& g4 ^) f1 q
& A' d4 h/ |, R$ \
2 I0 b$ S2 E8 ?接下来就可以进入正题了.6 \% `# d3 J, [% Z
5 M5 j6 L$ e3 g4 P+ T6 G, N9 ?
进行分区
/ q; E- q" @, K* m) m( X( q0 o既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
h: @0 c% K& Z- C$ \: |: [' ?6 p$ }: X, r- j1 O/ _
- _9 `, n, `7 v* _5 g7 c: I
+ S, K( i( v' Q1 C
以它为例, 我将它分为三个区.BootLoader区、 App1区、App2区(备份区)具体划分如下图:
( x. K, U: G @ x# X$ M7 JBootLoader区存放启动代码1 L4 {6 K; j1 L
App1区存放应用代码
* S' t9 z7 l* I' _0 e% wApp2区存放暂存的升级代码1 ]& ^5 [# T3 x0 S* Q) Z; Y
, h& U( x) |! g+ b
2 m& j2 W3 h/ L; w/ Y. g: n" z
/ K" _% |+ z! x' o3 g" _5 T总体流程图& C* k# ^* O5 k! g3 u
先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
' B& Z2 N# N% ]) v然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
1 A& Y, x+ a! J. m: Q/ V5 W9 l在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
6 _+ r' B6 J, s% @& Q( u( h1 o. k/ G1 |$ n9 b+ B/ n
7 O: C s/ `+ m5 r9 G1 H' ~ g. }4 E
% |" s. m; ~; h0 ]; \
9 }2 \1 ?, \4 ]' [2. BootLoader的编写
5 k/ K* T2 r4 R& s t7 Y; z本节主要讲解在线升级的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。6 l3 ]5 Z1 Q5 [7 H3 R
# Q, M" K% L2 m: o1 {8 O# M
流程图分析2 N9 e5 @# Q+ Z: g; O a) b
以我例程的BootLoader为例:& e6 c5 b. g$ f$ I8 q4 s% J9 r: K7 l
1 r+ G w5 N ~& X/ J
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示% ^( a/ S9 o; l$ ?4 Z
) {" K! E5 g- E3 U
) i. w" W0 J$ h: d, P7 X) ]. _
$ I' _8 l& i. _( r6 e+ ?! V) f$ Z- o" L6 r! E X+ @. N- @ m
程序编写和分析* N9 E Z# n/ o2 d; k; }% t- S
所需STM32的资源有:6 y" t6 w7 Z; F0 U, r
发送USART数据和printf重定向
7 m, W4 ?4 X& v! ^7 W0 s# zFlash的读写4 I% [1 f' T! ]5 Y, O* ]5 }
程序跳转指令,可以参考如下代码:
$ q. h; c1 z' o
' P/ {7 W# I* q# F* a- /* 采用汇编设置栈的值 */, F6 X6 ~6 {- @# H3 {, m2 \
- __asm void MSR_MSP (uint32_t ulAddr) O" n4 b s3 q- m
- {
, I- E3 a9 o" F" {6 q$ b - MSR MSP, r0 //设置Main Stack的值) m, e6 A6 ?+ Z; I0 j8 [+ q: |! J
- BX r14
4 c3 N; \3 \( K& X/ b: p - }
2 @" j1 D8 X+ k+ u! |. Z& P2 \
6 m8 l$ `( E5 m5 F( I- /* 程序跳转函数 */. v& }, ^( C5 Q2 e+ N* U. S% l
- typedef void (*Jump_Fun)(void);
9 ~- z" G1 l: p- Z- W - void IAP_ExecuteApp (uint32_t App_Addr)8 R& u: `1 j( h, b) B
- {0 D6 J$ v) a# b6 F( e$ @
- Jump_Fun JumpToApp;- v7 C% L" q: D- E5 V
6 ?9 m/ L. y; ^; r% O- if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法. E3 w) U, ^0 B$ ]. o, m
- {4 M' ~0 l, h4 b; x1 f' [
- JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)
1 `: k n( R9 I0 e) s Z3 J - MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
1 Z* {4 N3 W; r - JumpToApp(); //跳转到APP.
- m5 r( j0 B! E# G- U1 Y' g2 V ^; n - }
& h0 k; l, D" ?' k# M - }
复制代码 . o0 a- Y! `" _4 K; E" b4 D! r
在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
. B9 e' j1 @5 K/ D% z. I) |其他的代码请参考BootLoader源代码
9 G9 y, N1 B1 v1 J1 s- I8 A3. APP的编写7 H1 h Y. x5 R' y
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。# ^( }# s6 u! j/ |6 q" O# H
& W; b: J. p6 L流程图分析
9 M, I3 _2 D6 {4 N& q" U5 e7 C以我例程的App1为例:' ]+ N) b' ^4 M* {% ?0 X$ {& R
" {7 R- p1 U1 E
9 ]2 T6 l8 j& i* N9 ~ p
# W" Y4 S/ V+ a( C2 c# D% }& q4 G先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;7 U4 ?; c# F6 w) a/ k
打印版本信息, 方便查看不同的App版本;8 [ X! u2 r" y
本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
' F) U, ]( Z4 a( ]" M" X y1 {) K8 b6 k
程序编写和分析
. ]+ Y+ t5 Y3 F" i所需STM32的资源有:
, N, \% k; \' Y( P. }发送USART数据和printf重定向. l8 K" a! s" c h* P3 r
Flash的读写6 d, Q1 A Z5 y! u" n5 _9 }
串口的DMA收发6 N. ^9 ?* Q# H- A3 o
YModem协议相关; {6 ?" x* \, Q5 z" `. b9 Y
Ymodem协议
' v6 w# }- R% v8 z/ Z( z' L: X& j8 N百度百科[Ymodem协议]* a- P% d' t/ o, c3 c+ l
具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
& r; x. r: F. p" F- [Ymodem协议相关介绍可参考我的这篇教程 YModem介绍
7 ^4 W, N4 y' _2 j! R, a
# d# j/ g; _0 I. Y- {代码分析3 z7 v) J( t5 k+ n' o: R2 r1 I+ A5 u
代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明' K! L, w( V' e6 |, @4 F) y
' w" v' f4 a p( P7 n
后面放了我的源代码, 详情请参考我的源代码." g5 T7 P0 c$ V! x' H
: U1 v6 l. o' q9 W
主函数添加修改向量表的指令- l9 W9 _: r+ N$ M! }
0 W Y6 [: [6 [$ Y) u8 a
0 ~ T% A+ t( `
( O |- }, }$ H4 ~, _, Z1 W# ~5 {
打印版本信息以及跳转指令3 G% _5 R) `* M$ E: ~/ I8 T. d, \
* E& s# z0 g- ]; v
% e, Y/ G9 R( j0 _9 `
% k6 L9 S$ X+ l9 o
YModem相关的文件接收部分
0 r3 D: `/ p% d: u- /**& d6 G& Q5 P& q9 \7 y- ~
- * @bieaf YModem升级! o( l! d. C! C$ Q% Y& H
- *
3 A: W4 g9 ^& J0 e - * @param none* j" I. G N9 m$ p
- * @return none' v$ f# a' [- N j
- */8 i# A" B8 N% _& x* a7 x2 ?0 m
- void ymodem_fun(void)3 v( F4 |, c5 g5 R
- {' D; G4 T: \- W! `
- int i;
+ O* K5 L. C$ |! l1 U9 T - if(Get_state()==TO_START)
! {% t: Q$ O$ U# h- K - {
- i5 C. Y4 g. B$ l# k+ T - send_command(CCC);4 Q% m, l6 t7 z( k! J! m! g/ c
- HAL_Delay(1000);
\6 L9 c( N6 V$ ?" x$ w+ s - }
( d6 h8 }/ b# c$ m% o& Z; { - if(Rx_Flag) // Receive flag; E9 x6 v; X2 c7 @
- {
. m3 x1 ?! T( k+ ^; o: d6 P7 h) Z/ `7 @ - Rx_Flag=0; // clean flag' E! @+ u( p' v# Z2 Q" O4 O
- w0 H: s9 z, e2 ?: k- k: U: b- /* 拷贝 */% N; s8 b3 P3 A, v0 I
- temp_len = Rx_Len;/ t9 D; E5 v! E& c z; ^
- for(i = 0; i < temp_len; i++)2 ^, |) R) [6 e- S3 M' y
- {
% R) J) ~8 Q3 @ - temp_buf[i] = Rx_Buf[i];
% O* u) m5 m, v. I2 m4 }' c - }" O+ c/ _0 I! ^9 I) W7 `
* i* Y0 J0 }& d) y5 N( n. \- switch(temp_buf[0])2 D6 o1 H# ]5 h8 c6 d: O$ `
- {7 h/ K, n& X% C) q0 n# h
- case SOH:///<数据包开始
7 u V# [( P6 N4 p W( ~ - {
$ b* H3 M. i& x6 g2 Q - static unsigned char data_state = 0;* S1 c$ Q8 N. a2 C# {* f! x+ t* s
- static unsigned int app2_size = 0;
6 k8 q, z6 _ L7 |! R1 y* d - if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
& v* n2 y8 X, f# ]: u - {
L8 y+ y4 h7 A0 d) y+ a I: d - if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始( ~! Z# @- T6 s) c6 _2 i+ P) A/ E
- {1 v' a) c9 n5 {# E7 i8 m
- printf("> Receive start...\r\n");
6 X( J7 g0 \: t. V/ L
. V% b! ~- [: P5 ^! H- Set_state(TO_RECEIVE_DATA);
* ^' a. _4 q4 ^/ n1 ^/ s - data_state = 0x01;
- g4 u; `. k/ d5 V! W - send_command(ACK);7 v9 \! |. C6 H8 C8 m) [
- send_command(CCC);
7 W! ?2 s& V% Q1 M- w! p; q( V
7 f; y* N+ ^- ^: ~- /* 擦除App2 */: W8 o9 }- x6 c+ h! `( Z
- Erase_page(Application_2_Addr, 40);
7 C! ~3 D% f5 m2 L0 J7 d$ L4 c - }0 D* U- ^% s* W- T, h/ X6 T
- else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束0 W) @' W" B' a; c" h5 D
- {
' \, H2 K1 z' }2 I' O - printf("> Receive end...\r\n");0 S/ |5 w) B; @- i7 V. v6 C
- . g ^$ R W8 V
- Set_Update_Down();
' k+ {) w! g6 h8 A! [: X9 A - Set_state(TO_START);; F2 |& p6 z! u# ~6 q7 S) V4 t
- send_command(ACK);* t8 y$ K0 t1 \1 O" J6 H& g3 ]
- HAL_NVIC_SystemReset();4 C5 B% w& {1 `3 { R0 m
- }
, ^ U; k, Z+ X# U - else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据/ b; H" o0 d2 t1 F6 ^
- {
) o- S( I# O5 w2 t4 r2 F - printf("> Receive data bag:%d byte\r\n",data_state * 128);; K( U- R& `+ O; F+ Q+ p" t& ~
- 9 I5 _: E8 s1 p- p2 U
- /* 烧录程序 */+ |4 X9 z0 S+ k1 c# g5 Z! F" n- {
- WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
8 I, e- }' X' p$ Q9 _* w - data_state++;
4 k' p, Y. v' `6 |$ z
" _3 u$ E7 ^% O0 o- send_command(ACK);; D R; _6 S: ~9 E2 _3 X; U& B7 M' j
- }
# G; P* i# G; @. A+ O - }9 P6 d- e# o, c4 e- l! o3 \
- else8 N1 }" M8 D# l o
- {
" n( B3 q& l; V: V# \2 O" j - printf("> Notpass crc\r\n");
# q a. j' C: q: ]5 g0 { - }; G% E/ Z$ }' d5 | a( [& h
# y9 P6 F1 y% u. b$ |2 ]5 R) F% i- }break;
l l4 ?: i4 e& c6 ?) ^; P; H - case EOT://数据包开始
' j6 [; t8 a0 K! z* q - {
( e; w8 C! f9 k - if(Get_state()==TO_RECEIVE_DATA)
3 K M7 I6 a/ J9 r) h- c' P - {
- b+ J, P8 {/ g3 W - printf("> Receive EOT1...\r\n");) l3 j# B" k$ K3 h+ L2 a; I$ Y
( j4 i5 s- |! N! ~+ B2 t- Set_state(TO_RECEIVE_EOT2);
Q. M" I4 w5 r" `) b$ h- u2 b - send_command(NACK);( v4 [) v. o; u
- }! \4 X7 z. R, G, @- J9 j5 R" j
- else if(Get_state()==TO_RECEIVE_EOT2)
( a& @( a! G# w* s: c7 s4 B - {6 h9 B1 ^+ h6 R! w
- printf("> Receive EOT2...\r\n");
( \% v0 m6 P1 T: y, _* j - % {7 k, @( u. w/ F
- Set_state(TO_RECEIVE_END);
0 w1 U V5 b+ n( J% E - send_command(ACK);4 |( |2 P, f% G: g
- send_command(CCC);
. L1 @7 _; O! W - }! v# H8 q# o1 H1 L- B
- else
2 f/ E9 W* ^2 t8 R- N - {
/ N5 h( r: r- d - printf("> Receive EOT, But error...\r\n");' e4 n7 n9 \$ P& P7 `1 {& P6 G
- }% m! }: J3 ]2 j$ [! e$ h3 Z
- }break;
* W' O' K/ L$ @6 _ - }
" K! V% q- ^" E0 S- G" `8 U - }6 Z- t/ y" m- |, }5 a- R4 r
- }
复制代码 * p6 N3 i$ ]& f* B- I
其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.& F! W% i6 x: n7 B) X( k. Y0 J' }
( }6 e2 U) O! I% ?+ g) I
4. 整体测试2 a9 q6 _: Q) D6 N2 r
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
L% X9 E2 b* l1 w6 D. Y
6 u7 X. f+ |5 E6 Y" J; ?7 I: u8 f代码的下载
/ x8 |/ `/ _7 x) ]0 @2 T: Q由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。6 @- k" X n. n6 f$ e! p0 |
, c5 W; W) C, v* v0 h. g
L) j6 V) I7 o3 L* }0 _
- R. b' }4 @5 G1 L' ^& tBootLoader的下载& }* `# W6 y, N3 Z- ~; D
BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
" X; c0 X" n6 o7 V4 B/ K按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K). k$ R; ]8 I, x! ]8 a; F
2 B, C/ Y9 y( z/ Z* Y5 w% v' H9 [* N
% [$ j4 }, P5 R5 M, n4 S1 `- O& i+ ]0 V% Y, ~" u6 z, ]
烧录代码
; |0 X+ F/ Q- h% U运行, 通过串口1打印输出, 会看到以下打印消息9 |' |! n- n& j& C
说明BootLoader已经成功运行$ y$ n, `: e" P W
/ U- _: `- D7 k" C6 Q0 |
5 W/ [" g4 i: y
" [. O; h4 ~: @) ^! R) }App1的下载
g# @! w/ C* o. a) V) _9 gApp1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
6 { a: k! G5 X& c. M% v同时也要修改擦除方式为Erase Sectors, 见下图( a6 w# n5 h9 f. [8 Y8 a
$ [+ a L0 ?2 Y/ f6 P
8 k' E+ Y2 G- S# Q8 ?+ C1 z% O- O7 N7 |8 K# w. ^: Q: q
; M! x$ Q2 R7 @2 R1 L
3 h- r+ j9 b; \! {, Y# v烧录代码1 H: o/ B: H! ^# V
运行, 通过串口1打印输出, 会看到以下打印消息! [/ s+ G) J) k: {- I/ x
说明BootLoader已经成功跳转到版本号为0.0.1的App1$ k' H* {' k9 T. J
/ e! |0 K5 d9 Y( _7 _/ U
/ o1 g; P+ D7 n9 g W- R/ v( U
4 G2 x' A/ Y2 d" j+ L: n生成App2的.bin文件7 s6 \5 }- P! V- c
修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件( t9 W$ G4 m2 o! v4 U; ]
+ H! H$ }! Q4 D: n+ L) C生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件5 J0 C) q9 \, C% x1 b7 H
- N! V% B9 d1 H( B
7 ^7 ]! t) }$ Z( F% U7 d0 I5 J
$ t- r$ I0 R5 v' k- s; A3 T& m使用Xshell进行文件传输
7 l' G- [5 V6 b; Y$ R打开Xshell
( P3 S! O$ B9 h: J8 l代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的. Y. C- b$ W$ z! p. J
所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息. k6 J" a D+ X
y* P- o2 m0 T. E3 L1 H; A# W
~$ R- E5 o( w' ?9 P9 m q3 y
/ b" i0 ]+ Q7 H/ l6 O8 }你会看到App的版本成功升级到0.0.2了.% E& h$ _& N% \& B$ p
如果你到了这一步., E* j) r/ F4 _2 i: n0 H' O
那么恭喜你! 你已经能够使用在线升级了!
. m4 E; L! T$ e+ Q$ @9 x9 i
4 t9 D, a/ @2 |% Z0 ~5. 总结' D5 q) |+ Y7 K: k
通过本几节的教程,想必你已经会使用在线升级了,只要原理知道了其他的问题都可以迎刃而解了,除了使用YModem协议传输.bin文件,你还可以通过蓝牙、WIFI等其他协议传输,只要能够将.bin文件传输过去,那其他的部分原理都差不多。- n! `- z# [. `4 `3 a* f% Q4 X
2 s5 {# {0 M8 ?& \- ^, ]0 G
$ N) k8 e0 V' e3 R# T. ~5 Q$ R$ ~, R
7 y4 \5 `/ U; {3 Y/ o转载自:混说Linux
* f: r0 M) Q, \9 k3 I" T4 O3 g0 p1 e3 {
( J! D2 P2 Y, ` G. D. ?
|