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

IAP固件升级经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-2 17:48
什么是IAP升级?

IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级

4 A8 ?# [' M4 c1 z

UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级

  ]& D  P6 L- x

STM32的代码启动过程

要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧


: }  @% G* K7 C! w: w1 b6 j

在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):

  • 从0x0000 0000地址取出MSP(主堆栈寄存器)的值

  • 从0x0000 0004地址取出PC(程序计数器)的值

  • 然后取出第一条指令执行


    , N; X+ w! _7 r* H, w
微信图片_20230402174619.png
1 L+ \  a7 y6 t
' e$ S! N: Y+ q6 K8 l启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
    " K5 w; R2 o8 F
  2. ;* File Name          : startup_stm32f10x_hd.s* O3 d8 L+ K7 e+ }" o5 Y, d
  3. ;* Author             : MCD Application Team
      `3 r: h7 J& {& ~& a3 J
  4. ;* Version            : V3.5.0
    8 e4 w6 t' w) M4 [; `
  5. ;* Date               : 11-March-2011" \# A9 `$ W/ L0 T; ?8 p' G- H
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM # r2 ^# Q/ \2 @" G( k: r3 ]7 p
  7. ;*                      toolchain.
    3 L  \- o: N; d8 w9 x
  8. ;*                      This module performs:9 T( O2 X+ y) x- q8 H& X2 B' p* c- Z4 B
  9. ;*                      (上电复位后会做下面的几件事情)
    0 y- ]$ y- M: k+ X2 z
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)( |, r# d- Y0 N, {
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)
    6 P3 j, O1 V+ y6 x( G& A: x3 k3 u
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
    % B# L- z! Y! _+ E, A1 i3 i
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)3 B8 Z8 v, b5 }4 N1 n: e+ v( z
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data
    5 w0 q2 C& ?+ u. E/ x8 a/ Q* y% F
  15. ;*                        memory (optional, to be enabled by user)
    5 V- W; U% s0 l5 d# r4 m
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)" \& }- d2 i5 X3 B. L3 i
  17. ;*                        calls main()).6 D1 g  }, f# d3 p- W
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,
    - c- K6 C8 h, O* V3 H' P
  19. ;*                      priority is Privileged, and the Stack is set to Main.
    - ?: p4 ]  x- P! ^, C" T
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   5 d3 c0 {! Q' B
  21. ;*******************************************************************************8 {" U- U2 R# D( ?0 {
  22. 4 |. h' a. S4 k, E( W

  23. ' b6 R$ c: }. V& T
  24. 1 z+ C* D( _. [) n- m* |
  25. ; ------------------分配栈空间----------------, S  [6 x, V* F- e3 O
  26. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小* O6 z4 h: t2 \
  27.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)" `5 z0 ]5 G( B
  28. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
    ( R, V: r) |4 u6 H) B
  29. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到" H; Z; f' X+ x. f+ w
  30. ) e4 c9 ^5 F. A$ m. ^
  31.                                           
    ; @/ u+ Y7 I2 D! c+ e! I
  32. ; <h> Heap Configuration; |1 \" E- F1 k4 x
  33. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>; z9 A* `" t0 p
  34. ; </h>) s2 s# ]. N0 ?6 z
  35. ; ------------------分配堆空间----------------
    9 R4 T; u% T& C  T6 Z
  36. ;和分配栈空间一样不过大小只是512字节
    2 S& T7 D' _( I% T* i* g7 v+ v
  37. Heap_Size       EQU     0x000002008 B' t" R: {7 K1 s( q
  38.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3; n) v0 D# o$ g, o$ O0 v6 [
  39. . D. {& U* {' d2 T* z: U
  40. __heap_base        ;__heap_base堆的起始地址% r3 K4 A' H( X# F5 g
  41. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的! l! f4 s- G! T( n- A1 y
  42. __heap_limit       ;__heap_base堆的结束地址
      X" l. }! o0 l/ A6 r- M
  43. & E8 G3 l: N! C/ K$ a' C
  44.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐% w0 O6 `0 F+ F9 w% d, j$ [
  45.                 THUMB;THUMB作用是后面的指令使用Thumb指令集, s; u0 n( A, f! b
  46. & ~$ ~. b' \. c$ k, v  k
  47. ; ------------------设置中断向量表----------------
    + C2 T' s3 K3 m
  48. ; Vector Table Mapped to Address 0 at Reset
    $ l1 W/ t# |  N" [
  49.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
    ) T- f4 K" ^" e8 l+ {
  50.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性2 [( g5 l% [% E7 J. v0 {
  51.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用         
    5 `; ~& \$ @$ M# F  n% b
  52.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用6 E1 m3 f- Y' u) l$ H. m5 d" O! o
  53.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用
    % J4 P* s. \; t  w* Z5 [8 [) S
  54. - I: @; j$ ~' w, ]1 ^+ x: b

  55. . i9 H& N, \7 q5 a$ X; ~8 V
  56.                 5 D; k% w- j9 r5 z  ]
  57. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化5 q4 X" i7 i, g, {3 n

  58. 2 X0 a, L0 a; _6 Y2 i
  59. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
    / b+ C' {# X: r$ z! K; S) W
  60. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
      ?$ G5 J0 `2 b- O' L3 z
  61.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) 8 A3 R+ G+ ^9 ^% y+ y4 ?4 x
  62.                                                    ;* __initial_sp的值是链接后,由链接器生成( D  h& u; t! F" p9 N. F

  63. $ ~! v- ^# L* n0 [5 p  Q
  64.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
    ; t. E  {$ y. @  |: w+ J
  65.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
    1 s) d; a7 E2 p; Q/ M$ C( `# `( U( O
  66.                                                    ;* Reset_Handler是一个函数,在下面定义
    % g% j' P6 [/ R4 A3 n
  67.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》, ^% L( R' i1 N' P3 S- U5 e. p
  68.                 DCD     NMI_Handler                ; NMI Handler      
    ; Y: k! i; D& P8 F
  69.                 DCD     HardFault_Handler          ; Hard Fault Handler  x, y& B: }0 u$ z; A# [) w
  70.                 DCD     MemManage_Handler          ; MPU Fault Handler6 o0 g+ X$ R' l; }( {. ?, d( e
  71.                 DCD     BusFault_Handler           ; Bus Fault Handler" m0 s& o+ }3 h6 s$ m
  72.                 DCD     UsageFault_Handler         ; Usage Fault Handler
    . U! q* d8 ]  \' H4 y, S
  73. 2 B0 S& s5 x- q

  74. ) ]- y5 e# s* i" F$ ?4 x+ o
  75.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件/ a% ?- p/ w' H2 m3 k0 N% O
  76. 3 N+ y  t3 |/ w0 |: X7 h
  77.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel14 v: q2 r; S& O
  78.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2# ]6 ?, T# d! g' G. n
  79.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
    ; ^4 e9 Y6 f/ |0 _# D
  80.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
    $ L3 B3 i/ a% ?! b6 g' F
  81. __Vectors_End                                    ;__Vectors_End向量表的结束地址
    ( P$ |0 B9 D* i0 N- Y

  82. 5 ]1 v3 d  H. D0 b; T) @
  83. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小
    # ~6 f! V2 }! y

  84. + ^+ F; i4 _. R9 |# B
  85.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读
    * L" P, F/ {, }- y
  86. 3 S2 s$ L4 [% l! L- R7 j1 c
  87. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
    / S6 _4 }4 f! W7 L/ J
  88. ; Reset handler
    ( j8 V' P' x  f0 i$ \* {6 r
  89. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
      u4 \) B/ t0 w

  90.   ^0 N+ q2 t0 [# y! m4 x* X3 h' c, ~
  91.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
    # e7 n% b, J6 b. |$ V$ x7 C
  92.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数9 }( T& V. M$ [( Z" Q
  93.                                                 0 e! a: L9 b/ H# }  w
  94.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
    0 M; J  g* e2 G+ @/ ]
  95.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号/ @/ l5 L1 Y. _' q# o1 D  @5 `
  96.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟: v0 G9 u8 f( C! u. M' a: C5 g* K
  97.                 LDR     R0, =SystemInit
    0 Z2 ]8 K+ U1 f( T! N! t. s
  98.                 BLX     R0              3 l6 ]' T; @8 V. U: Y1 S  @
  99.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
    & j) q& ~, f: e7 A) v( m1 D* Z
  100.                 BX      R0  E8 W( j2 ]1 X& u/ K
  101.                 ENDP
    " h8 m. D+ ^; y& j8 I
  102.                
    0 q& W) E5 }+ E$ r" p; N4 H/ r. B
  103. ; Dummy Exception Handlers (infinite loops which can be modified)
    ' c) K/ h# E8 O5 @. e& k
  104. ;下面定义的都是异常服务函中断服务函数. }% f' p# ?4 v' W8 Z$ I' k
  105. NMI_Handler     PROC
    ; e/ |* D5 {- T1 e; q& b
  106.                 EXPORT  NMI_Handler                [WEAK]5 f3 o) U* {6 O( s7 H( W
  107.                 B       .. ], [. c2 a6 R8 m  H/ Q& B
  108.                 ENDP- c" i' L5 X5 z8 x3 M
  109. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件% \4 Q9 r8 ?2 S4 v! ^: T/ ?
  110. SysTick_Handler PROC1 @# ?. r: ]2 z/ f
  111.                 EXPORT  SysTick_Handler            [WEAK]
    $ O1 N% @( D, f
  112.                 B       .* q. c! K% d0 Y; f9 E/ D( m
  113.                 ENDP
    3 [! ?4 s. [+ z' i9 L7 w8 R# Q

  114. & ]* d2 u, L( |% Z
  115. Default_Handler PROC
    : P$ }9 c: p$ }% L' z! x2 k

  116. 4 B' N, E# j+ s) F" u
  117.                 EXPORT  WWDG_IRQHandler            [WEAK]
    5 C, e  w/ B2 G0 o5 Q
  118.                 EXPORT  PVD_IRQHandler             [WEAK]
    , g: F' \1 G& m# J6 N% N
  119.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
    ( d4 E" c" b% q! u& c
  120.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
    : l" X) Z' |4 {
  121.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
    6 V& {, L( D9 B6 a
  122.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]& V2 A/ i% Y/ o" c0 V

  123. / ]/ Y$ y- n8 _+ \1 `4 C: k
  124. WWDG_IRQHandler2 p8 W$ _+ }5 X% |7 k0 a! ~- b! T
  125. PVD_IRQHandler
    7 p! ^8 k) R& @! C' q3 ^2 i7 L4 I
  126. TAMPER_IRQHandler
    8 \8 t6 w% P6 E, s! {" E
  127. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件" M2 g3 l$ W. Y$ E' }% U" Z
  128. DMA2_Channel1_IRQHandler: M" Q6 X. a% ?  v% H
  129. DMA2_Channel2_IRQHandler% {) E: T2 ]( @; j7 E" J0 C+ L
  130. DMA2_Channel3_IRQHandler' ~8 I* I. z" Y% J( t. I# N
  131. DMA2_Channel4_5_IRQHandler
    , F, W! f; u  V' C$ Z) M; ]$ [6 O# \
  132.                 B       .+ e8 E" N9 }3 A" Y, T8 b9 I

  133. + E2 w* E: o9 b. w- e0 d
  134.                 ENDP
    9 w: s$ I# r+ |- k

  135. 7 y# f/ [  I) Q$ i4 ?( l
  136.                 ALIGN    ;四字节对齐
    - t2 ?# b- A6 j: g

  137. - X! k' g, a& s- |
  138. ;*******************************************************************************" y* h9 H' j) |" j$ q0 a
  139. ; User Stack and Heap initialization
    : [; l3 d! E8 m. n- B( A
  140. ;*******************************************************************************
    # N1 n! M& _( r; |$ b% R
  141. ;下面函数是初始化堆栈的代码' h+ m1 Y5 j, c# y6 g1 m$ c( V+ v
  142.                  IF      :DEF:__MICROLIB     
    ' S4 F! m+ c+ o3 d' j# D
  143.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义9 R1 A* U" H4 q: g  ]9 l; q
  144.                  ;这种方式初始化堆栈是由 __main 初始化的3 X* H/ j9 [; m  x' d8 b0 y
  145.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)4 Y9 ~) l4 @6 ]0 ^
  146.                  EXPORT  __heap_base    ;堆的起始地址
    + k* [+ z) Q3 C6 Y
  147.                  EXPORT  __heap_limit   ;堆的结束地址9 H: r" V/ a; S7 p8 [" L
  148.                 / i0 H! u' f; q& s* L( F& @; ^
  149.                  ELSE
    8 V' l- [2 E/ b8 o( b* D# ~
  150.                  ;由用户初始化堆
    : R) h! Z: R+ }2 ?
  151.                  ;否则编译下面的
    7 O4 l2 j- Y' k2 b9 D( f
  152.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现
    7 N5 l, v* ~, a' z. W* Y4 v
  153.                  EXPORT  __user_initial_stackheap
    / g; K4 y* \* S9 I1 k( `
  154.                  
      E8 `7 k, L  a$ e$ k$ b
  155. __user_initial_stackheap
    - g* ?0 n7 }+ H9 V1 }  g- N
  156.                  9 a  v/ R3 E0 e& A
  157.                  LDR     R0, =  Heap_Mem              ;堆的起始地址
    0 T9 g/ O' H" l$ T7 {2 i- ]
  158.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
    & y; L: {  ?$ J4 S$ e2 y
  159.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址  p8 ~7 q2 n3 M9 `! t" M
  160.                  LDR     R3, = Stack_Mem              ;栈的结束地址2 |' E; W* r5 K$ Z# b- y' T4 E; q
  161.                  BX      LR) @8 Y$ L2 b- n, R( B- X1 u9 `2 x4 Z

  162. % d* w+ U# q& c  Z1 e
  163.                  ALIGN
    + U9 s* O. j& I1 \) o! [

  164. 4 m' C* P: v1 X
  165.                  ENDIF" l& T: _3 u& N" |

  166. , o6 \( b. W4 a/ Z" z/ b# f0 G
  167.                  END
    / [6 _; E, V8 G4 y6 U# S

  168. . V6 `: L, a2 \0 I& A# y
  169. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码

3 F$ t* h. d+ G4 M6 X8 s: h& u
/ E% w% V2 ?) W

STM32的启动步骤如下:

  • 1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的

  • 2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟

  • 3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序


    - B/ `" r1 H# U9 }
IAP设计思路

大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序

调研了一下群里的小伙伴,下面这个流程比较通用一些,大概是下图所示升级流程:


! n7 C; B; w0 J8 u3 u) P

升级流程图
1 y, |9 t8 r9 c& _: g, g2 S0 N7 [# C+ _* e) Y
2 q5 H$ V: B  z6 D4 ~+ w- l9 u
微信图片_20230402175006.png , t( y- }6 F8 W  }  M* D

/ ]) r! p. l) h5 U4 B1 A" w

$ s( u" U5 m: [6 r( C" G; C4 zFlash分区

是以STM32F103RET6为主控做的flash分区,主要功能:

: P: g5 R9 U5 ^7 ~

微信图片_20230402174614.png

  `7 n( e) C$ j! n; p6 r: f3 o

  • boot区:0x0800 0000 到 0x0800 b7FF 地址的flash块划分给bootloader,用于升级固件,大小是46kb

  • 用户参数区:0x0800 B800 到 0x0800 BFFF 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是2Kb

  • APP区:0x0800 C000 到 0x0804 3FFF 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是224Kb

  • APP缓存区:0x0804 4000 到 0x0807 BFFF 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 224kb

  • 未定义:0x0807 C000 到 0x0807 FFFF 的flash块划分未定义区,可以根据具体用途定义,大小是16Kb

    * A& a! X7 s( `3 c7 Z
代码实现

硬件:

  • fallingstar-board(已开源,打板验证)
    8 t) j7 t2 p0 Y; K, k, ~

软件:

  • 内部flash读写

  • 串口DMA+空闲中断

    * }; Z% w9 j+ G6 ?$ I8 A8 T. ?
内部flash读写操作

这部分比较简单,直接上代码:

读flash操作:

  1. /************************************************************6 c& L; w  G0 y' h3 u
  2.   * @brief   读取2字节数据3 S7 \6 u& L* }! C# q
  3. * @param[in]   uint32_t faddr
    " y# [6 k) w, o$ F, b# p
  4.   * @return  NULL7 I& {0 s! n" l- p; K4 L
  5.   * @github  
    7 V9 D4 P& w: x6 X0 g  }& l& g
  6.   * @date    2021-xx-xx
    " y1 `2 s$ ^' U3 q5 _( V" Y
  7.   * @version v1.0' V! j6 A  }4 t. j
  8.   * @note    NULL
    7 c& Q( w2 \% c% [- a5 r! ^
  9.   ***********************************************************/) W: c' C7 O; D; f8 s
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)4 f; X' [0 w/ a3 h6 \% k, n
  11. {+ `& R: D" e" C  N- Y
  12. return *(__IO uint16_t*)raddr;
    * a9 B" @/ R7 {% C; F& f1 @
  13. }
      p5 K- O' o  F5 M3 \; }6 S
  14. /************************************************************. v/ m. r/ v4 X( K9 r1 ~% A  `
  15.   * @brief      读取n(uint16_t)字节数据0 ?- C6 ^! j5 k
  16. * @param[in]   uint32_t ReadAddr7 [. ^3 R. Q7 `* D9 {
  17. * @param[out]  uint16_t *pBuffer- i, [0 u  L1 }) b, O( @5 X4 ~
  18. * @param[in]   uint16_t len  a$ l4 g: ^1 D# Y( B  _7 \
  19.   * @return  NULL8 X5 x' h% m  u' J6 i5 R3 _
  20.   * @github  & Y5 z1 U. s& `" i
  21.   * @date    2021-xx-xx) l0 P( y4 _, f0 k- W% q
  22.   * @version v1.0- v$ w) n& R+ O2 @
  23.   * @note    NULL3 O% [/ J# X# w7 Q  n
  24.   ***********************************************************/  n, u$ m+ [3 @- J
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )    $ S2 e( z$ @  S0 J* @0 v
  26. {% Z. x* v' S. C4 s1 b' ]4 [$ u
  27. uint16_t i;
    5 u/ _) O# c8 {8 w5 `

  28. # p$ @8 s" W' |' M9 ^/ ?5 j
  29. for(i=0;i<len;i++)
    $ c1 L5 D* @0 h  z/ C- r
  30. {
    ) R1 x) }: W& U4 s0 p1 b  }
  31.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.
    + }! }& u$ i5 }
  32.   ReadAddr+=2;                   //偏移2个字节.
    0 X( f" {2 k/ i$ c' ^
  33. }, r* Y4 B5 [1 X/ P% t
  34. }
复制代码
4 F% F& s, }* e

写操作,注意写之前要保证是没有写过的区域即可:

  1. /************************************************************3 H3 j! z2 O* i4 N
  2.   * @brief   写入n(uint16_t)字节数据0 m  `( j7 L% r& X$ R
  3. * @param[in]   uint32_t ReadAddr& A' c/ r8 \( }* ?( F
  4. * @param[out]   uint16_t *pBuffer2 g: N+ U" d  |; }6 ^9 Z! q
  5. * @param[in]   uint16_t len/ l9 A4 @1 F- ?" N% N: ^
  6.   * @return  NULL$ f: M/ Q  Y, x5 \) U, k
  7.   * @github  
    . B8 U7 _! z+ d+ x1 S, u
  8.   * @date    2021-xx-xx
    ' v, a. }; N  s; P4 Z1 S1 z4 @2 {
  9.   * @version v1.0* ]" B: i4 c- h% [! `) ~
  10.   * @note    NULL5 w  d6 b) H! u  }. V, e
  11.   ***********************************************************/+ }5 u7 d- l8 {) ~
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   # q$ _: n7 p1 V5 C
  13. {        1 d4 a7 A. _$ A
  14. uint16_t i;
    5 A1 F+ n1 f. B+ X

  15. & p7 x) m5 V4 h5 y2 R
  16. for(i=0;i<len;i++)
    4 M6 Y0 X& ~, K7 ?! V8 p
  17. {+ `( p7 J  D; j: p
  18.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
    2 O' r9 T9 t& L( O7 {4 A2 G# H' `
  19.    WriteAddr+=2;                                    //地址增加2.
    8 y; Y5 ^  v& }: j! ~1 k4 H# |
  20. }  ; R) g) }- C! C
  21. } - O, v' Y0 c/ A8 F# L+ [
  22. /************************************************************: t9 T) h/ e# u' K
  23.   * @brief     写入n(uint16_t)字节数据
    4 R6 P8 n) R/ d& ?9 U
  24. * @param[in]  uint32_t WriteAddr" W( O) d) r; Q' F, h) j6 ~
  25. * @param[in]  uint16_t *pBuffer. w8 y9 H% `7 b! s
  26. * @param[in]  uint16_t len
    & ^: h8 J( a/ E% j) R
  27.   * @return  NULL
    4 X; V9 g% `( B+ ~7 d
  28.   * @github  4 r/ N9 [$ m1 f% I3 A& ^* |
  29.   * @date    2021-xx-xx7 B! F' F( p* t; H4 f4 h
  30.   * @version v1.06 X0 X2 e# y5 n+ w9 H- q; q  T
  31.   * @note    NULL" u) b( l- l& n# l0 ~9 d
  32.   ***********************************************************/; M1 V" D( D9 |# y# ~" {
  33. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
    0 i; y' u/ N" A% g# [
  34. {& C! J. }9 M# E6 I) p  r% ?
  35.   uint32_t SECTORError = 0;
    , S# y6 D- q# K! u
  36. uint16_t sector_off;    //扇区内偏移地址(16位字计算)( o; S6 Y! ]- W  _' i
  37. uint16_t sector_remain; //扇区内剩余地址(16位字计算)   
    2 y% u1 E4 M4 V2 E: p
  38.   uint16_t i;    1 O) K2 h9 q. \/ c2 ~" N; c
  39. uint32_t secor_pos;    //扇区地址
    7 J# j3 W& I/ G, ~: ], j
  40. uint32_t offaddr;   //去掉0X08000000后的地址
    , ^, G/ G8 ]: A! m6 B/ H
  41. " v" t0 ?' x; E, n* F
  42. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址; k$ ^' }  F% \9 `  D
  43. % {( A+ O6 n8 n
  44. HAL_FLASH_Unlock();         //解锁
    8 o1 R% J: e3 y$ p- h7 G
  45. 2 z7 ?* f' Y4 {2 H5 U
  46. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.0 _! k5 w% x  c: i$ e
  47. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT6$ d* q: a; J2 R: z
  48. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)( y) b1 k" ~8 Z0 h* r
  49. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   8 v  Y- k0 `3 B3 F. J
  50. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围4 L7 U! t5 R: a6 E
  51. % D5 D0 P& M% I0 c# \* p0 b6 w- y
  52. while(1)
    $ p2 U9 ]" _( w' w
  53. { 7 L) D; v9 W! g
  54.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
      N( A; @  d5 X! {0 l
  55.    
    / c' G+ w. u  x" W& r
  56.   for(i=0;i<sector_remain;i++)//校验数据
    % e3 G- v: G. \$ v* H& v( B
  57.   {3 b* H6 r3 S- R4 i2 T
  58.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)1 ^& J2 E! A6 I+ U0 t, f2 ^
  59.     break;//需要擦除     
    . U7 r( ^9 a5 i- w! ^' L
  60.   }* D6 @. C( |6 I8 @, X# V% {
  61.   if(i<sector_remain)//需要擦除) s' |& ^. A6 v! E
  62.   {8 y1 |3 f/ N* k* M/ B% B$ a+ j
  63.    //擦除这个扇区7 j& B9 _/ x  R8 n- f
  64.       /* Fill EraseInit structure*/
    8 T" A% v, @# T
  65.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;+ N8 q) g4 O. R
  66.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
    / T  z% V- k1 K# J
  67.       EraseInitStruct.NbPages       = 1;* ]+ W& w7 l# e6 N, ^6 z% L
  68.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);: y" M' ?, A  `" ]% i0 U
  69.    for(i=0;i<sector_remain;i++)//复制
    ) M  {: J' k. G* \) l5 X: `
  70.    {+ Y$ L- Z6 n5 z8 X1 R8 F4 o. N
  71.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
    4 x' ?* i% I$ m  h: A' G" q2 v
  72.    }6 x9 E/ n: l* X6 Y: d
  73.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  * |8 k2 H0 W) t2 A. [9 f
  74.   }
    , L1 T1 G& d% d. w* R6 |
  75.   else
    0 X; y4 Y) e4 I3 f; U
  76.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        
    - R+ w$ r6 V& t& k
  77.   if(len==sector_remain)
    1 n: A4 J. P; d" r* v
  78.    break;//写入结束了
    3 ~: q2 M- s7 i- F
  79.   else//写入未结束
    - d6 b9 }. k) C% i+ m
  80.   {# i2 U0 @/ T: y2 Y; o, X7 V
  81.    secor_pos++;    //扇区地址增1
    ' Q9 F, F+ {$ P# u- A8 n5 U
  82.    sector_off=0;    //偏移位置为0   # u6 d3 K* K9 s; O: d
  83.     pBuffer+=sector_remain;   //指针偏移3 }. s/ v; o/ V6 W8 m3 T) f- q
  84.    WriteAddr+=sector_remain; //写地址偏移   
    ) G$ Z1 J6 [; l1 V" L4 H- ?
  85.     len-=sector_remain; //字节(16位)数递减' M/ P$ n6 c* {4 c7 p" P
  86.    
    ( ?) F# E6 K0 B7 a' r
  87.    if(len>(STM_SECTOR_SIZE/2))3 d( z$ C/ z4 V# V
  88.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    3 q' J$ l. f1 m* g7 z+ n8 D
  89.    else
    ! c) A/ k' k7 x( @! B' r
  90.     sector_remain=len;//下一个扇区可以写完了
    0 ~* a8 I8 f4 L- ^  O
  91.   }  , g- h/ f8 ~4 a1 ]8 p) m3 }9 v% E
  92. };
    . N' R* F6 Z# j0 X7 O$ D2 ?
  93. HAL_FLASH_Lock();//上锁/ y, E. b& G) i6 A$ f
  94. }
