|
简介 本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识. 1. 在线升级知识 ]* n4 _( d7 m5 L
F3 S, D8 g! c! ]7 [( t1 X( L9 i什么是BootLoader?BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序, BootLoader程序是用于启动App程序的.
+ G1 g# B, Q1 N" l6 j STM32中的程序在哪儿?正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码
$ J, y8 f s* s- j: D5 t# @) X2 a
0 Q* |8 W5 L, q: M& V, N接下来就可以进入正题了.
; ^; S. v3 r# w# u! ^) x 进行分区既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
' C/ k( W2 v" T8 j# h8 T
8 R7 B$ U2 K* {( h+ l9 ?以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图: BootLoader区存放启动代码 App1区存放应用代码 App2区存放暂存的升级代码 $ x( h* E# |6 y6 m- V
; u' \# L4 ?5 t+ b& i
) n* Y% d0 T' |* \8 ], o& T. m& c总体流程图先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序. 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序. 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
% \; @( g; M4 j4 k- b5 C# i( f3 B
7 ~. \8 K% b. Y7 [4 Y# [% Q
, T' L1 C( i3 u I5 c7 m% V# o2. BootLoader的编写本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。 2 _4 |0 v# J8 B, V& g
流程图分析以我例程的BootLoader为例: 我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示
( |' p( S+ W2 a9 N7 I
, @6 r1 B+ p; E1 X/ X程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 程序跳转指令,可以参考如下代码: / g, c e/ S' a; z: i; L
- 1/* 采用汇编设置栈的值 */
1 [' f9 O0 @* n' l/ f2 T - 2__asm void MSR_MSP (uint32_t ulAddr)
1 b( {4 D) \7 N; _& A8 E3 K - 3{
5 | k* a0 a, [) q+ o: Y - 4 MSR MSP, r0 //设置Main Stack的值
! \- [( _7 Y' H, L; z8 I" _; y% l5 n - 5 BX r14
) z$ U b3 S0 ?. ^4 b - 6}
( u8 r9 j: s: q& o$ E" R5 F( {3 t - 7. H/ J2 C. H/ w
- 8
# V; R0 t3 s8 ` - 9/* 程序跳转函数 */
+ ^( @/ W7 w: @# S - 10typedef void (*Jump_Fun)(void);
6 E0 H6 a1 S( N6 q/ A& U - 11void IAP_ExecuteApp (uint32_t App_Addr)
- ]/ M0 g A0 q5 Q) @4 k# v2 u - 12{4 v' V/ L" y) d1 z- I: m, S
- 13 Jump_Fun JumpToApp;5 K/ _% r7 X2 u! @- Z
- 14
1 `1 A Q3 `0 n. I; q6 x2 u( R - 15 if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法." g' \: _" m6 f/ G/ g- g
- 16 {
8 w& b7 J$ M' c - 17 JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址). ~3 X% P% n1 c
- 18 MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址); U9 _8 [0 } B0 b
- 19 JumpToApp(); //跳转到APP.2 @; @6 Z* ]: H, `! Y+ F- @
- 20 }7 G' {+ }; B7 v3 E; H* k
- 21}
复制代码 7 r) g9 B% l! F. [
0 E; u+ S* A- Y1 B/ G3. APP的编写本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
2 `! G% x- M, C5 y 流程图分析以我例程的App1为例: 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题; 打印版本信息, 方便查看不同的App版本; 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示: " _; v4 M& `( o" D1 S" J( Q6 r8 q' O
4 @; D- [' L5 ~7 a; A$ k: C& }程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 串口的DMA收发 YModem协议相关
. x s- m5 {! E0 I2 Q3 f) @ Ymodem协议* E+ d& _# p4 s/ @ T" X
代码分析- 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
- 后面放了我的源代码, 详情请参考我的源代码.
- 主函数添加修改向量表的指令$ k8 o/ A6 v) G. p" [) l/ F
! z3 P+ T- n9 t" @: @( _. D0 c
@* ]) R b# _4 ?4 C( Q4 S- 打印版本信息以及跳转指令
" a/ @3 Z5 L( H2 H9 B5 Z
. B7 F7 F2 u1 a& u6 l! n) S
$ n( f( o+ K" T- YModem相关的文件接收部分
2 A4 i- U, L2 T2 O5 X' F: |
- 1/**7 f3 S6 M* e r/ ~& n' m! B, t
- 2 * @bieaf YModem升级9 F( u+ z m. R2 q6 X
- 3 *( P* q8 M, P( d5 g N% p
- 4 * @param none
: X( M4 b) h" R5 R& c: ? - 5 * @return none; [* s' H: t# A
- 6 */4 X! j S9 h! q- n
- 7void ymodem_fun(void)$ u [% R8 p: C# J( K5 b/ g D- S
- 8{
7 W/ D- x4 b1 Y* q W - 9 int i;
- I3 {& W8 H: N; S2 J1 r8 U - 10 if(Get_state()==TO_START)
1 H5 t: U4 Q3 e f1 W2 w E - 11 {
" N( D) `0 [) C- h9 U$ W0 E - 12 send_command(CCC);
1 x) X" B& B- c/ q - 13 HAL_Delay(1000);
7 p3 p5 O S) w2 e0 }5 V% h - 14 }: N, t( s6 ?: E3 n! x Q, r
- 15 if(Rx_Flag) // Receive flag
6 m' L4 h C1 x \$ d - 16 {
& z+ f- b5 P U1 M2 _ - 17 Rx_Flag=0; // clean flag
9 ^/ \* H0 U" K" D2 f' G6 w - 18- v! h3 u9 J7 j/ K) m
- 19 /* 拷贝 */" S% a6 s3 _0 K D2 k
- 20 temp_len = Rx_Len;
% a( n6 _ _( z% @9 P( { - 21 for(i = 0; i < temp_len; i++)
/ y# y0 |7 B+ g1 @1 ]9 c2 k - 22 {
% j3 v) P/ i) K# G - 23 temp_buf[i] = Rx_Buf[i];
! Y% K- f" ? ]) ?0 f0 {# e - 24 }
( y- Z9 [) J/ ?2 I; _" {" w1 v - 25, A0 U" {& l8 @% c
- 26 switch(temp_buf[0]). c6 _8 T; e" t0 {7 F
- 27 {$ Y( }% ~: Z+ \; ]1 v5 t7 T/ T9 y
- 28 case SOH:///<数据包开始
* _% t" Q" P8 C9 l! A - 29 {
3 d8 f' S$ e8 k; @( c - 30 static unsigned char data_state = 0;# @: Q& e8 K6 s# F. N% P
- 31 static unsigned int app2_size = 0;
( r: E$ N4 @2 P: \+ C - 32 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验8 \+ E1 x$ Q, n
- 33 {
4 y* |7 d1 I- g' }8 y p! i - 34 if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始2 C- d' _% i" z# m( r( w5 ^
- 35 {' Y$ F% q+ y. \9 K0 Z; C1 H
- 36 printf("> Receive start...\r\n");* I3 a2 |0 S% h/ t& `) r
- 373 D# j& ~! X9 n; E( {# h) T# {
- 38 Set_state(TO_RECEIVE_DATA);
( O) a) h1 U, k: H - 39 data_state = 0x01; j6 t" e9 k V# _: e+ p
- 40 send_command(ACK);3 \ _& E; s3 I3 [7 f! Q1 F9 J$ Z
- 41 send_command(CCC);
1 V% B2 P% z& ^& D5 K% E - 42
* g8 t( ~" _( D: Q - 43 /* 擦除App2 */
' ?" ^- {6 I5 n3 d# p - 44 Erase_page(Application_2_Addr, 40);$ Y& ~ t; {1 m! u& {( |7 k3 h" e
- 45 }* |! R6 _. ^ \+ H2 T
- 46 else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
- {1 W c3 V; e8 W6 T - 47 {7 U# |- D# e) M- r
- 48 printf("> Receive end...\r\n");
3 Z+ V8 o9 X5 X( A* w2 l# d) m - 490 {# A& H2 _, j! S0 c+ F% v
- 50 Set_Update_Down();
- }. b/ ` |0 ] l; A! b4 O - 51 Set_state(TO_START);
' H9 c7 i4 W7 r) D t - 52 send_command(ACK);
' O* _- b" Q0 V0 G - 53 HAL_NVIC_SystemReset();
- @ @$ I( i+ C; R' v% e - 54 }
7 |9 F J3 E- v - 55 else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据" M' ?9 m+ | ^- ?
- 56 {
8 ]8 A: W3 `$ [+ J4 W4 } - 57 printf("> Receive data bag:%d byte\r\n",data_state * 128);# p4 |: ^% @1 M- u7 B1 ~& `; B/ E
- 58
2 V- d8 J/ ]4 Q( `' Z7 r - 59 /* 烧录程序 */ F5 y5 u$ _; m# e& J# g6 E
- 60 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
# j9 V/ i; ], V$ x - 61 data_state++;" _% P+ m5 G, s
- 62. V# ~9 V Y- G, Y5 V" n0 y
- 63 send_command(ACK);
* ?4 c8 X/ d3 g7 R - 64 }
8 Y* k7 I% V0 d1 u4 N6 K+ ?8 Q+ F - 65 }
0 D$ c1 h C: n, N2 l9 g - 66 else' C. k8 l" F( ^) a
- 67 {
' Z$ W: P: O/ ~7 X5 m9 q - 68 printf("> Notpass crc\r\n");5 x8 o& [ K- z$ A8 A5 r
- 69 }; x( O- I9 A0 v' J2 T
- 703 }. U6 O2 B$ a. R4 C7 A
- 71 }break;* Z7 i' U+ Y$ E6 H+ V
- 72 case EOT://数据包开始 G' B' x% x3 c$ B5 y! I) v6 y! V2 m
- 73 {' o0 e6 V! G( Y. z* w
- 74 if(Get_state()==TO_RECEIVE_DATA), G) V J4 @2 N
- 75 {/ X3 p: X. b- I% N
- 76 printf("> Receive EOT1...\r\n");& h! c0 E( ^; N3 ~% T0 d" C, }
- 775 J4 l% ]3 Q8 k# L# \! W
- 78 Set_state(TO_RECEIVE_EOT2);
3 X! U8 D& ]- e- ?0 b/ ?1 i - 79 send_command(NACK); S8 a2 F) ]; ~9 i/ b
- 80 }
) e3 C( P) q- u) r - 81 else if(Get_state()==TO_RECEIVE_EOT2). {! \& g( ~7 n/ U
- 82 {& q6 \. Y. H3 G5 V
- 83 printf("> Receive EOT2...\r\n");
) o+ J4 f* F/ W& }; m6 @ - 84
# A) f4 n: _* \! Q# U - 85 Set_state(TO_RECEIVE_END);
4 m& G% J& ?2 y; Q2 C4 `+ y" ]. O - 86 send_command(ACK);
- t _0 i* B! z8 q/ ~ b1 d - 87 send_command(CCC);/ x. t0 \; l: G R
- 88 }
1 _+ e9 L; T1 C) b% M - 89 else
# J/ r b/ c, v+ K. j0 P! X - 90 {/ r" `5 H$ Y3 j" o# v
- 91 printf("> Receive EOT, But error...\r\n");
) A) g4 U" e# _ - 92 }3 T5 Y( x5 ^4 O- k) [5 h& n
- 93 }break; $ v+ Y) G6 _! K8 x8 j& k
- 94 }
& u5 X U- ?' j" z - 95 }
- a( Q9 c+ e( E - 96}
复制代码
{6 E+ W$ u- R. h1 |' \: \+ w) r0 s9 s/ v7 D
- 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接., ?8 v& y9 z% f: Z8 W: k* W3 D
% w5 s( j5 v5 {0 w8 \% E/ D7 f
* ^8 ~% Y# H) ^% C( p
4. 整体测试% |, m6 y5 z5 D" s5 d7 u) o# A
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。; w1 T2 V( T, j/ \ a! k
/ b# Q% J6 o3 |9 l* h8 k+ N
0 H. n1 g3 f5 q+ s3 p源代码
- d+ ?+ R# L8 Q* F3 r& a/ [( C2 QBootLoader源代码和App1源代码可以在原作者的gitee获取:- 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。! f5 W" U( |8 @7 i2 g0 t2 |
. @: v( H1 l( W* U+ ]% J
7 g: L% p( W9 j% rBootLoader的下载- BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
- 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
3 R7 h3 P+ O% N7 w' v: U: n " y0 ~- R. f$ m
( b2 f6 L. a3 E- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功运行
1 r6 D- _: z. B/ B. E) ^" B1 I$ `6 y : w e6 [ B3 Y. ?" i- t# W
/ J0 K$ Y3 q& M
App1的下载- App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
- 同时也要修改擦除方式为Erase Sectors, 见下图0 t6 G( q/ |8 ]9 N3 E* ?9 }
! K5 A! y; b& I6 c
/ V3 b: [1 p1 Q. P! p
. E. c) u k, K! R5 L
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功跳转到版本号为0.0.1的App1
' k9 F! ~9 h6 F# Z
2 ?- o- x& q6 q( R7 i1 t' @& v0 ^5 T8 p3 G9 H! ]
生成App2的.bin文件- Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件% g1 b$ t! T! G' Z9 \8 d
https://blog.csdn.net/weixin_41294615/article/details/104656577- 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
- 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件* C8 w. o4 j: p {" `5 T
! q% ]1 J1 i7 S. |. G* [3 m
6 x# {# S7 w5 I& B% c使用Xshell进行文件传输- 打开Xshell
- 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
- 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
- 你会看到App的版本成功升级到0.0.2了.
- 如果你到了这一步.
- 那么恭喜你! 你已经能够使用在线升级了!1 Y2 U. h5 d* _( v6 a/ ~$ ]3 Y' j
" \! Y; C8 X0 {& [$ U7 R! C5 Z. d8 o, o9 i4 |
5. 总结
/ ~8 a/ T' P7 J通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。8 a- b! r' _& R+ M
# g: R6 v! S2 L m% `6 U, f
_9 O4 A) T( ?; f2 b3 s- t3 _
% ^7 L) S! H0 ?2 \- H9 g转载自: ARM与嵌入式8 T0 v5 F4 U6 j' t
如有侵权请联系删除9 w9 ~6 S! z+ m* M. n) [1 G
" W* s) k# d ^& m" z2 ~# r# t
" b; b$ Z2 h9 x( C- Y6 q2 S9 P% ?4 k
|