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

【经验分享】详解STM32在线IAP升级

[复制链接]
STMCU小助手 发布时间:2021-11-7 18:26
简介
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识.
1. 在线升级知识
什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.
STM32中的程序在哪儿?
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码
N4UJAKKJH3O}(U8`Q@@9[7D.png
接下来就可以进入正题了.
进行分区
既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
BW0R)2)IIJ0L5HJV@~G%T4D.png
以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
  • BootLoader区存放启动代码
  • App1区存放应用代码
  • App2区存放暂存的升级代码

    5 ^$ I) \1 W0 A( s  y* C1 l
462((CSE}FTSZVZY30~ERDE.png
总体流程图
  • 先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
  • 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
  • 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:

    5 J8 y8 H/ }. X' [7 j; a
@I}5{5AT9RAE)FTXXO$ZR_D.png
2. BootLoader的编写
本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。
流程图分析
以我例程的BootLoader为例:
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示
PTF7]7~(@5[}@}_K9S4F8A3.png
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 程序跳转指令,可以参考如下代码:

    5 ^0 U( A' _# q4 Y7 i3 X
  1. 1/* 采用汇编设置栈的值 */. {/ j# ^) [1 J$ h
  2. 2__asm void MSR_MSP (uint32_t ulAddr)
    & }! T& ^& f9 F7 |9 C9 u. w9 N* s
  3. 3{5 }$ r, \6 S9 P6 B- W7 S
  4. 4    MSR MSP, r0   //设置Main Stack的值+ s0 e" Y9 S5 B4 _% _) {
  5. 5    BX r14
    ; \) ^7 w5 C5 ~- a2 k/ w$ d
  6. 6}
    & K. _4 Q1 i8 {
  7. 7* y4 e1 f4 q) g! O
  8. 80 E# K8 E0 g8 o2 I5 \* g# D
  9. 9/* 程序跳转函数 */
    ! W' g" H8 U% B+ Y& D/ W# R
  10. 10typedef void (*Jump_Fun)(void);
    " `3 r& c1 i; U  V2 ?; w) M: B
  11. 11void IAP_ExecuteApp (uint32_t App_Addr)$ L$ e2 i4 P/ q2 s! T) ]& M2 t& y
  12. 12{
    , f0 R" d; h' j* O
  13. 13  Jump_Fun JumpToApp;
    ( J1 U. {" v4 @+ }/ F- S8 s; M
  14. 14
    ' b. X8 T' J" q: u/ P, e
  15. 15  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.9 a  w7 a/ Q; ^8 @7 l9 B. f5 V2 `$ i
  16. 16  {2 F& @0 E* Y1 L! \) u2 M+ _
  17. 17    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)
    " b. {3 Q  o& O; R2 S: @& y- X  m
  18. 18    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    ; T6 V: E7 w, ?3 O
  19. 19    JumpToApp();                                                //跳转到APP.& {- L& R  e) T2 a: P6 r
  20. 20  }
    ; c! H% q: o3 W  {: F2 U! p
  21. 21}
复制代码
( p( ^: ~- i; g: n; M% M$ V
  • 在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
  • 其他的代码请参考BootLoader源代码

    * b6 X: p5 W: [$ `) E* [  `
3. APP的编写
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
流程图分析
以我例程的App1为例:
  • 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
  • 打印版本信息, 方便查看不同的App版本;
  • 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
    1 w: f: I  @/ S: Z
F8BA~T`0Q)O5(@A34)`RC~J.png
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 串口的DMA收发
  • YModem协议相关
      V, a8 w7 T4 A5 c
Ymodem协议
  • 百度百科[Ymodem协议]
  • 具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
  • Ymodem协议相关介绍可参考我的这篇教程 YModem介绍
    ( [  e1 s; `) V) j3 R- R
3 v; u0 j# E9 p2 L7 }/ Y( B4 p' {
代码分析
  • 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
  • 后面放了我的源代码, 详情请参考我的源代码.
  • 主函数添加修改向量表的指令
    . M8 r0 d: B1 o4 h
4US0$XQZB7%_7433~Z]TYL4.png
  • 打印版本信息以及跳转指令
    9 e6 J  c5 y# ]+ z