复制代码

  k+ o. Y# e8 o1 `7 t串口DMA+空闲中断接收不定长数据

在上篇文章的基础上,我们对结构体做点修改,增加bin文件数据总长度记录,以及bin文件接收完成标志(小伙伴们可以采用其他办法,不必拘泥于我教程中的方式):

  1. #define Max_RecLen 1024*3
    * F; d. s$ v/ A1 v, D
  2. 9 m8 R* u% m& s5 ^$ O  c! ]0 l! ]
  3. typedef struct{5 ?+ u0 ^5 T8 p1 A) O0 J$ h
  4. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区0 K- _( L7 I' X6 a) o) s1 G, R: x
  5. uint16_t RecDat_len;      //单包数据长度
    4 x' F3 |+ \8 i5 [, y
  6. uint32_t Cur_WriteAddr;     //APP缓冲区地址
    , h: m  M. f  m& u9 w
  7. uint16_t BinLen;        //bin文件数据长度+ v6 e0 k: I; j1 S7 r' K
  8. uint8_t rec_endFlag;      //单包数据接收结束标志
    5 k: p7 y2 ~! D0 \
  9. uint8_t Binrec_endFlag;         //bin文件接收结束标志 9 d  C# k) V7 X! d6 e" n
  10. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器* k* X3 P+ b. }1 s& @0 t

  11. ! e# y# Q; f, r8 q; q# |; a2 F  e
  12. }UserUartDMA_Typedef;
