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

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

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

$ |$ U5 W3 q* C  ^2 {什么是BootLoader?
BootLoader可以理解成是引导程序, 它的作用是启动正式的App应用程序. 换言之, BootLoader是一个程序, App也是一个程序,  BootLoader程序是用于启动App程序的.

* S6 T0 k& Y6 G/ b- l
STM32中的程序在哪儿?
正常情况下, 我们写的程序都是放在STM32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 感兴趣的话可以在Keil>>>Debug>>>Memory中查看, 右边Memory窗口存储的就是代码
" y4 q5 T% m+ {9 M# |6 ?
微信图片_20230921162906.png

; Z0 |8 ~; V/ V; [) R0 M
接下来就可以进入正题了.

1 q$ V3 K& g" A
进行分区
既然我们写的程序都会变成二进制文件存放到Flash中, 那么我们就可以进一步对我们程序进行分区. 我使用的是F103RB-NUCLEO开发板,他的Flash一共128页, 每页1K.见下图:
% j( v/ O1 K( Y- S
微信图片_20230921162902.png
5 K+ O5 `% _* b) z- V
以它为例, 我将它分为三个区.BootLoader区、 App1区、 App2区(备份区)具体划分如下图:
  • BootLoader区存放启动代码
  • App1区存放应用代码
  • App2区存放暂存的升级代码

    2 @9 k2 A5 ^$ W9 ~9 l! w! Z
微信图片_20230921162827.png

- f4 D0 U! a* m. u& g+ F$ i) J# V7 C. q7 ~/ z( N
总体流程图
  • 先执行BootLoader程序, 先去检查APP2区有没有程序, 如果有就将App2区(备份区)的程序拷贝到App1区, 然后再跳转去执行App1的程序.
  • 然后执行App1程序, 因为BootLoader和App1这两个程序的向量表不一样, 所以跳转到App1之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
  • 在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到App2区(备份区), 以便下次启动的时候通过BootLoader更新App1的程序. 流程图如下图所示:
    . @+ u! W; Y' Q, U
微信图片_20230921162845.png
0 z" `9 S: d% A9 @8 n& A
0 m. H1 O: j( r' q- v, u8 C
2. BootLoader的编写
本节主要讲解在线升级(OTA)的BooLoader的编写,我将以我例程的BootLoader为例, 讲解BootLoader(文末会提供免费的代码下载链接),其他的大体上原理都差不多。

) ]# v6 Y  A/ z* `1 p) h8 s
流程图分析
以我例程的BootLoader为例:
我将App2区的最后一个字节(0x0801FFFC)用来表示App2区是否有升级程序, STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF, 如果有, 我们将这个地址存放0xAAAAAAAA. 具体的流程图见下图所示

( U. _$ k8 d( w) a
微信图片_20230921162842.png
% i# y; L; M' H$ u/ ~  c
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 程序跳转指令,可以参考如下代码:

    0 H) _$ F6 o! y
  1. 1/* 采用汇编设置栈的值 */6 A2 u/ R4 b# Z0 C# ~$ S
  2. 2__asm void MSR_MSP (uint32_t ulAddr)
    4 [7 I$ A  A9 l4 f2 ?5 o- Z
  3. 3{! ~! `3 o* W& k& |0 ^
  4. 4    MSR MSP, r0   //设置Main Stack的值3 N) Q( O/ I1 M& N! Y0 }$ j% P) o
  5. 5    BX r14
    # A/ C+ C0 k! u( h! E* k  T" Y
  6. 6}
    + t9 A! n5 @6 n9 E% X
  7. 73 x) h% O. Z( k7 d  i8 I
  8. 8; W1 g0 `6 W$ W7 q
  9. 9/* 程序跳转函数 */
    2 F! ?* b! w8 P7 `& J5 x* K
  10. 10typedef void (*Jump_Fun)(void);
    2 \) E) {, A9 N9 Y% Y! `+ a, \
  11. 11void IAP_ExecuteApp (uint32_t App_Addr)' i7 m' R" Q( w
  12. 12{
    5 {3 [2 A3 \5 R9 \
  13. 13  Jump_Fun JumpToApp;; @! Q8 @1 a: @- g6 S' E3 v1 o% M
  14. 14/ D* K7 c5 ?. c! q
  15. 15  if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //检查栈顶地址是否合法., v" N" k4 w; p# ^
  16. 16  {% t7 O( U) E6 I- s/ v
  17. 17    JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //用户代码区第二个字为程序开始地址(复位地址)
    7 O% X5 M3 o+ g% _3 n
  18. 18    MSR_MSP( * ( __IO uint32_t * ) App_Addr );                  //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)0 P* r( _$ d' N, f
  19. 19    JumpToApp();                                                //跳转到APP.& l5 u6 n. s7 _- `6 v7 e1 A& s4 R
  20. 20  }
    + T! O: D) l3 g
  21. 21}
复制代码
2 R! K  G# ^. V
( R% `6 m; |5 B0 u! }( Z
  • 在需要跳转的地方执行这个函数就可以了IAP_ExecuteApp(Application_1_Addr);
  • 其他的代码请参考BootLoader源代码
    ( c/ ~' q. j+ z2 c* Q% B( E
3. APP的编写
本节主要讲解在线升级(OTA)的App1的编写以及整个流程的说明,我将以我例程的App为例, 采用Ymodem协议进行串口传输,讲解App的编写(后面会提供免费的代码下载链接), 其他的协议原理大体上都差不多, 都是通过某种协议拿到升级的代码。
! C! g+ s0 p' A7 D; t
流程图分析
以我例程的App1为例:
  • 先修改向量表, 因为本程序是由BootLoader跳转过来的, 不修改向量表后面会出现问题;
  • 打印版本信息, 方便查看不同的App版本;
  • 本例程的升级程序采用串口的Ymoderm协议进行传输bin文件. 具体的流程图见下图所示:

    ( I. m! f& y, t8 b
微信图片_20230921162839.png
4 F7 O/ L$ F+ p; o& h
程序编写和分析
所需STM32的资源有:
  • 发送USART数据和printf重定向
  • Flash的读写
  • 串口的DMA收发
  • YModem协议相关
    4 R9 G6 P' a3 F' ^) P8 F/ ]& y
Ymodem协议
  • 百度百科[Ymodem协议]
  • 具体流程可自行查找相关文档, 这儿提供一个我找到的 XYmodem.pdf(文末和源码一起提供).
  • Ymodem协议相关介绍可参考我的这篇教程 YModem介绍

    ) {9 w' Y0 _2 ^+ K/ ^- [
# ^, d0 x& _* i6 h
代码分析
  • 代码大多数都是通过串口实现Ymodem协议的接收, 这儿就不详细说明
  • 后面放了我的源代码, 详情请参考我的源代码.
  • 主函数添加修改向量表的指令2 v3 b) W1 A  P" H4 L& ^

- R2 ^4 a1 K, N& B, ^
微信图片_20230921162836.png

+ s) c2 F1 U; z: y4 y4 E
  • 打印版本信息以及跳转指令
    : `8 j" k7 A! v" O* C% X

3 Q0 x, y$ U. J& ]( O6 D/ f' Z
微信图片_20230921162831.png
: a0 B- q+ B4 A2 c. d6 d8 F
  • YModem相关的文件接收部分
    2 ~: _% k+ C6 G4 _, K8 @0 D5 R- K
  1. 1/**9 P- N5 j% g  a. R, @& k
  2. 2 * @bieaf YModem升级; z8 X# ]2 M' P( y! T) `+ }
  3. 3 *
    # M: {- P, S3 l
  4. 4 * @param none
    7 t7 ^2 P6 Y7 Z) K+ F
  5. 5 * @return none
    ; z0 b  O5 Y- K) Z( z/ L# y
  6. 6 */
    5 Y* N& H+ _( z7 f8 v
  7. 7void ymodem_fun(void)
    9 w( g1 Z! k9 Y' F' y( w/ ~5 D
  8. 8{
    0 [' X+ l8 X, ^0 F: B: J- }9 P
  9. 9    int i;" g. K3 m. i0 |' f6 h4 D; w
  10. 10    if(Get_state()==TO_START)+ V6 U/ ^! ^* q- ^! Y! O4 b
  11. 11    {3 D' c1 Y% k- T4 i' @
  12. 12        send_command(CCC);* ?3 a. l# r4 i$ V. q8 o
  13. 13        HAL_Delay(1000);* o$ S5 h' G$ X7 x" ]/ M) x$ }
  14. 14    }
    # x1 k9 l! D: y' W
  15. 15    if(Rx_Flag)     // Receive flag
    : K( f) w/ f) g, T2 [  n; \  ~
  16. 16    {  p/ V* F& v  Q( T3 Y
  17. 17        Rx_Flag=0;  // clean flag
    & s1 ]$ Q, P: r0 k+ W; N3 J
  18. 18: v3 f5 D& J( _+ Q, A1 V3 _: q
  19. 19        /* 拷贝 */7 e1 q% A/ r$ ]% n$ R- t
  20. 20        temp_len = Rx_Len;
    ( e8 B5 ?2 D) j3 |/ B
  21. 21        for(i = 0; i < temp_len; i++)3 U% M2 |6 @7 W, L
  22. 22        {3 m% U4 P! _- P" b" v3 C4 }7 a6 N
  23. 23            temp_buf[i] = Rx_Buf[i];  D7 H6 H( Z, |% n3 [
  24. 24        }7 e+ G, y( ^+ L4 I" _3 f, C8 C# @
  25. 25
    9 \" ?1 R$ m" h: W# z$ S
  26. 26        switch(temp_buf[0])
    5 l( h# O4 A/ I  T2 z' X
  27. 27        {
    ; G& O, M( ^7 E# T+ q+ @: k! p
  28. 28            case SOH:///<数据包开始# n8 d$ s4 f- l1 a& _. m5 `/ O. Z' Y
  29. 29            {( k* ?0 n  R$ p* [, O) I
  30. 30                static unsigned char data_state = 0;
    4 ^& n: V, z' E( A: }
  31. 31                static unsigned int app2_size = 0;
    $ _+ x0 {9 W2 F1 h' A' E
  32. 32                if(Check_CRC(temp_buf, temp_len)==1)///< 通过CRC16校验
    / Q2 ]- q+ Z) z, C) i
  33. 33                {                   3 S- v4 i! v# b% s' R) n- j( |" \
  34. 34                    if((Get_state()==TO_START)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 开始9 }8 K! @, X: Y
  35. 35                    {; B" r* S7 m2 X/ t  P
  36. 36                        printf("> Receive start...\r\n");1 L& F9 P& E  a" v- b- n  ^
  37. 379 b2 Q5 l8 p0 g" L
  38. 38                        Set_state(TO_RECEIVE_DATA);  p" m+ i2 n# t0 M, c1 L6 I2 t
  39. 39                        data_state = 0x01;                      5 O7 G# ?) Y1 Z, A, ^/ G( Y
  40. 40                        send_command(ACK);
    ; l. p. M& o' v# T
  41. 41                        send_command(CCC);' S5 n' m$ L* H# K4 b
  42. 420 ?9 N; a3 k! L) i- Z1 F# u
  43. 43                        /* 擦除App2 */                           
    ) g- c- Z) d4 r1 g/ g( u$ }% L
  44. 44                        Erase_page(Application_2_Addr, 40);# m% F, V, v4 b8 B
  45. 45                    }
    $ W8 {! w/ n# E! v3 r
  46. 46                    else if((Get_state()==TO_RECEIVE_END)&&(temp_buf[1] == 0x00)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 结束2 s3 K+ b9 \5 ~
  47. 47                    {
    0 }% N! F# m, r, v9 [% J
  48. 48                        printf("> Receive end...\r\n");( O4 i! X) e$ g% n4 l7 q% s% Q
  49. 497 y' W* ~/ i8 {( Q# e
  50. 50                        Set_Update_Down();                     
    ( B/ ^9 S! Q# u" U( E' c  ~- A
  51. 51                        Set_state(TO_START);
    " w! j) A# Q! Z6 m6 w9 [9 T0 D  m
  52. 52                        send_command(ACK);
    ; J0 T, a" h# U* P, t
  53. 53                        HAL_NVIC_SystemReset();
    7 k/ [$ I- O0 p" _
  54. 54                    }                  
    / F2 C+ t( z8 O1 Q
  55. 55                    else if((Get_state()==TO_RECEIVE_DATA)&&(temp_buf[1] == data_state)&&(temp_buf[2] == (unsigned char)(~temp_buf[1])))///< 接收数据3 e9 |9 u$ \' K5 S+ U
  56. 56                    {
    " r6 i5 ^, e" C
  57. 57                        printf("> Receive data bag:%d byte\r\n",data_state * 128);
    4 J- G$ W8 ?" @+ T) X$ A
  58. 58
    - K9 E2 q# {4 j6 C$ e( s
  59. 59                        /* 烧录程序 */
    9 {9 U/ {1 R% H! \$ z
  60. 60                        WriteFlash((Application_2_Addr + (data_state-1) * 128), (uint32_t *)(&temp_buf[3]), 32);
    4 V& ]6 x/ }( O3 o6 |% |
  61. 61                        data_state++;& k! ]% X( M  e; e* e
  62. 62
    - i' ^$ U- e; f  y
  63. 63                        send_command(ACK);      , N4 ~: l6 ^' N3 }% Y0 l
  64. 64                    }* U% e' c4 D6 ^! ]2 ]
  65. 65                }
    ) n7 S" Z5 T5 I% e8 I6 i
  66. 66                else
    , E( U' U4 l5 q; _) F! B" y( e
  67. 67                {
    - R: F  U$ v0 f  ]: x
  68. 68                    printf("> Notpass crc\r\n");6 K2 B/ y# s# p# k( Q  O
  69. 69                }( }7 l8 M- i* k; q! H
  70. 70, h; v4 v8 I1 {8 b& s) b
  71. 71            }break;  y4 z7 y  @, F  p1 ^' Q( ?$ r
  72. 72            case EOT://数据包开始
    ! o8 L3 h' j- k6 r. ~9 C9 t4 S: @- m
  73. 73            {
    8 s5 Z# H6 w- K+ W9 q& d1 x
  74. 74                if(Get_state()==TO_RECEIVE_DATA)! _6 ~3 ?( X% W, Z/ o6 N' T0 g& Y
  75. 75                {
    % W" E; r! b* x0 L1 M3 q3 m
  76. 76                    printf("> Receive EOT1...\r\n");8 P# q5 d" m6 \+ S% c
  77. 77" g* f5 H, w% |7 f, T7 I! M1 Y
  78. 78                    Set_state(TO_RECEIVE_EOT2);                 
    : Q$ m$ a) X7 G6 \: a' X4 }
  79. 79                    send_command(NACK);& s- c" H: e9 P2 d
  80. 80                }
    & X' V7 T% R4 `$ M
  81. 81                else if(Get_state()==TO_RECEIVE_EOT2)# W8 I# u; B: v, y, x2 h% F$ ?8 J0 E; I
  82. 82                {
    / `1 O- ?9 ]2 ]' }7 l" g( N' ^
  83. 83                    printf("> Receive EOT2...\r\n");
    8 h& d8 d$ ^3 z3 I5 P8 t
  84. 84! L" q- N; _1 S* B. o' I! ~
  85. 85                    Set_state(TO_RECEIVE_END);                  - z6 E4 F" h5 M! f/ I2 i7 f, a
  86. 86                    send_command(ACK);
    ' Z" p, O5 u1 g$ d- E2 _0 _1 u& P
  87. 87                    send_command(CCC);
    3 Z3 ~& n" w- y8 S% h
  88. 88                }
    ) c- P8 R) ]$ q- X
  89. 89                else
    0 X  v' z  ], ~  `
  90. 90                {' s6 T5 h8 q6 a4 [5 B% z
  91. 91                    printf("> Receive EOT, But error...\r\n");# `: ^  j! C! ^# G: f
  92. 92                }6 B3 f6 t' ?9 D; v5 @4 @  f; c
  93. 93            }break; 1 u/ w9 i1 y  }2 H6 o8 q
  94. 94        }
    - |3 L' {4 ^0 T" h; [9 A1 L2 S
  95. 95    }
    & J2 E& ?3 H# c& f; e- v4 S
  96. 96}
复制代码

% i" `9 O# \. ?% v" {2 u* i$ {
' g! E$ F5 x, V
  • 其中部分函数未在以上代码中展现, 详情请参看文末给出的源码链接.( u  p+ ^% V/ n& t+ B
8 d. a& d( W/ L$ I# O

" y  k/ ]- {3 m+ Q+ @4. 整体测试
1 k2 K1 y, w3 z/ q) A本节主要对前三节的教程做测试验证 BootLoader + App的升级功能。
: P6 ~: H0 F# e" [; l# S# i& ~7 J! z+ Z5 f

* s' n+ v+ j. d( W  l7 b, m源代码
  M; u* L3 [& Z: @BootLoader源代码和App1源代码可以在原作者的gitee获取:
  • 由下图可知两份代码的下载区域是不一样的,所以他们「下载的区域也不一样」
    " g! ]2 ~2 ~& j8 s7 n

. I2 X2 W+ l8 M# j6 ^* F
2 B/ K6 F! b: x3 m- ?' W
BootLoader的下载
  • BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
  • 按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X5000(20K)" p5 r3 |1 S+ F' Z

& h& L, @: ], F! D  u& n5 \
微信图片_20230921162823.png

9 @- p$ @7 [+ d4 J& _- D5 H; y' O
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功运行' X- ]1 B* a' c' n% [+ A+ l
; k2 t- a3 b) P& P
微信图片_20230921162819.png
/ V7 M& R% G! N4 d/ F
App1的下载
  • App1稍微复杂一点, 需要将代码的起始位置设置为0x08005000
  • 同时也要修改擦除方式为Erase Sectors, 见下图
    % w: s9 P6 B* M! G, w4 p
1 ~6 N5 }& M" {# L& p* Z# k
微信图片_20230921162816.png
) `5 S, D6 S) ^5 S2 _* u
微信图片_20230921162810.png

4 p( M9 a# G+ p$ Q7 A+ Z' T. Q
  • 烧录代码
  • 运行, 通过串口1打印输出, 会看到以下打印消息
  • 说明BootLoader已经成功跳转到版本号为0.0.1的App1
    # b+ Z4 m5 n2 a

/ [" q3 K8 N7 A: |- D! P! }
微信图片_20230921162807.png
3 A+ ~% X9 @3 W" _, s, }" n
生成App2的.bin文件
  • Keil如何生成.bin文件, 请参考这篇博文 Keil如何生成.bin文件
    / X$ _7 ^1 Q. ~- k# `$ v0 x
https://blog.csdn.net/weixin_41294615/article/details/104656577
  • 修改代码, 把版本号改为0.0.2, 并且编译并且生成.bin文件
  • 生成好之后你会得到一个.bin结尾的文件, 这就是我们待会儿YModem要传输的文件
    9 Q4 v7 }2 J( ?
6 W  l$ y2 b! ^  M: J& S$ Y  I/ u4 B
微信图片_20230921162803.png

! K; F2 [2 u" a! m# V4 `使用Xshell进行文件传输
  • 打开Xshell
  • 代码中, 串口1进行调试信息的打印, 串口2进行YModem升级的
  • 所以使用Xshell打开串口2进行文件传输, 串口1则可以通过串口调试助手查看调试消息
  • 你会看到App的版本成功升级到0.0.2了.
  • 如果你到了这一步.
  • 那么恭喜你! 你已经能够使用在线升级了!: B* D5 A- ^% W4 {, a" R

8 ?2 a$ g( ]; `1 Y) x/ Q

; Q4 o6 C1 ~! v5. 总结
/ H5 y/ W3 e' R! p2 U; U) \通过本几节的教程, 想必你已经会使用在线升级了, 只要原理知道了其他的问题都可以迎刃而解了, 除了使用YModem协议传输.bin文件, 你还可以通过蓝牙, WIFI,等其他协议传输, 只要能够将.bin文件传输过去, 那其他的部分原理都差不多。
8 x' N$ U% r9 v+ Z8 B3 T2 s1 P
% P# ]0 T* Y, k1 y0 |
4 o8 ]5 l0 O9 @4 C

( V7 N! [5 _& p3 T. K0 S转载自: ARM与嵌入式
- R; E8 X) Z% L" t0 l( \如有侵权请联系删除
; T$ Z! m' g1 G6 o4 V9 w
. W% {5 z! V- A$ K5 M1 a
; V7 i5 [( @6 U+ t" d

; Q5 r1 I9 {1 w8 o. E
收藏 评论0 发布时间:2023-9-21 16:29

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版