简介 本文主要讲解在线升级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区存放暂存的升级代码
, s, t; m/ y0 J) M5 {! U 总体流程图先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序. 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序. 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
0 z# v( T3 m9 |; t4 x6 |! x& f. s 2. BootLoader的编写本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。 流程图分析以我例程的BootLoader为例: 我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 程序跳转指令,可以参考如下代码:
# s! s' Y1 h5 }- T
- 1/* 采用汇编设置栈的值 */
% _$ f. d" Z; d K& }1 I) q8 L - 2__asm void MSR_MSP (uint32_t ulAddr)
: h' r3 G! F# @ - 3{
; w# q# }! t4 T4 p - 4 MSR MSP, r0 //设置Main Stack的值
6 v% _/ x8 x, _5 }8 z6 k# o6 Q1 a - 5 BX r14
2 f; I6 H, o% Q h - 6}
+ U. T( j+ K* ]7 a+ s( G' S - 78 r( Z2 A7 e+ p K( I
- 8
5 D4 E$ t+ k: K h7 R6 Q - 9/* 程序跳转函数 */+ f+ U' k4 V% ~- h
- 10typedef void (*Jump_Fun)(void);) s8 F8 t$ V4 m# }9 _9 f1 d
- 11void IAP_ExecuteApp (uint32_t App_Addr)0 Q1 k0 i% ^; E# S7 ^: ]
- 12{1 R! k" ^3 r% Q& ]" f: |
- 13 Jump_Fun JumpToApp;
2 Z6 a+ \, r. e2 J7 v5 Q - 14. J8 h# r2 h1 J7 s+ ?+ T
- 15 if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
) I2 X3 Q3 i5 K! Q( i! B5 w - 16 {' d4 [$ L$ d5 e
- 17 JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)) _; {6 ~ }4 O
- 18 MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
1 ^3 O( q' B& U( q7 i: z* r# u - 19 JumpToApp(); //跳转到APP.
+ |: \* s+ ]6 m: }1 @ - 20 }4 P0 E" i2 _' k% G {
- 21}
复制代码 + o1 `: p# r3 `
3. APP的编写本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。 流程图分析以我例程的App1为例: 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题; 打印版本信息, 方便查看不同的App版本; 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
& I, w* k; N8 n$ B. U7 X: D% M1 s 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 串口的DMA收发 YModem协议相关 * k7 q X' w& n, h0 p. b4 i' J. q% J
Ymodem协议
, l; x0 c* d+ Q8 s" l) x5 H代码分析- 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
- 后面放了我的源代码, 详情请参考我的源代码.
- 主函数添加修改向量表的指令' q+ D( q6 j/ L) a% `. p: l
- 打印版本信息以及跳转指令( R$ _( }# ^2 u( U1 T2 X
- YModem相关的文件接收部分, H7 x s$ C4 ?$ F
- <font color="#000000" face="Tahoma" size="2">1/**9 j- ^) ]# O( B* s- B! y1 Z5 g
- 2 * @bieaf YModem升级+ ?$ O" C; k4 y2 Y
- 3 *8 u& X8 y$ l2 o" M6 j6 H* K1 H
- 4 * @param none
! J8 ?$ ]; e6 W+ ~# ^" J - 5 * @return none7 l7 a7 D- D0 ]1 R U
- 6 */
" C- v" r; Y* R9 n - 7void ymodem_fun(void)
, c" H8 k" D( S# V& }8 ` - 8{
/ a. H; g: P4 I9 U - 9 int i;
2 f) l2 s* k. u - 10 if(Get_state()==TO_START)& O+ a2 j) T1 r' z& ]: N
- 11 {
) y0 ]* i3 I+ Z- h - 12 send_command(CCC);1 Q4 E( I* Q6 E9 d6 z) `" D
- 13 HAL_Delay(1000);
9 A$ b) A0 |3 i - 14 }2 }% q' S& Q- o) |
- 15 if(Rx_Flag) // Receive flag
; T8 M1 s7 L0 t* V- j! P- ^; G: s - 16 {' e4 E. a0 `- P
- 17 Rx_Flag=0; // clean flag+ Z; e7 Q5 F% J
- 18! k5 ~6 z* S3 K c6 O
- 19 /* 拷贝 */ ^8 K0 R; X/ Q2 B9 _2 f
- 20 temp_len = Rx_Len; h }6 z# L& Q4 Q
- 21 for(i = 0; i < temp_len; i++)6 i5 ?# K9 n3 s! S
- 22 {
8 C/ K. W0 l2 p2 W, L7 g - 23 temp_buf = Rx_Buf;: G, a- C' K3 B) m3 n
- 24 }% z* w9 s. M# a$ ~9 W
- 25
$ k& H- I w, K* Y3 s7 H0 E - 26 switch(temp_buf[0])
; k$ y- f1 g, q$ b4 M, k/ ^ - 27 {
/ i, U* A1 w$ m' M8 v - 28 case SOH:///<数据包开始
1 {# R! F' E; K+ L; ] - 29 {
/ r4 }! W! j0 q; l+ {# n - 30 static unsigned char data_state = 0;0 n' E. A8 D/ V* y8 U4 a; s8 q
- 31 static unsigned int app2_size = 0; {& h7 W8 g, {2 a
- 32 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验: B8 k: t; l) w0 c, B: T& N
- 33 {
' {/ u( g4 w" W- Y8 c! I2 G. o - 34 if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始 N2 a( t+ \% n" `( o$ ?* A. c. p4 R
- 35 {$ M8 ^7 M1 h( Q5 e/ O8 ~
- 36 printf("> Receive start...\r\n");* U# E+ U" l* Z
- 37, s2 X2 t0 h% ]1 C# {
- 38 Set_state(TO_RECEIVE_DATA);' e9 F( l% T2 n; ~! Z
- 39 data_state = 0x01;
s Q7 J) J/ y' w - 40 send_command(ACK);, F/ ^7 |/ f; A; |
- 41 send_command(CCC);
: z% Z [& q8 P/ }+ z4 ^ I - 42: n* t' |$ j( k/ R: g% C2 f
- 43 /* 擦除App2 */
# o3 h' W- X9 S! h' r2 `8 ^ - 44 Erase_page(Application_2_Addr, 40);
8 k/ F6 L& I* Y# m - 45 }
* L5 Y, {+ U' m1 I$ E - 46 else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
8 ^1 d' ?# I1 t" L& G - 47 {4 N6 t) b( E5 O$ J8 |9 h6 I3 @+ l
- 48 printf("> Receive end...\r\n");
$ K# W- ~5 h: P6 L3 j8 F - 49/ L; D. ?& { a) V9 P+ U
- 50 Set_Update_Down(); 6 [! L& i5 V7 `6 F6 @
- 51 Set_state(TO_START);
/ F2 j" I; T) o. i& X% ?8 p) U - 52 send_command(ACK);+ K* a3 h/ F* o/ J& m. ?$ F
- 53 HAL_NVIC_SystemReset();
# o4 }& b/ c) c7 t& Z" G5 v& `6 Q! d - 54 } 9 _3 o" D$ h* ?. }+ ^3 s
- 55 else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据- N8 H$ g' Y N0 O
- 56 {
4 o# x% {9 H( I - 57 printf("> Receive data bag:%d byte\r\n",data_state * 128);
8 e) j3 v$ R8 ^, @/ _) x) M - 588 k; J$ K4 v" z4 r, Q, ]4 m
- 59 /* 烧录程序 */
. d* G Q% ?3 J* Y) K - 60 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
& y' X3 D4 Y2 ^/ m - 61 data_state++;
8 p4 D( c+ C8 e3 P) u; l - 62
7 k/ _ a; T |+ K9 F3 x - 63 send_command(ACK); . n, x" {5 B8 p* j4 i. s
- 64 }& a1 I4 e1 h, C+ n. M
- 65 }
2 `, W' H8 @0 E - 66 else) X$ M, L# P% B/ H( V$ A: Y) e+ y
- 67 {, T- G: @# H) l$ C
- 68 printf("> Notpass crc\r\n");
, J1 J {% Y3 _" L# U ]' C6 P9 H - 69 }, d5 |0 k# P: B/ B! Y5 ]6 J
- 708 e K8 J% o5 H8 d
- 71 }break;2 n; U) j; L) J; R4 U/ P
- 72 case EOT://数据包开始
9 U: }9 K) C- m4 W- {' P - 73 {0 ?& T6 B( v0 X% d/ K
- 74 if(Get_state()==TO_RECEIVE_DATA)
5 Z% ?; |3 }- L - 75 {
7 z. n! N$ [4 ?3 |6 T- z - 76 printf("> Receive EOT1...\r\n");+ B8 j9 @% `8 J i
- 77; S, C$ ^# n' G" k) t/ F0 L9 }
- 78 Set_state(TO_RECEIVE_EOT2); , z$ P% t1 W M. b
- 79 send_command(NACK);- X) l' I1 w# c6 c; ]% F1 O
- 80 }
5 b% s( q- Y" E: [3 f9 \6 i - 81 else if(Get_state()==TO_RECEIVE_EOT2)
R1 C' f0 G! `7 ?" s# v) I - 82 {
, `5 ^: n" @+ Z0 i7 P/ P# r - 83 printf("> Receive EOT2...\r\n");/ |% a2 y0 k- s0 Y
- 84
6 u' A+ x( W P# C) r) v - 85 Set_state(TO_RECEIVE_END);
8 V; P$ V+ h3 j/ t - 86 send_command(ACK);
/ @( |" Q7 b% v1 s. y2 y6 z! c$ b - 87 send_command(CCC);) p4 {2 M7 `) Q/ e1 U) u
- 88 }
" N0 k/ g1 K, O$ { - 89 else
+ l, X; S! a: S" w - 90 {0 b; v/ R" e: S: ?: @/ b6 W
- 91 printf("> Receive EOT, But error...\r\n");
. a3 P8 t X9 F6 }2 i6 T! N* C7 W - 92 }
. n3 X6 R, _( f! b1 X7 ^9 p - 93 }break;
! o3 A2 r" b. Y. o2 A. I - 94 }
7 w. Y: P/ L- v9 g9 F4 h* s, T - 95 }, S6 V, Q) e; H4 n
- 96}</font>
复制代码
7 r2 _3 ?# L% F1 P- 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.
" ?8 k# L) E! P" g9 f9 Q
+ f2 u" ` C9 f4 Z% i! b+ M* x$ w4 e8 z. w7 l9 }: d
4. 整体测试
! v8 g$ d- v7 Y' ~ y" u9 g本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。- 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。
" `9 O2 H& m7 ] BootLoader的下载- BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
- 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
$ c! E0 ~" z( w/ w* i4 d0 c
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功运行
' }# Z; r. i/ W7 g# h App1的下载- App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
- 同时也要修改擦除方式为Erase Sectors, 见下图5 R/ I( Z3 W% D2 I+ b
2 R* m, ?5 ]/ b- ]" s- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功跳转到版本号为0.0.1的App1
" q5 \+ T' e/ w- G5 q 生成App2的.bin文件- Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件5 L. T& u" ~9 R L2 d0 ^) [
https://blog.csdn.net/weixin_41294615/article/details/104656577- 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
- 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
7 h% X' a: d' _# U 使用Xshell进行文件传输- 打开Xshell
- 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
- 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
- 你会看到App的版本成功升级到0.0.2了.
- 如果你到了这一步.
- 那么恭喜你! 你已经能够使用在线升级了!
" k9 ?( i9 P3 n3 _ 5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。/ y# |& ]+ H/ C5 C, Y: {
+ B+ a, ?( q' q" M( G
' g; c# D7 M; t( c5 f9 S, t8 w
1 ~( B8 ^# B4 w4 [* F
6 G; t: R; d9 A& [/ |! b$ j) e4 G% n
1 }& t2 h/ | m+ d
|
请问源码链接在哪?
兄弟,源码在哪下载