复制代码
4 O8 e8 w: C. U4 ~& G  J3 N

然后在串口中断中:

  1. /**
    7 i6 u/ j& d+ ^9 P! c2 S
  2.   * @brief This function handles USART1 global interrupt.
    6 P% E7 f/ m! A* i$ I
  3.   */
    2 y" l9 {8 d/ n0 S1 G2 T* h, ^
  4. void USART1_IRQHandler(void)
    3 `$ h4 {9 `2 L+ A* z1 `8 z! r, |, B
  5. {- }$ h/ d: T* D2 b. s. r9 r1 w5 W
  6.   /* USER CODE BEGIN USART1_IRQn 0 */( @$ o3 t  j5 h, x9 {
  7. uint32_t idle_flag_temp = 0;
    / D& E% ^1 j* H; N$ y& ?8 j
  8. uint16_t len_temp = 0;
    7 e1 n0 }1 g9 [" d
  9.   /* USER CODE END USART1_IRQn 0 */8 C+ |$ M7 H0 c2 q* |" C
  10.   HAL_UART_IRQHandler(&huart1);
    ( p& `  W+ ^6 c. P/ I$ o
  11.   /* USER CODE BEGIN USART1_IRQn 1 */# j# \6 |! w  A6 K7 U  s
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);: H* q8 W5 u0 m7 R$ T" H

  13. 7 S: O2 c' P$ a+ c( P
  14. if(idle_flag_temp)
    7 v+ a: w7 k$ m  f4 ?* C9 A$ F
  15. {# x3 r* Z, }. P6 q# @. E
  16.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);/ x/ N  y) A' Q) {
  17.   HAL_UART_DMAStop(&huart1);( u$ u2 k2 D# }/ c
  18.   
    & {5 i+ {. c6 z' ], d
  19.   UserUartDma.DMA_TIMCNT = 0;
    + j8 J: I5 \- E9 ]. v" J
  20.   UserUartDma.Binrec_endFlag=0;
    5 ]3 F7 N: x, {
  21.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
    ( U  k" Y& m3 ^( t
  22.   " u  q$ n1 o5 n3 t6 V
  23.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  8 s6 Z+ _3 n) s9 t
  24.   UserUartDma.BinLen+=UserUartDma.RecDat_len;
    9 X, _* u% N) ^6 K" n
  25.   UserUartDma.rec_endFlag = 1;- l# H7 [' j" |( ]" x5 l5 `; }
  26.   & s! ~  X7 N! X; [% K
  27. }& n; `7 X$ t7 v$ v/ [
  28.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);: G1 k  |! ~8 I# V; Q" M1 C
  29.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
    0 z. {  C3 n- a3 z! i0 D
  30.   /* USER CODE END USART1_IRQn 1 */
    5 d! T2 n4 _& \* B% I$ Q9 `
  31. }
