简介 本文主要讲解在线升级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区存放暂存的升级代码 : w8 I7 i! r1 x4 \8 n2 k
总体流程图先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序. 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序. 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示: / H3 U5 p$ s6 `5 [' I
2. BootLoader的编写本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。 流程图分析以我例程的BootLoader为例: 我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 程序跳转指令,可以参考如下代码:
9 G" |1 @* c7 `% E9 S7 E, b
- 1/* 采用汇编设置栈的值 */
" l/ b6 Z1 U% K" H$ M' d( \; ^ - 2__asm void MSR_MSP (uint32_t ulAddr)5 S% K& c* `$ J
- 3{4 E4 P" x, S& g5 K( D
- 4 MSR MSP, r0 //设置Main Stack的值% ?# Y$ @4 d0 }4 L' E& V- ~9 N, I
- 5 BX r14& [" k) u. B; e& P W8 v2 M6 R. F
- 6}
- y# S9 W; c# n' g. L" R8 Z: R - 7/ ?" U6 T' G4 h: X0 L+ \! ^' t
- 8
8 b h( u1 o' w$ w - 9/* 程序跳转函数 */' \+ Y' I4 j! d3 i2 z: s8 i
- 10typedef void (*Jump_Fun)(void); S" r; X8 k: f; y. h( ]
- 11void IAP_ExecuteApp (uint32_t App_Addr)
% B- j6 ~' N" j b7 j - 12{6 ]4 c2 Z* G0 m! w, O3 N$ S
- 13 Jump_Fun JumpToApp;7 s) g& t' L, p y: b3 N
- 14
5 x# T) u- d0 c# A. o1 c0 M3 a - 15 if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.5 y8 ~% I( g6 E
- 16 {) S$ F2 v- g1 L+ s5 r% v
- 17 JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)
! E* U/ M1 x4 A# g - 18 MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
% P2 a* j7 ^8 f - 19 JumpToApp(); //跳转到APP.
Y( g+ O6 U+ I. Q# @ - 20 }: {8 }6 P$ S% d
- 21}
复制代码 2 Y/ e1 c, a# }" t
3. APP的编写本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。 流程图分析以我例程的App1为例: 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题; 打印版本信息, 方便查看不同的App版本; 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
_0 i* e9 p j4 P3 ?5 r& V4 C 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 串口的DMA收发 YModem协议相关
. k6 R% L3 s2 ~" Z- p Ymodem协议
0 L% d5 Y' a1 K2 E代码分析- 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
- 后面放了我的源代码, 详情请参考我的源代码.
- 主函数添加修改向量表的指令4 l" I$ g4 `5 _, Y1 X- M
- 打印版本信息以及跳转指令% D+ \9 N! y! \4 r9 H( g. l8 Z- j% j
- YModem相关的文件接收部分
0 Q# B/ \, u1 _. U
- <font color="#000000" face="Tahoma" size="2">1/**- T: H9 |- H1 ~6 m S
- 2 * @bieaf YModem升级7 F _& Z: s( H
- 3 *$ ~% z) b8 X# U L4 E
- 4 * @param none
+ [! B; }8 L& X: a, s* { - 5 * @return none
# }$ d% k, \( P' a l4 k# d* u3 q - 6 */
, X+ y9 e: F1 C; v8 I- u - 7void ymodem_fun(void)
- M6 _# a j- X3 V% B - 8{
* V/ B$ I+ A/ ~ - 9 int i;
! ?0 {: Z6 M$ U - 10 if(Get_state()==TO_START)7 J; @5 m. k7 |5 ]: q1 E7 G
- 11 {
2 D3 L5 f$ Z, B - 12 send_command(CCC);! B' J( J( i1 _+ F
- 13 HAL_Delay(1000);
, r4 Y6 j" b3 X+ k E# [" o0 u - 14 }
4 p5 [9 W9 V' W6 X6 ~ - 15 if(Rx_Flag) // Receive flag
8 l: f5 ? E6 d& l6 x3 y: r3 M - 16 {8 o9 U4 X, P7 } Q1 ~
- 17 Rx_Flag=0; // clean flag
; V% I) @. ?& |# i6 W/ T( I2 J4 |6 L* i - 18
. W6 ~& s3 h& S2 `6 ^, T - 19 /* 拷贝 */
* H$ I5 r: l# N0 x0 o- N+ H& S+ m - 20 temp_len = Rx_Len;* E* t2 T5 J. r) ^+ o9 Z
- 21 for(i = 0; i < temp_len; i++)4 u' L6 K. s$ ]' ?
- 22 {6 b" O: m$ c# E0 M8 Q& y# s+ ?
- 23 temp_buf = Rx_Buf;9 L8 Y* X: G* N, Z
- 24 }* N0 v N" a2 A; k# U! |
- 25. N, h1 O. B8 J9 [8 u- E) i1 L# J
- 26 switch(temp_buf[0])
5 o5 ]2 R/ S, x; n2 ~ - 27 {
6 y, l% D |! e! t# Y0 A - 28 case SOH:///<数据包开始
) Q# m+ y; M* [" | - 29 {# M8 |" M. g. H6 z
- 30 static unsigned char data_state = 0;8 `, r( R/ A% n" U5 F
- 31 static unsigned int app2_size = 0;
8 J1 y5 }; W0 Z" `" U# u" Q - 32 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验: t1 _- n9 l" w( Q7 z
- 33 {
$ P" C& j+ s; H8 Y+ v - 34 if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始/ i! d4 K! f, X8 s
- 35 {
* I$ v. c( W/ `% H - 36 printf("> Receive start...\r\n"); y6 ]( ^$ v0 o8 [$ S- [; g
- 37
$ |, o' ?- i, j( @ - 38 Set_state(TO_RECEIVE_DATA);& x7 Q# h8 y: O! c( ?4 g
- 39 data_state = 0x01; ) j" [, I, N; F( U
- 40 send_command(ACK);
. |' b: M) W9 ^2 a) h - 41 send_command(CCC);
. ]/ Y) J0 @5 f0 c6 ] - 42% T0 k* p( S1 Y
- 43 /* 擦除App2 */
: D" Y* J& n( h6 S - 44 Erase_page(Application_2_Addr, 40);2 Q7 P( [6 n& K2 r6 M m
- 45 }
0 [9 R! x+ C% K1 v/ l - 46 else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束0 b/ \' ^+ t3 H$ M
- 47 {
+ t9 {+ B7 ]2 p6 Z - 48 printf("> Receive end...\r\n");
9 u! [. E- o/ ?% q2 r/ E; i - 49
4 \. C1 Q W, b7 p) g+ V' _ - 50 Set_Update_Down(); . r1 s, h. X8 k' F, W: q2 o
- 51 Set_state(TO_START);7 R7 j* }7 q+ t) X6 t2 K
- 52 send_command(ACK);
. r. k3 ]' f( @# R( h' [8 Y - 53 HAL_NVIC_SystemReset();
. Z1 S) g2 \/ R [" m* |- R - 54 } 2 ]. c& O6 i A6 }% D: \+ ?) I
- 55 else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
$ s( `+ c: L% G+ |5 D N* p - 56 {% i1 L3 `3 H& [* a1 E v/ }2 M
- 57 printf("> Receive data bag:%d byte\r\n",data_state * 128);
2 Q9 q! T. Y9 ^" J5 m0 j+ P$ Z - 58: a1 M: l; r% G6 p3 {
- 59 /* 烧录程序 */8 L4 U/ q- E. y6 o( H. C0 g7 u2 Z
- 60 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);' D3 Z3 V" a( n6 L
- 61 data_state++;- p# V/ O8 d: p$ n& _! R& |
- 62
. {. O; `! _/ c0 I( v4 H8 T. J - 63 send_command(ACK);
$ Y3 d/ N4 B/ Z2 b0 X - 64 }
5 C. E w$ o* b- F5 y+ Q - 65 }- ?( t1 Q/ T$ Z+ A! w1 x. e# S
- 66 else
; C! G2 G4 \: A' N - 67 {
; F4 t0 n: l& ? - 68 printf("> Notpass crc\r\n");
+ j* |% I% A( E- y3 @+ _7 { - 69 }0 p% s! k: ]& b. r
- 70
' t& d9 e2 h8 J" t: T - 71 }break;
E7 s% c9 u/ m" @ - 72 case EOT://数据包开始
/ |' D8 _5 A) t }9 h - 73 {& U7 `) E( `+ Z+ p% k7 Y
- 74 if(Get_state()==TO_RECEIVE_DATA)
: [" K+ m% ^! E1 L; b* x4 o - 75 {
/ K3 d, k& l" x5 A5 o - 76 printf("> Receive EOT1...\r\n");
) ~( j9 C" D4 y8 b1 E" H6 Z9 B - 77
4 Z- m2 i0 u9 N) M - 78 Set_state(TO_RECEIVE_EOT2); ' \( P! a+ K1 U, C {) k
- 79 send_command(NACK);
* P4 Q% z, C0 @* H: U( T2 _ - 80 }3 ` l, A Z4 N) u* y
- 81 else if(Get_state()==TO_RECEIVE_EOT2); q$ R9 p" N/ c9 H2 i p3 u
- 82 {
- [# n3 F, q$ Y: f- {/ C! ^5 w - 83 printf("> Receive EOT2...\r\n");/ E- _7 f" s, O5 Q7 f+ M2 u/ k8 L
- 84
+ E2 r& Q8 f" K - 85 Set_state(TO_RECEIVE_END); I1 i5 m" {( C; O, e+ h
- 86 send_command(ACK);
* V6 Z7 c% Y: p! a! o1 x - 87 send_command(CCC);. a9 K; x* s# I2 A+ l
- 88 }3 s8 b' f$ Q" `% L
- 89 else0 Z5 l" T+ j, R& U
- 90 {
. U. x# s# ~" j3 w4 z2 [8 z* V - 91 printf("> Receive EOT, But error...\r\n");, d3 x8 A) L# M9 I5 i" e
- 92 }. k6 Q8 I1 M- e/ K; R) \5 S
- 93 }break;
5 H( Q3 d [! j3 d - 94 }
0 `& S( W4 `$ \, A, C5 j) F- o - 95 }% I) x3 q) x- `3 L
- 96}</font>
复制代码
$ D/ X; l: c$ a7 d! e) _" N$ u7 [- 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.+ N/ W; U1 o6 N; s1 n4 u
3 n3 k5 d+ s( P- T7 q
* P5 Y u# z, i# L. X* R
4. 整体测试
0 p4 F" X& K6 X% F* d" Z本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。- 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。! p2 ^! k0 s/ u ^5 Y
BootLoader的下载- BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
- 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)5 G. l4 [+ F5 k x: i& w" {
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功运行) l7 r3 L1 }+ p0 \) l. i
App1的下载- App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
- 同时也要修改擦除方式为Erase Sectors, 见下图
7 U$ z6 F0 \" v, l: ? ! e, X& g O: T
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功跳转到版本号为0.0.1的App1% c' }/ a" `9 P
生成App2的.bin文件- Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件
7 P# A% m/ ^5 I3 j https://blog.csdn.net/weixin_41294615/article/details/104656577- 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
- 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
" c" o1 k: W2 q 使用Xshell进行文件传输- 打开Xshell
- 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
- 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
- 你会看到App的版本成功升级到0.0.2了.
- 如果你到了这一步.
- 那么恭喜你! 你已经能够使用在线升级了!( g: M! `0 ]' } u- R4 B
5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。5 o5 P: V. T! _# C+ W& l7 k
( [% J, [9 z+ R- I2 v
3 w6 p" y- C& V' l, R3 @ Y! m. k0 k1 u
* ]# o {% m/ O) f
6 M7 j4 S* o+ A5 n Y" A, `) \% }8 s" X2 ?( G
|
请问源码链接在哪?
兄弟,源码在哪下载