你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32在线IAP升级经验分享

[复制链接]
攻城狮Melo 发布时间:2023-9-21 16:29
简介
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识.
1. 在线升级知识
, M; V9 ?: q, g; }; Z
6 @6 `! H; X  y% L
什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.
0 C( j& ^' e3 _
STM32中的程序在哪儿?
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码

) U6 k% Z( J! |! s% c: y( I
微信图片_20230921162906.png

$ z/ D/ x  k8 O- Z. K! {2 e
接下来就可以进入正题了.

4 f; y. H; R7 B# n% _/ l! W
进行分区
既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
3 Y7 U: \7 V* J& Q# u* m2 N" B
微信图片_20230921162902.png
$ h1 m$ y9 m$ {1 X
以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
  • BootLoader区存放启动代码
  • App1区存放应用代码
  • App2区存放暂存的升级代码

    ; B8 @& _) `0 C& ]
微信图片_20230921162827.png

' [6 Q& ~) _. ~' d* m, ~5 c: j4 }; V
0 e3 L/ H, s$ I, b) m总体流程图
  • 先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
  • 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
  • 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
    & V3 @# D/ m1 }! u
微信图片_20230921162845.png
( L8 Q6 \, {6 [& l9 x+ b0 e

. P/ L% F. c/ }1 s! \8 J1 |+ y2 W2. BootLoader的编写
本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。
# _. e. R* R9 e7 Y$ o2 c
流程图分析
以我例程的BootLoader为例:
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

" @, L; j9 e, u: X" O. |2 U
微信图片_20230921162842.png

6 _* f  g6 m. A" r  F2 s3 j: {程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 程序跳转指令,可以参考如下代码:
    ! S, M& L, s9 @
  1. 1/* 采用汇编设置栈的值 */: C& B- I+ B; l5 g/ w
  2. 2__asm void MSR_MSP (uint32_t ulAddr)3 `% F2 [2 [! ^+ o  R
  3. 3{
    $ {+ Q/ C+ Z& j" x" m# ^
  4. 4    MSR MSP, r0   //设置Main Stack的值
    & `7 X  a) c5 J1 r5 \' S
  5. 5    BX r14$ ^! B  A+ A/ s2 j/ y
  6. 6}  w( f" n0 I/ n) W0 [- h
  7. 72 O4 a" D5 I* g+ N
  8. 8
    0 E; h1 m2 ]2 S; J5 O. K
  9. 9/* 程序跳转函数 */
    : w  O6 I2 m9 A) j2 x
  10. 10typedef void (*Jump_Fun)(void);4 ~; ?' d+ q' y" E( P& e2 o% P' h
  11. 11void IAP_ExecuteApp (uint32_t App_Addr)
    : q+ V7 e, N% k* ]+ P8 N
  12. 12{
    + V" C1 u/ _3 X% O) g# u6 |7 W
  13. 13  Jump_Fun JumpToApp;
    4 A" m. C, _2 E- ~! j; X
  14. 14; A" d) d. o8 N, z1 `& U' s
  15. 15  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.  V  s1 A& t+ `  u: Q, b4 w
  16. 16  {/ x; X( `: O! x) O$ j7 l
  17. 17    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)& L# t! \6 Z) {4 i, p& \2 z" c) u
  18. 18    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    ! u: n9 ?2 ^# c5 p0 V' E
  19. 19    JumpToApp();                                                //跳转到APP.
    , z$ |3 @& S; e
  20. 20  }
    + \5 a: ]7 o5 S8 ?9 l8 `
  21. 21}
复制代码

/ U7 u. O+ y, ]$ g- [) u
: ]$ y) i0 l$ m
  • 在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
  • 其他的代码请参考BootLoader源代码

    5 H. {2 e1 y  O* G! K* }
3. APP的编写
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
+ ?! W8 V& F1 R- E5 A9 l6 J( L" d
流程图分析
以我例程的App1为例:
  • 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
  • 打印版本信息, 方便查看不同的App版本;
  • 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
    1 m; E) _6 G9 N1 Z
微信图片_20230921162839.png
1 j/ I8 }! L" D8 k. G7 b
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 串口的DMA收发
  • YModem协议相关

    % ~# s& k/ Z4 C& b
Ymodem协议
  • 百度百科[Ymodem协议]
  • 具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
  • Ymodem协议相关介绍可参考我的这篇教程 YModem介绍

    9 M" |, x6 g% h5 A  j1 v% A
. W2 m1 R) ?8 [( a1 Y  F: n5 H  h
代码分析
  • 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
  • 后面放了我的源代码, 详情请参考我的源代码.
  • 主函数添加修改向量表的指令
    3 R5 G; m0 H% K) I& K

# r+ b2 v4 Z% l1 D
微信图片_20230921162836.png
) w1 Y) o/ u, g9 t% L
  • 打印版本信息以及跳转指令* K" E2 H  H7 X; U2 N: H# G& u
# D2 Q+ p5 x! i' t& V* w* S1 s
微信图片_20230921162831.png

# d/ c: Q7 C& Q; C/ G7 W
  • YModem相关的文件接收部分
    ! H$ F2 R  B2 w- I, z( f
  1. 1/**
    , z# [  w) z. K$ O3 S( w
  2. 2 * @bieaf YModem升级
    7 Q" {- S% p) W! K6 W
  3. 3 *
    6 E  w$ p- _7 q: I+ \/ `
  4. 4 * @param none8 f7 M/ a/ X! S
  5. 5 * @return none7 c" @/ R. u( S9 H, D
  6. 6 */
    - r. V1 _6 X0 z1 ^5 ]8 S. e
  7. 7void ymodem_fun(void)7 L5 ^1 b' G5 E& u
  8. 8{
    8 W, e& R- j* h1 _
  9. 9    int i;
    " {6 F7 ]9 O. D" b9 ~3 e
  10. 10    if(Get_state()==TO_START)) i7 A. v, S# P$ ]
  11. 11    {
    1 w: |9 s' o! E1 K# Q: ]+ j
  12. 12        send_command(CCC);  F$ }4 J7 ]0 Y  _
  13. 13        HAL_Delay(1000);
    : e" _: ^4 f  g5 G% u" R
  14. 14    }
    ) ~7 e. Y% P4 h2 p& F2 z# o  M
  15. 15    if(Rx_Flag)     // Receive flag
    3 ]6 [# O) A& U* Q  N- ~1 F! Z
  16. 16    {
    ; q1 p$ t  M& ^+ |, {" f; S0 f
  17. 17        Rx_Flag=0;  // clean flag
    * H8 N# g! g: w: h
  18. 18
    - P( P6 j  f( ]
  19. 19        /* 拷贝 */
    3 F5 n* f+ ]( Q
  20. 20        temp_len = Rx_Len;
    3 S5 T; C  c# A3 L
  21. 21        for(i = 0; i < temp_len; i++)
    5 Q1 p0 H9 I6 t6 F
  22. 22        {# f# Q; d- u4 i8 [' _
  23. 23            temp_buf[i] = Rx_Buf[i];" E7 p5 m: O. v8 }2 R
  24. 24        }
    % h7 I7 u, v7 _- D2 ]9 f
  25. 25
    4 I1 i2 C  N  ^$ a) u( _$ Q
  26. 26        switch(temp_buf[0])
    ( w. [3 @1 ~9 s
  27. 27        {
    ) L4 e6 |8 p& ?& ?" }5 }
  28. 28            case SOH:///<数据包开始! ?; s5 J7 D) _  \4 [8 T3 S( |
  29. 29            {6 z# T% Q8 \$ f* f0 c; r
  30. 30                static unsigned char data_state = 0;6 m0 `% |) T  D$ ]
  31. 31                static unsigned int app2_size = 0;
    / w4 ~3 z1 z. k; h$ ^6 T
  32. 32                if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验& N8 q7 i# p3 C: y" b. |
  33. 33                {                  
    + P8 ~2 A) N# q9 k7 r( D. ^
  34. 34                    if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始& C# W8 U# S; v# j1 b9 X
  35. 35                    {
    + L( q2 U2 n# j( F2 _$ G, L! d
  36. 36                        printf("> Receive start...\r\n");
    5 K& U) o0 t. O: J  [
  37. 37
    $ |1 @/ W0 B, s; k# Q' a+ v1 O* Q1 v9 g
  38. 38                        Set_state(TO_RECEIVE_DATA);
      b3 B" [7 X, ]! ^
  39. 39                        data_state = 0x01;                     
    9 o+ x! g8 M- D6 {* X
  40. 40                        send_command(ACK);! y" I) Z+ |8 b- j( x* k) t
  41. 41                        send_command(CCC);9 b) p: A+ a2 W9 z" E- O
  42. 426 H7 ~! K8 {, \8 H+ O. W' z
  43. 43                        /* 擦除App2 */                           
    # W5 V6 l1 _$ m# y# g
  44. 44                        Erase_page(Application_2_Addr, 40);
    : c& E% }7 W5 z2 q4 v8 V
  45. 45                    }
    6 P6 U  B& C& f$ V
  46. 46                    else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    & x2 J- M1 U5 E! O
  47. 47                    {+ C* I1 Y0 z0 \7 ]
  48. 48                        printf("> Receive end...\r\n");
    8 m3 w" C0 r. F+ @% V
  49. 49
    . @! w% d2 r6 l# C, _. {
  50. 50                        Set_Update_Down();                      : u0 D% A' Z( h0 D! ?" E4 `
  51. 51                        Set_state(TO_START);
    : N0 ]4 w0 q7 J6 H# `
  52. 52                        send_command(ACK);
    " N( ?2 X% \  p9 |
  53. 53                        HAL_NVIC_SystemReset();
    " G$ p; A, b% |- b% C' g: A8 N
  54. 54                    }                   ( |4 \9 d8 x" ]5 _
  55. 55                    else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据3 T" b' _; d" T/ e1 E$ D; |/ N- m
  56. 56                    {5 n% W4 L1 n5 y) e5 F) Y6 b) N; q# C1 T: i
  57. 57                        printf("> Receive data bag:%d byte\r\n",data_state * 128);
    7 u) v& K( k6 U+ P/ H3 E, ~9 m* {- O
  58. 58, G5 z7 J  A2 _$ A+ t6 g: s
  59. 59                        /* 烧录程序 */# ]  K6 M6 Q2 }! N7 j! V! ^3 Q
  60. 60                        WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
    : l9 s" u3 _# M# @6 c% I4 O) X* e
  61. 61                        data_state++;' \4 h! c% o: d$ }
  62. 62! Y3 t+ ]6 Q( f4 h4 v
  63. 63                        send_command(ACK);      " D; _  a1 G7 Y! s
  64. 64                    }& [4 \8 x: n0 c# V! q
  65. 65                }: u0 a2 W' p  o- ^& m2 p9 N% |
  66. 66                else
    ' ]5 u( g" f6 y  k& U2 ]
  67. 67                {
    6 o2 g3 ]. ?4 q$ H" `2 D$ T" l; |+ ?( Y. w
  68. 68                    printf("> Notpass crc\r\n");
    3 U0 s" ?( K5 g$ Q# ~2 M, V( B
  69. 69                }
    # @2 j9 e) m1 C3 \5 Q8 I: |
  70. 705 b9 B2 q7 L- G) y8 b9 j
  71. 71            }break;
    . ~; G9 ~% U% v
  72. 72            case EOT://数据包开始
    8 [# {: [& \% ?5 w. n; S
  73. 73            {) g  A* u. g1 ]2 U" ~
  74. 74                if(Get_state()==TO_RECEIVE_DATA)3 K! v* M1 c( \" t- \
  75. 75                {& F. s5 j7 I& F* k- n2 q/ K
  76. 76                    printf("> Receive EOT1...\r\n");
    5 d$ k5 U/ |$ {: k
  77. 77
    / ]8 ~) T; R+ l0 a0 f1 {# I2 I
  78. 78                    Set_state(TO_RECEIVE_EOT2);                 
    $ }+ }0 q6 f& u; a6 M9 Y! w
  79. 79                    send_command(NACK);; D' F6 J8 K" ]1 G
  80. 80                }
    ; Q* z% Q5 Z# T  L
  81. 81                else if(Get_state()==TO_RECEIVE_EOT2)2 n+ W* G  n2 @, P# L) F5 k& F
  82. 82                {
    ( Y: h' h  j$ u  |8 o! g
  83. 83                    printf("> Receive EOT2...\r\n");
    4 m$ P0 E( V" j* _- @0 \  q/ F5 Y, v* x
  84. 843 A4 f& q& l4 T! i2 ?
  85. 85                    Set_state(TO_RECEIVE_END);                  3 B# x1 y3 a7 {3 j# M6 |( o% _
  86. 86                    send_command(ACK);
    # e8 e) Z; L( j. g4 |3 S3 F
  87. 87                    send_command(CCC);( p; K" k  ]! k) Q( e' }
  88. 88                }) k& g) ]* `- [! X3 G  L( z% [
  89. 89                else2 D# |+ r8 W5 Q+ y5 m+ Y
  90. 90                {5 e" ^. f# [1 Y: P
  91. 91                    printf("> Receive EOT, But error...\r\n");
    5 S8 l2 w% {% Q0 R
  92. 92                }. \2 a; R! Z7 z! [! f+ W0 A
  93. 93            }break;
      e; A7 [1 v/ P. |
  94. 94        }. J) T8 P/ K( \4 J1 G
  95. 95    }
    - X3 b- M4 c* S- w% O5 b
  96. 96}
复制代码
& G7 i" @& ]5 C$ G8 h

/ l" l: n* X; m* Z, L
  • 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.- e/ h7 `7 P. Y0 C: D

) I8 t: U- E  {3 X  p- m9 x2 a  S

& m1 M6 M& ]% x# g/ C' r+ K4. 整体测试
: j8 x0 X& V% k. `1 f本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。7 ^0 X$ f+ d: U7 ?

! A9 |) N  [) x( @

  ]; f0 ~* Z5 e1 A6 `4 Q源代码( o- H; `; T  g% d4 S2 g" q
BootLoader源代码和App1源代码可以在原作者的gitee获取:
  • 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」
    ; s( ]; ^5 S% k5 _# P
' C; ~4 B7 s- V3 ~$ L
7 J3 {7 h2 C1 ^- n( e
BootLoader的下载
  • BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
  • 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
    # K& v1 m6 e1 }5 Y6 Y) t
) e7 T! l! n/ K
微信图片_20230921162823.png
% E; z1 ^+ e& H" r
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功运行) P* f) q: l9 ]1 t7 Z
& |# J! r8 Z7 V3 S' f) y7 v
微信图片_20230921162819.png

' \4 U: e7 j& F& q* f$ g3 \App1的下载
  • App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
  • 同时也要修改擦除方式为Erase Sectors, 见下图% v2 K& w7 B- D( C8 R, n! a$ d
8 ^1 c) H! b, o) }; j
微信图片_20230921162816.png
% e  |( z# |( ?! J4 b3 C
微信图片_20230921162810.png
& g1 ~! @  {- Y2 X: r
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功跳转到版本号为0.0.1的App18 E% @% l1 n4 R: {& g: W; S
( U2 H3 ?4 }" V( J+ X
微信图片_20230921162807.png
( }8 t6 K, {+ k; Q
生成App2的.bin文件
  • Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件* Q+ ^8 H$ ^2 ?
https://blog.csdn.net/weixin_41294615/article/details/104656577
  • 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
  • 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件9 T% |, O( m) S" q, o
* y3 Q( X0 z# [% l
微信图片_20230921162803.png
( c2 `$ [" G1 n) N' z; \
使用Xshell进行文件传输
  • 打开Xshell
  • 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
  • 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
  • 你会看到App的版本成功升级到0.0.2了.
  • 如果你到了这一步.
  • 那么恭喜你! 你已经能够使用在线升级了!7 a8 M3 b5 e: P9 r+ w! B
6 y) x  |& }- ]) u% o! n
0 T# A6 c1 F( m
5. 总结' p) c$ ]: r  R
通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。: m" n! N9 r4 ^
: E/ M1 x3 F6 g4 \  I
0 N, Y" X# V2 m* }+ c6 O' G+ o
# s/ a; T1 Y  B/ Y$ c( N5 I
转载自: ARM与嵌入式
3 L4 ]2 I0 p+ ~) T  w' `  V: T如有侵权请联系删除2 Y$ P% ~+ F& @
& o/ Q2 N# ~0 a$ X9 ?# L

6 L) k! q$ g1 g: j; n' r  m7 E
' |+ u7 T. i. j% |, S1 Y
收藏 评论0 发布时间:2023-9-21 16:29

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版