复制代码
+ S% S" n8 L2 F5 K! A
IAP代码设计
  • bootloader代码设计
      \3 f4 T( T. G6 ?- t

扇区擦除,用于写入前擦除相应扇区

  1. /******************************************************
    : v3 G8 v  l. p  O) H
  2. * Brief     : 擦除APP区
    7 a, ]# f* ~; T! r" G
  3. * Parameter :
    $ _* ]4 w& x' V1 j1 G
  4. *           *startaddr:APP起始地址- c, R2 J# s( r7 Q+ C* |+ e! h
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE- r; f+ w; f, y( q$ G' l! F( }7 `
  6. * Return    :None.
    ) \2 I3 v) F, E* P/ _/ H) c
  7. *******************************************************/
      i$ z7 S8 G4 B2 r2 }1 w8 j
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)9 N: f) I4 r. a5 i/ |; B
  9. {5 U$ V* T- r5 q
  10.   uint32_t SECTORError = 0;5 q2 e# x/ c3 V3 B$ r( R% q7 |
  11. 4 l2 ]0 z7 q5 P7 Z, o$ s& O, y$ G) W
  12.   //擦除APP区
    , P4 U' K* b. ^+ v4 a% n* g1 o  F
  13.   /* Fill EraseInit structure*/# J, @6 r$ b- C. }6 z
  14.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
    + f6 r; n& V. q5 D2 p' Y9 M& X4 [
  15.   EraseInitStruct.PageAddress   = startaddr;
    " q+ Z6 v" }7 n& A* r0 R
  16.   EraseInitStruct.NbPages       = pages;( D2 t. A& Z. ]
  17.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
    # l( }9 n3 Z) i: A0 t- Y3 [
  18. }
