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

IAP固件升级经验分享

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

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


8 \9 \' f( `) R1 d

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

& X2 N8 a6 \! Q1 K

STM32的代码启动过程

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

. C9 p7 I  ]# N% V, S) i

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

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

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

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


    7 _4 b; W- n2 k# [5 T8 V
微信图片_20230402174619.png   V* g8 }$ _& U# _/ @" T

6 G5 x) x/ k4 p" H6 a" |启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
    + ^7 l$ y# l1 Z8 m/ v; e
  2. ;* File Name          : startup_stm32f10x_hd.s
    0 H5 [: B9 S2 s1 a" w+ b2 ^
  3. ;* Author             : MCD Application Team
    # r6 `& a. u! b5 E- |6 X$ r+ s) o
  4. ;* Version            : V3.5.0
    * Q5 [* C; }  z1 W7 A
  5. ;* Date               : 11-March-2011
    " h$ O3 S$ \3 b' h5 ~' }0 P
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM
    9 D6 Q+ l, r: T' X6 n
  7. ;*                      toolchain.
    . W( D- H! S5 ?2 E
  8. ;*                      This module performs:7 @- C/ @" H" {1 c! E  U) F
  9. ;*                      (上电复位后会做下面的几件事情)
    3 Z6 K" b) X; P
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
      K2 _9 }& b  D8 B& X: p. S
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)
    " E8 S' \1 I( m
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)4 C' r, I- v% D' o* a( U0 @
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)  K1 P- W" X: a
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data 1 |# ^5 ~5 n  {7 F. ~$ k( Y; H- r* o
  15. ;*                        memory (optional, to be enabled by user)1 k7 y; k: o4 h# b# ~; o9 @. f% c
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)
    " m, [/ I- b+ z9 D& s: r. T
  17. ;*                        calls main()).
    ( L+ s+ U. B: K0 S3 H- I  C7 ]/ g3 I$ ]' w
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,% |) G5 e1 S& d$ u2 f8 c
  19. ;*                      priority is Privileged, and the Stack is set to Main.
    & A$ L6 H: b3 b# J
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   
    # ]" b: }7 H' P- E
  21. ;*******************************************************************************8 o5 X7 {: L/ D9 N6 ~. u

  22. % Q& n! K7 C- {, R5 L! z1 h

  23. % q2 u8 u: q  ]2 ?$ b; u

  24. 7 A" i, h6 C4 x0 l
  25. ; ------------------分配栈空间----------------. A3 p5 r! G3 s* {0 B
  26. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小) u, S+ e/ u: k! k$ E
  27.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册): T% R6 F) y# b# X9 E! ]* c* C
  28. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改  \% _: \/ P% B
  29. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到6 I  s  T, H3 N3 O
  30. 5 j% @; s# e2 C# F* F
  31.                                            5 p+ {/ @: t; X' ]' Q
  32. ; <h> Heap Configuration
    ; c  f, {. c6 W" Y
  33. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
    " @. `1 F. u$ D: j  U8 e
  34. ; </h>
    + @1 |! i/ [- w$ L2 w$ c
  35. ; ------------------分配堆空间----------------
    ) t: m7 j1 S) m% [
  36. ;和分配栈空间一样不过大小只是512字节- E2 ?" |, N6 B+ W6 w
  37. Heap_Size       EQU     0x00000200
    * X- F# ^; T& u, S! A8 B% a9 i
  38.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    4 e' ~  a% K3 e1 j* M
  39. 4 x  T6 G" b. H* ~* P& w7 |) B4 E* a
  40. __heap_base        ;__heap_base堆的起始地址
    ' }1 e# o# T5 W% u/ Y  m
  41. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的/ p8 Z+ }6 B/ J; k
  42. __heap_limit       ;__heap_base堆的结束地址
    1 U2 h/ c9 A& w$ L8 ^

  43. 4 V5 D, n  F" _% X6 C& `9 I2 o
  44.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐/ a; a. ^0 m* M0 f$ v: C6 x
  45.                 THUMB;THUMB作用是后面的指令使用Thumb指令集* a2 [! e* N# I3 n3 }; j

  46. 4 O$ o! w4 u. C  H1 T
  47. ; ------------------设置中断向量表----------------# C1 E& H) a( Q) a
  48. ; Vector Table Mapped to Address 0 at Reset& R; @/ S. C! b3 ^, t* J( q$ N- d
  49.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
    - ?! B, X/ y  O, A! R9 E
  50.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性( t0 z# b2 u% M/ ?* K
  51.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用          7 ~4 ~6 f2 {1 j6 T6 _
  52.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
    0 e* Y9 ]' h7 M5 d9 r* ^. {
  53.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用; `: S9 Z' J" C* p' ?* A# o
  54. - t- g+ \1 [; Y- U' x7 {1 C
  55. % C: C5 Z7 O7 t" b
  56.                
    ! n6 e$ q3 D4 a; K
  57. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化" c9 y8 C0 }  w

  58. ; P. e8 k) Z0 @- e
  59. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
    " c# g" ]  U  n8 q: K$ |4 }  [
  60. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,( [! u4 A7 [. i; \( R2 i6 P
  61.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp)
    % v' P8 t; ~- [' m5 O1 ?5 [
  62.                                                    ;* __initial_sp的值是链接后,由链接器生成
    ( V1 \3 p3 Q- _' j, i& P
  63. " y" M2 A1 s2 f, r2 f( j' h' v
  64.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,, L0 ?, l3 n5 e& l
  65.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
    5 i. F6 i1 ~) J% l4 s$ F" T" P1 x7 Q
  66.                                                    ;* Reset_Handler是一个函数,在下面定义
    1 S- Q4 E1 m& Z# {+ ^; G
  67.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
    7 ]9 k* z7 q! \/ b8 a% N
  68.                 DCD     NMI_Handler                ; NMI Handler      
    . @) X6 M& S! w6 ]* Z
  69.                 DCD     HardFault_Handler          ; Hard Fault Handler, t3 D) g* ~9 a) q+ _
  70.                 DCD     MemManage_Handler          ; MPU Fault Handler  r6 H0 U" S" v3 F. Y' m7 j0 e. |, i/ A
  71.                 DCD     BusFault_Handler           ; Bus Fault Handler
    3 @) K% ^. @  g- {' U
  72.                 DCD     UsageFault_Handler         ; Usage Fault Handler
    9 M* \  K$ W' k1 L

  73. , S- v% f! u3 c! U# O# B1 b; X# v

  74. " w, j1 u8 c2 ~; N  B) i/ {
  75.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件( Q: L' i, _, B# x8 G8 C  D

  76.   M- g' {( i& o/ i9 u' r. ]
  77.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
    8 j. m& E; J' {4 |& W6 \9 n  Q
  78.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2/ s& N+ K# _6 Z1 _8 ]$ B
  79.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
    4 Z9 [* `7 b$ W0 Z5 ~
  80.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel57 `7 y* P% q; C4 \
  81. __Vectors_End                                    ;__Vectors_End向量表的结束地址4 c8 G4 I( V. X2 P8 t4 i

  82. 7 T; f1 d1 ], Y( c6 f( l1 ]
  83. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小: j' |- V0 j1 X
  84. 3 k5 X7 Z; i+ K" a
  85.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读
    8 G8 N& T- ~' x. @$ I6 e' p
  86. + f+ G9 [* U+ v1 I" h2 ?# F0 z; W# D
  87. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               7 t+ j  `) K* b4 N5 `
  88. ; Reset handler! A! Q( B- ~7 F  C' A
  89. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
    & ~. L# c; \0 N" o2 W% H7 \( r

  90. # L' R$ \) V: i3 D# z, N  f/ W1 m8 s: Q
  91.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,% E, x8 b4 f  G7 P; c) H  O
  92.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数; K; d& i( J4 m3 N* W& O
  93.                                                 ( x8 g. Q( H# z  q
  94.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似2 \8 T: G+ m! d; P
  95.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
    8 U$ `' S# B- {
  96.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
    5 c5 x+ P* s0 A& t. B( x
  97.                 LDR     R0, =SystemInit; n6 A; ^% A/ ^& t
  98.                 BLX     R0              
    / m1 ]5 i3 `! |9 x4 b
  99.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
    ( [) ^$ k. V* Q
  100.                 BX      R0
    5 D& i7 e0 d; ~4 x5 |
  101.                 ENDP9 X+ E% ?( q, S/ m+ G( D1 ^8 p6 p
  102.                 ) m$ K! v' i& u2 z
  103. ; Dummy Exception Handlers (infinite loops which can be modified)
    + u' }9 z* k1 u0 k; y
  104. ;下面定义的都是异常服务函中断服务函数
    / y. p6 m1 U, Y* P. o0 @
  105. NMI_Handler     PROC. E+ s6 t! A1 q) U& T% Q: o8 o
  106.                 EXPORT  NMI_Handler                [WEAK]
    ! A$ T/ V1 I9 L% k5 a! d5 [/ v
  107.                 B       .' x* t# w# B5 R7 r! G. g
  108.                 ENDP( O$ `7 A( h9 C1 x. T1 p3 X
  109. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件6 R2 d  L* H/ x- R" O
  110. SysTick_Handler PROC
    4 A+ Z( d5 Y* |% g/ {& p* C
  111.                 EXPORT  SysTick_Handler            [WEAK]/ g1 L& a- L0 G9 U" c( t
  112.                 B       .
    - T$ V) L8 \" D
  113.                 ENDP& c$ C; n2 I0 Y* u5 a" I

  114. $ F3 h+ R5 Y; a% k" S
  115. Default_Handler PROC" R( D5 g8 o7 y: Z0 P

  116. 1 M) B0 M: z  E! a8 I
  117.                 EXPORT  WWDG_IRQHandler            [WEAK]" h, H; C! V' M7 l2 ^0 J* Z5 H3 s
  118.                 EXPORT  PVD_IRQHandler             [WEAK]! g0 P5 h2 [$ T* ^
  119.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
    + I3 ]8 y0 k$ T+ s" @
  120.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
    0 P1 H( x7 w) j2 `* {1 J
  121.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
    1 i4 G. D4 p( O) N. D* ^
  122.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]; C$ n' a! R2 s* B8 N

  123. ) @$ p. E+ t& e
  124. WWDG_IRQHandler8 _& h' z# j0 |, R; F/ h+ ^
  125. PVD_IRQHandler
    5 b" Y7 K  S8 i3 x  l' s4 @
  126. TAMPER_IRQHandler% o& r: [4 J7 Q# ?6 W5 H; W3 \
  127. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
    8 E7 S& f0 s; f
  128. DMA2_Channel1_IRQHandler3 ]- y. s. P+ ~- E. i
  129. DMA2_Channel2_IRQHandler! t6 p: a$ {, G/ y# `( N" X
  130. DMA2_Channel3_IRQHandler
    6 r8 D5 w/ ], R, Q( n4 u5 g8 O9 V
  131. DMA2_Channel4_5_IRQHandler
    . u$ s) T4 V9 |; D& _  A" @* V
  132.                 B       .
    - F( D% }3 a! ^# ^1 K
  133. - ~3 u6 R, K  p( a" c6 h. D
  134.                 ENDP- V4 @/ [2 F0 ^/ l4 s+ H- q

  135. 5 ]# l3 j4 v. C, j3 s
  136.                 ALIGN    ;四字节对齐1 Z& G! |  M) L

  137. 7 ^1 I" O' H+ k% ~4 l0 U
  138. ;*******************************************************************************
    8 V! s& _9 P4 `& M  Y8 x# \1 v
  139. ; User Stack and Heap initialization* Y* N4 N1 ~# }, K
  140. ;*******************************************************************************
    / f4 A5 X5 V5 w- P- y8 s
  141. ;下面函数是初始化堆栈的代码, ?( y: c" K; r! {1 z: S/ p+ {
  142.                  IF      :DEF:__MICROLIB     
      t' l  H/ c- M: Y0 x& A9 F
  143.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义* y2 j2 U% @$ A! H
  144.                  ;这种方式初始化堆栈是由 __main 初始化的7 t( B' e) H+ D# J" l* J
  145.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
    % [; C! K8 h9 S7 x! i
  146.                  EXPORT  __heap_base    ;堆的起始地址
    ; R0 j" I5 Z" i1 R. |; e
  147.                  EXPORT  __heap_limit   ;堆的结束地址. m4 o, }( R' _- ^
  148.                 : _* r) v4 E7 C" I
  149.                  ELSE9 x3 B9 p) N: s* e
  150.                  ;由用户初始化堆; t0 z) K0 x0 @
  151.                  ;否则编译下面的
    . V% |: X; X" m7 v& ]6 {$ Q
  152.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现) N: E, S# x4 ?' T: ~, ?- g- [
  153.                  EXPORT  __user_initial_stackheap  L: r- ~, Q' _+ i' K; @, g
  154.                  
    # x2 y5 l5 E7 z, J3 M, g$ l  V9 O
  155. __user_initial_stackheap
    ; _( s; |* b* Y# B2 E* V
  156.                  & X* Y# x; |! w
  157.                  LDR     R0, =  Heap_Mem              ;堆的起始地址
    ; B  j" Y, L% h3 a
  158.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
    . k; f8 `1 a$ Q4 X- P4 ]
  159.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址5 f% b  W) o" [' B% L. \
  160.                  LDR     R3, = Stack_Mem              ;栈的结束地址
    * d" F+ T( p: S6 O  N6 G) {4 ]3 n
  161.                  BX      LR0 i& m+ Z* K0 @( _3 G7 g2 w
  162. ! Q& U5 y7 _9 T8 G! N/ w3 e
  163.                  ALIGN, B- i5 J1 r! b2 E- d2 {$ M+ I/ H
  164. 7 r; ~( L% ^& d7 m6 H$ E' K4 }
  165.                  ENDIF
    7 [# ?9 M4 w3 i# }' n+ q6 N, \7 q' g
  166. ) F: |- s; z( |( ~7 v" T% y
  167.                  END
    ; x- q8 X, R1 T" S: Q+ K4 J

  168.   ~% N& b  O4 d
  169. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码
/ _. _  _( Z) ?2 c) Z

  F, `9 F; R3 f" g- @  p

STM32的启动步骤如下:

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

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

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

    1 y/ w9 A' F# P: s/ _1 ?) C! x
IAP设计思路

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

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


' i! s: R. n$ D, a+ \6 i

升级流程图7 A+ a* c7 {) Y& o

0 q- i" p& ~' E7 t* ^! p# t
* v. f9 g2 U, Y$ y/ ^3 ^
微信图片_20230402175006.png
: ]/ m* F1 W. a5 x9 K' l
" H/ Z  M- i9 O4 L
( G, n5 i9 H6 b$ r0 j  y8 r+ D
Flash分区

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


; g, g% [$ D: g: I; Z

微信图片_20230402174614.png

7 [. A" c+ _' I7 @% X  R) l

  • 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


    1 n- ?( [3 f8 G- }
代码实现

硬件:

  • fallingstar-board(已开源,打板验证)# z6 ^: M7 R  T! D2 L/ R+ a

软件:

  • 内部flash读写

  • 串口DMA+空闲中断


    $ S# r( {% g2 L+ ~/ k2 e
内部flash读写操作

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

读flash操作:

  1. /************************************************************0 W0 d. F4 S9 z8 z' E  F9 v6 M
  2.   * @brief   读取2字节数据; q9 z6 {7 }7 d
  3. * @param[in]   uint32_t faddr' f! V: S5 L% M* _
  4.   * @return  NULL9 A5 Q: D0 \; W9 e! j
  5.   * @github  5 z# t8 ?4 t( D
  6.   * @date    2021-xx-xx" L2 K, i- ?* S/ n
  7.   * @version v1.0
    $ L% W- j" T) A$ {' m& z
  8.   * @note    NULL4 Z0 v& I! y- z" }+ g  J$ C! ^
  9.   ***********************************************************/- m* l' X6 ^' K8 ~- M- f
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)
    # Y4 B3 \  ^, A$ x+ B
  11. {+ R; g3 D/ X9 V. b+ O& \5 \
  12. return *(__IO uint16_t*)raddr; ! U. t8 L: M6 Q2 R  |1 N: t
  13. }) F9 z* ?9 K! t" g/ o: g$ q
  14. /************************************************************8 }6 z; o4 F5 p* z7 b
  15.   * @brief      读取n(uint16_t)字节数据/ W9 ~6 ], e$ B; {% a3 T2 y* B
  16. * @param[in]   uint32_t ReadAddr
    4 y; g2 f) [6 E4 n- G5 W
  17. * @param[out]  uint16_t *pBuffer
    0 x% @) @2 O/ l: a/ l3 W
  18. * @param[in]   uint16_t len  w+ F4 x" U7 o- s4 i
  19.   * @return  NULL
    . s( y4 S+ v( u* M9 a
  20.   * @github  ; f$ Z+ {- V2 C( N3 s# b
  21.   * @date    2021-xx-xx
    % D$ d/ g' e% |  s7 i
  22.   * @version v1.0" c8 G, ^  e9 i+ P% P7 ~8 R6 m
  23.   * @note    NULL
    , x4 K6 z9 g- N. C' ]' u) n8 p
  24.   ***********************************************************/
    ) ?# g  ]5 W6 ]2 m6 l% q& e7 p
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )   
    ! ]! N" f# U8 [4 s2 S
  26. {$ ~: \- }; z- C* Q- P
  27. uint16_t i;
    / n0 v4 T3 d8 m' X0 \0 x. g
  28. * U& M8 s3 E$ Z3 c( A- b- _9 b, [
  29. for(i=0;i<len;i++)
    / o% K: b2 u/ {) _8 e7 ^; m. C% g
  30. {
    ! w. q3 N6 l/ u( x, ?) E
  31.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.$ C- \; o5 z* B& E6 B7 v/ P( h" Z  I
  32.   ReadAddr+=2;                   //偏移2个字节.
    ! s  E' S3 E+ K
  33. }/ P: q; C; T- W$ f
  34. }
复制代码

" C- l  N7 p, C& @3 u5 w

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

  1. /************************************************************
    7 @) h' N6 y. f  V  L& d( ~
  2.   * @brief   写入n(uint16_t)字节数据
    6 u. N$ p' |9 t1 [% }# I
  3. * @param[in]   uint32_t ReadAddr" \1 p5 @& ]$ u6 F& d3 G
  4. * @param[out]   uint16_t *pBuffer7 I; K' A8 G3 E7 U# ?/ V6 N, y
  5. * @param[in]   uint16_t len' j: Z, U9 p9 \  P7 m
  6.   * @return  NULL
    3 f& L3 E! z7 {$ |1 w9 t8 W' }
  7.   * @github  
    . j  E* b7 A  J. X/ |
  8.   * @date    2021-xx-xx
    3 v; L# F4 @! u  @2 j) O7 p1 g6 w
  9.   * @version v1.0, Z4 c4 A/ z; _! n5 P- G
  10.   * @note    NULL) w* P' W. t! S5 ?: H
  11.   ***********************************************************/
    : b! P, V& [& Y* h
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   
    & ?3 ~6 O: x* b. T4 \/ a7 w/ `( d
  13. {        
    $ C8 g  E9 Z% f" f2 [
  14. uint16_t i; $ O' Q. j) ^; d
  15. 5 f; M( F2 k4 B3 G! J% s
  16. for(i=0;i<len;i++)2 b. x$ {& {' V; U3 L
  17. {; K" X1 ~: z! L* I, `
  18.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
    & ], I  j$ E; r, X
  19.    WriteAddr+=2;                                    //地址增加2.
    1 @% w) G% z: A' W# `+ a9 m
  20. }    u( m# P4 {% |( F2 H
  21. }
    * w1 b3 O! D: a7 @* ~
  22. /************************************************************
    6 U( K, {/ y, p8 |
  23.   * @brief     写入n(uint16_t)字节数据
    - l, r0 l+ y" W2 B0 I, O
  24. * @param[in]  uint32_t WriteAddr
    - f  g. w% z+ l( Z% n
  25. * @param[in]  uint16_t *pBuffer# M7 W1 ^4 c2 ?+ x
  26. * @param[in]  uint16_t len
    , Y$ i1 ]+ s3 N8 Z% _
  27.   * @return  NULL2 D0 G$ L6 k" j+ v" o1 l$ G) ]
  28.   * @github  
    5 A1 W8 e% K6 p7 V+ ~9 G
  29.   * @date    2021-xx-xx  t# R  _: @* D3 B) V0 F& B* A8 w
  30.   * @version v1.0
    : r5 q" x3 H6 \4 ^0 ^- R: |
  31.   * @note    NULL$ @4 a9 R: m: p% e' k) K9 f5 v8 u
  32.   ***********************************************************/
    2 `& h5 D0 S3 j  r+ F5 @
  33. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
    : d; ^  ~: N, I( k5 ?
  34. {
    & q6 J3 u! V5 y( G% L2 ]& [
  35.   uint32_t SECTORError = 0;3 |5 j. U7 Z/ H
  36. uint16_t sector_off;    //扇区内偏移地址(16位字计算)/ f5 I; T' X; _2 `$ P
  37. uint16_t sector_remain; //扇区内剩余地址(16位字计算)   
    9 Q' k2 R* U0 e/ C2 D' y
  38.   uint16_t i;   
    + D  q/ n$ s( _6 t0 o2 V
  39. uint32_t secor_pos;    //扇区地址4 K: E! h2 n9 }$ B2 j
  40. uint32_t offaddr;   //去掉0X08000000后的地址
    % ~" o7 j1 N( l" w: ^7 m8 K. @0 ~
  41. ) X0 x, w; t% R8 o4 {* j* m
  42. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址, \4 e0 J" I0 P: q

  43. ; B: ^8 s% C  d6 \' f8 ^4 t. y
  44. HAL_FLASH_Unlock();         //解锁) `- u* U0 k, e  o6 G) f. h
  45. - q" f, s7 ]* f1 P$ s
  46. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.3 [2 @8 o0 U+ z% g- Q: P( V5 W0 C$ R
  47. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT6
    " a5 I7 D4 Z# u0 G- u- o0 |
  48. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)& g/ X) ~" |1 D9 L% u' Y
  49. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   2 C, T) U9 k' U# d$ c
  50. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围( o, W& J8 k2 {3 B4 A: E. t9 C2 W' j+ I
  51. ( e; {* f2 H' P+ z0 t+ D
  52. while(1)
    # U6 R2 _& c# a. `
  53. {   Z/ k$ K# U( d
  54.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
    , o$ d! a" F& m, ~2 E6 u2 K9 H1 m7 X
  55.     8 m/ \& k6 M7 m. P& g2 y
  56.   for(i=0;i<sector_remain;i++)//校验数据$ _! Q+ C" E8 T3 N# n+ {
  57.   {) G4 ^9 U, j( A! y
  58.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)
    8 Z+ K4 U2 J; @3 @6 d0 X$ ^
  59.     break;//需要擦除     
    1 T- _8 t, ?; b8 w' x- \: F4 t
  60.   }1 W5 w+ f# X- [# t
  61.   if(i<sector_remain)//需要擦除
    3 L" R: x4 k! N  ]
  62.   {1 t3 m* W( S( I: I+ u, H
  63.    //擦除这个扇区; H/ S0 B4 ~. d7 i
  64.       /* Fill EraseInit structure*/( p! Z4 l1 M2 J8 b% @0 V8 F" G
  65.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
    ! k- H- d# @3 G$ N. c7 R
  66.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
    % ]1 \9 |0 U/ C. f& N. x; y' }
  67.       EraseInitStruct.NbPages       = 1;
    : H' T: z; @* N
  68.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);+ _8 F( f( }1 R* {
  69.    for(i=0;i<sector_remain;i++)//复制6 [  `1 G. t4 `# @8 C' m) T" B  A6 L- r
  70.    {6 W! n  L8 Q+ X8 J/ B# G' z3 {& J, q
  71.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
    9 a+ N. b# i. q2 F" C
  72.    }
    5 n, U+ I, \( J/ Q. s- Q
  73.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
    + j$ t+ _/ V! S& A2 D, T
  74.   }
    ) u" V* B2 D# g# n8 e
  75.   else 1 i$ b3 v1 g* H4 x2 `
  76.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        , ^' c2 o% W  ^0 y
  77.   if(len==sector_remain)
    9 e# O; E  b. b' x: I( S
  78.    break;//写入结束了  u( ^: T# l0 j6 z- w9 N
  79.   else//写入未结束
    $ ]9 g3 i1 \9 C/ G2 h# `, S2 @- L4 d. Z
  80.   {
    ' Z) E6 T9 ?9 q/ ]) v* D, {
  81.    secor_pos++;    //扇区地址增17 I2 u6 p/ N$ f2 U0 y$ T! n# `$ P
  82.    sector_off=0;    //偏移位置为0   7 L  v! s1 |7 v/ U0 V" E
  83.     pBuffer+=sector_remain;   //指针偏移, f4 _+ [7 A+ I- Z, D& o( |  L( O3 a
  84.    WriteAddr+=sector_remain; //写地址偏移    / E! Q' d6 X# r
  85.     len-=sector_remain; //字节(16位)数递减
    , y5 D$ R2 W! v
  86.    
    # e. @1 L/ \' V
  87.    if(len>(STM_SECTOR_SIZE/2))
    3 O+ Y* z2 [5 v: e
  88.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    * ~7 m1 Y& `6 _, e; N( Y* u5 p
  89.    else ! a* K$ m  A7 m- V
  90.     sector_remain=len;//下一个扇区可以写完了
    : U. ?) f" {5 P7 ^" d5 _
  91.   }  
    0 I! y7 r5 w+ n4 r0 e! b" i; h; z
  92. }; / j& B: C9 g; ~. y
  93. HAL_FLASH_Lock();//上锁
    0 `% [$ S0 C9 S2 _9 F$ s
  94. }
复制代码

3 c2 s  N1 W8 B" V1 c) Y7 V串口DMA+空闲中断接收不定长数据

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

  1. #define Max_RecLen 1024*3$ c9 ^8 [0 v. D: Y! g! b* J

  2. , `6 h# b; |0 B$ j. E3 r" D
  3. typedef struct{
    4 b1 c6 m+ W7 k) A' U/ v' b5 A5 I
  4. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区: f4 c4 s; W' y4 h7 X: `& d
  5. uint16_t RecDat_len;      //单包数据长度
    . t: Z3 v( \7 Z/ X+ w. p0 r: A
  6. uint32_t Cur_WriteAddr;     //APP缓冲区地址
    9 S* D* C$ V/ w8 E7 X) Z
  7. uint16_t BinLen;        //bin文件数据长度
    8 C( Y% k* B+ z6 H
  8. uint8_t rec_endFlag;      //单包数据接收结束标志
    . K! ^  S! v; X) b& d- A! X! `
  9. uint8_t Binrec_endFlag;         //bin文件接收结束标志 7 G6 L; K# N6 ?. H
  10. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器
    ( E  x7 b) q# k# d: O& J

  11. 2 X8 C* m# K; e
  12. }UserUartDMA_Typedef;
复制代码
4 k, Q. e' Q' T  m7 Y  s5 u- m! k

然后在串口中断中:

  1. /**
    ) }6 O5 K# Y: L* Q
  2.   * @brief This function handles USART1 global interrupt.
    5 B1 ~3 G7 z; D' S# a
  3.   */
      n5 x  @  R) y. g; R" `' q, q
  4. void USART1_IRQHandler(void)
    & l% f4 A5 ?) u/ g
  5. {
    , }, \: S8 O. D' ^
  6.   /* USER CODE BEGIN USART1_IRQn 0 */. F6 S& a5 S" k- |+ F) r- D3 E: A
  7. uint32_t idle_flag_temp = 0;
    : O- r3 y! d# [2 S$ G
  8. uint16_t len_temp = 0;2 G" L8 w4 p, S, Y% l# \9 o) c
  9.   /* USER CODE END USART1_IRQn 0 */8 z5 U  Z- V8 B  N7 t$ T) L
  10.   HAL_UART_IRQHandler(&huart1);
    8 G2 g2 U1 W) j  q5 h
  11.   /* USER CODE BEGIN USART1_IRQn 1 */
    8 L' v0 _* T$ r; A
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);& a. _0 P# S9 z0 L2 a/ |
  13. 8 @  x4 W& e* V" g  s
  14. if(idle_flag_temp)& K6 ]4 |+ j" Q3 i
  15. {" E, o- M2 s& p
  16.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
    * I0 _; T3 ]% Y. @+ C9 p4 w5 r
  17.   HAL_UART_DMAStop(&huart1);! B& K, e: b! h7 @8 p
  18.   
    " X, H+ h7 `9 C$ h, o7 C
  19.   UserUartDma.DMA_TIMCNT = 0;
    ' |/ p  V6 f# M( I. R, o
  20.   UserUartDma.Binrec_endFlag=0;
    * ]0 ^/ ]. @& v2 W+ F* A4 Z
  21.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
    : e. w- F: |" c* e2 ~; W
  22.   $ {$ q2 {' }/ f9 g7 y- O8 L; Y
  23.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  
    / k3 [/ ^& @) O# E8 E
  24.   UserUartDma.BinLen+=UserUartDma.RecDat_len;; d5 r& l: j2 K5 F) g7 @$ J6 V
  25.   UserUartDma.rec_endFlag = 1;
    4 ?7 v9 Y9 p! W* v* x
  26.   
    . B  {* F. S$ K
  27. }
    6 u$ h+ x  r* s1 ?$ G
  28.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
    6 Z+ ]5 y3 r) n9 ?
  29.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen); + Z" d: F7 e3 E$ u1 h6 U
  30.   /* USER CODE END USART1_IRQn 1 *// ~' {0 J( n/ E+ P* {3 e5 S
  31. }
复制代码
% k' V8 k8 S' K5 j6 {9 f+ {
IAP代码设计
  • bootloader代码设计
      o5 A% r$ o# X0 U6 q- l; G

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

  1. /******************************************************
    + J8 x. M1 e- N: d' N3 G+ [1 U. ~7 l
  2. * Brief     : 擦除APP区# e. O3 V" e- z$ J3 O
  3. * Parameter :
      I4 Y+ H( |0 l) ?, A
  4. *           *startaddr:APP起始地址
    $ w7 n# G! Y" y/ t/ c$ K
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE
    . ]0 \3 P4 @9 L
  6. * Return    :None.
    & g: E# l3 _7 p
  7. *******************************************************/
    ( p* s* H9 Y' y6 e+ f. \: Z
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)9 d- D& d4 p4 @3 i7 B/ @2 z
  9. {' ?8 M% [( V7 f" P, }6 x
  10.   uint32_t SECTORError = 0;) e- T5 i9 Q* \7 d& v+ `
  11. * i: o; g) |4 v! G; Y1 T& k0 G2 ?% Z
  12.   //擦除APP区+ T3 S- k% h2 X: O
  13.   /* Fill EraseInit structure*/1 r6 P6 t$ }6 m6 ?3 t# H
  14.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;; w+ {* u* c, s3 F( U( G
  15.   EraseInitStruct.PageAddress   = startaddr;! Y- P4 ~3 X1 l1 {
  16.   EraseInitStruct.NbPages       = pages;
    * _% p; f! }% w5 J$ I; e* |
  17.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
    5 Q0 C  ]5 D$ _  r4 t
  18. }
复制代码
2 G' S( t, j6 v9 Q( ]

升级标志获取与擦除

  1. /******************************************************; \( G, F: N. B# j
  2. * Brief     : 写入升级标志
    * K8 |5 g# f5 c" V. o# ~. n
  3. * Parameter :
    + I+ ?% c- D- p: J: m
  4. *           *addr:标志存放地址
    & @7 a2 F3 n8 f. S: f$ J0 `
  5. *      *pdata:写入的数据5 B2 t+ D% {6 {
  6. * Return    :None.
    3 [$ ?4 L. J. E+ ]1 ]6 }
  7. *******************************************************/
    , ]* V$ Y  I2 }. R. d
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
    5 X6 w$ B1 r8 v) S, {6 M+ ~+ R5 p: Z
  9. {6 A0 e1 q8 O6 E. X' M5 g9 O6 O
  10.   BSP_FLASH_Write(addr,pdata,1);6 p3 y$ s) I5 Y# E
  11. }9 q; Y. W; }  j# V, r- E: h
  12. /******************************************************2 D# j$ f% o6 C; t! }
  13. * Brief     : 获取升级标志
    8 q0 J8 q: @# t6 E8 }0 O
  14. * Parameter :
    % l# a* y, h4 Q& A8 r6 ~
  15. *           *addr:标志存放地址
    7 ?+ b1 \- h% @; \4 c: V: u
  16. * Return    :None.
    4 B# J) X/ l! e/ c/ `! k6 U) [% ^2 x
  17. *******************************************************/
    7 _0 I. f/ x2 j$ I& [( y8 s" B
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)
    5 g& r4 }, y. _7 P" M
  19. {
    # a0 g9 C5 \! X* n$ r; [; _( u# h
  20.   uint16_t flag_temp;$ \9 {  W3 j, @3 J6 P; o# J
  21.   BSP_FLASH_Read(addr,&flag_temp,1);* K6 v/ Z0 W. M+ W
  22. 7 m: |& o9 K- G+ L& F* K6 L; u
  23.   return flag_temp;
    * _/ t  i! `4 V: [: q3 V1 W
  24. }
复制代码
  R9 t$ C, f% B% Z. T9 l$ T

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

  1. /******************************************************
    . Z4 v6 }: a3 R
  2. * Brief     : Bin文件写入app缓冲区
    + s1 q+ M1 v: ~
  3. * Parameter :
    , ^6 o. a- T5 L3 N
  4. *           StartAddr: 起始地址
    4 B% [, p* \1 Y& ?; \, j
  5. *           *pBin_DataBuf: 要传输的数据5 b8 I7 |% h6 F8 W' F. [9 s
  6. *           packBufLength:单包数据长度4 F+ w( h9 o( p# V$ o
  7. * Return    : None./ [7 b2 {- A. K8 b0 ^. z: y" c9 C
  8. *******************************************************/
    " n+ |. E1 n( Q" w8 G  n: m7 i
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
    ) K3 D3 `; e% x% E. x- \
  10. {4 ?3 G  g! Q. _. \4 ^1 P% T
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;
    - d+ h; u5 u2 c$ g8 Z% X0 V; v
  12. uint8_t * pData = pBin_DataBuf;
    $ r% ~5 L" L" m- @# s% c1 z" ?
  13.   . J( X0 _2 R3 E6 z1 y
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
    $ D+ ^. S9 e! M
  15. {         
    * Z8 q2 p% f4 Y$ V% w, B2 J
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;
    ) }: a) C* ?# n5 e
  17.   dataTemp += ( uint16_t ) pData[0];   5 c7 V) ~# Z* B  T: E% n5 l0 y3 B
  18.   pData += 2;                                                      //偏移2个字节
    ) |2 ]5 T; q4 Q2 _4 y- `, ^
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     
    $ d% G* Y+ S; X  n0 N; N% z
  20. } 8 ^; ^. E9 {9 Q; I

  21. & h5 T( Q, ]) }/ Q/ [
  22.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
    % u! ^. t* ~( `. A
  23.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.
    * |" l1 ]8 S: q
  24.    packlen_Ctr = 0;4 J, R% C9 s+ c/ X4 s
  25. }0 X, F. e: Q0 i8 H% ]* Y% F
  26. /******************************************************
    7 a3 O0 @6 T0 M- S8 c6 U, \; V6 p
  27. * Brief     : Bin文件从app缓冲区写入app区
    - W1 g3 t4 r: Z9 b2 Q
  28. * Parameter :
    9 w" h9 ]" k* Z$ a" I, _
  29. *           SrcStartAddr: app缓冲区起始地址6 b6 v; o" q" V3 }4 l- F
  30. *           DstStartAddr: APP区起始地址  v5 T( f/ K4 n; k* E6 W% ?9 E2 n! K
  31. *           BinLength:bin文件长度/ l# A. R3 P) c0 }; _% o# u' `* v
  32. 4 c5 `* W) W% L' Q3 X) [
  33. * Return    : None.
    - f+ c) [7 i3 v- c  L3 V" K
  34. *******************************************************/$ ]2 u5 V# L2 W
  35. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
    2 {! v4 q5 g, b- ]% {$ O. R
  36. {
    / S& H7 A2 V- j1 ]! E% K9 e! s
  37. uint16_t data_temp = 0;
    ' W  h( b, j; Y) p: B! r2 u
  38. uint32_t count=0;7 P) c& r' ^5 Y: u( W
  39. + r/ U/ b2 O/ ^! z

  40. % h; p+ u6 t% W7 c; t/ N, U4 O
  41. HAL_FLASH_Unlock();         //解锁
    " U, V6 g% {  [# U
  42. * J( Q  M8 w4 h% e
  43. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);4 {! J! B- p' R& U) @/ _
  44. HAL_Delay(10);
    2 m& k5 t1 N5 P

  45. " y. {) k8 o4 w9 w. q% G
  46. for(count=0;count<BinLength;count=count+2)
    , ], B% ?- M* O) X
  47. {* N( O9 f5 J! c6 v
  48.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);- `: r3 _$ Y$ x$ P0 o) p
  49.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
    9 M, g% L$ {9 r7 t. c6 O
  50.    SrcStartAddr+=2;: O# X7 n9 F9 j% n6 p
  51.    DstStartAddr+=2;+ C& S& a. x4 Q7 X
  52. }
    6 z0 H( E2 ^- k( E1 r- `; b/ b( J
  53. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
    ( X, }, D5 i& }1 G9 G! F8 h
  54. HAL_FLASH_Lock();//上锁$ p4 R8 U# m1 R8 |6 B7 q( h" P
  55. }
复制代码

# N2 W3 g0 c' m0 R- j  z# `

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

  1. /******************************************************
    # Z5 `7 x9 z* H2 _3 e2 B. ~
  2. * Brief     : 设置栈顶指针
    / @0 N& U0 ^  J: ?5 s
  3. * Parameter : - |$ j/ p5 U6 p, M* t: E6 V
  4. *           ulAddr:
    1 ?" a: T' T8 n  m" i  j8 L
  5. * Return    : None.* U2 h2 D* v. g% v% v  \# m
  6. *******************************************************/
    / N9 N. L& l; D# I

  7. 5 q* ?! I1 Y6 r* A
  8. __asm void MSR_MSP ( uint32_t ulAddr )
      c. A" H9 `# y0 G1 w$ N% G
  9. {9 a  u+ K1 i! j! W" h) K3 _/ |
  10.     MSR MSP, r0                       //set Main Stack value
    , p6 S& c1 l6 Z8 J3 p% A; w
  11.     BX r14
    - C* N5 |! [" |4 ~$ u
  12. }
    ( X* ]- G1 w  }* L

  13. ! m. C3 R! o3 a
  14. /******************************************************$ W# b: {! r0 t; J- t
  15. * Brief     : IAP执行5 C- d! `9 h  E' Y* R- J! F
  16. * Parameter :
    * y, m0 k* ~  M: t3 h8 |* F; Z2 ?
  17. *           ulAddr_App: APP起始地址  y) M3 v  j) ?4 s" i
  18. * Return    : None.' X# ?' R" x! U: p0 y9 _; F
  19. *******************************************************/
    & \8 r0 o' Z- v) o) c5 F! |9 g% E4 y
  20. void IAP_ExecuteApp ( uint32_t ulAddr_App )
    3 B4 V8 h/ `$ M: C/ j
  21. {
    0 g3 N& @: W& C* r
  22. pIapFun_TypeDef pJump2App; 8 W3 q& z4 B& B9 e/ ]

  23. 1 @# I& i/ y2 X0 s! H  F6 O) f3 c
  24. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法.+ c' o1 }. m- d: t
  25. { 6 ~* y( W- Q1 `
  26.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)  
    0 Z$ j& b  }0 p5 o) y
  27.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址). @* C. ?: O; H  }; {5 `- j
  28.   pJump2App ();                                             //跳转到APP.
    , r: F$ B# t: u4 K- x% f+ G
  29. }0 Q" g* z0 L# |
  30. }  
复制代码

* v$ M* ^9 e4 l; u

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

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

  1. static void Systick_Config(void)
    4 t2 u& c" g1 k7 h
  2. {
    / a4 B& h; Z2 I8 c; Z; q
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
    : K  ?5 d, B: Y0 B
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);' s+ B! n/ f+ _. o0 X% `4 T% d; a0 o
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
    7 }2 [. I. z% `% K' [( [
  6. }2 a" v# p! _" R
  7. 0 Q7 _7 A. E+ s4 Q% k& N& i
  8. void HAL_SYSTICK_Callback(void)* ~9 T  a# u. p9 I0 S: d4 \3 S, M
  9. {' o5 }8 ]# h* M
  10. UserUartDma.DMA_TIMCNT++;. b0 H8 e: i  h) s% _6 v
  11. if(UserUartDma.DMA_TIMCNT>5000)
    2 y4 j8 C3 [, d) O
  12. {
    . d1 \5 Y- t: z
  13.   UserUartDma.Binrec_endFlag = 1;+ F6 m( p8 @: [( W( m# W3 L
  14.   UserUartDma.DMA_TIMCNT = 0;
    4 R! R" }) [/ k. i
  15. }( G& J$ F% p7 W9 w+ g
  16.   TIMX_IRQHandler_user(); ! S( W6 s% J% ]6 k
  17. }
复制代码

8 b$ `, K5 [/ E  o% i

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

  1. * @' S3 u( K. V
  2. uint32_t Task_02()- s: J( ^1 @4 L8 e& a
  3. {
    1 r/ l6 v. ^1 j3 `

  4. : q7 H& v$ o! \) \
  5. if(UserUartDma.rec_endFlag)
    * [( a9 z. |/ S# e) B0 e
  6. {
    , p! I. N' P: t4 P; H' W
  7.   PRINT_INFO("update firmware\n");
    ( N# X+ w: o0 Z9 Y
  8.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  % W: s3 q' l$ |* |0 q/ [4 w
  9.   ! A: A: @% T* z/ l% S
  10.   UserUartDma.rec_endFlag = 0;1 B* e. z; [% V- q2 m: ]
  11.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
    % B$ J2 y3 q. V
  12.   ' {1 `8 I/ O1 A2 {2 c' B( a
  13.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
    6 K- _  L* W# o8 v& ]( v
  14.   UserUartDma.RecDat_len = 0;/ W6 l4 k- N9 d4 J/ Z- L: ]
  15. }
    , R  p+ u) X/ a2 T. ]
  16. if(UserUartDma.BinLen!=0)) K- V3 @# R  S, M+ y( C, [
  17. {
    1 x  d% f9 P3 l: N$ f& N+ A
  18.   if(UserUartDma.Binrec_endFlag)
    ' h: E+ P  U7 G, E
  19.   {7 M9 {  m2 L/ P" ?& g0 ?1 L5 F9 a
  20.     UserUartDma.Binrec_endFlag = 0;- m7 E/ x- J' e% f, C# \
  21.    
    9 \  S, E. S: Z7 L' Q
  22.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志4 t8 D$ w+ ^0 |2 A% z4 ]
  23.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志
    / j' t6 @; H- d
  24.     UserUartDma.BinLen = 0;  3 a1 t; T. O3 m) e
  25.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动
    3 w7 G# `8 q" k. @
  26.     HAL_Delay(2000);
    : \9 ~, b0 }1 H
  27.   }9 t7 R' @0 x& ^
  28. ( B0 U& y, x7 U9 @. f, H- ^9 _
  29. }( D1 O7 ?2 T2 N% a
  30. }
