iap升级问题 stm32f103r8_STM32在线升级OTA,看这一篇就够啦~

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

本帖最后由 Ber_thaw99 于 2020-12-3 13:33 编辑

" \; [( t3 j4 {" a' F* W" M

: m; }% W- o8 M8 t/ Z: ?; i4 }本文是博主在学习OTA时,up主阿正推荐学习的文章,原作者leafguo,写的非常简洁明了,在获得授权后整理发布,可以在文末点击阅读原文跳转到原文章。8 Z0 {5 L0 }4 L7 p

6 H5 M5 V' a3 Q, j/ j% k简介4 e. F5 e7 |  H* X* |

) l0 \+ F5 L* T. ]5 v% }

本文主要讲解在线升级(OTA)的基础知识, 主要是针对IAP OTA从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对OTA的认识.

" Y/ F1 |! r* ~( T# l1. OTA基础知识" Y1 n  j9 Y! r, [! C7 l# T8 @& b

. x- L) ^* g% g9 B. V* L

什么是BootLoader?

( H( L+ E. [6 ?% d

) _) G, I( p1 h! d$ u( _1 s. `

BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.

" Y3 n) }: G1 fSTM32中的程序在哪儿?

# Z3 X/ m# u0 U. R

4 F3 Z  c3 m3 ^4 ?( e# U

正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码

. u* c3 s. z/ q! {. F3 u9 I* g6 u

* c9 P- _! e; i6 U% {  j7 }

接下来就可以进入正题了.

+ o* l' q: c* ?0 j进行分区0 }( h# m$ O& v6 o

9 ?* r8 ^; u  z- ]# H* ?

既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:

2 Q& A. V4 l1 [1 U

f. Q: n$ f+ J" G; Y& y

以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:

3 s# J5 b8 a* JBootLoader区存放启动代码

App1区存放应用代码

App2区存放暂存的升级代码

) T5 C) u9 e5 x" M( Q; I

* R' a! v1 o0 t: P" t

总体流程图

, `& S6 F+ X8 n9 e* R9 D/ M% z0 b- `, l+ I3 G- @+ Q

先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.

然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.

在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:

8 b& ~8 u4 t1 v6 i+ n2 i( t

2 x! N! v, z$ v$ f; F9 `# ~+ l

2. BootLoader的编写

! X# Z! h7 m- E4 o4 o+ Y+ W8 x: f( L' o

本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。

6 A- q2 y! N$ A0 n+ Y( d$ N/ Y流程图分析

/ d, q: d) Z3 {: Z4 ?! B5 v- f; K' @$ w  C0 b1 |" u' w5 l- H

以我例程的BootLoader为例:

\  G( E: K0 U- Z+ W+ h

我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

/ r) Q* M5 F. _0 w8 J" C

% T/ U& Z  a& J8 I程序编写和分析3 l/ F/ P! m& C; W. |5 [1 ~

1 N0 o1 o' d" V% J+ @

所需STM32的资源有:

. n, u; a6 D( S4 V发送USART数据和printf重定向

Flash的读写

程序跳转指令,可以参考如下代码:+ i9 M# x9 l- S. G& J* T1 l2 u' Q/* 采用汇编设置栈的值 */% \- D# ~3 x8 t8 f, ?# S/ C) H% C1 ^

__asm void MSR_MSP (uint32_t ulAddr)# @, X1 H! Y' L& K

{/ _" K8 [$ y1 s! _% |+ y

MSR MSP, r0   //设置Main Stack的值

6 e' B- W; M& Z$ c1 ]. N) B( J! r    BX r14

, Q: m8 v& I4 @( X9 `0 c% b5 c% h}

7 C; v) O4 N  h% p; G- T9 {. Q) Y6 @' q  m6 H

2 ^: U* Z) J- ?/ R/* 程序跳转函数 */7 S# A8 k5 q2 y. O5 {

typedef void (*Jump_Fun)(void);% A1 ]% p4 M" {) h3 U

void IAP_ExecuteApp (uint32_t App_Addr)

. [3 u7 W. \: i  S0 e9 J0 V6 |# e{\; o9 J7 q1 k' N  a3 a6 E; D

Jump_Fun JumpToApp;

) R8 d! u( b# U  J; Q* B0 J

: n$ _5 F8 ^5 X: Y9 z/ y" B7 ?  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.

3 b* z2 p6 |' j  {

. E" Y, M( Q9 s, \9 K- }4 c5 g    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)

( g! p0 ]- [: R# z    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)/ x  ^2 q( C* X  q/ Z5 L5 W

JumpToApp();                                                //跳转到APP.

Y  w& }1 b3 }( v+ N; m  }

& p3 J: w6 ~( J# L}

/ E% ]; [) j: ]9 d% [在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);

其他的代码请参考BootLoader源代码

8 E8 A) K% Q% p3. APP的编写" Z5 Y$ Y7 W2 c% g( T1 J. X8 Y

# V; T( g4 t7 B6 A- y

本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。9 }5 g5 G( Z& Q- J# L1 F$ P" a3 Z

流程图分析

6 F* L+ q% |- n, ?' q7 M$ N6 y+ _" C' n; W8 y

以我例程的App1为例:: j' B6 G9 W9 W9 b

先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;

打印版本信息, 方便查看不同的App版本;

本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:

! W, R% C1 S: I* V' }1 [

' i1 E, p! `: K6 O. C% j  E

程序编写和分析

# a, m6 X/ x/ g* O) t% O

( f% n6 T7 s; v) F

所需STM32的资源有:+ v2 `2 u" M* a9 E3 e4 Y  X

发送USART数据和printf重定向

Flash的读写

串口的DMA收发

YModem协议相关5 V! Y7 n; B: ]& XYmodem协议& q2 R% |9 x. C

3 i; j7 v  |4 S7 R4 s( u* d3 W百度百科[Ymodem协议]

具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).

Ymodem协议相关介绍可参考我的这篇教程 YModem介绍& x! J# f+ H& w- V" t/ l2 u2 Y0 M1 h- E. W/ a  i+ w1 M

代码分析

+ j7 ?5 i8 R( h/ u- Z1 L: ~0 E) U7 R

代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明

后面放了我的源代码, 详情请参考我的源代码.

主函数添加修改向量表的指令1 r9 e+ v5 R9 e/ d/ r

2 f1 D: C0 D. s3 K; Y

打印版本信息以及跳转指令

* N( Q1 G" Q0 x" |

4 `. f% A, a: z1 {9 a9 k1 d$ O- X. T

YModem相关的文件接收部分

6 r; I+ b+ _# D& G6 |# V) k( J/ w2 p5 H& \% g3 ~2 L  }7 A

/**

4 u; O* B) J! |- J1 G * @bieaf YModem升级3 k" H( g$ o, i, ~0 i9 B

*

4 h$ d% H( v0 m, W * @param none( }3 y, I- _% m

* @return none

. s8 _- G7 c# f% x+ ?. X( H */+ ?9 X2 n/ \2 F% b

void ymodem_fun(void)

6 i5 |# ?0 t* a! t: ^{

1 o6 {8 B& h) n$ p/ Yint i;

; U" X9 w/ [2 M* F2 ]; m% m. o6 ^7 Cif(Get_state()==TO_START)* h3 l7 R! B: B* j! d

{

! ]2 S5 ^4 N" E) ^" K0 c# Isend_command(CCC);

% i+ M5 U" o  O; o( mHAL_Delay(1000);

$ I# i: ~" Q* }}

- U4 T. G/ m2 @* [/ K4 t, |if(Rx_Flag)    // Receive flag

- q( o  _7 G* X, I1 q/ ~4 A{9 Z6 y2 L; Y* I

Rx_Flag=0;// clean flag* _1 j2 |0 X) v/ B, b

" ^% t$ m; \, H& d5 x, i9 _

/* 拷贝 */1 v* N' F7 R" U# X: n

temp_len = Rx_Len;

, g4 e$ E' Z0 H& I$ c1 nfor(i = 0; i < temp_len; i++)

0 A( U3 l- P0 U6 C' t3 i{~1 A( Z* P: m4 e9 f

temp_buf = Rx_Buf;: K. q/ ]1 F% M& e4 ~0 e$ u

}

9 X8 }" e% }; ?; v2 m$ [5 s/ ^

switch(temp_buf[0])( c1 ~2 B3 y9 G( ?1 Z+ n

{

. l* x' y& n) u! N  \1 icase SOH:///

! H' T+ |2 v. S0 s6 c{

3 U6 o* h# |, H; jstatic unsigned char data_state = 0;7 `4 }% D! U$ A

static unsigned int app2_size = 0;

0 f$ M4 j- q4 \2 e5 bif(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验

$ S) i  P. X: J* R6 r{* {# F% W  {, J4 I2 I6 R

if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始

) T# I8 p( F% c  H1 |{

6 h- }- m# L" p- E& N8 L/ x& Bprintf("> Receive start...\r\n");" Z0 Z* F$ j3 @& ?  d$ Y' j& l+ ]/ o

# k- H  \5 y1 E; KSet_state(TO_RECEIVE_DATA);

D& s1 p3 |5 X7 Wdata_state = 0x01;

o7 T, X2 F, b- w2 w6 U- W9 `send_command(ACK);

2 D8 q* k# U; A" C8 ^9 Wsend_command(CCC);- h% T; I5 G" ]4 l( B

$ o9 V+ @: S0 `+ F- c0 |/* 擦除App2 */

h! l3 @; a3 Z) ZErase_page(Application_2_Addr, 40);* Y% }; t: {) W) n- u% z4 m: c( @1 |

}9 _* Y+ X/ {8 [9 m' c: R% }8 t

else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束8 k3 M7 {1 i' E0 Q1 w; P- R/ b/ n; G$ t

{

1 }0 @* ?% N  A" M1 y6 X% f# eprintf("> Receive end...\r\n");

9 ^, z8 l4 W: t6 H8 M9 V+ _. ?8 [) w

Set_Update_Down();

% c) Y) N4 `+ F" ?Set_state(TO_START);

. y& V, V* f6 h3 Z, ^- b+ j- Y: [send_command(ACK);

5 g; E2 X+ q& D* }* q0 p# D: s; pHAL_NVIC_SystemReset();# i1 l* _, w' {* C. z+ Y+ T

}! v& q% d; ]. I( o+ [

else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据

* A4 v! x6 ^( ]3 F# S) g) J{

5 v& x. ~5 B3 i  w. tprintf("> Receive data bag:%d byte\r\n",data_state * 128);$ ]2 B. I/ m/ K" F1 W

9 G9 y' q  H8 K+ e5 d/* 烧录程序 */

v7 e% G9 v" X9 m6 B6 BWriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);

& T9 h# I6 @( L# d, ?' N+ \: g# Edata_state++;

# a) o& B; x% iX; k% m3 y' ^; x- p

send_command(ACK);

; X- e: _8 ^' s7 k* z, [}

) t* Q/ v- V8 E}0 o* D" d( G! x! H

else

! p: X; W1 y. u8 `% B; Y{7 B" l. N! K; H8 ~

printf("> Notpass crc\r\n");G/ n* F0 V2 |0 C6 b3 P

}

5 Q& \! [) d+ E: v8 M

, R/ M2 ?9 t+ w}break;

% i+ }8 r1 h6 G2 \2 Ycase EOT://数据包开始6 Y3 I) R# ^' j' _6 m$ b1 ^

{

" O- A0 \. O" a, Qif(Get_state()==TO_RECEIVE_DATA)1 S4 z& t7 H+ Z% O1 a- d

{1 u# t$ O" y' R

printf("> Receive EOT1...\r\n");

3 I; _  C) {! {, J0 h

8 I: E9 r& A; l7 k0 x% mSet_state(TO_RECEIVE_EOT2);2 m9 q- b) y( |$ Y) G

send_command(NACK);8 D" `. _* f% y) X) Q

}

' w6 A" L) C/ M! P8 C/ Melse if(Get_state()==TO_RECEIVE_EOT2)

) c! t# P; H" o2 [2 {2 F5 f{: N, T! l+ @1 p: w5 c5 v) D

printf("> Receive EOT2...\r\n");

: f( q5 ^' G# ], I: X; H) A0 W& r7 ]) x8 i* d. ?7 ?: f2 j7 s

Set_state(TO_RECEIVE_END);( k6 c$ |7 k% |- T8 {/ Z8 p+ O+ L- _

send_command(ACK);

v) \( ]# m" W; usend_command(CCC);

! g% l  G: C( \9 g}a% r# {, ?) f/ k

else4 t4 p7 @1 v: _7 S7 F2 S

{

6 k+ H4 o* D2 Uprintf("> Receive EOT, But error...\r\n");3 S) t; }. R- ]& |3 A' ]: c! x

}

2 j$ q; S4 s* t2 Q, v/ P}break;$ B; g- R+ a3 U5 {* }

}; j! f1 T% n% c5 b- v

}

, d" X0 ^- T. G}$ w5 W% |! c% `

其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.+ b% p, s7 v0 r6 z' @& H" T4. 整体测试8 p7 o9 U7 `7 R. _& J; ~( R' \

+ J6 w* p4 F, G7 Y: o0 I; {5 P4 X  G

本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。1 F. _+ r8 [* x9 o5 O/ M: K

源代码; H" U( i% ~; U5 r$ L& l% W5 O  N

2 r3 g! r5 F4 C1 Q: I. c  f

BootLoader源代码和App1源代码可以在原作者的gitee获取:6 w6 @+ R2 G5 x

https://gitee.com/leafguo/leaf_notes/STM32CubeMX/STM32CubeMx_OTA

( N4 M" H3 T, u. L代码的下载2 g, X* l" F4 A9 I/ ?' Z$ _2 X

9 i& a* y" J* K0 v6 E: O# j

由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。

- V- I; M7 K, B( O* i" v2 {8 t

8 u& c+ b7 O! E8 R4 U+ S. a

BootLoader的下载

; P* y4 P; A- @$ E8 Y' C1 [0 o/ Y$ ]& [4 p$ t# j  H

BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置

按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)1 S. q3 |( B. p

/ Y' Q6 F( E) [8 v# e" m' N

烧录代码

运行, 通过串口1打印输出, 会看到以下打印消息

说明BootLoader已经成功运行

r* _: |( T5 z5 U. K2 N

1 \* _( a  {8 D0 r, }1 Z) BApp1的下载, \$ C2 Q6 }3 h( G# t6 Z! @

8 W0 `6 T& y3 B/ ^App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000

同时也要修改擦除方式为Erase Sectors, 见下图: c5 I# n4 q: U) \; Y

- M0 ^) ]) a$ f! B; l. {9 E

E! Y& p& F2 p' b7 @

烧录代码

运行, 通过串口1打印输出, 会看到以下打印消息

说明BootLoader已经成功跳转到版本号为0.0.1的App16 m6 R2 ~8 {* a

6 b; m5 s. E) U% B6 D

生成App2的.bin文件

2 M, y5 V  K) M( ~8 A" m5 g$ _1 s  _+ N$ H& O3 z

Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件2 ~$ n6 A0 A9 o0 o7 \4 S

https://blog.csdn.net/weixin_41294615/article/details/104656577, Q7 {6 L! f9 L3 c

修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件

生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件- v( {8 ^9 _# c: n. t+ Y  t

& @9 C, l. [+ x% v. G8 i( u( c3 _使用Xshell进行文件传输

3 ]- u5 I: F! u, t1 Gk2 u! \2 O& a6 t, p" E3 J

打开Xshell

代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的

所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息

你会看到App的版本成功升级到0.0.2了.

如果你到了这一步.

那么恭喜你! 你已经能够使用在线升级了!4 K- X/ E3 T; k$ S" |6 f4 m4 V5. 总结

* H0 `1 V5 l" v: Y# e" o) A/ C1 y! x1 ~3 ~

* F# I% N7 `4 ~7 q( a2 R

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

你可能感兴趣的:(iap升级问题,stm32f103r8)