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

详解STM32在线IAP升级

[复制链接]
STMCU小助手 发布时间:2022-9-16 17:32
简介
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识.
% @5 @1 g! D7 Y! {$ X+ V
1. 在线升级知识什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.

7 X' n/ n( h% t3 s
STM32中的程序在哪儿?
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码
640 (12).png
9 }2 g0 y7 S7 @* W" [3 b. t
接下来就可以进入正题了.

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

' C9 r( J+ _/ D. V% H+ ~, M
以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
  • BootLoader区存放启动代码
  • App1区存放应用代码
  • App2区存放暂存的升级代码

    ' s$ Y  ?7 H1 i, N

    0 C3 }+ n6 \/ y5 ~8 h
640 (10).png

' h8 u1 C7 ^; p3 s! B6 J& ^
总体流程图
  • 先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
  • 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
  • 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
    ' `$ k. G! Q3 y5 A  a' E
    " J9 d4 {) l# a8 h; L
640 (9).png
' g: u* {5 g: s- B. A1 H
2. BootLoader的编写
本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。
流程图分析
以我例程的BootLoader为例:
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

' K$ ^( S3 m& _+ ~( a1 F
LF%B}{P}2R$}KQ${~)WT(}N.png

, T) G0 ^7 Z8 T* f5 P5 V1 b  H
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 程序跳转指令,可以参考如下代码:
    * S; {! w4 q$ G! h+ l

  i6 X+ C$ ?- c2 L' E, g5 ~
  1. 1/* 采用汇编设置栈的值 */& x# K" e. E: q$ h, c( u2 Q
  2. 2__asm void MSR_MSP (uint32_t ulAddr)
    / b) A5 m: [6 D
  3. 3{
    ! \# _# o* S' C* h
  4. 4    MSR MSP, r0   //设置Main Stack的值
    5 J% X% I0 A% \5 C6 W3 e6 s
  5. 5    BX r14" o- p4 C1 Q; A! c8 N
  6. 6}8 x/ e# A5 l7 ?# u
  7. 7
    8 s6 ?3 l' ^3 T4 z; l  T$ e, |) I9 s
  8. 85 h- g# U) B$ T2 X
  9. 9/* 程序跳转函数 */
    # a4 O  M2 b$ |1 u. N0 r" v
  10. 10typedef void (*Jump_Fun)(void);6 C( F# }/ j# Q! l( u$ R' a
  11. 11void IAP_ExecuteApp (uint32_t App_Addr)
    5 D: g) W  v9 l" q
  12. 12{6 k) k, L3 {& v5 y
  13. 13  Jump_Fun JumpToApp;- d9 [+ g: T* b8 m$ @! l
  14. 14
    & U& w$ S  _2 }& l; y$ \+ x5 Y
  15. 15  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法.. w: l" o1 @! s5 R% b
  16. 16  {
      l: |! Z/ m. H. s% H0 y
  17. 17    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)
    # x% [" R7 b: }; I8 X
  18. 18    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)% Y! B& {" p( L5 o+ \( m
  19. 19    JumpToApp();                                                //跳转到APP./ X/ r3 s+ u4 |- s
  20. 20  }
    1 q- t8 x& V: ]* W! {& ~
  21. 21}
复制代码
7 d& D$ [3 [" }- e! r: S) ?
  • 在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
  • 其他的代码请参考BootLoader源代码
    ; _* x5 q2 V7 @% d# I
    & k3 B2 I0 }5 s
3. APP的编写
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
流程图分析
以我例程的App1为例:
  • 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
  • 打印版本信息, 方便查看不同的App版本;
  • 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:

    + F7 L6 ?0 W, c8 \6 p9 V) M" d

    * h9 Q' p1 u6 @& H+ I0 \1 G$ R
