|
简介 本文主要讲解在线升级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区存放暂存的升级代码
. T: k. u7 f# z" n/ z+ N) X$ ` 总体流程图先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序. 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序. 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示: ( Z7 _- O+ H3 s4 [2 }
2. BootLoader的编写本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。 流程图分析以我例程的BootLoader为例: 我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示 程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 程序跳转指令,可以参考如下代码:
* S; W6 U4 g8 X& O. ^* u# A2 U. n& l) N
- 1/* 采用汇编设置栈的值 */
2 p$ o3 M) N6 X% o. Z! | - 2__asm void MSR_MSP (uint32_t ulAddr)9 P0 l( {3 i1 t2 y! L" o
- 3{7 I; i* D# C9 Z% ^7 }
- 4 MSR MSP, r0 //设置Main Stack的值5 Z- D/ K- w" [- W6 j3 T$ a
- 5 BX r14# L5 |9 z O" u/ B' A% F
- 6}
* [. y. r4 r( m8 P. u) O2 B - 7- |0 S* I+ Y# U
- 8
/ w: s) G; X, C/ t8 w - 9/* 程序跳转函数 */* k8 A1 s6 K3 K& i4 g1 n
- 10typedef void (*Jump_Fun)(void);
( ~& N' B" e. y - 11void IAP_ExecuteApp (uint32_t App_Addr)
( M5 A/ H9 _& S# b' i - 12{5 U) @4 k& F7 [) Q
- 13 Jump_Fun JumpToApp;
- j' i) l: E8 u% r - 14
( ?) z9 Q+ q% g" S - 15 if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.7 v% ]( ]) F, c, {0 t) w! o
- 16 {
6 U: }4 J; W V: A M% B/ w; a - 17 JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址)
: \6 {. \3 H' H: a; T9 { - 18 MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)3 A1 E- i2 Z2 n
- 19 JumpToApp(); //跳转到APP.
' D3 @6 k) o0 T# M `# e/ c - 20 }
. _4 ^$ b9 A7 o+ u; a - 21}
复制代码
! a) i$ r( T; t' H 3. APP的编写本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。 流程图分析以我例程的App1为例: 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题; 打印版本信息, 方便查看不同的App版本; 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示: v; n$ Q5 Y1 `$ \: R
程序编写和分析所需STM32的资源有: 发送USART数据和printf重定向 Flash的读写 串口的DMA收发 YModem协议相关
+ }7 K( Q* b, s& p a Ymodem协议
8 ~: O: ^' |' ^9 P0 s6 A代码分析- 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
- 后面放了我的源代码, 详情请参考我的源代码.
- 主函数添加修改向量表的指令
8 Z) _- A8 k6 \& F
- 打印版本信息以及跳转指令0 \ ^ j9 R- o) T4 y7 Z0 [; Z. z
- YModem相关的文件接收部分, N' k; m$ J& f
- <font color="#000000" face="Tahoma" size="2">1/**
; X* }$ Z: Q D - 2 * @bieaf YModem升级
" Z8 R8 x* i( e" H - 3 */ R$ S3 c! |4 q7 u7 R$ l( z5 Y
- 4 * @param none
4 `& W( h% c# R" q9 a6 h, C - 5 * @return none Z2 B4 k/ g/ L6 Y
- 6 */6 [ [$ I5 K: U4 ^3 q' u/ f
- 7void ymodem_fun(void)
* S: G# y# D- `3 g. O - 8{
# Z) Z4 ]1 P+ ~# v+ E4 l1 I - 9 int i;
; B2 e& o- U' w' D9 R" k - 10 if(Get_state()==TO_START)% a* u$ ?. g, H3 k
- 11 {. ^ A8 |) L9 r# F( f5 u
- 12 send_command(CCC); t8 c( I2 m9 h- o/ K
- 13 HAL_Delay(1000);
5 D$ D r" ^% {' O) I - 14 }5 I$ i% p% O1 m; @) t
- 15 if(Rx_Flag) // Receive flag* V2 s+ |: m6 z- i
- 16 {
! F# a" g+ n, r7 ?) i - 17 Rx_Flag=0; // clean flag- Y% W. x( l7 _2 j
- 189 b2 F/ P+ i$ i; I) |, w
- 19 /* 拷贝 */
- Q h2 {( h# c5 w4 q - 20 temp_len = Rx_Len;1 b8 w) d- |( j: e3 g
- 21 for(i = 0; i < temp_len; i++)
5 j. x6 D8 q' J9 @5 u" t K9 D - 22 {7 o N7 K3 Z+ P
- 23 temp_buf = Rx_Buf;. x4 t: m( F" ~+ s& t: a3 Z& O
- 24 }
8 D3 a( t% \& | - 25
f, W; o, C7 B7 i - 26 switch(temp_buf[0])
4 |+ ?0 N' } Y! B( o - 27 {. y# J9 Q I+ l) N2 O
- 28 case SOH:///<数据包开始
" {0 k1 M/ O! S, o - 29 {
- f' k: Z" u- y - 30 static unsigned char data_state = 0;
z% f. u. K2 a8 K6 F0 @ - 31 static unsigned int app2_size = 0;0 G* H# S( V2 b: F4 r* [' s J
- 32 if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
- J* M6 K5 D& Q& u1 B; u& q4 c9 z - 33 {
. W" J- z! o1 {% C" C4 E( \ - 34 if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始7 o3 x1 A7 _- y1 s( U
- 35 {
8 C. E, ^6 [& b: v - 36 printf("> Receive start...\r\n");" b+ p4 ?9 @2 L1 O. a" w# n
- 37$ Z; w0 U0 M) j/ u8 k+ Z
- 38 Set_state(TO_RECEIVE_DATA);* k2 s9 U/ {+ u+ R2 w3 _
- 39 data_state = 0x01; 0 L+ I) o+ Q6 n. Q9 S
- 40 send_command(ACK);% B- L4 |+ V" |7 ]3 b8 i
- 41 send_command(CCC);
0 m! c( l7 `) M: r - 42
( N7 x" R! c1 H - 43 /* 擦除App2 */
# R; W/ l" j* D3 Q; B3 u( C - 44 Erase_page(Application_2_Addr, 40);
/ t {7 G+ }+ K, N) V/ | - 45 }! A0 \, f( c: D8 y" R
- 46 else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
3 G6 _# {1 S+ W. B - 47 {' V% s# f/ Q+ Q- P. `. r) z5 {; B
- 48 printf("> Receive end...\r\n");' \$ x* O3 G/ U. U6 j
- 49$ S9 A. G0 N# j+ j1 s9 N4 I3 a
- 50 Set_Update_Down(); 9 p9 s' A% w* _9 ~) k! R# M1 N6 s
- 51 Set_state(TO_START);
; A$ }) F) |% k2 h - 52 send_command(ACK);) S9 M3 v1 g) Y
- 53 HAL_NVIC_SystemReset();
- A" W4 r2 T% X5 P - 54 }
* p6 I3 W T y, b - 55 else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
6 w7 W" @; \' x6 R, c# ]1 U3 S! |* B - 56 {& K( \$ y8 E) R
- 57 printf("> Receive data bag:%d byte\r\n",data_state * 128);: z# H8 Q/ H, l$ w
- 58
7 I/ r" k) J! k7 e3 j* L - 59 /* 烧录程序 */
0 P' Q7 W8 A8 F/ i4 u - 60 WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
. n# _5 D1 V! u$ O4 F - 61 data_state++;
1 M6 [& U) _1 M - 62
5 [2 {' o) r6 g4 M3 ^; [# q - 63 send_command(ACK);
" c& ]/ _3 c$ l' j$ ^5 ^7 C - 64 }$ _* F$ f, e6 ^6 z1 i* t, @" `8 F
- 65 }
- T# D' e+ I* V. U7 U8 r+ l# I - 66 else
1 _. e+ ~2 {2 `7 E- b) {3 V$ d - 67 {& F1 b/ e+ N' y! L& J
- 68 printf("> Notpass crc\r\n");
: _9 S3 P0 f3 V - 69 }
+ T7 u/ U' C& q0 H# V, g# ]9 E: M2 d - 70
! z- ~7 X7 h" r0 p- ?' |6 R5 f - 71 }break;7 q: c O" ]4 ^- u% i" a! f
- 72 case EOT://数据包开始# J' Q9 y% ]5 {9 x" X% j
- 73 {
, w. C( B& u* ?- z2 S% h - 74 if(Get_state()==TO_RECEIVE_DATA) V8 L4 f8 N% R7 u
- 75 {0 P8 m6 z: Z1 z* ^ R
- 76 printf("> Receive EOT1...\r\n");$ R9 u6 {$ s. p% m: H8 _2 g& p2 J
- 77
) [! k+ h8 W: L- @6 w+ v6 t - 78 Set_state(TO_RECEIVE_EOT2); $ A0 L4 K6 G8 J: B, U
- 79 send_command(NACK);
! d0 U v0 F; N0 s8 M% q0 G - 80 }
( U$ ]% }9 y% Q& ]7 s1 e - 81 else if(Get_state()==TO_RECEIVE_EOT2)
: J& f/ k: s0 i" i5 Q; j - 82 {" Y* _2 n+ u+ J b5 q3 m+ t
- 83 printf("> Receive EOT2...\r\n");2 e5 s$ v' ~+ \' X4 Q4 {3 b* g
- 84
* f' n4 a* G5 j3 k3 A: {) O - 85 Set_state(TO_RECEIVE_END);
1 ~! J. j5 {7 H2 Z& S6 T B% [ - 86 send_command(ACK);
, t; H9 O) t. @6 @ - 87 send_command(CCC);
( b+ d) z$ M: @6 m8 h0 A - 88 }8 T# E( j" C; D' J* i6 L, j) k
- 89 else
' N3 M/ |; Y' f/ p3 a5 A+ j - 90 {
; O: |, G+ P0 l& ~/ k8 {5 x% h - 91 printf("> Receive EOT, But error...\r\n");
! L% ~3 E. O! Q6 ^4 n - 92 }3 [$ u4 s' M2 t/ i" v0 y
- 93 }break;
/ Z7 F$ S* V- h9 t - 94 }
7 G9 s% N6 b) H% O/ p - 95 }) z) u0 Y0 y0 {3 Z- t
- 96}</font>
复制代码 : M, W& F, K6 l4 Q
- 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.
% f2 v# \7 e8 p5 [! ] n e+ G( k% ^! p0 u) q& Y
. e6 L+ z, c2 a5 R4. 整体测试
: c! k. p$ h* N9 o本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。- 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。
]# z( T( B u: e3 q7 }0 k* L BootLoader的下载- BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
- 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
& q6 F% F1 |6 e4 h* T* f
- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功运行; m0 M, a; d% B& |: e# E( T
App1的下载- App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
- 同时也要修改擦除方式为Erase Sectors, 见下图" Y$ k( ^% l( h( l7 s j
1 e% V3 X, C& {& |7 z( u- 烧录代码
- 运行, 通过串口1打印输出, 会看到以下打印消息
- 说明BootLoader已经成功跳转到版本号为0.0.1的App1
( x7 F' o& e7 r6 Q5 F8 V3 o1 X 生成App2的.bin文件- Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件
q0 \% Y, _* @ https://blog.csdn.net/weixin_41294615/article/details/104656577- 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
- 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件( ~+ B) }' K& d4 l) D" e. n
使用Xshell进行文件传输- 打开Xshell
- 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
- 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
- 你会看到App的版本成功升级到0.0.2了.
- 如果你到了这一步.
- 那么恭喜你! 你已经能够使用在线升级了!$ l' V" t* [# v9 m- ^
5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。$ T% ~* }# V( o
$ F8 E' H( K% i6 p
; Y2 m% @( p$ l2 [# Y* L8 O# W4 f6 Y# O! [
8 K8 G% O4 O$ g5 c
6 O! G6 z! J' g& U7 d. l% w+ B) S* @ K: r$ B3 N
|
请问源码链接在哪?
<br /> <br />
兄弟,源码在哪下载