~(Z]G@U4`Q_1)EM2[PI($SY.png
  • YModem相关的文件接收部分
      \/ h/ X6 z+ A; x' v
  1. <font color="#000000" face="Tahoma" size="2">1/**' Z7 A9 _/ Y0 r
  2. 2 * @bieaf YModem升级
    0 D/ R* _+ M# d" a: q
  3. 3 *. ~3 R* e  J3 _
  4. 4 * @param none; |( D* s, i1 R4 U  f; l; M7 H
  5. 5 * @return none
    0 r4 }; T# G+ k) f3 u8 w, G
  6. 6 */8 Z. j" R) J1 Y. Z% T. Q4 ]3 E
  7. 7void ymodem_fun(void)
    : Q+ V  A) M/ O# {
  8. 8{
    * X8 e5 r8 z, s. y# e0 u7 C
  9. 9    int i;" k, @/ J3 {0 ^- ]
  10. 10    if(Get_state()==TO_START)
    ! D6 d/ U. {* z1 G+ t; p
  11. 11    {9 w5 A- e' |1 @* z( ^
  12. 12        send_command(CCC);
    ) B/ i; s' G9 k. j5 |
  13. 13        HAL_Delay(1000);' I7 W& L/ Q. L& O+ R  O
  14. 14    }
    ; t6 ]' G4 g  S5 v3 _; R6 Y" G
  15. 15    if(Rx_Flag)     // Receive flag
      T7 d' p6 n* Y8 _7 U0 |
  16. 16    {  o" l4 v! k, d
  17. 17        Rx_Flag=0;  // clean flag
    # ~; s) y! \7 p1 y- n. q' m& o; B
  18. 18
    3 i" h4 G' D3 N
  19. 19        /* 拷贝 */( y) j7 M+ ?7 |) Z4 Z) W' L$ C
  20. 20        temp_len = Rx_Len;% G- m1 o: u; v& P6 U4 S
  21. 21        for(i = 0; i < temp_len; i++)
    5 |2 ]# O. E; c& o9 ]# V6 ]1 g
  22. 22        {; Q3 n. o. Q; u0 X0 J
  23. 23            temp_buf = Rx_Buf;
    ) l" ]5 }, R$ Q. ]9 ]! H, E0 P
  24. 24        }% M7 S! g( B. W7 k. C, g& C! J
  25. 25+ k# D" @+ _$ `% p$ Z( M. W
  26. 26        switch(temp_buf[0])% I9 E: x0 b( R( S9 G/ E; L: G
  27. 27        {" ?; A1 E! P; j) c
  28. 28            case SOH:///<数据包开始  x7 s9 r/ G8 W
  29. 29            {" Z; ]% Q' R/ C, Q6 Z- }) R) q
  30. 30                static unsigned char data_state = 0;7 b5 B, H3 j; ?( x& J
  31. 31                static unsigned int app2_size = 0;
    - u% ~6 ], t6 j6 z1 M7 L" R6 Y
  32. 32                if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
    ; x& Z: w. w) @7 h, v8 N5 c
  33. 33                {                   1 q) a; T! ^: P1 |( O& @5 m
  34. 34                    if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始. r' k( X1 K6 {
  35. 35                    {8 m  G* J  s  z, ]' e
  36. 36                        printf("> Receive start...\r\n");
    1 R; K2 U9 M. J
  37. 37
    : y" v5 e6 K  `
  38. 38                        Set_state(TO_RECEIVE_DATA);4 S/ k0 t; H! `1 B$ X, {
  39. 39                        data_state = 0x01;                      ; P, U# Z: Y4 O2 X0 `
  40. 40                        send_command(ACK);0 |; O/ U0 Y3 z( t7 L
  41. 41                        send_command(CCC);; a  H2 u0 o9 ?7 M8 n' I9 d$ u! U
  42. 42& e$ s  |4 o+ j7 ], D
  43. 43                        /* 擦除App2 */                            ( z) W( o: s* M: b+ W/ s" l
  44. 44                        Erase_page(Application_2_Addr, 40);
    + Q' ?/ s3 s/ H( m1 d0 r1 S( e+ _
  45. 45                    }
    : k% X; S7 _5 G5 x" ?
  46. 46                    else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    5 O- D, Q9 _( t- t: s+ {( b( m3 ^
  47. 47                    {' k6 @- k. A% V/ ^" K  T( @
  48. 48                        printf("> Receive end...\r\n");2 l! Z( f* i, Z% N
  49. 49
    5 T1 y, I* L+ ?9 R" {; R
  50. 50                        Set_Update_Down();                      % V# ?; z/ q; |  A( C7 F+ K
  51. 51                        Set_state(TO_START);
      W3 Z1 ]1 S( k0 o* w8 `
  52. 52                        send_command(ACK);; }# t% E% I8 T) o2 H
  53. 53                        HAL_NVIC_SystemReset();
    8 |2 S% q( |3 y* Z4 T. X, K$ C
  54. 54                    }                   . u% [! }0 f/ F( H7 W3 Q) ?7 u( A6 H
  55. 55                    else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据
    ! R* J# B, E5 H5 q6 y
  56. 56                    {7 q% [5 M, o% @$ ]
  57. 57                        printf("> Receive data bag:%d byte\r\n",data_state * 128);- D: `6 j% {; I! e( m
  58. 589 c$ _- \2 U- v% d$ u. {
  59. 59                        /* 烧录程序 */9 Y0 L3 v, F* x
  60. 60                        WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);5 B2 X$ P4 Y# W% Y: S4 y6 T, V
  61. 61                        data_state++;
    + Y4 s% q4 {6 i' r# x9 C' M
  62. 62
    , y' ]$ _9 q8 b# `* O
  63. 63                        send_command(ACK);      3 a7 h1 E- g5 N5 Z: Z% @0 Z* O3 Y& b
  64. 64                    }: {6 f) `: M* ]7 u/ u) C
  65. 65                }
    # e2 ]& H( u) g9 `9 d0 |
  66. 66                else
    - B1 p: t4 G2 d  C2 b
  67. 67                {
    . o1 @  M  \( k  B9 U  q! B
  68. 68                    printf("> Notpass crc\r\n");& m# m0 |( a# L5 J
  69. 69                }( l0 L! P& \+ m2 ?/ f
  70. 70& ~  |( E1 e8 N4 H! Z, R  z0 q
  71. 71            }break;3 ^# w3 }$ t" K# |: q2 e! w
  72. 72            case EOT://数据包开始
    ( p1 o& ^* W5 W
  73. 73            {
    * O6 C5 N3 X0 e+ X4 M
  74. 74                if(Get_state()==TO_RECEIVE_DATA)
      n" r, S7 E/ u; _7 G
  75. 75                {
    3 u( V, D: V. G- C2 W  }
  76. 76                    printf("> Receive EOT1...\r\n");
    5 \4 R/ @3 o' K' g
  77. 77
    7 I  @6 [0 D$ k$ x# r1 K4 g
  78. 78                    Set_state(TO_RECEIVE_EOT2);                 
    ( }. d3 G# `& e' K6 V9 Y$ R2 ]3 S
  79. 79                    send_command(NACK);
    ) B3 [9 K8 Y& ^$ M$ \+ d9 _3 N! g% z1 T
  80. 80                }
    ! h- a2 O' i7 F+ D, R. E
  81. 81                else if(Get_state()==TO_RECEIVE_EOT2)- r) Q- S( a" o; K2 Z
  82. 82                {! k! W' b- b. L& o3 t6 j2 ~* U% X
  83. 83                    printf("> Receive EOT2...\r\n");( H. N, @2 E! ^
  84. 84
    % O& W9 b% k! @" }  U7 O
  85. 85                    Set_state(TO_RECEIVE_END);                  
    ( e. Y: h( Z$ N
  86. 86                    send_command(ACK);
    4 Q( o3 n+ K( d$ {
  87. 87                    send_command(CCC);. R: f1 ^5 O& F/ ]8 v
  88. 88                }& d$ W; b8 m, K9 l# Y$ V. q
  89. 89                else
    6 x0 q5 F2 Y, Q5 g6 I; ?: W: @
  90. 90                {9 U( `+ l$ J" H8 z! ^
  91. 91                    printf("> Receive EOT, But error...\r\n");
      P* A0 Y4 \# i0 K5 F
  92. 92                }
    ! n( T: Y& Q2 T4 j  E
  93. 93            }break;
    7 N) I3 Q9 T2 n8 Z. G8 w& J
  94. 94        }( O% R7 ]. }8 ]% O
  95. 95    }
    8 {6 Z3 m( E5 U/ r2 o
  96. 96}</font>
复制代码

8 _5 e; ^6 g0 {' Q3 C
  • 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.
    5 b; ?" f' y! C  X) B
' O7 j- H& G7 W# e0 m

; [& _1 |# e5 C2 j7 ]0 _: z. Y( n% M4. 整体测试
" [. \# H- B8 r% W( X. k/ u6 Y
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
  • 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」+ \4 T4 A, N) L$ q4 ?0 s6 e
BootLoader的下载
  • BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
  • 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)# u0 P3 y, x* R0 g% o+ i; ?
FKUV_R6NCJ[~O%FI[7J7JQJ.png
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功运行
    & e" @. }5 N" |" M! p# A
IG6)EU1E%$]R1`D$]_VWTT2.png
App1的下载
  • App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
  • 同时也要修改擦除方式为Erase Sectors, 见下图
    0 |$ a( G; e) A$ i2 w7 }( R
B$UXEB9C6553_{6OI(P`~(S.png

7 V6 s' A# t  @
_)7S6(O2B1LH)4[1IX89{`D.png
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功跳转到版本号为0.0.1的App1( \) Z) t: D4 ?- S6 L
6NGFK{CET[T`BH54T35D_)O.png
生成App2的.bin文件
  • Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件
    9 Q6 j* @: b4 e$ ?2 G5 S" U
https://blog.csdn.net/weixin_41294615/article/details/104656577
  • 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
  • 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件9 f# d3 Z7 j! b: \2 E) O' ^% ^
(YC7BN_)4QUKK1[V73{@4KP.png
使用Xshell进行文件传输
  • 打开Xshell
  • 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
  • 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
  • 你会看到App的版本成功升级到0.0.2了.
  • 如果你到了这一步.
  • 那么恭喜你! 你已经能够使用在线升级了!
    $ U% I9 O# p# P
5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。0 q8 m1 x& Y1 h
$ |, L+ U& |# l& F- l
7 k* I. I5 R& y, D" G) {
: R/ S. g7 f6 X+ u3 h0 p4 c5 y8 \6 ]5 `
; P  }% t: v7 ]* J- H$ `

" ^! z- ]8 t; [+ o1 ~+ w" n
' v1 K' r& n/ P8 i$ E  f" o
1H@F~]UEUE674KO0]$NYP0C.png
收藏 1 评论0 发布时间:2021-11-7 18:26

举报

0个回答

所属标签

相似分享

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