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

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

[复制链接]
攻城狮Melo 发布时间:2023-9-21 16:29
简介
本文主要讲解在线升级IAP的基础知识, 主要是针对IAP 从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 帮助大家加深对在线升级的认识.
1. 在线升级知识  ]* n4 _( d7 m5 L

  F3 S, D8 g! c! ]7 [( t1 X( L9 i什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.

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

$ J, y8 f  s* s- j: D5 t# @) X2 a
微信图片_20230921162906.png

0 Q* |8 W5 L, q: M& V, N
接下来就可以进入正题了.

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

' C/ k( W2 v" T8 j# h8 T
微信图片_20230921162902.png

8 R7 B$ U2 K* {( h+ l9 ?
以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
  • BootLoader区存放启动代码
  • App1区存放应用代码
  • App2区存放暂存的升级代码
    $ x( h* E# |6 y6 m- V
微信图片_20230921162827.png
; u' \# L4 ?5 t+ b& i

) n* Y% d0 T' |* \8 ], o& T. m& c总体流程图
  • 先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
  • 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
  • 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:

    % \; @( g; M4 j4 k- b5 C# i( f3 B
微信图片_20230921162845.png

7 ~. \8 K% b. Y7 [4 Y# [% Q
, T' L1 C( i3 u  I5 c7 m% V# o2. BootLoader的编写
本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。
2 _4 |0 v# J8 B, V& g
流程图分析
以我例程的BootLoader为例:
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

( |' p( S+ W2 a9 N7 I
微信图片_20230921162842.png

, @6 r1 B+ p; E1 X/ X程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 程序跳转指令,可以参考如下代码:
    / g, c  e/ S' a; z: i; L
  1. 1/* 采用汇编设置栈的值 */
    1 [' f9 O0 @* n' l/ f2 T
  2. 2__asm void MSR_MSP (uint32_t ulAddr)
    1 b( {4 D) \7 N; _& A8 E3 K
  3. 3{
    5 |  k* a0 a, [) q+ o: Y
  4. 4    MSR MSP, r0   //设置Main Stack的值
    ! \- [( _7 Y' H, L; z8 I" _; y% l5 n
  5. 5    BX r14
    ) z$ U  b3 S0 ?. ^4 b
  6. 6}
    ( u8 r9 j: s: q& o$ E" R5 F( {3 t
  7. 7. H/ J2 C. H/ w
  8. 8
    # V; R0 t3 s8 `
  9. 9/* 程序跳转函数 */
    + ^( @/ W7 w: @# S
  10. 10typedef void (*Jump_Fun)(void);
    6 E0 H6 a1 S( N6 q/ A& U
  11. 11void IAP_ExecuteApp (uint32_t App_Addr)
    - ]/ M0 g  A0 q5 Q) @4 k# v2 u
  12. 12{4 v' V/ L" y) d1 z- I: m, S
  13. 13  Jump_Fun JumpToApp;5 K/ _% r7 X2 u! @- Z
  14. 14
    1 `1 A  Q3 `0 n. I; q6 x2 u( R
  15. 15  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法." g' \: _" m6 f/ G/ g- g
  16. 16  {
    8 w& b7 J$ M' c
  17. 17    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址). ~3 X% P% n1 c
  18. 18    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址); U9 _8 [0 }  B0 b
  19. 19    JumpToApp();                                                //跳转到APP.2 @; @6 Z* ]: H, `! Y+ F- @
  20. 20  }7 G' {+ }; B7 v3 E; H* k
  21. 21}
复制代码
7 r) g9 B% l! F. [

0 E; u+ S* A- Y1 B/ G
  • 在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
  • 其他的代码请参考BootLoader源代码
    4 Q# m0 K  O# h/ r; ~8 i
3. APP的编写
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。

2 `! G% x- M, C5 y
流程图分析
以我例程的App1为例:
  • 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
  • 打印版本信息, 方便查看不同的App版本;
  • 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:
    " _; v4 M& `( o" D1 S" J( Q6 r8 q' O
微信图片_20230921162839.png

4 @; D- [' L5 ~7 a; A$ k: C& }程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 串口的DMA收发
  • YModem协议相关

    . x  s- m5 {! E0 I2 Q3 f) @
Ymodem协议
  • 百度百科[Ymodem协议]
  • 具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
  • Ymodem协议相关介绍可参考我的这篇教程 YModem介绍

    8 [* X6 w( N* L, z. i
* E+ d& _# p4 s/ @  T" X
代码分析
  • 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
  • 后面放了我的源代码, 详情请参考我的源代码.
  • 主函数添加修改向量表的指令$ k8 o/ A6 v) G. p" [) l/ F

! z3 P+ T- n9 t" @: @( _. D0 c
微信图片_20230921162836.png

  @* ]) R  b# _4 ?4 C( Q4 S
  • 打印版本信息以及跳转指令
    " a/ @3 Z5 L( H2 H9 B5 Z

. B7 F7 F2 u1 a& u6 l! n) S
微信图片_20230921162831.png

$ n( f( o+ K" T
  • YModem相关的文件接收部分
    2 A4 i- U, L2 T2 O5 X' F: |
  1. 1/**7 f3 S6 M* e  r/ ~& n' m! B, t
  2. 2 * @bieaf YModem升级9 F( u+ z  m. R2 q6 X
  3. 3 *( P* q8 M, P( d5 g  N% p
  4. 4 * @param none
    : X( M4 b) h" R5 R& c: ?
  5. 5 * @return none; [* s' H: t# A
  6. 6 */4 X! j  S9 h! q- n
  7. 7void ymodem_fun(void)$ u  [% R8 p: C# J( K5 b/ g  D- S
  8. 8{
    7 W/ D- x4 b1 Y* q  W
  9. 9    int i;
    - I3 {& W8 H: N; S2 J1 r8 U
  10. 10    if(Get_state()==TO_START)
    1 H5 t: U4 Q3 e  f1 W2 w  E
  11. 11    {
    " N( D) `0 [) C- h9 U$ W0 E
  12. 12        send_command(CCC);
    1 x) X" B& B- c/ q
  13. 13        HAL_Delay(1000);
    7 p3 p5 O  S) w2 e0 }5 V% h
  14. 14    }: N, t( s6 ?: E3 n! x  Q, r
  15. 15    if(Rx_Flag)     // Receive flag
    6 m' L4 h  C1 x  \$ d
  16. 16    {
    & z+ f- b5 P  U1 M2 _
  17. 17        Rx_Flag=0;  // clean flag
    9 ^/ \* H0 U" K" D2 f' G6 w
  18. 18- v! h3 u9 J7 j/ K) m
  19. 19        /* 拷贝 */" S% a6 s3 _0 K  D2 k
  20. 20        temp_len = Rx_Len;
    % a( n6 _  _( z% @9 P( {
  21. 21        for(i = 0; i < temp_len; i++)
    / y# y0 |7 B+ g1 @1 ]9 c2 k
  22. 22        {
    % j3 v) P/ i) K# G
  23. 23            temp_buf[i] = Rx_Buf[i];
    ! Y% K- f" ?  ]) ?0 f0 {# e
  24. 24        }
    ( y- Z9 [) J/ ?2 I; _" {" w1 v
  25. 25, A0 U" {& l8 @% c
  26. 26        switch(temp_buf[0]). c6 _8 T; e" t0 {7 F
  27. 27        {$ Y( }% ~: Z+ \; ]1 v5 t7 T/ T9 y
  28. 28            case SOH:///<数据包开始
    * _% t" Q" P8 C9 l! A
  29. 29            {
    3 d8 f' S$ e8 k; @( c
  30. 30                static unsigned char data_state = 0;# @: Q& e8 K6 s# F. N% P
  31. 31                static unsigned int app2_size = 0;
    ( r: E$ N4 @2 P: \+ C
  32. 32                if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验8 \+ E1 x$ Q, n
  33. 33                {                  
    4 y* |7 d1 I- g' }8 y  p! i
  34. 34                    if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始2 C- d' _% i" z# m( r( w5 ^
  35. 35                    {' Y$ F% q+ y. \9 K0 Z; C1 H
  36. 36                        printf("> Receive start...\r\n");* I3 a2 |0 S% h/ t& `) r
  37. 373 D# j& ~! X9 n; E( {# h) T# {
  38. 38                        Set_state(TO_RECEIVE_DATA);
    ( O) a) h1 U, k: H
  39. 39                        data_state = 0x01;                        j6 t" e9 k  V# _: e+ p
  40. 40                        send_command(ACK);3 \  _& E; s3 I3 [7 f! Q1 F9 J$ Z
  41. 41                        send_command(CCC);
    1 V% B2 P% z& ^& D5 K% E
  42. 42
    * g8 t( ~" _( D: Q
  43. 43                        /* 擦除App2 */                           
    ' ?" ^- {6 I5 n3 d# p
  44. 44                        Erase_page(Application_2_Addr, 40);$ Y& ~  t; {1 m! u& {( |7 k3 h" e
  45. 45                    }* |! R6 _. ^  \+ H2 T
  46. 46                    else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束
    - {1 W  c3 V; e8 W6 T
  47. 47                    {7 U# |- D# e) M- r
  48. 48                        printf("> Receive end...\r\n");
    3 Z+ V8 o9 X5 X( A* w2 l# d) m
  49. 490 {# A& H2 _, j! S0 c+ F% v
  50. 50                        Set_Update_Down();                     
    - }. b/ `  |0 ]  l; A! b4 O
  51. 51                        Set_state(TO_START);
    ' H9 c7 i4 W7 r) D  t
  52. 52                        send_command(ACK);
    ' O* _- b" Q0 V0 G
  53. 53                        HAL_NVIC_SystemReset();
    - @  @$ I( i+ C; R' v% e
  54. 54                    }                  
    7 |9 F  J3 E- v
  55. 55                    else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据" M' ?9 m+ |  ^- ?
  56. 56                    {
    8 ]8 A: W3 `$ [+ J4 W4 }
  57. 57                        printf("> Receive data bag:%d byte\r\n",data_state * 128);# p4 |: ^% @1 M- u7 B1 ~& `; B/ E
  58. 58
    2 V- d8 J/ ]4 Q( `' Z7 r
  59. 59                        /* 烧录程序 */  F5 y5 u$ _; m# e& J# g6 E
  60. 60                        WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
    # j9 V/ i; ], V$ x
  61. 61                        data_state++;" _% P+ m5 G, s
  62. 62. V# ~9 V  Y- G, Y5 V" n0 y
  63. 63                        send_command(ACK);      
    * ?4 c8 X/ d3 g7 R
  64. 64                    }
    8 Y* k7 I% V0 d1 u4 N6 K+ ?8 Q+ F
  65. 65                }
    0 D$ c1 h  C: n, N2 l9 g
  66. 66                else' C. k8 l" F( ^) a
  67. 67                {
    ' Z$ W: P: O/ ~7 X5 m9 q
  68. 68                    printf("> Notpass crc\r\n");5 x8 o& [  K- z$ A8 A5 r
  69. 69                }; x( O- I9 A0 v' J2 T
  70. 703 }. U6 O2 B$ a. R4 C7 A
  71. 71            }break;* Z7 i' U+ Y$ E6 H+ V
  72. 72            case EOT://数据包开始  G' B' x% x3 c$ B5 y! I) v6 y! V2 m
  73. 73            {' o0 e6 V! G( Y. z* w
  74. 74                if(Get_state()==TO_RECEIVE_DATA), G) V  J4 @2 N
  75. 75                {/ X3 p: X. b- I% N
  76. 76                    printf("> Receive EOT1...\r\n");& h! c0 E( ^; N3 ~% T0 d" C, }
  77. 775 J4 l% ]3 Q8 k# L# \! W
  78. 78                    Set_state(TO_RECEIVE_EOT2);                 
    3 X! U8 D& ]- e- ?0 b/ ?1 i
  79. 79                    send_command(NACK);  S8 a2 F) ]; ~9 i/ b
  80. 80                }
    ) e3 C( P) q- u) r
  81. 81                else if(Get_state()==TO_RECEIVE_EOT2). {! \& g( ~7 n/ U
  82. 82                {& q6 \. Y. H3 G5 V
  83. 83                    printf("> Receive EOT2...\r\n");
    ) o+ J4 f* F/ W& }; m6 @
  84. 84
    # A) f4 n: _* \! Q# U
  85. 85                    Set_state(TO_RECEIVE_END);                  
    4 m& G% J& ?2 y; Q2 C4 `+ y" ]. O
  86. 86                    send_command(ACK);
    - t  _0 i* B! z8 q/ ~  b1 d
  87. 87                    send_command(CCC);/ x. t0 \; l: G  R
  88. 88                }
    1 _+ e9 L; T1 C) b% M
  89. 89                else
    # J/ r  b/ c, v+ K. j0 P! X
  90. 90                {/ r" `5 H$ Y3 j" o# v
  91. 91                    printf("> Receive EOT, But error...\r\n");
    ) A) g4 U" e# _
  92. 92                }3 T5 Y( x5 ^4 O- k) [5 h& n
  93. 93            }break; $ v+ Y) G6 _! K8 x8 j& k
  94. 94        }
    & u5 X  U- ?' j" z
  95. 95    }
    - a( Q9 c+ e( E
  96. 96}
复制代码

  {6 E+ W$ u- R. h1 |' \: \+ w) r0 s9 s/ v7 D
  • 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接., ?8 v& y9 z% f: Z8 W: k* W3 D
% w5 s( j5 v5 {0 w8 \% E/ D7 f
* ^8 ~% Y# H) ^% C( p
4. 整体测试% |, m6 y5 z5 D" s5 d7 u) o# A
本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。; w1 T2 V( T, j/ \  a! k

/ b# Q% J6 o3 |9 l* h8 k+ N

0 H. n1 g3 f5 q+ s3 p源代码
- d+ ?+ R# L8 Q* F3 r& a/ [( C2 QBootLoader源代码和App1源代码可以在原作者的gitee获取:
  • 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」。! f5 W" U( |8 @7 i2 g0 t2 |
. @: v( H1 l( W* U+ ]% J

7 g: L% p( W9 j% rBootLoader的下载
  • BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
  • 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)
    3 R7 h3 P+ O% N7 w' v: U: n
" y0 ~- R. f$ m
微信图片_20230921162823.png

( b2 f6 L. a3 E
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功运行
    1 r6 D- _: z. B/ B. E) ^" B1 I$ `6 y
: w  e6 [  B3 Y. ?" i- t# W
微信图片_20230921162819.png
/ J0 K$ Y3 q& M
App1的下载
  • App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
  • 同时也要修改擦除方式为Erase Sectors, 见下图0 t6 G( q/ |8 ]9 N3 E* ?9 }
! K5 A! y; b& I6 c
微信图片_20230921162816.png
/ V3 b: [1 p1 Q. P! p
微信图片_20230921162810.png
. E. c) u  k, K! R5 L
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功跳转到版本号为0.0.1的App1
    ' k9 F! ~9 h6 F# Z

2 ?- o- x& q6 q( R7 i
微信图片_20230921162807.png
1 t' @& v0 ^5 T8 p3 G9 H! ]
生成App2的.bin文件
  • Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件% g1 b$ t! T! G' Z9 \8 d
https://blog.csdn.net/weixin_41294615/article/details/104656577
  • 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
  • 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件* C8 w. o4 j: p  {" `5 T
! q% ]1 J1 i7 S. |. G* [3 m
微信图片_20230921162803.png

6 x# {# S7 w5 I& B% c使用Xshell进行文件传输
  • 打开Xshell
  • 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
  • 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
  • 你会看到App的版本成功升级到0.0.2了.
  • 如果你到了这一步.
  • 那么恭喜你! 你已经能够使用在线升级了!1 Y2 U. h5 d* _( v6 a/ ~$ ]3 Y' j

" \! Y; C8 X0 {& [$ U7 R! C
5 Z. d8 o, o9 i4 |
5. 总结
/ ~8 a/ T' P7 J通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。8 a- b! r' _& R+ M

# g: R6 v! S2 L  m% `6 U, f
  _9 O4 A) T( ?; f2 b3 s- t3 _

% ^7 L) S! H0 ?2 \- H9 g转载自: ARM与嵌入式8 T0 v5 F4 U6 j' t
如有侵权请联系删除9 w9 ~6 S! z+ m* M. n) [1 G
" W* s) k# d  ^& m" z2 ~# r# t

" b; b$ Z2 h9 x( C- Y6 q2 S9 P% ?4 k
赞 收藏 评论0 发布时间:2023-9-21 16:29

举报

0个回答

所属标签

相似技术帖

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