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

IAP固件升级经验分享

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

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

+ {( i; y$ S; j. l( w) g1 G2 h+ U

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

% |) u: h7 x' Y( J. K: t! Q

STM32的代码启动过程

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

8 m9 S, ]- E6 v$ o% N1 X

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

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

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

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


    . w1 s. y/ _+ l9 C/ v4 F" ]+ s- g
微信图片_20230402174619.png 6 |# w3 _% \6 P7 N. j8 C1 Z0 J

# B- g. O/ @* K/ P启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************2 X  M, D6 \+ X* _5 E
  2. ;* File Name          : startup_stm32f10x_hd.s
    6 S! ~' ?* l* }1 a
  3. ;* Author             : MCD Application Team! ]' E3 v5 y3 M  c: a  ?
  4. ;* Version            : V3.5.0* J% u7 H* b4 `
  5. ;* Date               : 11-March-2011, j& O5 X. k& ~+ W% a1 P  ]4 m) j- B
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM
    - T, J) Q$ s* C. s3 Q5 i
  7. ;*                      toolchain. ) I# O- K* \; L6 k# l
  8. ;*                      This module performs:8 o% k* Z: L8 ]* X0 |
  9. ;*                      (上电复位后会做下面的几件事情)
    ; n+ h+ p6 w. w6 r
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
    5 d* G. @4 ]# X/ Q- ]6 S
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)" y' k5 i8 X5 p
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
    2 g7 `+ K. n* Y' J0 Q3 ^8 T
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
    ; R' p0 f7 q; h( `  Y4 M
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data
    3 L( D) S* D' h6 s  k+ G9 ^
  15. ;*                        memory (optional, to be enabled by user)' s  m2 \5 @$ X
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)9 b' O0 T& A* @, c
  17. ;*                        calls main()).
    2 R5 O: R8 G# r
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,
    ' G% W, Q9 w) L: p# s
  19. ;*                      priority is Privileged, and the Stack is set to Main.
    $ _: q* h- J) I# F. I7 {
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   
    4 f* ^8 ?) ?, J9 Q- }. m4 N$ ?3 z
  21. ;*******************************************************************************8 w" n! ^: d3 U( v: E" T& B
  22. 2 C6 N  c" ?; g
  23. 8 O. y- s, t: G" m: N3 a) X
  24. - X- _" b: B4 S  [1 ~3 T4 Y
  25. ; ------------------分配栈空间----------------
    ; O- D, U/ D; N% R$ k: }! z# B/ r" i
  26. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小/ g5 Q: D7 g# A
  27.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
    % E3 r2 }0 W+ X# x2 b' C
  28. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改: ^3 y* ~8 ?' |# @1 B! Z0 o" o
  29. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到5 V5 k3 \! ~: z
  30. 9 Z# x" k; Q1 r6 z
  31.                                            ! \0 J3 h2 J! D: B2 v
  32. ; <h> Heap Configuration
    9 F' C1 f) }/ F+ s0 V' O" `
  33. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
    , h5 d, H* z+ Z6 z5 e
  34. ; </h>2 r1 D$ r' L- l, M. s; t6 N# I: B
  35. ; ------------------分配堆空间----------------( M* M0 B! u3 ~* y  c
  36. ;和分配栈空间一样不过大小只是512字节" a- T3 M+ |9 o7 ]: w0 M
  37. Heap_Size       EQU     0x00000200
    0 q5 t/ o# v" L. s  v: z! P' d
  38.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    8 @* o+ b0 P$ A9 r4 M) c( m) ]
  39. ) a- J" {/ K! Z2 b) T7 l$ S
  40. __heap_base        ;__heap_base堆的起始地址
    % Z' s4 x) Y3 j6 d7 X4 f% x
  41. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的9 n& S" z9 z+ G2 X. ?
  42. __heap_limit       ;__heap_base堆的结束地址  m9 v, E( Y  E" {) o
  43. " Y1 t  {& C% \0 S; E
  44.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐& t$ u' X$ n! X- C/ @/ @! B2 J
  45.                 THUMB;THUMB作用是后面的指令使用Thumb指令集
    0 \  l. {& ~0 }, l
  46. ! q8 [, I6 s7 c* S% |
  47. ; ------------------设置中断向量表----------------
    : U; l) W+ m9 _0 p& h4 M
  48. ; Vector Table Mapped to Address 0 at Reset0 J% s' `# Y) v0 i/ Y
  49.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
    7 }$ b% m+ @, Q# {/ |
  50.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
    # J4 e( ~  ~! h0 K  j& |* B
  51.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用         
    0 V& n, z6 ^8 v
  52.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用4 n& b! U% p0 @; {5 @( M0 x
  53.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用5 \0 G% O$ `) q3 Q" ^

  54. 0 r3 I1 N! F: ?- `
  55. ) q% z% Q; `! Z' H$ A
  56.                
    / P, a, V! O1 ~
  57. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化4 |! ~9 Y6 H% l3 D! k
  58. " z5 v6 J* {. ^( D
  59. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
    # `" p0 X' w7 P* k6 E- k3 P
  60. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,7 c6 I# U! I# r
  61.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) " D' e$ R0 v# ?
  62.                                                    ;* __initial_sp的值是链接后,由链接器生成
    ; N2 h7 {3 G0 Z8 g, w) r/ i& d

  63. 1 p4 l0 v9 J" g9 B
  64.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,9 @' s  M: d$ K  P
  65.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
    4 ~) V8 `& a( E/ N0 c/ N$ S, }* u
  66.                                                    ;* Reset_Handler是一个函数,在下面定义
    * q0 e' J( A, \* \$ G5 P
  67.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》$ K' f7 U# ]% D# Y8 r
  68.                 DCD     NMI_Handler                ; NMI Handler      # b5 Z: d% B9 o! \
  69.                 DCD     HardFault_Handler          ; Hard Fault Handler' F3 A) |' g! u. J: N4 Z
  70.                 DCD     MemManage_Handler          ; MPU Fault Handler3 M" F+ f) p* H; [2 Y3 ^
  71.                 DCD     BusFault_Handler           ; Bus Fault Handler9 y/ O% X' K; N6 g
  72.                 DCD     UsageFault_Handler         ; Usage Fault Handler
    9 K8 B' \% f  P9 v+ i, {7 N
  73. ( ]- I; ?; ]0 e3 S
  74. $ C6 Y6 ?' o: `  w$ w
  75.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件2 F9 K$ K7 r5 d6 B0 K
  76. 5 I. q+ b) Y. }5 `- ]
  77.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1- ^- D3 L8 n# \7 z, T4 v
  78.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel25 W/ s( O" Y! C, Z. Q; [! l
  79.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel36 b8 y6 A/ V' O' i! H' g
  80.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel58 [$ p, ^, ?* @5 e$ [; s2 `
  81. __Vectors_End                                    ;__Vectors_End向量表的结束地址6 j8 X" m" e9 Q7 r+ S
  82. ; l0 [- M1 r7 {4 W0 l/ ~
  83. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小
    4 S$ R# `. R+ c. d, R

  84. - L6 ]+ q0 M. }) G# T5 r
  85.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读5 m) r  }) |0 `: ]
  86. 5 n# g6 D- I# Z9 P0 P! g
  87. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
    $ h3 a$ H* j4 P" r
  88. ; Reset handler
    + |2 ~9 T  v7 z& O
  89. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序9 t2 t: z( X. X$ W  j
  90. 1 P9 r: A" s% a6 m  i0 H
  91.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,7 k0 R3 B& `9 P; d
  92.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数
    , M( y& Q- c% q. i# B# B
  93.                                                 
    9 x$ ?, I8 @( l% O/ Z; w3 b2 t
  94.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似8 F0 H/ v* J% K, `; ?6 l/ Y1 f
  95.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
    . U) Y/ @4 V/ k7 V
  96.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟6 @: R* m4 j1 X, A6 C' f
  97.                 LDR     R0, =SystemInit
    ) n2 A. B# x0 E3 Z" i) H
  98.                 BLX     R0              5 |# |% d+ g/ P: y
  99.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码" e2 ?+ `. k# g3 m: V' V+ b
  100.                 BX      R0
    % B) W0 p8 J* P1 \
  101.                 ENDP
    ! J8 j/ @) e5 j7 q' d4 O. B
  102.                   |  x! C2 Y2 C: @; g/ w( _' B
  103. ; Dummy Exception Handlers (infinite loops which can be modified): v+ o9 @3 r4 ~8 c! _/ \8 j  s) f1 ~, V
  104. ;下面定义的都是异常服务函中断服务函数- h/ u; G7 S% E( T1 d+ u7 v
  105. NMI_Handler     PROC# N0 ?0 S5 ]( n. z
  106.                 EXPORT  NMI_Handler                [WEAK]( O- i8 H/ D% i
  107.                 B       .
    0 _1 c0 K& t- u& _6 {! t
  108.                 ENDP- x. w  }- A; z9 t# i
  109. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
    ; I5 S4 g" O  m
  110. SysTick_Handler PROC3 l3 R: t9 \* l% b0 ]2 Z
  111.                 EXPORT  SysTick_Handler            [WEAK]$ j, m, k% I% Z6 Y
  112.                 B       .! z+ J" D7 Q. @3 ]1 W" V/ v
  113.                 ENDP. P" ^; A6 e# c* [

  114. ) l$ I( Q! R! Q9 z
  115. Default_Handler PROC) D. Q- q* p4 R. y/ ^- y* S$ I* A
  116. 6 o- H( Z  R* i0 s/ O
  117.                 EXPORT  WWDG_IRQHandler            [WEAK]
    6 l2 V# N- Z" f! i3 o- u9 Q4 c: _
  118.                 EXPORT  PVD_IRQHandler             [WEAK]7 L! X0 S  n$ P5 F* E  U) ]
  119.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
    9 R6 d( I9 q5 }* E/ ~
  120.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]
    * Y, H8 }3 R' ^- _4 [
  121.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK], \2 ^" N" m& M" ?5 Z$ H
  122.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]2 m! b5 i4 a( z* N2 b% u
  123. 5 m/ \, ]- x! M, T% F3 P2 p
  124. WWDG_IRQHandler" V  J1 k7 ~  k* j" K7 j3 w9 z
  125. PVD_IRQHandler& ?# O' C7 D- r: c3 a' F' ]
  126. TAMPER_IRQHandler: f8 Z' ~2 l7 w1 u$ b, p5 Q
  127. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
    4 }5 \" d7 H7 g! p
  128. DMA2_Channel1_IRQHandler2 Z  H' n* ^! J# l
  129. DMA2_Channel2_IRQHandler; u% B$ [/ x( J6 d) }  B
  130. DMA2_Channel3_IRQHandler9 m/ i- u$ M# z! N. C  T8 u
  131. DMA2_Channel4_5_IRQHandler; B+ i9 x* V6 c
  132.                 B       .
    1 u" v7 k- n; c+ M

  133. ' k, I# f$ n7 r+ a8 k
  134.                 ENDP
    : w/ V( O% X5 |! R

  135. ' S# Q6 {/ D7 o6 y2 s4 t/ d. C
  136.                 ALIGN    ;四字节对齐
    ' u( A9 J9 w1 \4 Q6 X
  137. % u, D+ r! x+ x7 Z! t0 w
  138. ;*******************************************************************************5 Z* A8 V- v; [' g7 S  w
  139. ; User Stack and Heap initialization+ ^) I9 k5 [7 p) m( E3 @
  140. ;*******************************************************************************! S) f: w5 j; ?2 E* |1 E
  141. ;下面函数是初始化堆栈的代码
    ; b4 v. B" B# w, H9 m
  142.                  IF      :DEF:__MICROLIB     * y1 g# I1 v. [$ @* u
  143.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
    , z# A9 Z) h+ m3 o! X* D
  144.                  ;这种方式初始化堆栈是由 __main 初始化的
    4 G1 G. p$ _8 S1 C3 z
  145.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
    3 b/ ?, ?; a7 W. v+ l7 v0 @
  146.                  EXPORT  __heap_base    ;堆的起始地址
    8 U/ @" G& }; x
  147.                  EXPORT  __heap_limit   ;堆的结束地址3 b* Z% J9 A6 t- f1 [  u/ F7 P: J+ u
  148.                 8 s$ a! h6 L! L1 ~4 c3 H
  149.                  ELSE# Y" ?( q  r" y4 n4 z6 c
  150.                  ;由用户初始化堆
    ! a/ q! l' C& C* @$ v# C
  151.                  ;否则编译下面的
    : g& y0 R0 [! G8 V+ ~) k! d  K0 ]
  152.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现0 p+ {: g& s3 S/ Y9 L2 R
  153.                  EXPORT  __user_initial_stackheap
    . E. p6 r7 t2 A0 s( |0 o$ N
  154.                  . {5 |% D, x# }6 u6 l# L
  155. __user_initial_stackheap
    5 E& L: O" e* L9 K9 v. V
  156.                  
    & w7 [& e$ c( V9 S5 C
  157.                  LDR     R0, =  Heap_Mem              ;堆的起始地址
    ' A7 [, A) m* c$ q: n
  158.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址- z8 L7 A) w# p4 ~7 M2 w# e
  159.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址
    4 m8 s* @! B. X7 s, R3 E7 o
  160.                  LDR     R3, = Stack_Mem              ;栈的结束地址
    3 N/ }! o) _! s& T! v0 r4 L
  161.                  BX      LR
    5 ~# l+ E$ K2 o

  162. % E# X! d) T& F9 }5 D7 I
  163.                  ALIGN
    $ A2 @' v9 h& o: z& [

  164.   L% l0 P. m) W) f3 R: i
  165.                  ENDIF
    / ?9 |& A. y+ N# ]) S3 n
  166. " D! M3 e6 c& ?- q( ]9 C
  167.                  END
    ; L' ~6 b4 G# {( ?) p4 X; c
  168. 1 M5 B6 p# `& X  S" t. X* F
  169. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码
, w. N8 {# m) k. K7 [
  I; Q7 E; W! B4 l) M1 o$ b0 u: O2 x

STM32的启动步骤如下:

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

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

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


    9 E) o5 w$ @. U6 }3 e
