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

IAP固件升级经验分享

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

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

9 D# ?0 ]" H4 B

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

1 R4 |' R+ Z" q7 }' k+ }

STM32的代码启动过程

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

- d$ b# A' {9 K: h7 M* M0 y7 p4 w

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

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

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

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

    / |$ U! S3 i6 _. p- r, ?
微信图片_20230402174619.png
0 F, R2 \% y+ _; _& s8 N/ R+ D5 d6 m" p. k4 h+ V
启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************9 `0 A2 n2 K4 h/ F" G7 s: ?8 G
  2. ;* File Name          : startup_stm32f10x_hd.s
    / {/ n8 q6 V  U# g; B
  3. ;* Author             : MCD Application Team
    . q, b1 }3 e, V5 A. D
  4. ;* Version            : V3.5.05 c3 P7 p! t) Q  P2 o" b/ |# ?
  5. ;* Date               : 11-March-2011
    ' l3 p3 r/ x/ o5 l* K- R
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM
    ! t7 F4 [4 G3 I/ _3 F* ?$ @/ ~. a
  7. ;*                      toolchain. - \+ k1 O' q  l0 I5 `0 v
  8. ;*                      This module performs:1 v# F' o( P# t# L2 o
  9. ;*                      (上电复位后会做下面的几件事情)0 S2 ]% d* k( Z9 s3 \6 _8 y1 b. G/ r
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
    3 y( x& B$ b  k+ D- b" T5 x
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)+ j! f1 }9 G4 Y7 W
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)* o7 `/ _  a) s6 E! r
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
    2 P2 V# S5 s$ u+ n% e
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data
    : w. A. ~" {) E! e! n1 K
  15. ;*                        memory (optional, to be enabled by user)
    # I* X+ G" n) R4 q
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的): ]) A: e" [0 V2 h$ r1 \
  17. ;*                        calls main()).% j' E) k) K. P: M' V& N
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,
      a( E0 G+ d0 j' e
  19. ;*                      priority is Privileged, and the Stack is set to Main.2 o; t" c( U) u' j
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   , s( U. D% _$ o. {  r
  21. ;*******************************************************************************
    : [, s3 y7 `' \3 V+ X* M

  22. ) e4 H0 Z$ i4 z8 }% g- z

  23. 2 H: [& M3 B* H, J4 {6 t4 L
  24. $ Z1 G/ T) A0 u9 Q2 d* M2 u
  25. ; ------------------分配栈空间----------------
    & N0 c  t* q& y7 h
  26. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
    0 V" G: y9 h$ L+ r
  27.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
    $ V! ?5 C- _" i; f) v
  28. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改' I) N, O/ [; r1 T
  29. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到
    5 I/ A; {( T" }. u( u* H* c0 {

  30. ' l7 C3 O: ?+ ]  o# n9 I7 S& u
  31.                                            8 S; N$ g; P9 S/ W: n
  32. ; <h> Heap Configuration' b" y" s# k$ w- D
  33. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
    6 K$ N* p% Y8 ?9 W
  34. ; </h>  C: F  E6 V3 h# Q& `" K  f0 {
  35. ; ------------------分配堆空间----------------% V; [8 _0 C$ Q+ g. y  C
  36. ;和分配栈空间一样不过大小只是512字节
    8 c5 G% O3 @) L: W4 [! K( S' v2 y
  37. Heap_Size       EQU     0x00000200. E0 C4 o& J" I- w- m, i$ _
  38.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=30 W( @! s! W; ]
  39. 4 D& Q8 w& p2 s8 B' z- G% S
  40. __heap_base        ;__heap_base堆的起始地址
    4 N! m, s3 m1 e- B- T) d" |6 z& c1 S
  41. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
    , }! A! L6 |9 t
  42. __heap_limit       ;__heap_base堆的结束地址
    8 I$ n2 Q. W6 F$ r& T

  43. ) W4 H& X2 M% B% L. n
  44.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐0 Z; ~) y7 {0 T( V+ w2 ]4 ~( ~
  45.                 THUMB;THUMB作用是后面的指令使用Thumb指令集
    5 j0 [& D7 w" H4 p3 ?0 P
  46. * \; |0 g+ @! V8 U7 @5 x. O
  47. ; ------------------设置中断向量表----------------
    % d+ H. r0 M/ V4 C; P2 a! i* f
  48. ; Vector Table Mapped to Address 0 at Reset6 k6 }& S1 _2 w" d% ?8 R8 K7 m7 R
  49.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
    4 z7 _* i8 u. u& J/ V- D3 d1 x6 l: h
  50.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性2 d* B( N) P, x8 \- I& Q4 ]1 ]
  51.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用         
    ( H5 k$ a0 y) [% D+ U. a5 e8 t
  52.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
    3 [! H! f2 M5 w  J
  53.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用8 r& t& f* X: t2 b$ _

  54. - D* d* _/ O6 K) T  V7 K" N$ e

  55. $ T4 z8 a5 u- }9 Y, h. |
  56.                
    % ~: `, Q* Z4 P! {! i' F/ U3 q! V
  57. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化$ |) K  M4 v2 k1 p" h6 q
  58. $ D  f4 R# L- r) B  `! H
  59. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
    * E+ c* o- V1 \, U( `
  60. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
    5 M6 F" s: C3 `
  61.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp)
    ) N7 A' P8 v" p0 e( a; c: d) i
  62.                                                    ;* __initial_sp的值是链接后,由链接器生成# k# v; M% K* L; p+ T& ~, Q
  63. # M. }* s6 \( e' p
  64.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,& Y: k! \$ z: S# G0 u2 Z$ g: Q
  65.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
    : c0 B+ b. U& k% F( {
  66.                                                    ;* Reset_Handler是一个函数,在下面定义9 ?1 ?! w; p* c; M- d
  67.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
    / O; j4 ?0 K& I' r
  68.                 DCD     NMI_Handler                ; NMI Handler      
    & j3 J1 u" f' D+ i' E+ W/ }! @
  69.                 DCD     HardFault_Handler          ; Hard Fault Handler
    ( h" v9 F" V" B% x
  70.                 DCD     MemManage_Handler          ; MPU Fault Handler% E! E1 [7 m2 p. g; Y
  71.                 DCD     BusFault_Handler           ; Bus Fault Handler, f! R* p( O* j6 h" u( l! _7 v
  72.                 DCD     UsageFault_Handler         ; Usage Fault Handler0 o% o. P  Y8 O5 D! i$ Q
  73. ' A8 t6 }" L2 P, |0 `, I" P
  74. & E  e4 s0 P( f# O+ [; d0 M, w
  75.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件9 k4 |5 ]3 B& ~: B: p
  76. . V! C5 H* e( ~8 U) e: U
  77.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel10 i- a5 o2 y' ]* I( Q5 N. V
  78.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
    $ b5 C8 D6 z4 ]. Z/ A6 f
  79.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3! h( u4 _3 m4 k' |7 H( n1 ~
  80.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel50 i4 l3 |4 G* y0 s
  81. __Vectors_End                                    ;__Vectors_End向量表的结束地址! ?2 \! G2 m. ]2 B' [
  82. 8 I" w: a& Q' T8 N* K4 m
  83. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小
    0 U* c# ]* _# m
  84. + V2 q* @7 B, r, Z; f2 h% S
  85.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读7 ~) ^. E" J' E$ h( f0 D5 G

  86. / h1 H7 z+ A: t# e2 Y/ \+ k
  87. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
    ) O* c' z# N. P. {( [0 m  ]
  88. ; Reset handler
    ; I2 X9 {' e3 E' [/ n2 ^
  89. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
    . }0 Q& K3 g$ U/ s* L( b

  90. ; G! s# R9 X' ^+ k/ E( Z- D/ Q
  91.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,8 V: z& m8 W6 e4 W3 [
  92.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数
    3 s0 o" ~3 S" V4 ^( j/ L; S( T0 J
  93.                                                 $ Z! a3 F8 |& Y) U" Q$ B" m, t; F
  94.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
    - n- c! Y: X( @& S
  95.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号! W! `8 ~5 `: r- w7 g! @; a
  96.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
    : K- R& g2 c3 W4 _) T
  97.                 LDR     R0, =SystemInit
    9 [% e  K1 x5 U8 L, \1 b: l
  98.                 BLX     R0              3 ~4 C4 }! E2 J% s/ b  ~
  99.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码- r6 F) u5 X$ b( _7 C2 ]. U5 s
  100.                 BX      R05 k& M$ @; m# a0 C# [
  101.                 ENDP, I. m+ e4 \- i  Y- f; z
  102.                
    2 D5 H- b: }( z. i' @: M
  103. ; Dummy Exception Handlers (infinite loops which can be modified)  y* M" b3 G1 r, r& r* T
  104. ;下面定义的都是异常服务函中断服务函数8 C6 q, L6 I2 {( z2 q" u0 p
  105. NMI_Handler     PROC
    - \/ @1 A% y. D7 @3 Y! y' q
  106.                 EXPORT  NMI_Handler                [WEAK]
    8 {8 g: N8 W1 I( j
  107.                 B       .: U. p- S4 m& k& b( {1 g
  108.                 ENDP
    6 G% h9 z$ ^* I% n5 F/ k) T
  109. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件1 k* _- E: S4 W0 q0 E8 t  {! Y. Y
  110. SysTick_Handler PROC
    , T) |  b' T0 W: A& ~% k
  111.                 EXPORT  SysTick_Handler            [WEAK]: A+ W2 |1 B, G  Q5 h: a2 j% H
  112.                 B       .! c& b6 q8 K# Y! t
  113.                 ENDP/ n* X. e+ T7 P  H- c
  114. ( b7 i! x/ l+ [, x% q8 W1 ]& F
  115. Default_Handler PROC9 X3 P$ G% d- j. S+ @
  116. 4 b: K  r* u; t: G6 p% G
  117.                 EXPORT  WWDG_IRQHandler            [WEAK]2 a2 C9 f/ ]7 q3 k# e
  118.                 EXPORT  PVD_IRQHandler             [WEAK]0 l( v$ r4 L* h1 V# T
  119.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件# ^! @+ x3 x9 U7 j( b: t4 w
  120.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
    % m  V: O  m7 _* n; v; V
  121.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]
    3 }: e. _, t( b+ I. R$ e/ {8 A. I
  122.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]
    0 z0 Z/ \% V1 D( [
  123. 2 b; e+ ^" b1 I$ I: y
  124. WWDG_IRQHandler: Z" |) y# @3 |4 r  q4 B- ~6 f
  125. PVD_IRQHandler
    " B9 w3 @3 Q8 c5 U6 u# @# h& c
  126. TAMPER_IRQHandler
    2 r1 `" `! H) U# y1 c6 {; z
  127. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
    ' q( ^' O6 M% f# q
  128. DMA2_Channel1_IRQHandler
    ( I* @+ v" @0 z# L
  129. DMA2_Channel2_IRQHandler5 O( ?- K* ^) {  j! |
  130. DMA2_Channel3_IRQHandler
    * d" V9 J) W) j3 V* ^
  131. DMA2_Channel4_5_IRQHandler
    . e# ~% e9 u7 I  p
  132.                 B       .
    - P; x% k1 C" d( M3 M8 k4 t$ C
  133. & [- P* K) V1 {  y
  134.                 ENDP7 j0 W2 a; w9 e! ], Z! A$ ?
  135. 3 V: ?3 R$ \' N2 W$ N. X
  136.                 ALIGN    ;四字节对齐
    9 o( A, ]( K0 `$ b0 z! Y, o: `
  137. ' K& V8 P$ g2 x9 M( T; g
  138. ;*******************************************************************************% k) Z5 x7 {$ `$ u
  139. ; User Stack and Heap initialization
    3 [! Y% K) ]' u0 G
  140. ;*******************************************************************************
    $ G) H/ _) ^& o) [+ m& u1 {
  141. ;下面函数是初始化堆栈的代码2 p" J* F, t5 M9 U
  142.                  IF      :DEF:__MICROLIB       K( n* {* t' x" l
  143.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
    2 Y3 ]+ C0 l/ X" E' @
  144.                  ;这种方式初始化堆栈是由 __main 初始化的
    8 L' Q# N* M( A$ c  j" O
  145.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
    ; r1 Q+ \9 Q8 m; n9 q3 D
  146.                  EXPORT  __heap_base    ;堆的起始地址
    + [7 V% Y: n1 F0 Z
  147.                  EXPORT  __heap_limit   ;堆的结束地址' c* `) |# I8 u# L6 ?! i9 r
  148.                
    5 Z! Z- P7 K# ?$ s0 J9 ^% C
  149.                  ELSE8 c( T+ w, x. P2 u+ i% f5 g
  150.                  ;由用户初始化堆% O8 j" e/ d; x6 K8 E3 h
  151.                  ;否则编译下面的
    3 L: b  q3 W+ ?% h
  152.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现+ ?5 ]+ S2 `2 @+ E. y9 }
  153.                  EXPORT  __user_initial_stackheap
    0 q5 H. {6 ~9 ~) R
  154.                  
    , ^# p$ W+ \0 i6 C1 ^% M
  155. __user_initial_stackheap5 ~3 v2 j% M) G9 o, f$ W
  156.                  % w4 T3 R+ r/ U) P" Q( O; ~
  157.                  LDR     R0, =  Heap_Mem              ;堆的起始地址! h% N7 k. r# w2 Z' }
  158.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址
    5 _* M# n& j3 |8 u5 Z5 S0 H: V
  159.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址% b# @1 j' n* y" n* v. Y
  160.                  LDR     R3, = Stack_Mem              ;栈的结束地址* Q; @! Y' z' B! `
  161.                  BX      LR
    ) H: e# F1 x1 j% H/ m; d( R

  162.   Z) [- n8 N  E  I( w
  163.                  ALIGN' u) V- H* u( Z! [

  164. / H1 C  n5 n  r6 B' {
  165.                  ENDIF2 d4 ~& T) u' `9 z1 {& [
  166. " f5 v* U% [' \% _4 i1 g# e. I. W
  167.                  END& {+ p  n- B# ?$ x: O* @$ g
  168. : P: G9 a; @9 x1 b
  169. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码
' {5 s! b6 l( W$ f' [$ _; B2 F

) u* N8 F$ n' a

STM32的启动步骤如下:

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

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

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


    & I; d- ^& q* f" Y
IAP设计思路

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

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


- F7 L+ k5 k& c+ h$ E8 `. Y( E

升级流程图" s- r$ n7 d1 F0 u
1 k# a& X4 J4 ^7 r7 M- A
) E" i: s( w; `6 v
微信图片_20230402175006.png ! G& i7 @7 x! L" B  [* ^

% U) ?5 H  P6 F( }0 l1 ~2 x, p' X( @
3 j' g  N1 S# q  X6 B2 B$ ~3 y
Flash分区

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


0 U! A- {/ A/ U

微信图片_20230402174614.png


& K$ B( {/ X3 n. @  ?$ c0 D  h% @

  • 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


    2 ~  @9 ~) `7 z+ ~8 j
代码实现

硬件:

  • fallingstar-board(已开源,打板验证)
    " v9 O% t, w  G

软件:

  • 内部flash读写

  • 串口DMA+空闲中断


    / \6 V6 `6 k0 W" z6 Q
内部flash读写操作

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

读flash操作:

  1. /************************************************************
    $ H/ B6 e/ w! T
  2.   * @brief   读取2字节数据
    " g1 f' m  {9 a2 k7 @: i
  3. * @param[in]   uint32_t faddr2 ?3 S+ Y6 P- M" {& L& ^0 u
  4.   * @return  NULL9 K3 i9 X# S+ i+ E. ~2 V7 ^0 L9 [
  5.   * @github  1 h1 p; Z' O4 D/ W6 ^
  6.   * @date    2021-xx-xx
    0 \: b3 e4 N# D% v. l; |" _* j
  7.   * @version v1.0
    0 U& n0 F3 A9 S/ b9 M2 K# e
  8.   * @note    NULL
    : Z6 s: k! x/ ^
  9.   ***********************************************************/
    % M$ r* d8 S% w
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)) M3 C6 y* {$ @! G. `
  11. {6 G# z2 P2 _2 f' r
  12. return *(__IO uint16_t*)raddr; , K" F7 U0 i3 R( u0 ?
  13. }' O1 W0 N: p; i# l
  14. /************************************************************
    0 u3 O8 r3 t9 N) J3 t
  15.   * @brief      读取n(uint16_t)字节数据
    8 _7 R' ]+ p+ ?) i: N4 O; C& n
  16. * @param[in]   uint32_t ReadAddr: O* i0 P4 Z8 Z
  17. * @param[out]  uint16_t *pBuffer
    1 h# y( J! A% D, P+ C
  18. * @param[in]   uint16_t len; [5 I& _1 r8 g8 S' B% j
  19.   * @return  NULL
    1 J2 Z- |  T+ C/ j, \8 o
  20.   * @github  
    ' W* j$ Z. U& ?& L  \% s3 h$ L: ]
  21.   * @date    2021-xx-xx
    : o+ o- U4 J/ q# @: L+ }" r( F  n
  22.   * @version v1.0
    - c9 C7 G& \- W; i6 k' g0 S
  23.   * @note    NULL
    ; b2 q4 j: |: e& _1 ?0 T" g& I
  24.   ***********************************************************/2 Q+ n+ z" D9 r0 w) i+ d3 o
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )    ; f" m  E' g7 S) J
  26. {
    6 D4 K2 b4 V2 a) h- f2 X3 o
  27. uint16_t i;
    & W8 T; x2 H) T; d  `
  28.   W% e: T$ d- O
  29. for(i=0;i<len;i++)6 m6 W4 r2 r/ \' q2 l7 F" B* V( [, m
  30. {1 s- j2 m2 z0 M+ G
  31.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.
    ; o4 ^8 A. M2 D+ K
  32.   ReadAddr+=2;                   //偏移2个字节.
    0 f% O0 J% r- M4 ^2 V$ j1 W1 {
  33. }
    ; t3 c# m. m& z
  34. }
复制代码
: V0 H2 R# Y, i+ ]  k0 e  S

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

  1. /************************************************************
    " s. M+ q& r; Z: c& d
  2.   * @brief   写入n(uint16_t)字节数据- o6 o( d5 J. N/ W0 E7 s
  3. * @param[in]   uint32_t ReadAddr
    + B5 C% @1 s) L7 h/ ^; F
  4. * @param[out]   uint16_t *pBuffer! R" o% {/ w' J1 B- U- h1 r
  5. * @param[in]   uint16_t len
    0 ^, G( U2 L& X' q
  6.   * @return  NULL
    5 `$ h3 U) B3 w0 x5 \, k
  7.   * @github  
    ; J; A: z+ w# L
  8.   * @date    2021-xx-xx
    : N6 G) c& i2 j3 a9 @
  9.   * @version v1.0# D' |, o+ I8 h* I
  10.   * @note    NULL
    0 w; U2 A5 L, f* U6 G
  11.   ***********************************************************/
    , l9 L$ D2 ^- y7 Y, ]3 F
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   
    ' m3 @. L. o7 }
  13. {        0 J6 P; Y- b+ c# j4 q3 @# |
  14. uint16_t i;
    " E: _, k6 v+ ~) a
  15. & B" ~8 s+ ?, A. M4 r
  16. for(i=0;i<len;i++)
    ' H; _+ i, d1 ~8 M* B* u
  17. {
    : w/ x9 I% T7 ]# @' X- k9 [: v
  18.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
    2 X9 N/ d, m+ r2 f9 l: p
  19.    WriteAddr+=2;                                    //地址增加2.; K1 J: n. o  W; \
  20. }  # x+ C. m/ T# P# {- I/ \' @
  21. } , Y5 Q/ D* \# h
  22. /************************************************************' U6 N+ a) W5 l# j
  23.   * @brief     写入n(uint16_t)字节数据& Y% \2 [4 a; G
  24. * @param[in]  uint32_t WriteAddr& u. ?6 Z  T  K  w4 a
  25. * @param[in]  uint16_t *pBuffer* u1 M9 Q  A1 K) l3 G, K4 e/ e
  26. * @param[in]  uint16_t len
    2 p5 l9 b: j7 t7 j5 w# P6 ~
  27.   * @return  NULL) j; l8 S* V. J# ]8 Y4 l
  28.   * @github  
    * H1 P& a+ j: B( ~/ b. N
  29.   * @date    2021-xx-xx! i1 v: A$ K& p* n& x
  30.   * @version v1.0: h; U/ i' D3 Q# H! @: K  D
  31.   * @note    NULL  b( I( t6 _; ?" ^
  32.   ***********************************************************/
    ) S8 w& x( C7 F- ^& U9 C
  33. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
    4 U5 l! x) u* c# ^
  34. {+ @" `- L" z/ S7 `$ ]
  35.   uint32_t SECTORError = 0;/ I( u) e: n4 R; \& X! R9 m7 _
  36. uint16_t sector_off;    //扇区内偏移地址(16位字计算)
    # ~2 n! q  ~% D  V8 e) G0 \, Z
  37. uint16_t sector_remain; //扇区内剩余地址(16位字计算)    # u' ~* \4 e4 ]' U) ?* [5 A$ t
  38.   uint16_t i;   
    . V/ z' }! g$ [) G3 [% T
  39. uint32_t secor_pos;    //扇区地址
    ! M. R" o# S7 }4 T
  40. uint32_t offaddr;   //去掉0X08000000后的地址
    " r& ?: @6 E3 a: p9 a

  41. 4 |. y! f. z4 Y9 E: }6 z
  42. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
    ( Z7 Q; t( }  J
  43. ( \; ^. B& Y/ a3 K& o: s2 I1 l
  44. HAL_FLASH_Unlock();         //解锁. F% S* y3 s: {! ?. O9 I

  45. + V9 P& t5 w4 m/ G* a
  46. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.
    ! ?" U1 N* Z" M' j9 ^
  47. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT67 v& q  N( [' \- o- A% \% E- A
  48. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)% p1 W% D% g% }3 y
  49. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   * S: F) g/ q/ R: b& }  C% f
  50. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围# C8 H$ c7 g6 z- H0 C' a' S5 X8 d8 F
  51. 1 ~! v4 T3 C& I/ K( W& n5 i( H' n
  52. while(1) ) f* z! J( e* s( A4 C1 U: b& N
  53. {
    & @" q1 G/ W# }
  54.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容* \5 N6 [, j, S8 D
  55.    
    # k- g9 D0 K. _$ ?9 A! A
  56.   for(i=0;i<sector_remain;i++)//校验数据9 Q! l3 X! {% s- c; a  j
  57.   {: T: C- {8 Q$ G0 n6 P
  58.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF), N+ W$ ?  e; @/ b+ d
  59.     break;//需要擦除     
    + k/ F  Z* ^" [1 o: b. l
  60.   }
    + w4 [! V; ~" {$ H
  61.   if(i<sector_remain)//需要擦除* t+ v8 s$ b& @/ v( P: @  g, R
  62.   {4 M# W" W, R0 @
  63.    //擦除这个扇区4 `5 t0 s: z- b
  64.       /* Fill EraseInit structure*/
    * G3 P5 v) G, P' |' W, q/ L; g) V) E
  65.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
    5 s9 b: Y- q# _- p, _
  66.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
    2 L8 y4 \# u: U, e4 Y3 ~. o
  67.       EraseInitStruct.NbPages       = 1;
    7 d8 }7 u" D" \+ s: ^) V5 D
  68.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);+ C$ X$ U3 j" J/ n* z& s6 D$ G
  69.    for(i=0;i<sector_remain;i++)//复制
    ( a# S* R) j0 j9 F8 K5 r
  70.    {
    / x8 @! ~: Z' R( T/ L1 V! X
  71.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
    7 k1 L+ ~- P8 q5 w% d" _6 @
  72.    }
    , N5 f: \- ^. s  R3 [
  73.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
      H, [: ]# d* d' v3 v# b2 Y) z9 O
  74.   }
    " y1 ^$ \5 q! W6 n% m: `
  75.   else ) X% N4 `* A. U: p5 D' _
  76.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        6 {% P* ~  s: P9 I
  77.   if(len==sector_remain)
    0 q4 Q# U" T% m; b
  78.    break;//写入结束了( g- S0 g1 A3 O  V7 n5 ^
  79.   else//写入未结束
    3 q& \4 d) n5 E% ^% L: H' s
  80.   {$ T2 I6 S8 g) ?8 z: K
  81.    secor_pos++;    //扇区地址增1/ V% Y4 x9 Q6 |
  82.    sector_off=0;    //偏移位置为0   * U/ T( U7 K0 ^5 K2 F6 j1 ^
  83.     pBuffer+=sector_remain;   //指针偏移4 ]4 o( c+ b+ M8 y+ P
  84.    WriteAddr+=sector_remain; //写地址偏移    2 {' T$ I7 A4 |* S: J; o" Y
  85.     len-=sector_remain; //字节(16位)数递减/ h3 H' G+ Z* M% n! c
  86.    4 x# ~4 G( T& t* a1 H0 }5 J0 j
  87.    if(len>(STM_SECTOR_SIZE/2))
    ; G3 M+ X% o% G! ]6 X9 d# A
  88.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    ) f" d4 E# F% o* }2 ]9 M2 G
  89.    else 0 t$ ?% C" {" R$ T( b
  90.     sector_remain=len;//下一个扇区可以写完了# H. m" X- C8 w# L3 y
  91.   }  # g: l1 X1 k* q# Q; E5 H
  92. };
    0 \5 A: o) P, G$ `
  93. HAL_FLASH_Lock();//上锁& p4 H0 V3 M& v; L4 \1 Y  l$ w
  94. }
复制代码
, a2 e( b0 I) u% T5 I* b; v
串口DMA+空闲中断接收不定长数据

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

  1. #define Max_RecLen 1024*3
    $ n- l" _- h% S* W; F! g

  2. : b0 A# m. F$ a0 U5 F
  3. typedef struct{
    & d: n9 w; C$ {5 g5 i  F( Y
  4. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区4 t" t0 ^: O  v& U
  5. uint16_t RecDat_len;      //单包数据长度
    $ a( `6 k, O5 Q
  6. uint32_t Cur_WriteAddr;     //APP缓冲区地址: o1 |2 m% U$ i+ y& i
  7. uint16_t BinLen;        //bin文件数据长度
    / P$ s  @8 m, @
  8. uint8_t rec_endFlag;      //单包数据接收结束标志. z1 p' b) y/ ?# S, _: c+ V, S
  9. uint8_t Binrec_endFlag;         //bin文件接收结束标志
    * J1 ?' W3 [8 s& G4 o4 T/ q
  10. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器+ g, Z6 O5 ~/ {& _+ D' A0 z
  11. * j5 ]6 r" P" s! Y
  12. }UserUartDMA_Typedef;
复制代码
7 O8 W; V$ i4 W2 H* A4 q' d

然后在串口中断中:

  1. /**. e% t, w$ M8 k# B- w6 H
  2.   * @brief This function handles USART1 global interrupt.: n( r0 I$ K# W$ T  ~; y, v
  3.   */
    , U$ y3 k' r3 Z/ h4 W5 a( x7 ?- P
  4. void USART1_IRQHandler(void)2 m# `9 R) c7 v: p& I
  5. {, a7 {; }- Z& H5 k0 j
  6.   /* USER CODE BEGIN USART1_IRQn 0 */8 {' o  F7 z0 P% _8 S: G
  7. uint32_t idle_flag_temp = 0;
    , v/ l- k; x5 R5 g5 p. e
  8. uint16_t len_temp = 0;: F" j- f5 G- Z5 m, N9 Y: [! k
  9.   /* USER CODE END USART1_IRQn 0 */
    / \/ D0 r- |4 D' k% c
  10.   HAL_UART_IRQHandler(&huart1);
    % V; |. f4 d0 V) `
  11.   /* USER CODE BEGIN USART1_IRQn 1 *// [) g% `* x8 x; k
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);0 f- F  j+ k0 ~
  13. 4 @1 S" b/ p3 r4 c( l7 Y$ e2 i
  14. if(idle_flag_temp)3 i* Z! I4 F1 s. N$ Y- z
  15. {  N! x+ K' a* I$ t5 ~
  16.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);5 V2 h0 r4 _* B3 T5 H8 X! Z
  17.   HAL_UART_DMAStop(&huart1);
    + Z3 W2 E4 ]4 [0 X$ |& Z+ L1 I
  18.   
    ( k" z7 j1 o% d. B
  19.   UserUartDma.DMA_TIMCNT = 0;
    : N  o  d0 E6 j( s* o
  20.   UserUartDma.Binrec_endFlag=0;1 C3 s  K$ i' L: d# r0 z
  21.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);9 D. w" q1 P! b5 [* a/ h' R
  22.   : _: {/ J3 f9 d" W2 b0 X+ Z
  23.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  2 n! N! H; Z' M4 Y
  24.   UserUartDma.BinLen+=UserUartDma.RecDat_len;
    6 l5 L! M; B+ N2 X, t5 p% \0 i" |
  25.   UserUartDma.rec_endFlag = 1;
    ( _2 l- s0 T7 h3 z
  26.   ( s9 [+ E0 I) s
  27. }
    " l5 j, `3 y( X  l" l
  28.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
    # D0 {. b" j' w! `! S+ t
  29.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
    7 s: s% G/ t; e
  30.   /* USER CODE END USART1_IRQn 1 */
    ; B6 @% @( P( i- X9 O9 x
  31. }
复制代码

! v% C" b  `+ F4 a* W2 F6 e$ k8 lIAP代码设计
  • bootloader代码设计
    ! G9 F3 Z9 e1 z- G0 @

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

  1. /******************************************************
    9 L! v4 @: C9 a& w
  2. * Brief     : 擦除APP区
    2 }2 d6 G6 u& B" s# P0 U
  3. * Parameter :
    ) C( j, e% {9 |) }* j* w! d3 N: P
  4. *           *startaddr:APP起始地址0 \& g. B$ k9 ~6 y- }
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE
    4 ]% w0 n" K7 Y+ _' F+ f# ?
  6. * Return    :None.
    1 g7 r4 U! N/ {4 Z& R% G' b) F3 |
  7. *******************************************************/7 K4 N% z3 Q" V; W& C* L
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)
    ) ^( C+ Q, e6 K+ m
  9. {; {3 F% I+ |5 h
  10.   uint32_t SECTORError = 0;
    " |+ X5 m4 D7 R9 V7 d
  11. # D4 t' Q& Q* B
  12.   //擦除APP区0 U1 n- r' B8 C5 B9 B# I0 E: N
  13.   /* Fill EraseInit structure*/
    0 _0 h; \7 \+ W! J
  14.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;/ d  Y; @- y6 K% ?. B6 V3 L' ~
  15.   EraseInitStruct.PageAddress   = startaddr;
    - Y5 M  H% v, u
  16.   EraseInitStruct.NbPages       = pages;
    ! n! h# B& F7 ~" x5 l0 i8 I
  17.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);. m- a3 H: Q! T8 B. `
  18. }
复制代码

) t9 C/ H9 w1 S

升级标志获取与擦除

  1. /******************************************************6 Z$ z; ?" F/ {: g1 x. y, Z
  2. * Brief     : 写入升级标志0 }7 b0 s' u3 {3 x8 n- i
  3. * Parameter :
    2 r. b2 i0 I8 M. a7 d5 _# p
  4. *           *addr:标志存放地址( y2 I- X) |, C+ Z3 k
  5. *      *pdata:写入的数据
    ( J9 k/ ?. }2 r
  6. * Return    :None.* r' T. e- p$ s- c; q
  7. *******************************************************/
    $ n5 D" q5 I$ G% Y6 s0 M; u# Q
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
    3 V+ P6 j( @2 B* ^
  9. {
    ' e7 R4 _1 p+ u$ B$ g
  10.   BSP_FLASH_Write(addr,pdata,1);
    + i9 a& N1 F' S+ l
  11. }
    . E# V  e5 x* _) L+ Y+ I4 [
  12. /******************************************************
    6 @8 C  N0 D* y! ?# v
  13. * Brief     : 获取升级标志
      P% o3 l' w/ v
  14. * Parameter :
    0 p" a- Y9 O0 R
  15. *           *addr:标志存放地址2 H7 i* l0 G/ N9 K( n8 Y( N1 F
  16. * Return    :None.
    ' U9 Y) J$ }+ F4 U
  17. *******************************************************/
    & B! q- ]# N4 [% n& ^& L* b# t. ~
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)
    3 w! N$ d% j& W+ Z4 ?1 A
  19. {1 N; o9 w# q$ i( x
  20.   uint16_t flag_temp;, [0 x4 K! s4 `2 K- E' h
  21.   BSP_FLASH_Read(addr,&flag_temp,1);$ X" |7 E( i  h0 i4 i

  22. " P0 @1 X: D) K" |/ m6 F
  23.   return flag_temp;3 I8 O7 z1 E5 Y
  24. }
复制代码
% W4 y& |, @/ {& m

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

  1. /******************************************************
    1 K# X4 ?! E7 n' F2 v
  2. * Brief     : Bin文件写入app缓冲区1 ]. ^! q2 \: x, Z: d: M
  3. * Parameter : " |5 _; ?) D4 j
  4. *           StartAddr: 起始地址$ \' F0 [: S% v1 [2 d( D; ?, Y
  5. *           *pBin_DataBuf: 要传输的数据; z( K  b" j2 A. I9 `8 n# H, k
  6. *           packBufLength:单包数据长度, `( Y0 J( _" w8 r5 X
  7. * Return    : None.5 W9 v. j" `( v/ _% S' l+ I3 N& x
  8. *******************************************************/2 u, W+ M3 M) `6 U. q- _  p* e% ?1 v& D
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
    + B  a7 l1 g9 @! m8 \, L5 X, Q
  10. {
    - A/ S( t* U6 g( u9 C6 T6 B; G
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;
    + T8 n0 F( b& w$ p3 Y: A8 k
  12. uint8_t * pData = pBin_DataBuf;
    4 n% J- }* v& @2 I( O; `* k9 \
  13.   # h& g* M5 n" I7 a' H
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
    ( \3 s+ y) z& B7 Q/ @( T
  15. {          7 m4 n1 I3 z0 S3 ^6 J) g1 ~+ o
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;0 n1 V# d( f. L# u5 m
  17.   dataTemp += ( uint16_t ) pData[0];   $ g# r. Z4 W- @8 G
  18.   pData += 2;                                                      //偏移2个字节
    % L, b: b  K) V( J, h' N0 V7 m
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     5 J4 @6 \& ], ?& N/ }( d: A3 }4 ]
  20. }
    : l. P% R$ q0 n# O

  21. 0 \5 H8 |* M" e
  22.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr ); 4 f+ X6 e! F4 a3 M2 _
  23.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.
    5 A2 S5 N" H) M! R: m+ B6 N6 n
  24.    packlen_Ctr = 0;2 z& v* |% ?2 Y* t+ t$ j2 _
  25. }
    0 r+ `% Z) V- y2 g! K' f/ {2 _
  26. /******************************************************. z' O: y1 e* R: }
  27. * Brief     : Bin文件从app缓冲区写入app区
    + \. S7 f0 D* b9 M9 d
  28. * Parameter :
    & P! h4 n1 c0 ~( r3 s, F
  29. *           SrcStartAddr: app缓冲区起始地址) Y$ t+ N; g* J- c# d# b9 I
  30. *           DstStartAddr: APP区起始地址
      E5 D( s5 V- y5 v9 W
  31. *           BinLength:bin文件长度2 @2 d' Q9 J' p+ E% t7 X  {9 p
  32. 4 S& w% y/ \7 G% t, Y
  33. * Return    : None.: H" i2 m9 l% S- C2 g) [
  34. *******************************************************/! f/ V# F; V: g3 \% H4 F
  35. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
    5 F0 x5 c$ ]0 r, J5 B# W1 S5 K
  36. {
    ' m" w, h: X* g& c7 N+ L. [
  37. uint16_t data_temp = 0;9 |: o) @" e% [6 y4 n
  38. uint32_t count=0;! t% D: M7 F. Q% ]: [9 O1 W$ }( A
  39. 3 _" C, P& C$ c- n8 E
  40. 8 L- t# u+ Q% r1 Z) B7 X
  41. HAL_FLASH_Unlock();         //解锁8 Z2 z+ |$ ?6 a

  42. ; F/ B6 ~5 g* L9 {
  43. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
    ! C% h/ z1 G7 Q" z8 ?
  44. HAL_Delay(10);; V& k3 z# {* Y3 M6 L  F

  45. ' M, ?6 F) P9 r! b/ E
  46. for(count=0;count<BinLength;count=count+2)
    3 M3 R+ a8 D8 e0 b( t6 L; o
  47. {
    4 O4 Z/ ~, z7 X) O) Y
  48.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
    6 [: O' Z/ |6 K. \, m* q
  49.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);; b( }3 b1 X$ F+ R3 C( ~
  50.    SrcStartAddr+=2;
    * f) g! b# z# D' w2 U; u
  51.    DstStartAddr+=2;5 F3 f& Z  I( }. I
  52. }2 |' f9 h5 a- _
  53. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);! ^7 X1 n8 u. V+ P* R, O8 d
  54. HAL_FLASH_Lock();//上锁
    # I# x+ w3 T& t3 k1 {! h
  55. }
复制代码

9 x1 e8 x( y2 g# M2 u

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

  1. /******************************************************/ D" R* i, J/ A5 p. b
  2. * Brief     : 设置栈顶指针
    & d! Q7 K( E/ ?, Q  y
  3. * Parameter :
    ( }9 n( ?& C" ?3 Y; `
  4. *           ulAddr:
    , y9 S8 F1 l3 C5 N3 r5 W
  5. * Return    : None.
    / j/ B4 u! j# _9 l
  6. *******************************************************/
    $ G% o* o8 |# O4 Q7 R
  7. 4 t6 X& N4 T( c: D
  8. __asm void MSR_MSP ( uint32_t ulAddr ) 0 `  }( S1 S1 k+ a2 C0 {
  9. {
    ! J9 c, e0 Q3 g, {
  10.     MSR MSP, r0                       //set Main Stack value7 z* n& j7 ~' Y7 V3 e4 T
  11.     BX r14
    ) E& R' L  m" H9 n' k
  12. }% L1 u. f, D# K6 c3 I9 z

  13. ! r6 B3 G9 o- m& w
  14. /******************************************************
    % X+ d+ q/ a1 v2 n3 Q( U
  15. * Brief     : IAP执行
    ; |' K$ u  D) s- i
  16. * Parameter :
    $ M8 K4 {: b  w$ a1 ]
  17. *           ulAddr_App: APP起始地址
    * ]. X! J% r1 D: V9 [# M3 ~
  18. * Return    : None.
    2 p2 D$ f4 W( X9 c$ P
  19. *******************************************************/# I! {" r4 h9 c- o0 C% T$ Q5 C
  20. void IAP_ExecuteApp ( uint32_t ulAddr_App )1 |# x, `4 H/ f- x. q, ^/ O
  21. {
    % l# L! p; u( s
  22. pIapFun_TypeDef pJump2App;
      n+ u4 G4 S' _2 ~
  23. 8 v. Y& M3 n$ p5 L
  24. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法.
    , N) A4 o/ B+ Z% r( {- l. s
  25. {
    1 ^# C6 }+ j1 a! A6 v# ]
  26.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)  
    0 F: V) c  A, i6 U4 b
  27.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    & A0 @# b6 v0 E  a
  28.   pJump2App ();                                             //跳转到APP.9 ?8 C1 `1 v! A8 U0 P
  29. }
    6 ?  ]5 b) ]$ B  u
  30. }  
复制代码

1 d- f  b* L* h- w- C: s) G# \& A' j! V

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

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

  1. static void Systick_Config(void); E$ `& s2 P. r* Q- m) a) N) T
  2. {
    . S0 ]& \: {- s5 [; V/ h
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
    * g# B4 J5 ~% n5 p4 J  J  A
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);, }) Q+ E4 {; B2 h# ^) v5 `7 [0 W
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);5 [/ `+ x4 ]9 Q& ~7 Q: g3 P3 O
  6. }6 `- d. J% @8 T( x

  7. ) F( M" Q% G/ C4 ~
  8. void HAL_SYSTICK_Callback(void)
    ( T. S9 ?  D# {
  9. {1 I: E0 t, p7 Z' A% T" s4 i# E1 i
  10. UserUartDma.DMA_TIMCNT++;6 O+ ~  D. O: u" x' P
  11. if(UserUartDma.DMA_TIMCNT>5000)
    1 Q' N* p" F& P7 u3 q
  12. {. v7 o8 d, i% q% q% V
  13.   UserUartDma.Binrec_endFlag = 1;
    - X( }  C8 b2 O) |
  14.   UserUartDma.DMA_TIMCNT = 0;
    7 g6 e6 i2 Y  k
  15. }  P8 l+ ^4 f6 X7 s
  16.   TIMX_IRQHandler_user(); ' I/ K- U, X2 n- }5 c; J0 V  `5 E
  17. }
复制代码
- X$ @2 W8 F$ D; x0 k$ P* X0 `

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


  1. : P- ~) `- |( D& ?& l# k
  2. uint32_t Task_02()! ~8 s' w/ x3 f. d, `$ f9 p  W
  3. {
    4 n/ J; e- y! {8 c0 k; z

  4. 2 s! {! h) I2 J* T' F
  5. if(UserUartDma.rec_endFlag)- ?8 Q' y- _9 F$ r9 y
  6. {
    % ^; t9 X* \; ~1 X! p2 W  d
  7.   PRINT_INFO("update firmware\n");! I. ^9 ]! Q# ]5 S1 `
  8.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  # C# P/ |0 h% ^& i
  9.   * n& j: U4 c4 I
  10.   UserUartDma.rec_endFlag = 0;  N6 m$ |5 l  @" S& w/ C
  11.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);5 E" Z0 Q: w+ a* L  P% v% L- ~
  12.   
    0 _  C. A2 G8 p0 l. s0 c! S
  13.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
    ; u& f( g' ^2 W4 I7 V8 M
  14.   UserUartDma.RecDat_len = 0;
    # b% x5 U6 q4 B# v
  15. }) Q4 U! k$ U/ g/ Z
  16. if(UserUartDma.BinLen!=0)8 S: I  A. p7 e+ b; N
  17. {* B# W( T7 V% G; [1 n4 ~  D: D
  18.   if(UserUartDma.Binrec_endFlag)# y8 z) C: n0 c/ ~1 _
  19.   {3 }# G4 O8 [9 a0 ^' @1 O* u+ i
  20.     UserUartDma.Binrec_endFlag = 0;( z! v2 J  W9 e$ \6 J0 v- N
  21.     ! j1 |+ ~5 ]4 o: C" k4 Z
  22.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志& F, x9 f6 q) a: I2 `! {1 m
  23.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志8 g1 Q& r4 E# o. l: P1 b
  24.     UserUartDma.BinLen = 0;  
    ( l3 C/ H6 U  |, o( [7 z, @& f
  25.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动* m7 V; f" M/ g
  26.     HAL_Delay(2000);5 o, u2 [4 T  m2 C1 S& l0 d
  27.   }
    3 N6 _5 {4 m: R5 j: S" J& U
  28. / n9 ~3 z; h7 ]+ @2 c
  29. }
    ! A/ ^* j" o3 H2 t& {: a* F/ t
  30. }
复制代码

( s- Z- D. ?. T8 E/ Q- \0 A

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

  1. PRINT_INFO("-----IAP Menu--------------\n");
    % y* H7 I& d& A: J% u' P  L
  2. PRINT_INFO("-----Download APP BIN------\n");
    8 Z- b; j$ k/ q  P4 N8 O( o) I! s2 Q
  3. PRINT_INFO("-----Restart To RUN APP----\n");
    ! L5 e) `/ ]' Z! \6 O8 A
  4. PRINT_INFO("\n\n\n");- L2 h( k$ s9 c& r, z1 y9 S
  5.   e) u  v8 ]/ I$ k" p
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))% M* Y( l7 d- d/ s2 w4 t. Q7 y. H
  7. {
    8 S5 q1 J4 U2 R. X
  8.   PRINT_INFO("-----Restart.....5-------\n");
    ( q# N. M9 T5 n6 F
  9.   HAL_Delay(1000);2 Y, d9 o- P; W! X, r1 C2 \! G
  10.   PRINT_INFO("-----Restart.....4-------\n");
      V8 n6 l# r2 o6 q+ R0 _5 l
  11.   HAL_Delay(1000);0 {$ w$ T, E: ?" u- O6 g6 l
  12.   PRINT_INFO("-----Restart.....3-------\n");) B! C# c0 ?3 H, R
  13.   HAL_Delay(1000);0 s6 n. m9 P4 K3 E3 `8 B/ s( \
  14.   PRINT_INFO("-----Restart.....2-------\n");. J2 [9 O4 g. A- U6 m: X2 _
  15.   HAL_Delay(1000);& J9 q' I+ W9 f2 l
  16.   PRINT_INFO("-----Restart.....1-------\n");
      M7 N& j" T2 @2 E8 x9 u
  17.   HAL_Delay(1000);
    0 o8 l/ ^$ P! V7 S& X( F
  18.   % h( Z8 f$ t5 v4 C+ C) E
  19.   PRINT_INFO("the device need update\n");8 V  H$ K8 z) [; c0 j" {8 I1 O$ l4 X
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
    : M! W; u- g' c. U
  21.   PRINT_INFO("firmware write OK\n");1 {1 z3 y0 g' v& {
  22.   PRINT_INFO("please double click the button the execute the application\n");
    . m" }9 o) G  |' ^5 [$ l
  23. * L2 ]2 `, d/ E: O
  24.   HAL_FLASH_Unlock();         //解锁* t6 n  h2 S$ L  Z/ D
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    # A& T, f7 I- K" W2 b
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     - h4 s9 K0 H& j7 A5 F, I9 n% y
  27.   HAL_FLASH_Lock();          //上锁
    1 d  R; }, o5 w/ p
  28.   & K& q* B# Q( _) [2 u
  29.   PRINT_INFO ( "开始执行 APP\n" );
    6 q# f# R  v0 o( l* W- W7 J
  30.   //执行FLASH APP代码) f$ r1 o3 q; \. ^- e; E
  31.   IAP_ExecuteApp(APP_START_ADDR);9 n9 k8 p  o9 L
  32. }
复制代码

: M) g; Q, X2 V4 c6 S5 S

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

  1. PRINT_INFO("-----IAP Menu--------------\n");
    1 p/ U8 m  X9 F3 Z
  2. PRINT_INFO("-----Download APP BIN------\n");! m. L3 Q! H* Q' d6 h' r
  3. PRINT_INFO("-----Restart To RUN APP----\n");
    ( W$ ]# F' i& ]; v) u7 d1 U$ f
  4. PRINT_INFO("\n\n\n");2 W& r$ U7 q& j: X5 Y6 |

  5. & A8 u9 c4 y% M& L, c% V2 F
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
    4 v3 _6 o7 f% L$ G! E6 v* z
  7. {
    1 r# b4 R' S" Y( [+ t  H# {
  8.   PRINT_INFO("-----Restart.....5-------\n");1 j% X0 x8 _- l& C$ N& |
  9.   HAL_Delay(1000);
    , r5 I$ k; F% y
  10.   PRINT_INFO("-----Restart.....4-------\n");
    / T3 |* H0 M: a  C" R, i
  11.   HAL_Delay(1000);
    % O! G* D9 B! l! |) W6 q3 u4 d& c
  12.   PRINT_INFO("-----Restart.....3-------\n");) U" B8 J  {  V! |, }0 l% \/ o
  13.   HAL_Delay(1000);
    ; v- O# K- M' D9 }* E" z, ~
  14.   PRINT_INFO("-----Restart.....2-------\n");
    $ m& i: G$ L' ?! j" \3 g3 a
  15.   HAL_Delay(1000);
    1 L' }1 h. E$ q, \
  16.   PRINT_INFO("-----Restart.....1-------\n");# O  _; f4 |3 G( L2 Q7 `' e
  17.   HAL_Delay(1000);# K/ o# E" n( T% i
  18.   5 N' Q: X  x7 t+ Z4 }5 H8 u
  19.   PRINT_INFO("the device need update\n");9 ^$ e7 N, c- q$ H/ m7 l
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
    ! d8 r6 X4 }) Y' _/ h: Q8 U0 v( v7 O
  21.   PRINT_INFO("firmware write OK\n");
    ; S. Q4 U8 b1 N" G7 T) t% Z
  22.   PRINT_INFO("please double click the button the execute the application\n");
    ! E& Y5 t0 e3 s% q+ P

  23. - ?0 e4 r, G/ k
  24.   HAL_FLASH_Unlock();         //解锁
    8 [9 T+ Q" J$ m7 j( C( t5 u% \
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    7 w- M( p# `/ o. b6 Z
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     
    ; Y& q- T1 Z+ ]8 S) D+ l
  27.   HAL_FLASH_Lock();          //上锁
    ; Z0 F, E  c3 {4 m" r
  28.   8 y- h3 {& @2 g+ m7 v5 B+ B
  29.   PRINT_INFO ( "开始执行 APP\n" );
    ' H% `- C# U( V$ S, j: x" m- V/ O) b
  30.   //执行FLASH APP代码
    1 p; ~! a4 |& B/ m' r
  31.   IAP_ExecuteApp(APP_START_ADDR);( R  t) A, U9 k! Y) [
  32. }   B% f, C1 X% ^7 k0 Q& {
  33. else//不需要更新,执行APP
    5 t# F  F+ u8 B. E+ r6 f. s
  34. {# k+ S5 g$ y( q
  35.   IAP_ExecuteApp(APP_START_ADDR);) s- b" i- W7 ~: N1 ~! d) e
  36. }
复制代码

% j, o5 |5 L+ f1 n* |

bin文件生成


% f# N( F, i/ u; S: |! S4 y" W# U

微信图片_20230402174611.png

# y' ]" `9 C) C! i- d/ f$ {

输入:

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

( O) J. M7 f  b' [9 {3 }

参数意义:

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

  • --bin -o  IAP.bin :

    / s$ d/ W) _7 U* {7 N8 o5 M1 U; r& H

输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:
    / V8 w& `, |' K

axf文件目录及文件

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

5 Q; r% [+ g: G3 a+ e) q& o
微信图片_20230402174607.png * Z8 y( |4 u0 m6 n& J

. X" K% z7 K, f$ r, V& O 微信图片_20230402174604.png
$ v3 c1 v# Y2 C
% r& P; O+ o) j* H- g" x
  • APP代码设计
    - [. P$ }, Y% j, O7 I8 w1 \

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

) q6 t: g- u0 J& o: y: k9 S" y) T

  • boot区代码执行效果6 }3 i( p, N, P0 @3 P; V& s, r
7 w7 h# e5 F, _. {. m* G* R
微信图片_20230402174601.png 0 o  @% {2 A- e( j

0 L! A( o" e( _+ p8 D
  • APP1代码执行效果1 e+ v6 p9 l' P) D
5 L7 R3 G1 r+ M0 S6 y! b4 g* L
8 r; V; \& P/ A" a+ i
微信图片_20230402174557.png * M& z9 k9 J4 y8 j! X3 i1 v
5 Q0 A! `: O- C

+ Z0 D8 l8 g) ?2 O5 f
  • 接下来再进行一次更新
    ) m  N: Z5 [: C  v

2 L( ?# Y" B; ?; _
8 H9 u8 o$ J& ^& B7 M7 f! W
微信图片_20230402174553.png
6 s; E3 i4 W; l' ~& I4 f. G; p# S+ w. n
! V! D2 q' w; C5 l* n4 Z  v

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

# J+ u9 m8 D& O/ y# K$ C; [) f
微信图片_20230402174550.png
& p3 V; n0 t% _" x* P! _8 O# o
% y! d# j" m, T

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

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


6 V3 c( j9 R8 P: R转载自:Embedded小飞哥
3 P9 g6 {1 C9 ?3 V. T7 ?: O如有侵权请联系删除
" a$ D" Q4 |) \! ^) c' I
$ m/ t' w$ b' T' w2 b! V* {" t+ L
赞 收藏 评论0 发布时间:2023-4-2 17:48

举报

0个回答

所属标签

相似技术帖

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