复制代码

! L- W. X/ h0 C

升级标志获取与擦除

  1. /******************************************************
    0 ^- L+ X8 B! U/ M6 ]1 }( w8 P( U2 h. e
  2. * Brief     : 写入升级标志" M# P2 G. {: @9 g: |
  3. * Parameter : 6 N  H9 p3 R& A: @
  4. *           *addr:标志存放地址; @2 S9 G8 _+ l+ |& D
  5. *      *pdata:写入的数据
    ( k" u4 L* e. T. b; I
  6. * Return    :None.
    . Y! a4 q* {: K- j. {
  7. *******************************************************/
    : S1 t! @- x- }& S
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata). c5 C) N2 m, j) j  A7 o
  9. {' h: R& g$ Y& |7 B3 X
  10.   BSP_FLASH_Write(addr,pdata,1);, d2 ?% Q9 N+ W1 }  {  a( M/ f0 N) z
  11. }
      m2 B2 x8 S) C, f7 }2 [
  12. /******************************************************( z" t3 @: j0 t6 P/ W# ^' D
  13. * Brief     : 获取升级标志! G4 A) _! n1 H$ b* ^, Q
  14. * Parameter :
    9 g, c, R% m5 D9 N+ ~
  15. *           *addr:标志存放地址8 T' F( J' |9 s, R) w
  16. * Return    :None.
    4 q  o( G2 }& i1 @3 w
  17. *******************************************************/- x/ W0 u/ g, P1 q/ h$ t
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)2 s  K$ i- {2 j# h8 A
  19. {6 u& M" F, ^; G  p& L
  20.   uint16_t flag_temp;
    8 J9 P1 v% A: Q/ ], ~9 D. _  L
  21.   BSP_FLASH_Read(addr,&flag_temp,1);& a! v: |( \  ?

  22. & ]# r4 A) ~3 [+ h/ f& O
  23.   return flag_temp;2 |1 ?# A- x2 d4 Y  k; |
  24. }