$SV)]B2@7_(E{6GRSDK(_%S.png
, k* x7 y! h6 K2 P* x( ^* X8 e% Y7 W
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 串口的DMA收发
  • YModem协议相关

      d& h' U9 G  [8 h2 _; }
    ) |; B; z, ~$ T  g6 u% M; L
Ymodem协议
  • 百度百科[Ymodem协议]
  • 具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
  • Ymodem协议相关介绍可参考我的这篇教程 YModem介绍
    / l7 U" O5 O7 D

7 Z  f" c  @. E0 @) j7 Z, X2 y# R代码分析
  • 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
  • 后面放了我的源代码, 详情请参考我的源代码.
  • 主函数添加修改向量表的指令! o) O- {3 i& A. w/ k) T2 ~# r
1 D4 ^3 J, L2 p2 D
640 (8).png

4 m. q* T  Y3 V0 r9 z; k# g1 O
  • 打印版本信息以及跳转指令9 q2 ]9 ^7 b( `! q. M% d/ V) K( L
  • 7 L: V; b$ ^' g! M7 V  r# Y

    9 I3 J. D3 ]5 U3 C* G; v
640 (7).png

. I4 Z, Z6 X5 ~  P; X9 L
  • YModem相关的文件接收部分7 O6 q8 u. {  J0 [6 _( x9 }: V
  1. 1/**
    + }- K1 |+ b, X4 z1 w/ Q  z
  2. 2 * @bieaf YModem升级
    1 Q' O4 e: z7 h6 P
  3. 3 *
    0 K" r4 s8 I. O6 m* X* T' v) B  x
  4. 4 * @param none
    7 x7 [, A. x9 n. q9 S7 s8 |# w
  5. 5 * @return none
    + }! p. w1 {6 u- C4 _& F
  6. 6 */9 k" W! P' b. p9 {( w
  7. 7void ymodem_fun(void)
    : m* f; w' g' _5 Y
  8. 8{
    " e" v- {9 b: r" W7 j
  9. 9    int i;# C4 ^% y6 M# n; R& F6 `
  10. 10    if(Get_state()==TO_START)
    / i1 @0 A! s3 F0 d
  11. 11    {
    ( l+ i5 T: u. d" \. z! _
  12. 12        send_command(CCC);$ z. ~+ b* I' W# n6 Z; d* v# }$ K
  13. 13        HAL_Delay(1000);2 s: _: T( ~8 H
  14. 14    }
    3 Q& o$ Z# V* O' w$ L9 B
  15. 15    if(Rx_Flag)     // Receive flag) P: v* E) q: ?( J4 f8 q" n5 Q' o
  16. 16    {
      T  @: t6 D: }7 u9 d/ \
  17. 17        Rx_Flag=0;  // clean flag
    ( k8 D$ B0 @' o: E- \8 Z1 \
  18. 18
    % d9 d1 v: B/ j
  19. 19        /* 拷贝 */
    ; i& Z  P* ~$ a4 G& g
  20. 20        temp_len = Rx_Len;
    " m* p" h* [! m& X3 J
  21. 21        for(i = 0; i < temp_len; i++)
    2 {) Q8 g/ a0 o$ e+ h
  22. 22        {
    " y" P' i& c! L& [2 G. z  Q
  23. 23            temp_buf[i] = Rx_Buf[i];
    # m9 @1 F$ s* B1 c3 Y: D
  24. 24        }3 W7 D) i' c+ D! G% e4 t
  25. 25
    & ]4 g. [" L# I3 u
  26. 26        switch(temp_buf[0]): B+ [6 b. P3 c+ l. r" i
  27. 27        {% h3 q4 `" }9 a0 i" h& F
  28. 28            case SOH:///<数据包开始% W) P- ?# n+ o  w+ H: `7 ^2 G- a# ~
  29. 29            {
    - Q( q" F* D5 z7 l
  30. 30                static unsigned char data_state = 0;5 f7 B  O2 X1 e$ C, k4 Q; n
  31. 31                static unsigned int app2_size = 0;
    + x; _2 v9 l& O8 Z3 n
  32. 32                if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验9 u+ O/ x1 V# @% J0 A
  33. 33                {                   ' W; l0 H+ X' R4 S% R
  34. 34                    if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始
    * x9 n# f+ v" {7 W
  35. 35                    {0 R# j( |# t- ~' O- s0 `
  36. 36                        printf("> Receive start...\r\n");
    0 r( p; ]6 f8 j9 G6 W7 x3 }5 ^$ u
  37. 37
    9 m& A: h* A3 x2 n0 X. S
  38. 38                        Set_state(TO_RECEIVE_DATA);9 k9 Y+ q3 S7 u3 B" E7 X) R/ O4 k5 `
  39. 39                        data_state = 0x01;                      $ ~' O; I/ j( O1 N: y" w
  40. 40                        send_command(ACK);: ~, @9 f+ R  M. N3 C3 T
  41. 41                        send_command(CCC);6 Z  ]* U' u( Q& \, c. E/ o4 T
  42. 42
    - i4 W% W) ?- g) @3 N, t
  43. 43                        /* 擦除App2 */                           
    : s4 o4 Z" P9 f0 `( s6 w  t0 |
  44. 44                        Erase_page(Application_2_Addr, 40);1 O" r& f1 I" A* X/ S/ T' g/ `
  45. 45                    }% |; e) F4 K" @5 r) q6 D
  46. 46                    else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束9 U. X! `  [4 l& A
  47. 47                    {
    5 }7 V$ L. N: X1 p
  48. 48                        printf("> Receive end...\r\n");% c. v7 l- I: X0 f8 U0 q
  49. 49
    3 j: Y) ^3 d# j# E. V- u
  50. 50                        Set_Update_Down();                     
    7 O9 M( l7 u/ }" }
  51. 51                        Set_state(TO_START);
    - \' |' T, I1 V8 y8 s% I
  52. 52                        send_command(ACK);6 k6 i, J4 r' @6 R$ @6 u
  53. 53                        HAL_NVIC_SystemReset();
    8 q% Y* |7 a8 q4 D, U
  54. 54                    }                   # A( m8 q( b, Q- Z3 r; E0 V( I
  55. 55                    else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据. E5 w7 \5 q  V8 W
  56. 56                    {
    % b8 [& |; }& o2 g: t1 k; j
  57. 57                        printf("> Receive data bag:%d byte\r\n",data_state * 128);
    7 F' {% F2 Y- c  X3 P0 a* Q, n
  58. 58
    . V' g: z+ }8 {4 `4 L
  59. 59                        /* 烧录程序 */# Y- s% @) M3 \1 I# x) p/ ]& t8 q: r) g' _
  60. 60                        WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);5 ]$ O9 d% S9 {$ n/ y. H0 Q1 {
  61. 61                        data_state++;$ o( `( H' R5 o3 x& Y) \/ h$ Y
  62. 627 o) {! H9 o8 }" [; [
  63. 63                        send_command(ACK);      
    7 K' }1 O% y% [5 O
  64. 64                    }
    3 _: j. k9 P0 W. j, E6 N
  65. 65                }
    1 ]6 j) h2 `- @, Z( a
  66. 66                else
    4 I% n$ z9 D: G* c& u" T
  67. 67                {! W& U  M/ f$ }9 M/ I
  68. 68                    printf("> Notpass crc\r\n");4 U. f! o5 I9 X% S/ Y( b7 Z
  69. 69                }, h7 Q* E- _: V- y6 F
  70. 70) o+ i2 X% [/ _' O
  71. 71            }break;5 P- m9 j( B. K% E" ?; C* N3 c
  72. 72            case EOT://数据包开始
    4 b- E* Q" X; F/ F/ l
  73. 73            {* ?- w4 q7 M9 P8 Y/ q& o
  74. 74                if(Get_state()==TO_RECEIVE_DATA)
    : [# r7 }( b) L6 d$ S' ^
  75. 75                {
    9 A9 Z8 w0 s% h8 p2 F4 P* c( m3 l
  76. 76                    printf("> Receive EOT1...\r\n");0 x* {/ O- [* N" @
  77. 77& d3 p' F+ I- k  S$ A: g" P  e+ f: i
  78. 78                    Set_state(TO_RECEIVE_EOT2);                 
    & V: Z2 |, c" ?. Y- O9 N: j
  79. 79                    send_command(NACK);$ K$ g! q- c3 f
  80. 80                }. \  `! ?2 O/ {/ D2 s! @  ~( Y
  81. 81                else if(Get_state()==TO_RECEIVE_EOT2)8 P$ o3 p. P# O+ \3 L
  82. 82                {
    5 m* U: Z# ]) k8 ^
  83. 83                    printf("> Receive EOT2...\r\n");
    1 p* q* h9 C& s7 k& ~: L
  84. 842 P; |' b9 n/ J
  85. 85                    Set_state(TO_RECEIVE_END);                  
    * v. ?$ b& p3 `. H# z- t) d) ?
  86. 86                    send_command(ACK);+ c% ^4 W- S$ @; y1 E
  87. 87                    send_command(CCC);1 D7 G' w9 @1 w2 M8 j5 ]: X
  88. 88                }
    / t# D2 \8 t% v
  89. 89                else
    1 `5 q5 C6 A8 D4 E/ Y
  90. 90                {8 ?# {; R& _+ Y6 i
  91. 91                    printf("> Receive EOT, But error...\r\n");  z; F0 g1 N& G7 X! s' j
  92. 92                }
    2 g& }6 D8 e1 y- o, {
  93. 93            }break;
    2 l8 c) f, v7 U5 i3 B
  94. 94        }
    4 ^% r, J5 i# s5 G1 V; f9 X( G# L) k
  95. 95    }+ `1 O+ t& ~( {, e. D
  96. 96}
复制代码
+ N# R5 ~1 i) y' P- \5 M; I
- ~1 G: \) U- n' d4 ~  @
  • 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.0 r" X) i& l& z( w, o1 p

$ {, t' k5 w+ u2 k0 w: X4. 整体测试
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
  • 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。
    - `4 c1 b& M( x: i% r8 r
  ^1 @  q5 R" B1 x* m  E  R
640 (6).png

* |; A9 \; i4 e4 i* [. [3 l$ W* L
BootLoader的下载
  • BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
  • 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)0 w6 g! _5 w3 U) t6 s  l0 y/ Y
    5 ]! r4 I9 F% a% k- _* C
640 (5).png
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功运行
    " U5 w+ G  r7 p1 d2 H
    % S; Y# q# C( t0 q* t* ?, Y$ j! s
640 (4).png
+ L; H# D: z! [. r" |  Q  t
App1的下载
  • App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
  • 同时也要修改擦除方式为Erase Sectors, 见下图
    0 H8 y- F+ M% P6 G& a; q
  • $ I  n- z7 ?" t$ K3 V2 c( w) R

    " I. g8 R* a% ~$ x$ S  `( [& p' F
640 (3).png
+ k- {' L- z% e8 S( q1 D7 `
640 (2).png

# J% T6 @$ d- ~$ U  x% G
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功跳转到版本号为0.0.1的App1
    . Q: p9 w" i: I& F6 I) _: i* R

  • * J" n; p6 A& ^1 t# M; e
    2 g) o5 |) G% h9 T0 @
640 (1).png

9 `& ]% j4 N, L+ \3 u$ ~- b2 ~# N+ `
生成App2的.bin文件
  • Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件7 k( `) i+ }7 O
    - `  O4 J; e# ^- K' ^
https://blog.csdn.net/weixin_41294615/article/details/104656577
  • 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
  • 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
    9 i$ |8 Y2 h  y; k7 v' l/ y: b  a

  • # R- o' N! U: D+ ?2 k+ C4 Y3 B

    * H2 t5 i# B0 u% s& u7 e
640.png
: J, L( R- f. n
2 u7 l% {) R: W3 I5 Q
使用Xshell进行文件传输
  • 打开Xshell
  • 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
  • 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
  • 你会看到App的版本成功升级到0.0.2了.
  • 如果你到了这一步.
  • 那么恭喜你! 你已经能够使用在线升级了!
    , p& [% l5 x( h! E% ^

    ' J' y) d& X1 J1 F; A
5. 总结通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。
% R2 E5 n) [' f; u5 q
- W+ H  q6 N) K' B7 o
. Y8 }* F3 G7 a- }2 l6 t8 X; }. e

1 L9 U2 B/ ?/ O+ @% P; R转载自:mculover666
3 \' u, V, t$ W, }
赞 收藏 评论0 发布时间:2022-9-16 17:32

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版