简介 本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识. 1. 在线升级知识什么是BootLoader?BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序, BootLoader程序是用于启动App程序的. STM32中的程序在哪儿?正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码 接下来就可以进入正题了. 进行分区既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图: 以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图: BootLoader区存放启动代码 App1区存放应用代码 App2区存放暂存的升级代码 ! K& K- n. Y9 O, b- o5 S8 r
总体流程图先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序. 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序. 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
, l. e+ Y! E( E- y0 Z- z& b 2. BootLoader的编写本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。 流程图分析以我例程的BootLoader为例: 我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 程序跳转指令,可以参考如下代码: : f0 e( n L4 V
- 1/* 采用汇编设置栈的值 */! ?$ r9 z0 M% X4 r/ _% w( W" J- u
- 2__asm void MSR_MSP (uint32_t ulAddr)9 g5 h1 j1 E s7 k3 J2 O% m
- 3{# N% d8 s5 I, f0 o$ _/ e
- 4 MSR MSP, r0 //设置Main Stack的值. o7 L* X! @+ {4 [9 L/ M" T h
- 5 BX r141 V, t2 Y. @) X+ j* J. N
- 6}
# M% s- z8 n1 Z0 Q - 7/ T6 @, h6 \2 K) G% P& A
- 8
1 R) w; R" N$ L2 n; Q7 X n - 9/* 程序跳转函数 */
3 k" S1 n, v k; y: U7 R - 10typedef void (*Jump_Fun)(void);& N3 t7 ?% L. M: _
- 11void IAP_ExecuteApp (uint32_t App_Addr)
5 J# M+ P7 @. K) L) u* ^% I - 12{
/ ^+ g9 l8 w% ^( P - 13 Jump_Fun JumpToApp;/ y7 F6 J; F0 q: y& x. w, j
- 14
& {7 M6 \# M+ t) y3 ]7 H d - 15 if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
6 _+ X9 e F3 m" U, J - 16 {0 e! c @0 G5 B# u+ v6 D2 i z
- 17 JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)% b6 m; L# x: T/ D$ q
- 18 MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
6 F' ?3 [; L0 E4 R" c - 19 JumpToApp(); //跳转到APP.
0 H6 O: K1 r n, N - 20 }
; _2 b; V1 |/ X! z) I( m - 21}
复制代码
& m- k: X2 H* z9 `) |( g* E' J 3. APP的编写本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。 流程图分析以我例程的App1为例: 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题; 打印版本信息, 方便查看不同的App版本; 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示: . W6 F# p! a" X5 y+ A
程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 串口的DMA收发 YModem协议相关
" W ]& L- j& K8 q Ymodem协议
( r5 M+ G- Y+ X% g: B) }2 \代码分析- 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
- 后面放了我的源代码, 详情请参考我的源代码.
- 主函数添加修改向量表的指令
) x+ f! F2 E/ X: c G2 n$ I) H6 ]' ~
- 打印版本信息以及跳转指令
4 @3 R5 U9 I6 x
- YModem相关的文件接收部分0 _& T6 s2 W/ A9 Q- d8 `% v1 f4 f5 v
- <font color="#000000" face="Tahoma" size="2">1/**
( r% B+ Q) H7 I+ ~9 o- j( Q - 2 * @bieaf YModem升级
; {" ]' I! U4 k! K( K/ i3 O - 3 *
5 d4 S( ^8 {" `- E - 4 * @param none
7 f: m" V5 Y: A - 5 * @return none
& I( m7 K/ G h q - 6 */$ U, S- Y5 N/ l" {& E) `: T9 a: @+ H
- 7void ymodem_fun(void)! ~& G; C* G" h M( V, B
- 8{7 n7 _9 x1 X. _5 P6 Z
- 9 int i;
$ ]6 L4 J6 I5 T. p - 10 if(Get_state()==TO_START)# |2 L9 T' N: ~2 W
- 11 {. u3 h& `: R$ r; n
- 12 send_command(CCC);/ Q P! t) E+ R" N+ q- h
- 13 HAL_Delay(1000);
% S& x0 u) v' ?* E - 14 }
9 `' t/ i- M. b; D* b) ]: @. F - 15 if(Rx_Flag) // Receive flag
3 w7 J% L. j8 l1 M/ |; G. I - 16 {
- T2 I5 g/ d( ]! m; C, V% ?" ] - 17 Rx_Flag=0; // clean flag
6 d9 a' \6 F, w& @0 w - 18
1 R; _: w6 O5 V0 {; Q$ f1 k - 19 /* 拷贝 */
* _) H# _ ^) r9 r! H6 A - 20 temp_len = Rx_Len;
. ~+ M* [, H1 u e - 21 for(i = 0; i < temp_len; i++)
- A- A- F2 e" J4 H1 P; Y - 22 {7 w5 X3 L4 c9 W d' F
- 23 temp_buf = Rx_Buf;4 H2 j+ K7 }/ W+ J" d
- 24 }( M: g" P& j. R; ^9 D
- 25# q7 W. l4 m1 D
- 26 switch(temp_buf[0])' ^- H- R4 c9 d
- 27 {
: [* i+ C* a" c! C( G( c+ ? - 28 case SOH:///<数据包开始
6 W% {, L4 u0 {4 Z$ q' O - 29 {
& b/ S: A, V" ^) N - 30 static unsigned char data_state = 0;7 s1 H+ V5 A) a7 v: e7 h- J
- 31 static unsigned int app2_size = 0;
. N; I: K3 i5 U. J) G& g - 32 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验) ~# [, A' Z F. O, j& Y" B; l6 w+ T
- 33 {
/ Q+ `* G* c: C) f - 34 if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
- ?4 b, L' U; e; X5 ^3 X - 35 {. ^ f7 W4 e2 ^1 {
- 36 printf("> Receive start...\r\n");
5 s. D2 _: b+ l$ O9 F' c M - 37
4 K5 j$ B% }7 M: X& X% e6 g" V - 38 Set_state(TO_RECEIVE_DATA);
# F$ _% H6 w! }: _ - 39 data_state = 0x01;
5 j# O% u& i) F, M/ }; p - 40 send_command(ACK);
1 F4 |$ C5 Q: A8 j0 f - 41 send_command(CCC);
7 o( K- a3 l5 T0 j. W - 42
% ~3 \) y) P8 Q ]: \4 ~3 ?& y, z - 43 /* 擦除App2 */
' ~8 _" E' _# W - 44 Erase_page(Application_2_Addr, 40);* G$ O- N* U( k C$ M' n
- 45 }3 c6 d. h/ }" {1 O
- 46 else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
6 A# ?5 [7 H- ` M% M - 47 {
% }4 a( d# ?% \" M8 c) y9 i - 48 printf("> Receive end...\r\n");. M1 b! X4 {+ c
- 49
4 @) u$ o" J$ I p6 S - 50 Set_Update_Down(); 4 u' f) ?4 L) X4 ]
- 51 Set_state(TO_START);
: F: |5 t$ n; @2 A) H, J2 c - 52 send_command(ACK);: P$ W+ b/ y2 x1 g
- 53 HAL_NVIC_SystemReset();0 b3 S7 v0 o; \8 F# v
- 54 }
) u1 ~% w- M( W - 55 else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据) Q% i2 B3 r& H. V7 A
- 56 {% K- V$ |6 F. e* x" {9 q, F
- 57 printf("> Receive data bag:%d byte\r\n",data_state * 128);! u* q( g( e; F' Q8 g4 w9 R0 g
- 582 M5 B3 i0 E+ c
- 59 /* 烧录程序 */
# H) j: |" W6 M/ j7 p8 N8 S - 60 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);! W5 }* S3 K+ F, t" ?5 U- |- O5 X7 f
- 61 data_state++;+ r% p F( [6 t6 u* T
- 62# L d! k/ G$ [, Z* H! m4 ~
- 63 send_command(ACK); . M/ ~) d7 P/ d) V: q
- 64 }
! ^$ X. F2 ^/ U A5 w2 n- R3 R - 65 }
& U2 U* d; a. D. k - 66 else8 O/ E* A m7 g# B% v! D* \
- 67 {
% r/ s( Y, F# G8 W, | T- I; c - 68 printf("> Notpass crc\r\n");7 t( ^: l* S- G8 H' I
- 69 } W8 I: s R# o! E
- 708 c# M+ x8 ?: d9 @. _6 v7 l
- 71 }break;
6 U& p! X! Q9 l - 72 case EOT://数据包开始
, H, U. c. `4 ^% r) V3 m - 73 {
+ l" |% N3 q* F) P1 P - 74 if(Get_state()==TO_RECEIVE_DATA)
# ] D' X$ y, r, H3 C - 75 {
) q- b7 n* F7 s- D - 76 printf("> Receive EOT1...\r\n");6 J; G2 f; a8 A, j1 Z. \
- 77
: j) l5 B1 ~8 Y, r, U, b - 78 Set_state(TO_RECEIVE_EOT2); $ ?. _. \: v1 e/ y: A# D9 v
- 79 send_command(NACK); k+ f' v1 O( {
- 80 }
7 R' T7 O5 n% ~; q# G - 81 else if(Get_state()==TO_RECEIVE_EOT2)
3 \) I- S- r; ^) s! o! d - 82 {
0 t: k J& |9 V - 83 printf("> Receive EOT2...\r\n");
0 I# [) R. a# j5 q6 ~ - 84: a* [; E: p3 x3 N1 Z
- 85 Set_state(TO_RECEIVE_END); . z! j# [! G. u7 Q7 F6 d8 S
- 86 send_command(ACK);
& j( W* {' J {% ~ - 87 send_command(CCC);
! T8 O- v" P- R- ~ [ - 88 }
( E0 @9 V1 c5 F. {/ E4 i$ Q - 89 else
( e) b0 Z. p1 b8 o - 90 {3 }3 ~0 `: V9 s B: w
- 91 printf("> Receive EOT, But error...\r\n");
: K( Y+ i, |; U Y5 m - 92 }
# C8 `& |& J4 `7 g# ?; f - 93 }break; 4 U% _4 G4 l$ ?# ?! {
- 94 }* x0 L* `. F* z! D( p# r
- 95 }
8 }" Z- I0 J) ^! r - 96}</font>
复制代码 / n% V" @+ l* ?- `
- 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接./ n# \1 q; a; M. J
* ]. k( J5 N! }2 y1 v# o( @
0 H& b% c4 T3 ?# o& H0 z
4. 整体测试9 M+ G% x9 ]& D; x
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。- 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。9 `, L2 u5 ]& L8 W( ~8 ~
BootLoader的下载- BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
- 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
8 f2 O8 H1 _, a2 M% B
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功运行. u2 h9 T4 S; A, O% u
App1的下载- App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
- 同时也要修改擦除方式为Erase Sectors, 见下图
& M' q$ O' D7 M: C9 a
) s; N/ H& |; U5 b$ R- B/ K- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功跳转到版本号为0.0.1的App19 x2 @# K/ ^* K2 Y
生成App2的.bin文件- Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件! S' R9 n1 o" k$ @3 y
https://blog.csdn.net/weixin_41294615/article/details/104656577- 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
- 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件4 {- M( W" s# x$ P$ N5 d
使用Xshell进行文件传输- 打开Xshell
- 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
- 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
- 你会看到App的版本成功升级到0.0.2了.
- 如果你到了这一步.
- 那么恭喜你! 你已经能够使用在线升级了!- J1 p% u' Y$ y7 w, J
5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。
) [% p* [8 I# \0 x }- w% ~" u( f% _: y$ V3 H
0 v; r6 v$ ?( b
6 J- ^/ ?8 h/ `* \3 ?6 l8 k8 n& D1 k' S$ u( e
d6 ]! D1 R8 G4 Z" |
+ O; B6 c1 k/ P. l( s' N" Y4 U- T
|
请问源码链接在哪?
兄弟,源码在哪下载