复制代码
7 Y( y: @! h- ^; Q+ c2 _0 F

接下来就是APP缓冲区数据写入,APP区与APP缓冲区数据倒腾了

  1. /******************************************************
    5 W7 e# x- N, X* v5 b
  2. * Brief     : Bin文件写入app缓冲区
    8 ?0 C1 m. o+ P7 m0 r
  3. * Parameter :
    ' g0 e% u0 Q- ^) D' d+ v4 U  ]
  4. *           StartAddr: 起始地址
    " |8 c- v6 t- S) G, p" m& z
  5. *           *pBin_DataBuf: 要传输的数据3 T! C7 q* m0 X6 Q+ c* N. q+ k
  6. *           packBufLength:单包数据长度+ [" A6 N9 C% w4 `- p
  7. * Return    : None.- d; K/ ]' l, b
  8. *******************************************************/
    " K8 Y* H" f. O$ d/ f2 M3 L: J
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
    ; \" ^0 S0 E6 H; R& e
  10. {
    % G+ ]2 {% t. M( z7 y* d. x
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;8 P6 g2 ^: _& [; n9 a
  12. uint8_t * pData = pBin_DataBuf;
    1 {0 Q' c: e2 n6 v9 y) D
  13.   
    * B3 f0 X1 ~) g0 f  O3 w
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )/ c; Z; h& l! W. u0 v& P+ p
  15. {         
    0 {+ s9 s' Y7 c3 k7 P
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;- f  T, F2 }& u0 F
  17.   dataTemp += ( uint16_t ) pData[0];   
    5 c) \8 ~. h2 C: H! z
  18.   pData += 2;                                                      //偏移2个字节
    ' C. e* \/ h( s- \, b/ a
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     
    0 }2 K6 K3 Z+ ~1 N3 w
  20. } + x7 y- R7 \0 w5 I/ ^1 h; ]
  21. & N) x1 {* ?, c0 C
  22.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
    9 f  _1 Q, Y- b
  23.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.
    # D+ B+ c( d) j+ _; {& z
  24.    packlen_Ctr = 0;
    5 B' d8 l2 J0 Q6 F
  25. }
    ; J# r+ T. s1 p/ i7 V( S
  26. /******************************************************6 F( Z, e/ @0 @: U4 Y8 m3 R
  27. * Brief     : Bin文件从app缓冲区写入app区
    $ u* m. A9 I8 }' h0 V4 n* @- j
  28. * Parameter : 4 s7 \: v+ _8 M# r' {1 Z
  29. *           SrcStartAddr: app缓冲区起始地址
    + K# [4 D: t2 o. P
  30. *           DstStartAddr: APP区起始地址3 r9 P% N/ @2 N5 o! {" Y1 f: ~- v
  31. *           BinLength:bin文件长度
    . f3 I* W: \0 g7 n+ X$ C. H

  32. 7 |4 T1 u3 R1 V$ B, W9 h& O! Y. O: _
  33. * Return    : None.
    % \6 R3 f6 V' v# Q1 ?/ ~. J2 ?7 E
  34. *******************************************************/
    / x% p) O5 J  b  p0 m9 ~& I
  35. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
    $ L! N1 s2 R7 S
  36. {8 P3 a5 t+ S9 X% [% D0 V
  37. uint16_t data_temp = 0;
    , j+ S* E" H7 i% t$ ~0 e- n
  38. uint32_t count=0;5 y4 w2 a0 ~/ L, p/ Y
  39.   u! `. ]. ?' U: f* {

  40. $ Q( c( k. S5 y
  41. HAL_FLASH_Unlock();         //解锁6 z. L) S  M$ c# {  V  @
  42. " X' o1 E: S' h4 ]
  43. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);! B* u9 Y) c/ Y5 P
  44. HAL_Delay(10);
    3 `2 B! y7 o( G, o% ?& o
  45. ! M0 K  `' ^8 C* K' V2 C; R
  46. for(count=0;count<BinLength;count=count+2)
    # e5 v& K2 I! f. D' a$ K# a9 }0 p
  47. {
    9 ~* E# K  F4 v: x$ [: S
  48.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
    0 c' @% f6 B# l1 `' H. U9 b
  49.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
    " o3 q0 ^1 B$ n2 d" h- X
  50.    SrcStartAddr+=2;2 }/ S  E! {6 y" D. W2 p
  51.    DstStartAddr+=2;
    + W% e+ ]& C/ p6 X& G- W
  52. }! B! N2 k, m- ~/ H
  53. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
    1 U7 Q' i3 R1 z0 Q) u% b! p
  54. HAL_FLASH_Lock();//上锁9 D, L* X+ s6 w
  55. }
复制代码