IAP设计思路

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

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

- [# p# ~  D3 g9 E3 I

升级流程图/ K0 X* ?, ]# t$ g0 t4 t

1 J! h( M0 Z  w. e
# T" ~9 N. s  j* T# Z; E  D1 F. M, F
微信图片_20230402175006.png
) N. c  s, R2 o+ b, a" R) ]4 X, a* G) r/ n
1 f0 I( b8 R% K/ u/ N
Flash分区

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


. n6 u0 D7 S0 V. M

微信图片_20230402174614.png


# E/ u  i9 C# M

  • 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


    5 \( Q" f. K1 t0 p' \9 f1 b
代码实现

硬件:

  • fallingstar-board(已开源,打板验证)( y$ ?/ L) u$ S  y. ^

软件:

  • 内部flash读写

  • 串口DMA+空闲中断


    ( M8 E8 J, v1 |3 O, @+ l
内部flash读写操作

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

读flash操作:

  1. /************************************************************; L6 k( t/ y# b+ M/ y! B
  2.   * @brief   读取2字节数据
      t. H$ v3 |. n( M; [2 R
  3. * @param[in]   uint32_t faddr- C. D" N4 ^9 j! X& |6 h) W
  4.   * @return  NULL
    + y- E. z" J2 l/ ~6 w9 z8 g6 i
  5.   * @github  
    ! W  Z% M( S# @
  6.   * @date    2021-xx-xx% Q/ p3 k' V' `# C1 ^5 n+ f
  7.   * @version v1.0
    ; F3 R$ o4 C2 U, y5 z
  8.   * @note    NULL7 k4 f# M4 C' L9 y: U
  9.   ***********************************************************/
    : V6 ]5 o* {9 r, ~% Y9 R2 O4 c- t2 }
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)5 ~8 V" x" l7 X* S3 ^
  11. {
    & y% a& R& e( z; G0 J' o- I
  12. return *(__IO uint16_t*)raddr; + Q' r5 P3 a9 o6 G
  13. }) V! F; t" Q  `
  14. /************************************************************; M% f( N9 a  L
  15.   * @brief      读取n(uint16_t)字节数据
    ! b& x/ L1 {) y! @* q, a4 S( b
  16. * @param[in]   uint32_t ReadAddr5 `0 x. C7 x: V2 M  S, {$ L. H2 _
  17. * @param[out]  uint16_t *pBuffer/ i* c' u( G  }8 P2 F
  18. * @param[in]   uint16_t len% m# v- O3 j# D& F
  19.   * @return  NULL# E1 I- H7 b8 K3 z/ _& m
  20.   * @github  ) @# N1 i9 Z$ ?0 d0 e* l- j
  21.   * @date    2021-xx-xx
    - z; p; W: D+ f5 Q( d
  22.   * @version v1.0" C& s# _) r5 J# @
  23.   * @note    NULL9 m7 h# ~* k; l
  24.   ***********************************************************/
    ' i5 R& e& t7 E/ g. ~7 Y! |  k& e
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )    ) v$ ^" H. y2 ^7 `% P$ E1 v" w
  26. {0 v; C! o) O; W# `5 s. C! K  C4 _5 M
  27. uint16_t i;
    3 q6 ^' }- g. k1 l, D
  28. 6 |( H4 l& U( b9 M
  29. for(i=0;i<len;i++)
    7 I) C3 Q, G: r$ M: e( \
  30. {
    0 Q2 B" |/ g) y- M0 x) T
  31.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.  v  A0 M3 [& N7 m' k1 x
  32.   ReadAddr+=2;                   //偏移2个字节.
    7 p0 `, J4 O8 _# t
  33. }- w: z3 m7 W9 x! n
  34. }
复制代码

+ d* [5 B; i$ J. _; d

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

  1. /************************************************************
    ' C. ^0 F* l" U6 ^* D. P
  2.   * @brief   写入n(uint16_t)字节数据% q) x/ a% J! i
  3. * @param[in]   uint32_t ReadAddr
    1 Y" q' `& a. G. e0 g0 H
  4. * @param[out]   uint16_t *pBuffer5 B0 d% p# e, e# {
  5. * @param[in]   uint16_t len
    # r7 I+ J( d. ?0 [9 K0 [- u
  6.   * @return  NULL8 d# u, C. V3 S  }$ {
  7.   * @github  ' h: V' Y% K& ?( X- u* a* @; ^1 P( ~
  8.   * @date    2021-xx-xx3 E" r- t% H, ~9 @. j
  9.   * @version v1.0
    7 y% K: e0 \3 l
  10.   * @note    NULL
    , t4 ~- ?; Z) F0 S) A% F2 h
  11.   ***********************************************************/! _# q  S1 @5 u3 A. ]0 v3 h
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   / O3 V8 t. x% j; _) V
  13. {        
    % a; V; i% v( n; ~  g$ C9 Q* M
  14. uint16_t i;
    : \9 z9 E+ e5 f' n! q; m3 B$ G" u
  15. / G: P2 @% J4 }3 h
  16. for(i=0;i<len;i++)
      R& {; H! h% T5 _5 L1 B- h4 D
  17. {
    1 M+ Q( U# \# z( o& l
  18.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
    2 O+ D4 O% J+ }: ?% h
  19.    WriteAddr+=2;                                    //地址增加2.
    8 ?' h! ]6 M5 _, P5 T
  20. }  
    ) u, f4 r! [6 a, M! Q' P+ J5 o
  21. } 6 _; w) S/ q. a( c' \6 U4 {
  22. /************************************************************' s: `9 o* a& P2 W! U. j
  23.   * @brief     写入n(uint16_t)字节数据
    ( `0 g' i0 S0 l
  24. * @param[in]  uint32_t WriteAddr
    9 X' B- ?& D1 {3 G: M
  25. * @param[in]  uint16_t *pBuffer: ]/ }; u/ k( }5 u
  26. * @param[in]  uint16_t len2 N5 H  a: A; w" A8 l$ J, l# Q: I& O
  27.   * @return  NULL
    . D( F8 O( N; E( X
  28.   * @github  # c" m$ t9 g( F4 B" u* v! a
  29.   * @date    2021-xx-xx% E  r& w1 ?2 @' t% x
  30.   * @version v1.0
    - b6 r* P" N7 T% w- b( J
  31.   * @note    NULL0 K% }* B: \. x6 ?
  32.   ***********************************************************/
    # @& T" x1 A, J6 ^
  33. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
    & n) ~2 F8 b+ R
  34. {; s6 d- l) t1 D& V
  35.   uint32_t SECTORError = 0;* w. Z. Z3 }; g' l6 W
  36. uint16_t sector_off;    //扇区内偏移地址(16位字计算)
    # p2 y; Z6 N* M! [1 d' j4 T  O- }
  37. uint16_t sector_remain; //扇区内剩余地址(16位字计算)    8 Z) Y# t0 P$ d' d7 x
  38.   uint16_t i;    9 k8 E+ A7 l7 i
  39. uint32_t secor_pos;    //扇区地址
    4 w  J" Q4 S8 {/ S3 e  u$ b) X
  40. uint32_t offaddr;   //去掉0X08000000后的地址" N9 v6 Z6 Z9 }( p$ E0 J1 i

  41. - W- n, Q; B/ C! S+ w. O
  42. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址3 u: `$ a7 Z4 d: D, r3 I
  43. 0 y+ ?+ I/ s# ~8 }, U
  44. HAL_FLASH_Unlock();         //解锁
    ) G% b! n7 `, p& \9 I/ ~

  45. * _) K# v0 M& p# l
  46. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.+ P/ K, u3 f  @1 e
  47. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT66 Q* O$ `0 N/ X: y+ S* F5 y7 f
  48. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)
    ; o, C1 I" s+ R" v0 q
  49. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   % Y3 t  Q& ?6 j; F1 _# x
  50. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围8 K4 z5 L- G7 l$ N8 s/ k' J( Y$ [! S4 j
  51. 3 C* L3 Y1 d  v
  52. while(1)
    ; \1 m! C# L! K
  53. {
    4 @- z9 b+ `" E6 T2 K
  54.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容1 A" A+ I$ F1 w& h# Q6 }5 ^
  55.    
    1 h. I- N5 n  M  z! x( O, k4 T
  56.   for(i=0;i<sector_remain;i++)//校验数据
    4 [1 d) J  p; P5 j. l/ p& {* Q
  57.   {' F% m! T& u+ s! M; {; E
  58.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)2 `1 a* G3 ^8 H2 A% m) t& n6 f
  59.     break;//需要擦除     
    8 d$ @0 F+ U& n- s. g7 L
  60.   }3 N7 L5 U4 O* _# y$ r- f
  61.   if(i<sector_remain)//需要擦除( T5 [  U( [/ T3 c, g9 }
  62.   {
    $ u5 W- x  Q* B- f* y3 v0 P
  63.    //擦除这个扇区
    # `8 |0 M. h' T. O8 {; O
  64.       /* Fill EraseInit structure*/
    . N5 p+ o  z5 r! }1 T
  65.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;/ J* \" ^; x! @, C5 y. U
  66.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
    3 T. v$ a6 I* W- b% h; T  o
  67.       EraseInitStruct.NbPages       = 1;! W! `- [- \# }; L9 G. R
  68.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
    * g# B2 \: S- ]+ x# O
  69.    for(i=0;i<sector_remain;i++)//复制: W- y6 ]4 L! q1 Y7 I9 i
  70.    {
    1 K' U. n6 I: L" B, }5 o; @
  71.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
    7 l( Q  v4 v0 `, \1 L. c; _
  72.    }
    $ e" y7 @& C8 T$ _# i& T8 v, h  _
  73.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  / n' j/ l; Z; d" a: ^; r* V; L
  74.   }
    9 X6 L% t4 n4 b' a6 M8 ~
  75.   else % v% X, X8 C( j) Y$ v( J3 A0 r
  76.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        
    : e' N6 ?  S1 A! H, ^. h' I, z
  77.   if(len==sector_remain)8 X; a# `7 x  a+ A3 m
  78.    break;//写入结束了
    ) _6 d( Z. a. A) v. V0 h
  79.   else//写入未结束$ V* \  W5 ]2 S# @* R
  80.   {4 L8 i' T2 D& J7 U( U
  81.    secor_pos++;    //扇区地址增1+ L3 J  Y+ I: m
  82.    sector_off=0;    //偏移位置为0   
    * N% ?$ C7 [6 h" @0 Y' Z* l
  83.     pBuffer+=sector_remain;   //指针偏移
    ! r4 @! ^* _% @% x+ t. S
  84.    WriteAddr+=sector_remain; //写地址偏移   
    + ]& t* l/ k" P0 O9 P' H
  85.     len-=sector_remain; //字节(16位)数递减1 U$ p9 M. `2 ^/ p3 d
  86.    + E% H5 o" z1 T# V% O# G, e9 S& j
  87.    if(len>(STM_SECTOR_SIZE/2))+ t. d' O, o9 g) u0 o
  88.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完* A  \% p# s+ C) h
  89.    else $ {( g! x3 A7 u: {; f* O, \/ @
  90.     sector_remain=len;//下一个扇区可以写完了
    7 [8 r* ~, H( b% B0 B( P
  91.   }  # q0 Q, U  s$ @( L0 M! d0 E
  92. }; 2 J4 m  [& _$ N" D4 M; L1 N" Y/ W
  93. HAL_FLASH_Lock();//上锁5 T* f. H: z; c! [8 X  Q$ d
  94. }
复制代码
; [3 B; I" a0 H2 [4 c! j
串口DMA+空闲中断接收不定长数据

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

  1. #define Max_RecLen 1024*3) }. s. b+ t3 ~# y
  2. - u$ {1 ~4 K# w7 r
  3. typedef struct{
    1 m7 R+ o  R0 U( w- y
  4. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区
    " q' e9 m+ a7 o" b
  5. uint16_t RecDat_len;      //单包数据长度2 ^4 c+ h6 K! l7 y1 Y
  6. uint32_t Cur_WriteAddr;     //APP缓冲区地址
    ) V) ?" Y- Y& ^7 F. o
  7. uint16_t BinLen;        //bin文件数据长度* o+ E, _6 |( c/ |
  8. uint8_t rec_endFlag;      //单包数据接收结束标志
    : M  ?. }% H9 {; k6 K2 T9 g
  9. uint8_t Binrec_endFlag;         //bin文件接收结束标志
    4 I' w' E4 R# P
  10. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器
    6 n/ X6 \2 l0 I- {3 G

  11. 1 [% B  r, K4 y# b
  12. }UserUartDMA_Typedef;
复制代码

" n# M, Y& V8 z0 u

然后在串口中断中:

  1. /**
    1 \! N: C0 i+ y# ^5 u4 J
  2.   * @brief This function handles USART1 global interrupt.
    8 l4 K3 g+ o, u5 k
  3.   */3 j4 [( J" f1 Y: h
  4. void USART1_IRQHandler(void)
    . m: `* u2 }0 W8 q; k) W- D+ j
  5. {+ ^; w1 _, t+ Z
  6.   /* USER CODE BEGIN USART1_IRQn 0 */( m8 |: j2 o& ~3 o
  7. uint32_t idle_flag_temp = 0;
    / z; G; w+ W( N
  8. uint16_t len_temp = 0;
    1 H9 J# W' p' |  \+ `: c
  9.   /* USER CODE END USART1_IRQn 0 */
    5 e3 F4 I1 k8 O1 U$ X7 x$ Q' g% l
  10.   HAL_UART_IRQHandler(&huart1);
    $ H, \* J2 n& [4 G; V0 G" c
  11.   /* USER CODE BEGIN USART1_IRQn 1 */% F' C) B$ q  r8 D7 _6 C1 i
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
    6 x& `8 m3 E8 u
  13. , y* V' F8 n  h
  14. if(idle_flag_temp), _5 ]8 R* J6 v6 a
  15. {
    : P+ n7 h2 u) e+ j! |
  16.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
    + p  t5 p' D' m$ u# }+ W/ O0 }) F
  17.   HAL_UART_DMAStop(&huart1);8 J- ?* Y7 {# k$ Y% V
  18.   3 }* g' [# C4 b
  19.   UserUartDma.DMA_TIMCNT = 0;6 G2 m5 p! i& B# D# I
  20.   UserUartDma.Binrec_endFlag=0;% F  y) o. \3 B' x+ u; I0 B
  21.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);- L3 |  f: `' t4 x
  22.   ) o/ X9 K: _  T5 N4 H
  23.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  0 Y7 o+ `6 g4 l
  24.   UserUartDma.BinLen+=UserUartDma.RecDat_len;
    5 i+ ^1 i+ ]+ T; f
  25.   UserUartDma.rec_endFlag = 1;
    . L$ n% N4 C# O1 c% p
  26.   : r& w8 @  T, m
  27. }
    + ^: E6 V) V! y0 g+ R7 Z- V7 u; c# N
  28.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
    * v7 Y5 i9 l% r) ^3 `9 _3 y0 u
  29.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen); ) s  R' P3 b2 F9 C$ O5 H  C
  30.   /* USER CODE END USART1_IRQn 1 */* f  L( ]6 N5 \4 U
  31. }
复制代码
/ J' c: \8 ]& J0 v. B& D
IAP代码设计
  • bootloader代码设计
    ' h, J" X& q% U3 |; |) l- ^% p

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

  1. /******************************************************% g  ~8 P3 u1 A5 f7 Z+ D1 D# h; R
  2. * Brief     : 擦除APP区
    + Y1 _5 I, L* f) n
  3. * Parameter :
    : e) V* v9 P2 [* X' V* c- J& _
  4. *           *startaddr:APP起始地址  X9 S$ j: B2 Y0 c! d/ B4 `
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE
    & g% V7 P9 n1 _
  6. * Return    :None.
    1 ~$ h0 U2 ?" {, P2 s
  7. *******************************************************/( d. l5 J7 q# o) U
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)- [9 t4 K  S( B; K2 P0 j/ U# x
  9. {2 f) {7 K' n& P3 m
  10.   uint32_t SECTORError = 0;
    ( M1 G  Q: j! t8 Z4 b) e: A- W% E

  11. & D* s' K6 V4 ?% _, S
  12.   //擦除APP区* n0 {5 j" s* f: G2 z: B
  13.   /* Fill EraseInit structure*/
    ( r$ `6 J) |' f3 {5 w! c% }3 E
  14.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;8 V% a3 L" s5 s$ e  M
  15.   EraseInitStruct.PageAddress   = startaddr;
    1 O8 U/ s1 X0 O! Z
  16.   EraseInitStruct.NbPages       = pages;
    ; T- U% N( {0 _6 Q' h  B
  17.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);; F# |. p8 N* S0 ^% V& c
  18. }
复制代码
* E! q' m/ E+ b$ Y; M

升级标志获取与擦除

  1. /******************************************************- m2 R5 h/ j/ v5 M8 X9 b
  2. * Brief     : 写入升级标志, ?% k1 D& J; f
  3. * Parameter : ; t3 I2 h% w; d, m8 H) Z' N$ b- m
  4. *           *addr:标志存放地址
    7 K9 o4 ]7 z4 p3 P
  5. *      *pdata:写入的数据0 y% r! o/ B+ t, d! T
  6. * Return    :None./ _! A5 A# ~6 u
  7. *******************************************************/
    , I; m3 f6 B' g( [8 i$ @
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
    , g  y' A3 U4 S  I# _
  9. {/ \; b/ e4 L# i  y( ]% g
  10.   BSP_FLASH_Write(addr,pdata,1);
    - G3 |' ]& Z$ g( F: d% \6 R% D1 v: h+ y
  11. }
    - P: a( {. y( H$ y, N; t! K: p) j9 s
  12. /******************************************************1 f3 s2 d( U* n. l- \
  13. * Brief     : 获取升级标志
    2 h1 i! ^9 i. x8 X
  14. * Parameter :
    3 I0 Q! M+ F& I3 @3 a' Q0 Z
  15. *           *addr:标志存放地址
    3 d# z& w# l3 e  t; b/ y3 v- z3 p
  16. * Return    :None.
    8 s& b- g& R8 w& n) u% _  V, x
  17. *******************************************************/7 g- k4 g+ @$ I' r7 Y  r
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)1 [2 R1 h- p8 \" c; C5 E4 A
  19. {
    / p4 |$ n2 s% |6 I% c
  20.   uint16_t flag_temp;
    9 A1 Y; e7 F# Q& G
  21.   BSP_FLASH_Read(addr,&flag_temp,1);
    / U- D: \, E1 I+ P" y
  22. * ~) q0 E% {! q9 n7 y* i% B. O
  23.   return flag_temp;+ H& n7 ~  w: k8 [
  24. }
复制代码

* Q1 A3 n9 H/ C) Z% f+ @( c; S

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

  1. /******************************************************
    " V. z  w# w1 r/ g& P/ `3 [
  2. * Brief     : Bin文件写入app缓冲区
    ' h: D# \; K% |
  3. * Parameter : 4 U/ c8 C& I) D9 @( Y, F1 W
  4. *           StartAddr: 起始地址
    0 e* i6 ]( m3 P& _
  5. *           *pBin_DataBuf: 要传输的数据% R1 u" x+ F1 w% ~
  6. *           packBufLength:单包数据长度5 X0 l8 I8 ~6 W& N; n! R
  7. * Return    : None.8 Z) a/ Z! g( `+ e  i  O
  8. *******************************************************/
    - G4 |# z9 }( L4 Y+ C8 t" Z! J* e
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)6 w  Q, @- I  M. R( q- G2 x( O
  10. {
    ! J, J8 X0 b2 A4 d* Z# u
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;" u0 Z# x8 M% l5 Q
  12. uint8_t * pData = pBin_DataBuf;
    / ?( l( v  b  Z$ h. E
  13.   ( ^9 ?3 I7 I4 H5 \2 G2 p, V6 q
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
    $ E6 A' v1 C( R
  15. {         
    , b3 K- z0 b+ X, W
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;
    * ?1 [4 g* M' [2 v" D% y( }2 k
  17.   dataTemp += ( uint16_t ) pData[0];   6 m: j" v9 D4 k* j& V
  18.   pData += 2;                                                      //偏移2个字节7 z! W. G, C% @$ `# F* n
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     
    ) E% z3 l! ?% v7 s1 ^
  20. }
    # J! t* L/ a2 \
  21. 5 N1 E$ }# Z5 X# A- q* X
  22.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
    + k! z% W' I( A9 @# A; r6 U8 L
  23.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.4 [" R4 |; W5 g- Z7 g
  24.    packlen_Ctr = 0;
    ! A' _3 w7 ^5 m" d" Z. [
  25. }) y$ a& G2 ?/ w( r  O0 D- }- a6 O
  26. /******************************************************' ?6 v8 ^- Z- Z# L
  27. * Brief     : Bin文件从app缓冲区写入app区
    2 ]' K# I* b- Q  u# I- r
  28. * Parameter :
    " p" P  z; i3 h* d( e9 }. N# m+ a) z
  29. *           SrcStartAddr: app缓冲区起始地址7 U$ O3 ]4 s7 Z. f* j0 C8 J  L
  30. *           DstStartAddr: APP区起始地址
    4 o+ \( J* M, x9 s2 M5 t5 X5 F# r
  31. *           BinLength:bin文件长度& S4 D# O  I2 G% |

  32. ( c, P! x6 W8 g8 F
  33. * Return    : None.$ B2 A% m) X: ~
  34. *******************************************************/
    1 Y) T8 B8 D0 l! I3 R
  35. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)' @; v1 Z3 m% M: P) p* j9 T
  36. {
    + Y; X- V7 {" _" m6 x
  37. uint16_t data_temp = 0;8 X6 ]9 G, L8 ]% v! B
  38. uint32_t count=0;; X. D( d* b7 \( z

  39. : N# J. |& V* L( S

  40. ) `  G2 C2 W: B% A4 u# w
  41. HAL_FLASH_Unlock();         //解锁
    4 k1 j5 a$ s- g  ^7 d6 D  q
  42. : c, P* h& E1 S  e& N' m3 J$ M
  43. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
    7 V# i2 r/ g3 _9 X# x. ?$ P* D
  44. HAL_Delay(10);8 l# ]3 |& E0 X$ G' h

  45. 0 g/ x0 j; Q& b( @& G+ f
  46. for(count=0;count<BinLength;count=count+2)
    ; Y( C, b2 T8 y
  47. {, r* B( R7 r& v( U& D5 @) y
  48.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
    5 T! \& I% b5 ~5 m. m' ^
  49.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
    3 c$ M) m, w6 ^; v  u) ]
  50.    SrcStartAddr+=2;
    " X% k0 O( F8 M. h
  51.    DstStartAddr+=2;( k- m* @3 @. y3 {' y, c4 k: ]
  52. }- e5 z- b1 A& Z/ l1 x, b
  53. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
    ( u: ~* \9 W1 U- a( V# i7 }
  54. HAL_FLASH_Lock();//上锁
    5 t3 X7 z- H. I6 z
  55. }
复制代码


3 E2 K8 d& ?) g% I* ?

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

  1. /******************************************************: E, Z3 i0 ~: C
  2. * Brief     : 设置栈顶指针$ O$ L- a$ u, R4 x6 s
  3. * Parameter : 0 Z2 k2 B- c; E8 `. {" G
  4. *           ulAddr:
    - D/ r1 o$ W2 B) b7 B- c; z
  5. * Return    : None.
    / ~2 D) D  ^* m
  6. *******************************************************/
    1 T# ?0 h, L0 l

  7. $ d) v/ C% S5 n5 L, j, x! g
  8. __asm void MSR_MSP ( uint32_t ulAddr ) ) U9 v" A" c5 B( M  `
  9. {
    * D/ z% p( r# x1 A) ]
  10.     MSR MSP, r0                       //set Main Stack value! N) v) h; c) G$ E
  11.     BX r14
    ( I; e& w" C2 B: |! i, @% P
  12. }
    0 G1 z2 B6 N; F5 i' {

  13. 3 }( O' k* d7 V0 [! d4 }- G0 l
  14. /******************************************************
    1 ?1 b$ ~' K4 [. j6 Q( s
  15. * Brief     : IAP执行( Y3 C! r# c1 m  t% j
  16. * Parameter :
    5 w  J1 C- B% d
  17. *           ulAddr_App: APP起始地址) _6 A5 u. q- D* m, n. f
  18. * Return    : None.: q0 \' F7 v1 V; t
  19. *******************************************************/
    1 L- I# X+ W% I  P" ^4 P
  20. void IAP_ExecuteApp ( uint32_t ulAddr_App )
    1 r5 Z; U: f) v( v  S- J! t
  21. {
    4 _: o) X/ ^' ?  @4 u+ e
  22. pIapFun_TypeDef pJump2App;
    & c/ t" H# u. m' B1 y

  23. # o% b" W9 W5 P/ C! J
  24. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法.
    ; ^7 ]: I/ P$ E/ }; l
  25. { & r$ d. r0 k' e& d
  26.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)  
    0 f: \' w5 Q1 I4 P' |7 u
  27.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)* @" c0 ?+ P# g/ K* K( c
  28.   pJump2App ();                                             //跳转到APP.
    % x# b  g& Y  c: a2 x7 b0 F1 _, M
  29. }
    * p& Y; @7 [9 I. |/ _) d, w
  30. }  
复制代码

# @8 K5 J, q# A

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

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

  1. static void Systick_Config(void). j5 f0 _: q5 m  m
  2. {
    " ]/ G& Z+ s& H- M3 c0 Q
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms# J- l1 m9 `- z$ s: }7 Q8 |4 A
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);  a- q; N% G/ i' t
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
    7 y2 n' E% x3 C
  6. }0 q% O. A9 x5 o' w

  7. % n  m( c( q  B) d# z2 E5 j1 @
  8. void HAL_SYSTICK_Callback(void)9 @2 B( |8 P9 F$ y- R; P$ f2 L
  9. {
    ; J* ^1 H  j. S' \% z3 A* g5 v- i
  10. UserUartDma.DMA_TIMCNT++;) f" k& \1 g& d8 O- R1 F% a
  11. if(UserUartDma.DMA_TIMCNT>5000)
    ' o# z/ `5 D" U8 o, @" p, \
  12. {
    ! C, Q5 E) [4 m! _- _8 C
  13.   UserUartDma.Binrec_endFlag = 1;
    . P5 w; o$ v4 [3 R% B- f
  14.   UserUartDma.DMA_TIMCNT = 0;
    % Z& l* F, x) M
  15. }" Z& q# r5 ?# x% i
  16.   TIMX_IRQHandler_user();
    7 b: z! X1 R% q; x
  17. }
复制代码
6 [2 d' D* P8 ]% I

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

  1. 5 y8 x6 D4 R! H1 g5 j' B0 r
  2. uint32_t Task_02()6 l1 b8 U' y6 B  h, g0 V
  3. {
    * @# q0 C  K* a3 d  F8 U) a" k" L# \
  4. 1 L! g1 [" d. O- _2 I
  5. if(UserUartDma.rec_endFlag)
    % X$ K, [0 y" b: T% S0 Z; u
  6. {
    4 d# x* H9 s3 O, I
  7.   PRINT_INFO("update firmware\n");
    1 r! H0 x9 e0 j/ r/ c+ P) r
  8.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  
    3 K2 L" D9 x7 T4 Z
  9.   
    ) V, N4 L8 u2 M: m$ a5 ~" A
  10.   UserUartDma.rec_endFlag = 0;
    / T4 b! t$ h+ z0 n$ D. y
  11.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);; R' L. H4 X+ w; Y/ ]
  12.   7 Y% F2 y: ]# `+ x
  13.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);) T. C7 X) U, U. u0 f: h
  14.   UserUartDma.RecDat_len = 0;: S4 P$ G: d! E
  15. }
    " {( E! {- `7 M( X" _# |# h
  16. if(UserUartDma.BinLen!=0)1 W( b& c: C1 F2 d' H: O
  17. {1 }9 D4 l" ]% f0 E( g2 b) L8 F
  18.   if(UserUartDma.Binrec_endFlag)7 K; m" k+ c2 S
  19.   {: n1 k& I" @( o: j" s
  20.     UserUartDma.Binrec_endFlag = 0;
    + b' A3 [* q" y8 L7 `1 P; J: n$ o
  21.     0 e! B1 q1 [4 Y" X6 h
  22.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志" U( F# k% E" o! Y# T
  23.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志
    # O8 Y- |% w' a: x- M7 j, e
  24.     UserUartDma.BinLen = 0;  ! |% h1 ?( N) D8 s+ l
  25.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动
    : ~+ m' X# ?0 c8 O8 K
  26.     HAL_Delay(2000);
    & j' O& {1 g6 e1 V
  27.   }
    " K/ l+ J' y4 P; h. \

  28. 4 `) V1 H; V: q2 x4 {1 f
  29. }6 p+ L3 d- \! u. J6 G7 @' J
  30. }
复制代码

9 f) }4 Z/ I! G2 b$ K

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

  1. PRINT_INFO("-----IAP Menu--------------\n");; R) y1 I7 g9 z
  2. PRINT_INFO("-----Download APP BIN------\n");
    " W3 t; I, U  [% g2 D  R
  3. PRINT_INFO("-----Restart To RUN APP----\n");
    8 v8 ]  M) v- x0 ?0 Z
  4. PRINT_INFO("\n\n\n");  _; P1 y& ^( E, P$ ^1 e

  5. : k/ y# g8 J, D3 W- j3 K
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))5 o8 c0 O- S6 F0 h* B# h+ L" V3 u& B$ o
  7. {
    7 Q2 E- y# r) U5 e& ~% l* {# m$ p
  8.   PRINT_INFO("-----Restart.....5-------\n");: R+ Q5 @$ o7 v
  9.   HAL_Delay(1000);0 }+ R9 h0 Y. b% [- l8 g
  10.   PRINT_INFO("-----Restart.....4-------\n");
    % r9 N3 B- t$ P6 V  d
  11.   HAL_Delay(1000);' Z3 c/ _5 I7 r2 ^! r+ S
  12.   PRINT_INFO("-----Restart.....3-------\n");
    + d' t+ |4 z: ]5 P, z
  13.   HAL_Delay(1000);: }3 _5 p) C. I$ K( e
  14.   PRINT_INFO("-----Restart.....2-------\n");
    6 {6 D5 }. `- f# K1 L% U4 L
  15.   HAL_Delay(1000);, S( b2 p% F4 a. t0 ~5 f, m
  16.   PRINT_INFO("-----Restart.....1-------\n");
    1 o$ b6 D5 l% p3 b
  17.   HAL_Delay(1000);
    9 L3 V, a- O( E5 U4 l
  18.   
    5 x, B% [# m2 q. [3 u& i
  19.   PRINT_INFO("the device need update\n");8 G! e- Y6 e2 H5 y- x
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 1 Z, d! Q: h! j5 t
  21.   PRINT_INFO("firmware write OK\n");) I2 C( W5 C; C7 @( C* C
  22.   PRINT_INFO("please double click the button the execute the application\n");7 s) V( Q& ]  J9 Q% E# E" v# H
  23. 7 R% a0 D& n/ b1 R# [3 i) f' j
  24.   HAL_FLASH_Unlock();         //解锁
    7 v* M% {, k) x0 c# k  r
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区: W) Z" u% T( S; n/ D0 h& y; A
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     $ k! j4 ]4 m" K+ K$ z4 b4 i1 M1 u% Y
  27.   HAL_FLASH_Lock();          //上锁
    + X: G+ U! a7 o' v
  28.   
    , o; b! C: H9 g7 M
  29.   PRINT_INFO ( "开始执行 APP\n" ); % r/ Q) s% L& Y+ ]; F+ B9 G
  30.   //执行FLASH APP代码
    8 F: ?" n6 h6 k: Y
  31.   IAP_ExecuteApp(APP_START_ADDR);' n" ~5 U& h/ g" ]8 l4 U& Z: g
  32. }