复制代码
4 }# w! ], _/ Z5 m  r4 W

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

  1. PRINT_INFO("-----IAP Menu--------------\n");# G7 q$ U( e) F% M# l0 m/ G
  2. PRINT_INFO("-----Download APP BIN------\n");! Y( g9 c9 ?. F
  3. PRINT_INFO("-----Restart To RUN APP----\n");
    2 X4 O& S2 w5 N( @$ L
  4. PRINT_INFO("\n\n\n");  n& q% j6 ?7 p% t  \
  5. 7 ]8 h0 M$ |2 {! w( X& \
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))7 }/ e+ V* i# ~) X! X
  7. {
    + t" G  `0 d* h
  8.   PRINT_INFO("-----Restart.....5-------\n");
    : }) ]0 O& Q6 V- H# {
  9.   HAL_Delay(1000);+ B8 l( s0 M# i% E
  10.   PRINT_INFO("-----Restart.....4-------\n");+ a5 d+ G- `4 c! d: S8 J* x! f. i
  11.   HAL_Delay(1000);
    # W* ^9 ^, [/ A' ~5 I* {
  12.   PRINT_INFO("-----Restart.....3-------\n");; @$ ^6 U6 g* d5 ^# }$ B
  13.   HAL_Delay(1000);
    5 {7 f8 U" S8 C$ `
  14.   PRINT_INFO("-----Restart.....2-------\n");6 F/ [& _- Y8 ~/ P0 u: J! {2 [
  15.   HAL_Delay(1000);; A1 U; n- j% v  R0 c5 W
  16.   PRINT_INFO("-----Restart.....1-------\n");
    & [; o3 L3 c& w6 C
  17.   HAL_Delay(1000);
      X# r$ M& w/ w
  18.   8 W; O- p6 H0 p8 i. L/ `, L
  19.   PRINT_INFO("the device need update\n");! `/ |2 U9 c' |) }: g3 Z
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
    " {/ [; j9 \) M! U6 L2 A
  21.   PRINT_INFO("firmware write OK\n");$ c. ?, x) l* c% c9 t
  22.   PRINT_INFO("please double click the button the execute the application\n");4 X3 }4 `* b6 w3 C

  23. " }$ I9 w0 U% k, B- w% }
  24.   HAL_FLASH_Unlock();         //解锁
    1 V2 F$ h' A! D8 e" a/ Z, V; W
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区7 y) {- a5 S" y& E
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     5 E, u1 S  j  S: e
  27.   HAL_FLASH_Lock();          //上锁
    3 _. z, K$ g& \# r
  28.   
    $ A& f7 x. s8 n
  29.   PRINT_INFO ( "开始执行 APP\n" );
    , ~# Q) l5 p' G
  30.   //执行FLASH APP代码
    ' b/ E- [% A) e' G" p: r4 H4 x
  31.   IAP_ExecuteApp(APP_START_ADDR);
    + F1 B& d! V' G+ k+ O% D
  32. }
复制代码

0 p! k2 j# w5 W: f; o, t

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

  1. PRINT_INFO("-----IAP Menu--------------\n");+ J4 q* M  _' D) W; }/ T$ r) S$ Y
  2. PRINT_INFO("-----Download APP BIN------\n");
    : x. E3 K; v' ]
  3. PRINT_INFO("-----Restart To RUN APP----\n"); # l3 |4 K- [; ~$ Q3 N! a
  4. PRINT_INFO("\n\n\n");
    - e* I3 ?) `0 U% z" `- ~' z/ B

  5. 4 |6 d! t+ i# ]& R3 ]) ]
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))9 N- e& |9 W2 o3 e2 J. s3 B
  7. {
    " e- w6 ~# W4 D/ Y
  8.   PRINT_INFO("-----Restart.....5-------\n");" R, Z  k2 O8 @3 a
  9.   HAL_Delay(1000);
    6 _- ?) o! g/ M& u. Z) D7 Z
  10.   PRINT_INFO("-----Restart.....4-------\n");
    3 z3 }4 q; ?; ]  P* I1 z
  11.   HAL_Delay(1000);
    ' \- X# Y3 ]0 \2 v# y- \
  12.   PRINT_INFO("-----Restart.....3-------\n");
    ) Z$ q- v. ~. Y8 a* Q  ]
  13.   HAL_Delay(1000);
    ; j$ R$ T( L0 I
  14.   PRINT_INFO("-----Restart.....2-------\n");
    ( z" |- ?6 H% [/ o3 B" A. a* [
  15.   HAL_Delay(1000);
    2 }2 D, y- Z; X5 n, y' J( [
  16.   PRINT_INFO("-----Restart.....1-------\n");3 e! U3 u0 n0 U0 P+ Q! E/ j" }
  17.   HAL_Delay(1000);
    ; S2 p0 t  W5 V- e9 t3 b( ?6 m
  18.   
    ; S0 M( K. a6 x0 B" t7 t
  19.   PRINT_INFO("the device need update\n");
    ( I5 S; v9 l- j
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 7 H1 A3 \- X$ y' a/ ^; ^
  21.   PRINT_INFO("firmware write OK\n");
    # y: Q( [1 f/ C
  22.   PRINT_INFO("please double click the button the execute the application\n");
    ) a; J# D( n: L/ \" D2 V6 n) B
  23. : P1 S; l# L" `  K
  24.   HAL_FLASH_Unlock();         //解锁
    8 U: w$ h0 Q% j5 c
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    7 m+ D+ G' c* ?( e8 a; T1 S
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     5 d# P$ ?8 j& ]
  27.   HAL_FLASH_Lock();          //上锁; O5 ^: y+ E9 \" R
  28.   5 k* l# f' p/ {+ h% C1 Q' e( q
  29.   PRINT_INFO ( "开始执行 APP\n" ); * I5 f. n( ]4 ?& c  ^$ E( X
  30.   //执行FLASH APP代码% c# o# I& D% M
  31.   IAP_ExecuteApp(APP_START_ADDR);. ^6 r: z5 @+ ]
  32. } # l, I! b  H2 N9 R& P
  33. else//不需要更新,执行APP) {" v( [# `) {# i& \2 ^# h
  34. {
    2 F/ x$ ]/ k. ]# J
  35.   IAP_ExecuteApp(APP_START_ADDR);
    9 {* k2 N' B6 t; d) B
  36. }
复制代码
/ }' E5 \4 H$ m9 v) w

bin文件生成

3 Y: [- e9 Y1 z6 B

微信图片_20230402174611.png


4 U/ B6 F1 w1 p6 E3 Y

输入:

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

: L% p* s4 c1 k

参数意义:

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

  • --bin -o  IAP.bin :

    , A% z, W& z3 s, c; h% j+ w

输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:
    ; P3 i: H( |3 b/ r

axf文件目录及文件

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

5 n' S7 ~$ M# b2 @
微信图片_20230402174607.png . e0 S$ C8 X# I( \( p5 F, F

- z& Y+ O8 ?2 w) J$ O 微信图片_20230402174604.png & P" H7 W; b& Q5 q; P
; _! f; l( G/ B; ^
  • APP代码设计1 y) {  g; o7 C: o+ u% t

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


2 y& `& U! f3 r. b) Z

  • boot区代码执行效果6 R, r# v+ t* {

3 }0 ]+ X& a& @ 微信图片_20230402174601.png ; m5 G3 ~- e3 _2 |7 ^

, u* g! _0 s$ Y0 _" R
  • APP1代码执行效果- q8 R6 w; o0 ]; m$ T

- L" R  f8 h: T* K
& Z; n* S3 B" m
微信图片_20230402174557.png $ g' R: G: {/ f: @  q% R7 J

0 X6 K. j( R! W

6 z9 Y) F; K4 m4 v& k6 I
  • 接下来再进行一次更新
      c: W: w! C# J, J9 q

" q! ]7 P8 A, J0 C8 @( x
5 J' w4 ^/ z* i: D) @: y
微信图片_20230402174553.png : \" ^7 W7 M/ f7 c# v' G

( u9 m, T5 w3 U5 ?* |* U

; |# P4 h3 g% ^) P! ?1 Q

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

2 Q$ K, T' c2 v
微信图片_20230402174550.png 8 p4 c  ~+ m4 l( L: F5 D4 }1 h! h# K
* {% B& {/ }; w$ Q1 T) v

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

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

, u! _. t% @# T
转载自:Embedded小飞哥1 i3 N4 `" Y/ W1 p7 y) z2 h4 E
如有侵权请联系删除2 n9 Y& h8 z3 q+ `5 f

0 B; [/ ?$ ]# v1 I8 v; g
收藏 评论0 发布时间:2023-4-2 17:48

举报

0个回答

所属标签

相似技术帖

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