& P( M3 N5 Y1 v/ B( v

最后,要设置APP程序的运行地址

  1. /******************************************************
    1 X, P) [) r9 r9 _7 Y$ p
  2. * Brief     : 设置栈顶指针0 I, u: g9 |% Z+ g: k% e4 G
  3. * Parameter :
    $ D+ I* F# n9 z% C* B& x
  4. *           ulAddr: " c0 f8 c; U. F' m
  5. * Return    : None.
    $ ]4 A" l8 _' j, d6 x6 U( V
  6. *******************************************************/  W. K( K7 R4 y6 {7 G0 S
  7. . V2 N9 `! a2 A  j0 C% X
  8. __asm void MSR_MSP ( uint32_t ulAddr ) ; o  K: u1 r  T2 d9 }9 ]
  9. {
    . I3 d4 j9 W: R1 g! |
  10.     MSR MSP, r0                       //set Main Stack value
    1 Z0 E8 H1 b! W% E9 w. C5 u
  11.     BX r14+ Y0 a( {" Z1 q
  12. }
    9 T3 n8 @0 }- P

  13. - L' E* j: z- |0 w2 x) `
  14. /******************************************************" z- V; q1 _9 x* g
  15. * Brief     : IAP执行
    $ {0 h! v7 L+ U' w( X  [
  16. * Parameter :
    4 D- v  j( ^( f! f7 T6 S2 [
  17. *           ulAddr_App: APP起始地址
    * Y+ g* }, e- i% u( G
  18. * Return    : None.$ N5 Y1 R2 c2 ?
  19. *******************************************************/. D9 I& X! x. x% Q; p+ c: q; s
  20. void IAP_ExecuteApp ( uint32_t ulAddr_App )$ p8 M" G5 F) B. Y
  21. {
    % L( w9 y- ?: M. o, K; {! f8 x
  22. pIapFun_TypeDef pJump2App; . i. g& K8 F+ a

  23. 7 n% x" H+ Q  D' K2 t
  24. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法.: [- E  l, U. I7 X% j6 r
  25. { # k7 R9 j. T  \
  26.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)    r9 g8 T8 ]8 o6 V
  27.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    , p5 j) d/ M7 }8 o6 q/ v& Y( h
  28.   pJump2App ();                                             //跳转到APP.7 o( T: g1 M9 O7 x
  29. }
    ( z" b0 u. W0 o8 p: p
  30. }  
复制代码
; d5 b: @& q6 q% e: m+ h& _+ y

IAP相关的代码就这些,主要是数据的倒腾,其他倒也没什么复杂的

目前小飞哥对整包bin文件传输完成判断,单包采用DMA+空闲中断的方式,整包文件传输采用的是串口中断在5s内没有数据过来,认为一包数据接收完成,置位接收标志,这部分小伙伴可以自行判断

  1. static void Systick_Config(void)
    ' S( `' z; b1 I, p
  2. {& I/ L: t& E* K4 L: f8 X- L
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms4 c3 @- t4 x& Q) c$ Q4 E* d
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);  v9 S% h0 v4 A. Y6 ?& J1 s( [+ g* b
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
    ' ~$ ], N$ F4 b0 E
  6. }! K2 q7 u4 E+ T. _& {5 r
  7. 4 F- ^" f8 e+ P  l) B; }
  8. void HAL_SYSTICK_Callback(void)
    6 K* k- l8 K8 [6 Q
  9. {
    8 u  u. P, Q% d( }/ O
  10. UserUartDma.DMA_TIMCNT++;3 N2 m' y+ s5 H9 U
  11. if(UserUartDma.DMA_TIMCNT>5000)! z4 \5 }6 A: U! }: _0 a0 w/ j5 p
  12. {
    0 B1 R1 w! d& n* o, b
  13.   UserUartDma.Binrec_endFlag = 1;* Q& Y# C$ z2 f, {
  14.   UserUartDma.DMA_TIMCNT = 0;
      E3 x+ u% |# ^0 P' o3 }
  15. }5 _! U5 c. U; d0 p# n- H) U
  16.   TIMX_IRQHandler_user();   Q; u$ X5 X# I3 |, K
  17. }
复制代码
% |# ?' a* E9 m; b9 _3 y9 f

bin文件传输完成后,写入需要更新标志,重新跳转至boot区,检查是否APP代码需要更新:


  1. ! M3 C( Y/ B& D2 s) c; m( ^* g9 B
  2. uint32_t Task_02()
    2 R4 D: j4 H! h9 e! U( K4 n
  3. {4 W  g8 t0 Q* I5 W/ o2 p

  4. 8 o7 n5 N$ d2 j) A/ O. n
  5. if(UserUartDma.rec_endFlag)5 T" V$ \1 p2 A) g
  6. {$ x% I( q2 o/ W  }
  7.   PRINT_INFO("update firmware\n");
    5 m8 ?$ G' n/ d# o, Z# D
  8.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  
    # ?/ m  V) C4 ?, }/ P
  9.   
    * c: ]  n" u- U# h2 c" c
  10.   UserUartDma.rec_endFlag = 0;
    ) z9 F3 @+ X$ ?
  11.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
    - v# f% \& x; A4 e* C) Q, G/ h1 s
  12.   ! v9 F  i4 k) ^8 p
  13.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
    7 g0 v4 N+ V) E% d5 ?3 i  T! X
  14.   UserUartDma.RecDat_len = 0;4 }% J! j" Y$ e$ d* |# M5 B1 p
  15. }0 r# g( z  d0 S4 ]' }' L9 I" Z% ~+ w
  16. if(UserUartDma.BinLen!=0)
    7 y* S0 }- ~, H) a, I. y
  17. {
    5 a! h  D; h2 r+ R: X5 ]. T
  18.   if(UserUartDma.Binrec_endFlag)8 X. N1 |- P7 p  q2 I' e
  19.   {" h$ e+ p+ ?" p" J9 p8 {
  20.     UserUartDma.Binrec_endFlag = 0;
    + H4 x& r* l3 U/ v6 B5 d
  21.     + V8 E, O7 h2 L# X, h2 m( N4 t! l8 X
  22.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志
    7 d# R! d7 `6 i" s2 G/ [$ {: [9 z
  23.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志
    5 o" p9 Z* u4 M2 P- l0 \' T8 p% u% B
  24.     UserUartDma.BinLen = 0;  & j$ D, a, M* A9 x/ K9 I  l
  25.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动
    $ B# s" ]) q4 }! X2 ]  f. N& w* a) p
  26.     HAL_Delay(2000);+ ~, w, b7 S/ `7 z" T8 l
  27.   }5 W, M# q9 b- h! X8 ]* q

  28. 0 F+ t0 O  u# Z, R  d2 R$ \7 U
  29. }
    9 @, e% ~) E3 y0 e/ S" O
  30. }
复制代码

. M+ v1 `/ d5 i8 R

MCU复位之后,初始化执行过程中,对APP升级标志进行检测:

  1. PRINT_INFO("-----IAP Menu--------------\n");" t9 z/ W# N  u/ F- M5 M
  2. PRINT_INFO("-----Download APP BIN------\n");
    5 p5 \3 K3 E% L2 A
  3. PRINT_INFO("-----Restart To RUN APP----\n"); # G$ n0 \7 a& F1 C" U: v; m+ [
  4. PRINT_INFO("\n\n\n");
    4 ^5 z! E; r9 G4 g* B# }8 a) p

  5. 8 f& k+ a; }/ |7 H/ c" d; ]2 k
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))# F" o: ~+ m* }; A
  7. {! L: [5 S) p- [* t7 t
  8.   PRINT_INFO("-----Restart.....5-------\n");
    ; n2 Y( m* w7 |9 T
  9.   HAL_Delay(1000);
    " F7 D+ z& A6 z) n
  10.   PRINT_INFO("-----Restart.....4-------\n");
    # ]- \8 N" m. q
  11.   HAL_Delay(1000);! |: y2 E/ @8 v" T
  12.   PRINT_INFO("-----Restart.....3-------\n");$ X5 H2 k% \+ i4 h; {; Y/ Y
  13.   HAL_Delay(1000);
    8 b, x2 w& a$ S  l: @* r
  14.   PRINT_INFO("-----Restart.....2-------\n");( o  @. E; L0 ?/ q3 i  C
  15.   HAL_Delay(1000);( _/ V) E4 E/ F. r  P2 Y# ^
  16.   PRINT_INFO("-----Restart.....1-------\n");4 B" x1 t9 K- f% D: q
  17.   HAL_Delay(1000);
    # b+ x- w; q1 f  E2 L% Z/ D
  18.   
    - @, @2 v# i' |6 ~8 x
  19.   PRINT_INFO("the device need update\n");0 T0 p( t# U  o
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 8 k* I( D6 g' ^7 x
  21.   PRINT_INFO("firmware write OK\n");
    * s0 E2 ?9 M$ w4 B  a8 X  m
  22.   PRINT_INFO("please double click the button the execute the application\n");
    ) [1 j! P2 L. S; [* M

  23. , t. M) V* _0 k2 c
  24.   HAL_FLASH_Unlock();         //解锁
    3 v  C. L6 I. s4 h
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区1 v' i! ?  h+ y
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     / ~) m0 g" g9 ]- t+ _/ s
  27.   HAL_FLASH_Lock();          //上锁) q2 s8 R2 K* m2 m, ?
  28.   
    * n, J, ^  |/ d2 K. |
  29.   PRINT_INFO ( "开始执行 APP\n" );
    4 \* G  O- ?: E- O+ \
  30.   //执行FLASH APP代码
    ' y1 v% h8 k* C0 U# q3 k7 b' U& z
  31.   IAP_ExecuteApp(APP_START_ADDR);
    4 a! f2 d& x4 Q8 ]% T( Z
  32. }
复制代码

' }: Q, ?' G; z4 n$ f  `: f. |

倒计时5秒后,代码更新,并执行

  1. PRINT_INFO("-----IAP Menu--------------\n");" M) J: D& v# y! `: A- b% F: R! i' G
  2. PRINT_INFO("-----Download APP BIN------\n");* J1 A; [& ~! d) y8 P
  3. PRINT_INFO("-----Restart To RUN APP----\n"); * o! ~- [( F0 e2 A4 Y
  4. PRINT_INFO("\n\n\n");
    5 y' ]4 P( B. B+ [) W0 L0 I

  5. ' a  k7 {( V# x0 g. W/ n1 l
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
    / A' O. m& s5 p, B* x) B; R# ^
  7. {) a- q" F: f3 f1 A
  8.   PRINT_INFO("-----Restart.....5-------\n");  w3 \1 R; C9 s" F6 m
  9.   HAL_Delay(1000);4 W" q2 a* \4 n: R3 j; T& t
  10.   PRINT_INFO("-----Restart.....4-------\n");
    8 }9 |  [8 S9 v  c# s2 t" Q  q* Z
  11.   HAL_Delay(1000);
    8 u# V' v8 j$ s- c
  12.   PRINT_INFO("-----Restart.....3-------\n");
    3 G1 ~2 N4 H" w! |+ j, h  b6 e
  13.   HAL_Delay(1000);
    4 |5 v; w- U9 U0 }# n
  14.   PRINT_INFO("-----Restart.....2-------\n");
    : b; ?* }: m9 t! y
  15.   HAL_Delay(1000);
    ) W: M* e0 J) ^1 `
  16.   PRINT_INFO("-----Restart.....1-------\n");( o$ P$ V8 b# H' X% |
  17.   HAL_Delay(1000);
    # X$ x3 x. I: P5 D" e# C
  18.   $ {' F9 d- h3 [) ^7 Z5 z
  19.   PRINT_INFO("the device need update\n");0 }4 B8 A( t! C! J6 q* k
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 5 h+ n# S. Z; l; O5 @& T
  21.   PRINT_INFO("firmware write OK\n");5 r! Y3 C: j5 \& j  j3 n% |
  22.   PRINT_INFO("please double click the button the execute the application\n");1 j$ J; H2 F3 ?2 ^- z5 v

  23. 0 M0 [5 I. z+ K  z
  24.   HAL_FLASH_Unlock();         //解锁
    : e' x- P( h7 }! H. p: B5 k( W5 Z. f
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    ) C: ?* `$ K' J2 F2 M$ K* }0 V
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     
    1 z5 B# Z4 k$ G5 n3 Q
  27.   HAL_FLASH_Lock();          //上锁
      ], n8 N( s2 f
  28.   
    + T- m* r/ _% p! k& A; K
  29.   PRINT_INFO ( "开始执行 APP\n" );
    5 h* ?5 _# \5 m! i, p' Z9 \# c" N
  30.   //执行FLASH APP代码3 p* z( S+ ]  r" u1 K0 b
  31.   IAP_ExecuteApp(APP_START_ADDR);
    0 e! y' Q' E0 e, I; z
  32. }
    * h4 @7 X+ c5 O0 [+ ^
  33. else//不需要更新,执行APP
      ^+ t' S  R% `7 s/ F% o
  34. {$ {1 U7 i+ ?" ?4 q/ u! S7 {
  35.   IAP_ExecuteApp(APP_START_ADDR);# n5 K- ~  Y# x
  36. }
复制代码
, x; z9 X  }! z$ Y

bin文件生成


5 A, g. _2 J# @

微信图片_20230402174611.png

$ I+ I8 P: ]; M  G% U1 ~% h; c

输入:

C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o  IAP.bin  UART_CircleQueueTest\UART_CircleQueueTest.axf

! d) x. g% v+ ~$ g- N

参数意义:

  • C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe:fromelf.exe文件目录地址,一般keil下自带有

  • --bin -o  IAP.bin :


    ( ?! I6 ~" U' a! [) o

输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:
    , P, F  y/ b0 B  }1 l$ N

axf文件目录及文件

要注意boot区的地址范围,根据前面的设计,46K,拿出计算器....


; P9 B! I$ \1 W& P  m7 c 微信图片_20230402174607.png - o/ o  M% o$ \: [2 E) R' x
% ?8 G% O' B* O' M
微信图片_20230402174604.png
( z9 Q  {' X( t8 k; G- \
) [9 l2 _' u- Q+ `( }. h8 w% T
  • APP代码设计1 n' l" X& I5 w4 ?5 L6 T8 {

APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码,如下:


$ W: M4 U0 f" T5 d

  • boot区代码执行效果' J1 L* ^# o! D4 n# x* q0 j
5 f) J" r- M* N6 _/ y
微信图片_20230402174601.png
) _0 e! ~! D) l6 f% Q4 s! _& T/ V& D% V
  • APP1代码执行效果5 @9 m' q( u) {7 R" j( |' N* R" ?( Z
) S1 J& F1 Z; m# P
- i& Z8 }7 d0 R1 l/ _  ~" Z0 P7 x; j
微信图片_20230402174557.png , W$ w7 d  `  R% j5 z7 f
7 j, Z: ~8 M. X2 \  S
* [! j. M3 {, V" q4 N
  • 接下来再进行一次更新/ ^9 O  V6 l: J6 J& J! j% x
2 B: }# D& e0 f/ }: B
" S2 R: S' a8 Y7 ]+ j; O
微信图片_20230402174553.png 6 M( j: K/ L0 z: x" y" U

9 y" M1 q  \9 G1 {
$ V4 ]+ ]7 g' N  y$ \

注意APP BIN文件的flash地址设置


. C& x  a7 j* O: I' ~ 微信图片_20230402174550.png # [0 t. e; N  ^6 _! V
" E$ y- ^* A* R5 b

OK,至此,设计部分就完成了,涉及的内容还是非常多的

本文仅仅用于原理性介绍及IAP功能演示,与工程中实际使用的还是有很大区别的,后面会继续给大家出工程应用方便的教程,会涉及到签名、验签、加解密、校验固件完整性等等


% [5 r* n( S# M" s/ S转载自:Embedded小飞哥
) U& c7 q3 j9 ?, e1 H2 W如有侵权请联系删除9 b- \" n# q/ K+ N  o( U

0 O, z/ N3 n5 V9 W
收藏 评论0 发布时间:2023-4-2 17:48

举报

0个回答

所属标签

相似技术帖

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