复制代码
% r/ K3 H# h% l# F9 l

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

  1. PRINT_INFO("-----IAP Menu--------------\n");, G/ g, V5 h, w5 R
  2. PRINT_INFO("-----Download APP BIN------\n");
    1 M! I( g) x: k( d# r) D
  3. PRINT_INFO("-----Restart To RUN APP----\n"); 5 L) c# }! m- k5 s; b/ Z1 C6 U2 K* H
  4. PRINT_INFO("\n\n\n");3 @% ^% T# p/ V$ O$ y: v; V) r1 @
  5. 5 a# H0 x! e3 W; v9 F& g8 A2 y
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
    " A3 O1 B3 D! @- F3 W" t/ o# e
  7. {- H7 x# A* F( Y. ?; V7 x
  8.   PRINT_INFO("-----Restart.....5-------\n");
    9 G$ |# _  J+ k+ E2 u, s9 K  L
  9.   HAL_Delay(1000);
    ( N) n$ M8 Y! {" a. ]( T+ D8 X
  10.   PRINT_INFO("-----Restart.....4-------\n");( ^% ^/ D' L/ W0 n6 @" j3 K" C0 a
  11.   HAL_Delay(1000);
    , g- s6 @& P( \. W/ k% l
  12.   PRINT_INFO("-----Restart.....3-------\n");: o' c7 A$ i8 |6 Z8 W5 Y5 O, T! X: A, v
  13.   HAL_Delay(1000);  k5 ]7 S' M4 D" l# k5 G; |
  14.   PRINT_INFO("-----Restart.....2-------\n");' ]1 G0 n/ l6 }- W( ?
  15.   HAL_Delay(1000);, {' R' p- Q+ ?
  16.   PRINT_INFO("-----Restart.....1-------\n");3 U- k  C3 ^5 `
  17.   HAL_Delay(1000);1 v1 m3 A. p* B4 ]# O
  18.   
    8 u) I% O. u- }
  19.   PRINT_INFO("the device need update\n");' O0 r5 r% `4 X
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); " S9 e3 e! a4 g0 J% o. a
  21.   PRINT_INFO("firmware write OK\n");! p: Z3 b5 A4 P: \
  22.   PRINT_INFO("please double click the button the execute the application\n");
    $ r3 C# y  _/ v7 Z! \

  23. % R0 d7 X3 B8 C' p: N* O
  24.   HAL_FLASH_Unlock();         //解锁, G% q# s  f9 e; S) E
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    : z9 q( Y% N* \' [/ ^* H4 C. u
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志       R4 J- |* A5 t, L( `
  27.   HAL_FLASH_Lock();          //上锁- a+ g2 M& C( D" S1 r) v8 f
  28.   
    ) z/ i5 ^% x* \0 S3 m& S
  29.   PRINT_INFO ( "开始执行 APP\n" ); 6 `  y6 q+ F, X2 t" v
  30.   //执行FLASH APP代码" _- L1 B; c2 u9 U9 H
  31.   IAP_ExecuteApp(APP_START_ADDR);8 F+ ^8 `3 G  {; ~
  32. }
    8 D8 X3 N+ _4 G0 ?9 a5 g! j
  33. else//不需要更新,执行APP* ^3 j; G9 _+ W9 i
  34. {
      M$ T( j" E4 @; r
  35.   IAP_ExecuteApp(APP_START_ADDR);5 v# Q& r4 }( `1 i
  36. }
复制代码
! g% E6 N5 m' e8 U* i2 z# f+ D

bin文件生成

0 x' P9 c; q8 E; Y  P1 `

微信图片_20230402174611.png


' G/ J6 i9 i' n2 N( w

输入:

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


# {( R- @; c7 c! V7 m$ y, |

参数意义:

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

  • --bin -o  IAP.bin :


    " A. L* `5 a: m; V

输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:+ c2 v& d( S- f5 |' F

axf文件目录及文件

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

" T: U' i; G! D7 f. N, v0 Z, u* u
微信图片_20230402174607.png
% Y5 b+ [! a) ?: k; }& Z
* N; p2 S, N1 Y0 j* f5 Y 微信图片_20230402174604.png
$ k5 L" K0 L3 M3 i) a3 g  B2 F7 i% E/ i) Y, S- Z+ u" f
  • APP代码设计' D* I+ b/ ]' q' }

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

( y7 R  [! u9 q

  • boot区代码执行效果
    5 \( N( o/ Q% B
1 B3 q/ ^5 x- c! y  c
微信图片_20230402174601.png
; X1 t8 e7 u% c4 ^' D6 C# {; h" t* e- q) A
  • APP1代码执行效果9 S- r3 @4 s& p5 ?

8 e' Z2 D8 O/ y# h

8 V0 k7 m; l% e% a 微信图片_20230402174557.png
/ S2 G9 V7 M; C) o9 i  f5 d- k+ l. K
& f; e# q8 w2 I0 E: X7 |

, L, l+ |1 k) @
  • 接下来再进行一次更新
    . O, h  X' D' Q1 v1 C

% ^, D- H. w/ ]' X2 X" a  p

3 S" v5 C+ W5 m. Z3 m 微信图片_20230402174553.png
6 J1 ]9 ~  U* A* s5 @! }6 d8 J+ s3 p+ U0 n. u0 h- ?
5 V4 F3 E- _. w' m

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


& V' I) y* v. ~7 z! J 微信图片_20230402174550.png
+ V7 p* m: q9 P( v# V1 c# ^3 v3 L7 N% P8 P0 N: j: R2 Y

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

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

" {% z5 n: I+ Y
转载自:Embedded小飞哥
7 K4 K1 F6 y  b如有侵权请联系删除
) P1 n% X2 F3 q0 H, Y4 m# F( |8 f  J8 Y  ]9 w) b( m1 L+ F
赞 收藏 评论0 发布时间:2023-4-2 17:48

举报

0个回答

所属标签

相似技术帖

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