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

IAP固件升级经验分享

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

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


4 `& R: t# J% k. z) p9 c% Z

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


5 S5 C5 s! t$ I# N/ q

STM32的代码启动过程

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


9 p; y( ^7 D& n7 C4 j, V0 Z

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

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

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

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


    8 j4 J) p3 Z& i! j
微信图片_20230402174619.png 5 A/ @* D# @1 @; N' W

  K/ g+ L8 u9 j3 a: G+ p启动文件源代码分析
  1. ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
    * Q! B% Q4 C( [& e: p( a4 I2 O9 w
  2. ;* File Name          : startup_stm32f10x_hd.s
    ( ]% l/ j9 l' {4 l& \* B) y6 s, ?1 J
  3. ;* Author             : MCD Application Team
    8 n) s2 N4 W( `
  4. ;* Version            : V3.5.0
    ' j5 I+ h/ R+ V! m
  5. ;* Date               : 11-March-2011' t) D( j1 p" a1 g1 r( `% `4 j, z
  6. ;* Description        : STM32F10x High Density Devices vector table for MDK-ARM
    + Q& c+ e* f8 F' P
  7. ;*                      toolchain.
    $ K& {, ?* l% x* |; i
  8. ;*                      This module performs:( c; w$ T' M) e) X! C
  9. ;*                      (上电复位后会做下面的几件事情)) N3 H; A' |- d- c- v# H; T1 k
  10. ;*                      - Set the initial SP(设置堆栈,就是设置MSP的值)
    : i4 O8 N" l5 e$ G. V" Y$ z/ o$ j
  11. ;*                      - Set the initial PC == Reset_Handler(设置PC的值)
    ) v% K( W/ \" A' e  E
  12. ;*                      - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
    - j. i/ U4 H5 d5 J2 E9 V5 \
  13. ;*                      - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
    0 [3 Z* l9 Y) I& |# r8 G
  14. ;*                        SRAM mounted on STM3210E-EVAL board to be used as data ) `6 C5 y$ |* m) Z- J  i9 J  ]
  15. ;*                        memory (optional, to be enabled by user)
    ; h# f- u# r6 [3 W
  16. ;*                      - Branches to __main in the C library (which eventually      (调用C库的__main函数,然后调用main函数执行用户的)* d3 F- ?! P8 J/ Y! J5 K, {
  17. ;*                        calls main()).
    2 N4 Q6 l# s% \, o+ i1 V- w) Y# Q
  18. ;*                      After Reset the CortexM3 processor is in Thread mode,
    , ]0 n% H5 k2 `! Z3 D
  19. ;*                      priority is Privileged, and the Stack is set to Main.
      h0 F4 c/ F9 e! l% z( s
  20. ;* <<< Use Configuration Wizard in Context Menu >>>   4 ~! l3 c8 |. {) G% a
  21. ;*******************************************************************************+ G& g0 B6 D/ v6 Q6 J9 U) t5 }

  22. 1 j! _4 i. l6 L0 P
  23. * I! S, Y. {' L: _, r2 ~  \

  24. 8 M) Q6 O; `2 H0 d/ R
  25. ; ------------------分配栈空间----------------
    , |2 w( m; n' L3 \$ R/ M
  26. Stack_Size      EQU     0x00000400      ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小& T2 k! ]# p7 v8 r, e) E( |
  27.                 AREA    STACK, NOINIT, READWRITE, ALIGN=3  ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
    ( G4 f0 Q2 b) W  Q
  28. Stack_Mem       SPACE   Stack_Size   ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
    2 g9 E3 d7 C1 [- i; A3 Q; o& r! C1 n
  29. __initial_sp      ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到0 M; o# s3 |: k* p8 `; y0 B% s) G+ {% W
  30. + m6 L& O% K3 c+ B5 _8 @8 d
  31.                                            ) |1 H$ r: `+ G* P- S& p
  32. ; <h> Heap Configuration
    # `9 }: {( Z/ }% L$ W9 u. S
  33. ;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>7 e. H) u/ {- N& O# W
  34. ; </h>
    , T& Q2 e5 z" `! s0 u' w! x7 `& u
  35. ; ------------------分配堆空间----------------
    ! n4 R: n9 S5 U
  36. ;和分配栈空间一样不过大小只是512字节
    ' h, @' s) ]* `
  37. Heap_Size       EQU     0x00000200
    ( b9 J1 l4 O3 h- K5 N
  38.                 AREA    HEAP, NOINIT, READWRITE, ALIGN=3
    + r7 h$ X2 O' K* ]" i

  39. # x0 ?, R' a! s
  40. __heap_base        ;__heap_base堆的起始地址7 l& T/ e1 [: I! g8 g
  41. Heap_Mem        SPACE   Heap_Size      ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的( R6 T9 U* s2 q0 ~
  42. __heap_limit       ;__heap_base堆的结束地址+ ^5 y9 v- |' q6 D

  43. - M1 n* F3 H; I3 O) E
  44.                 PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐; w/ G8 A' J) p" |8 u, ~
  45.                 THUMB;THUMB作用是后面的指令使用Thumb指令集% b+ w8 [. N' ]: p1 J/ O9 T& I
  46. % D3 ^) ]& o: s5 I/ |$ q' g+ V
  47. ; ------------------设置中断向量表----------------1 f/ C" n( H) d; l8 D
  48. ; Vector Table Mapped to Address 0 at Reset
    ' f! ^1 y3 z4 C& @
  49.                 AREA    RESET, DATA, READONLY      ;定义一个段,段名是RESET的只读数据段
    / D* j. G) E- Y2 |
  50.                 ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性% s8 B1 E# f$ C  J1 O* K+ j  z
  51.                 EXPORT  __Vectors          ;声明一个__Vectors标号允许其他文件引用         
    ! [' J7 S7 s; F/ d4 O2 U. t
  52.                 EXPORT  __Vectors_End      ;声明一个__Vectors_End标号允许其他文件引用
    : D1 r" e3 h5 |" Q
  53.                 EXPORT  __Vectors_Size     ;声明一个__Vectors_Size标号允许其他文件引用
    1 Z" c3 d0 ?+ z. O# u7 ]
  54. + Z2 }; ~- ~* W4 O+ Q$ J7 p" w+ m6 p0 S: K
  55. - u" c7 ?% P( @/ ^
  56.                
    & r9 M! Q$ J/ L5 Q# f
  57. ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化
    $ }4 w. ~% Z3 b) l

  58. 2 g* o; H' q' M$ [+ @
  59. ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
    4 Q! `3 k9 V7 O7 i0 l3 I. B' @. F
  60. __Vectors       DCD     __initial_sp               ;* Top of Stack     定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,: z3 {1 P2 i! \, m3 G; p! N
  61.                                                    ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) 4 P% q: j. O$ B/ t# H/ p
  62.                                                    ;* __initial_sp的值是链接后,由链接器生成: ^: N  }) V) \2 h% ^  S

  63. % U; M2 _: }0 p$ Y7 M; g& C6 D
  64.                 DCD     Reset_Handler              ;* Reset Handler    定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,! }$ @$ |8 @2 j- G$ k
  65.                                                    ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)/ N7 e# V* x9 C3 K6 J# @/ T
  66.                                                    ;* Reset_Handler是一个函数,在下面定义
    4 H3 t# n; h( G3 f
  67.                 ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》  y- R  d, Q7 D! Y( V8 s9 ?
  68.                 DCD     NMI_Handler                ; NMI Handler      % c4 O$ [7 G. o7 ?2 G. R/ |
  69.                 DCD     HardFault_Handler          ; Hard Fault Handler
    5 L1 V4 @( V4 u' _- ?8 `
  70.                 DCD     MemManage_Handler          ; MPU Fault Handler& m: {/ ~- m: R4 i" v
  71.                 DCD     BusFault_Handler           ; Bus Fault Handler1 h% I3 D: T# ^- e
  72.                 DCD     UsageFault_Handler         ; Usage Fault Handler- }0 S% ]6 v; I! E
  73. 1 j4 a: q4 u6 L: d$ t

  74. / {# k/ g# R5 S, f6 m" }0 x1 A
  75.                 .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件
    $ W. K7 k& b4 r: p& N5 t- T

  76. / S) m, {7 Z5 Y
  77.                 DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1& C0 n; x8 v! X# g' H
  78.                 DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2; ~( ]8 b0 U# j2 q
  79.                 DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3& ~6 ~7 i3 b9 m/ ]* `& C5 j
  80.                 DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5/ n0 R* x  {3 K
  81. __Vectors_End                                    ;__Vectors_End向量表的结束地址) J. g: y$ J. W

  82. 7 I; w" f- P! _9 _3 Y  a7 U; x/ L
  83. __Vectors_Size  EQU  __Vectors_End - __Vectors   ;定义__Vectors_Size标号,值是向量表的大小3 M# j) I8 P) |+ c. Z* g

  84. , Q5 L" \1 x! }& L3 z6 x/ d
  85.                 AREA    |.text|, CODE, READONLY  ;定义一个代码段,段名是|.text|,属性是只读) u3 q8 I4 c* B2 _) n
  86. 7 y; u5 L6 C5 Q+ Q1 |; b
  87. ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)               
    # C! A+ f! S  t1 v; b) S
  88. ; Reset handler% \4 _- S) Z$ T5 i6 B
  89. Reset_Handler   PROC                                      ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
    : D1 k( N( W$ F) I) z
  90. . Z" `' g# j, @. V
  91.                 EXPORT  Reset_Handler             [WEAK]  ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,3 h; ?3 |# P$ A; N
  92.                                                           ;*编译时使用另一个地方的函数,否则使用这个函数6 r* u8 K/ q6 V+ W
  93.                                                 4 N' y; f; {. `( G, a' Q# U
  94.                                                           ;*IMPORT   表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似3 G: \+ `+ i2 B* ]( I
  95.                 IMPORT  __main                            ;*__main 和 SystemInit 函数都是外部文件的标号
    & |. x1 W% p- [' R: L
  96.                 IMPORT  SystemInit                        ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
    / I! w; k0 ~. h4 K! Y6 s$ w
  97.                 LDR     R0, =SystemInit/ G* \6 q8 X$ Z2 a) U3 G
  98.                 BLX     R0              
    % K6 q  S7 d/ V9 L
  99.                 LDR     R0, =__main                       ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
    / C8 D5 ~+ V0 q- @) u
  100.                 BX      R0
    + N; A# j$ {# ^2 F" c; Q
  101.                 ENDP& j" ?5 B$ f  ?+ ?: O% Y# |
  102.                
    & U4 A) U9 |$ w& s, A! v% x# k& H
  103. ; Dummy Exception Handlers (infinite loops which can be modified), h, s/ {/ B6 x* L
  104. ;下面定义的都是异常服务函中断服务函数; B, x+ N1 u7 l/ v6 p' _
  105. NMI_Handler     PROC
    / l4 [) V1 P6 n4 a8 C
  106.                 EXPORT  NMI_Handler                [WEAK]. G. g- n1 ?% x7 [/ u! M) h* o
  107.                 B       ., \2 g5 x5 J6 d
  108.                 ENDP
    2 J# i  x$ J) c
  109. .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件9 c" u8 p4 Z2 E. Y$ C% ^- w
  110. SysTick_Handler PROC
    ( ~. X6 [. z, A9 z% U8 K
  111.                 EXPORT  SysTick_Handler            [WEAK]) Q+ f3 a" A! o5 X5 g" w9 e
  112.                 B       .
    ( f" g6 y# G& i
  113.                 ENDP! S# }" P8 D+ _# k9 O6 }9 m2 K
  114. % a! M' O" A- l6 a" E3 u: f
  115. Default_Handler PROC
    , U" [: V! g# N+ J
  116. 9 }* [; t. m1 z; W) i6 D' s
  117.                 EXPORT  WWDG_IRQHandler            [WEAK]3 c) {/ ^+ j" d0 o, d5 y4 Q
  118.                 EXPORT  PVD_IRQHandler             [WEAK]
    $ t0 i) c' u7 _" t6 R( d; _  G; E' m
  119.                 .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
    2 D! h9 L* M! h$ @; [/ R7 Z
  120.                 EXPORT  DMA2_Channel2_IRQHandler   [WEAK]) A6 D* v; C4 d$ T/ b! |; ~" C
  121.                 EXPORT  DMA2_Channel3_IRQHandler   [WEAK]8 H- i( t/ I0 D$ e- r$ P
  122.                 EXPORT  DMA2_Channel4_5_IRQHandler [WEAK]0 d" q& s2 F" g# b
  123. 4 {9 z3 M' j1 I" Z. V3 ]
  124. WWDG_IRQHandler
    & d8 f5 |# L/ J; e- F" ~
  125. PVD_IRQHandler! J: J( A! U$ W6 F
  126. TAMPER_IRQHandler
    : c- s8 E5 U$ e6 T& i+ k# }1 |
  127. .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
    # `2 {5 Z; Y+ N8 W
  128. DMA2_Channel1_IRQHandler/ v3 n! D5 I: m) G. Y
  129. DMA2_Channel2_IRQHandler+ z" O0 C8 }9 e: Z" o, h' C# M' W8 p
  130. DMA2_Channel3_IRQHandler% j9 p/ v0 ~* k+ y0 Y
  131. DMA2_Channel4_5_IRQHandler2 w3 O8 Y1 o" \! o) u! ?7 ~; _
  132.                 B       .
    * k) L2 `  M* s) C. L/ D
  133. 7 g6 e7 u% B+ f5 O! I; y% ]
  134.                 ENDP
    . S9 [" t- G# G

  135. # L: ^3 P$ M# g) Y
  136.                 ALIGN    ;四字节对齐
      G0 j0 P' l; q( W$ @
  137. 8 h1 j2 G7 y1 e. K. }8 o" a
  138. ;*******************************************************************************0 s, @$ D& }' s+ h8 u, G
  139. ; User Stack and Heap initialization
    8 ]- |3 n! u  p3 B% o2 s
  140. ;*******************************************************************************
    + g9 n. x: A6 Z  D
  141. ;下面函数是初始化堆栈的代码* I3 ~# d' l) g  f
  142.                  IF      :DEF:__MICROLIB     4 G9 C# u# K' N- n) h, G
  143.                  ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义! g" `$ d9 u' M+ X( t9 l9 o
  144.                  ;这种方式初始化堆栈是由 __main 初始化的
    $ A7 P1 E0 |3 q
  145.                  EXPORT  __initial_sp   ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)# [4 S: s- Y4 a! `/ V; ]
  146.                  EXPORT  __heap_base    ;堆的起始地址
    1 I) m/ L! `1 k9 i9 H  Y, l
  147.                  EXPORT  __heap_limit   ;堆的结束地址3 w- L' z" I( S1 m3 H/ h
  148.                
      }- d6 f" L& Z2 ^" x2 R
  149.                  ELSE5 f) _5 O1 Q1 @9 D0 ?) u* l
  150.                  ;由用户初始化堆
    0 N+ P& \/ R# c9 m$ X0 J
  151.                  ;否则编译下面的
    " [$ X  z+ e/ c' z7 j
  152.                  IMPORT  __use_two_region_memory      ;__use_two_region_memory 由用户实现
    $ u; b  \" M0 A: \9 u, a
  153.                  EXPORT  __user_initial_stackheap# A6 n4 H. i! l/ d5 f7 G
  154.                  
    5 m  g6 V( r6 j% z/ Q0 q9 y
  155. __user_initial_stackheap. O- v0 X- n7 ^4 b" c+ ~
  156.                  ' z& q! a; s# B" q
  157.                  LDR     R0, =  Heap_Mem              ;堆的起始地址) D: \/ l) D1 u. m+ o  U4 X
  158.                  LDR     R1, =(Stack_Mem + Stack_Size);栈顶地址' X  m: [( W. T9 k
  159.                  LDR     R2, = (Heap_Mem +  Heap_Size);堆的结束地址7 t9 {  a: h; `. a4 H4 X) e1 v
  160.                  LDR     R3, = Stack_Mem              ;栈的结束地址
    6 c! I4 b4 n) u! D) V1 e
  161.                  BX      LR6 m7 ~" Q. J0 a  ?! g1 I, N

  162. 7 S8 `; o0 P7 p5 D3 K- \) G7 y
  163.                  ALIGN- l. W# j' T: n6 I& K
  164. 5 ^# e0 T7 S6 @* S: A0 N; {
  165.                  ENDIF0 j3 O6 f# x5 g  T% ^
  166. 7 e' \( Z0 c; z+ H9 W' ~8 m" L' K
  167.                  END& d" a2 X% |( P/ b, q( N$ |

  168. 2 l/ ]' `2 K8 m! k
  169. ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码

( y$ |* X) C; ^9 L. T0 U, L
9 O3 M$ L& O$ M. k3 Q: R* N

STM32的启动步骤如下:

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

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

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

    4 f8 y- R, o  G/ c' R
IAP设计思路

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

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

- n6 n5 G  ^8 N' T* u

升级流程图! i* Q1 `& A/ K8 s

5 M! @- v& i8 z9 M, R7 W" F( c. g

' B6 B) l6 S6 g* B: j) m! l2 R 微信图片_20230402175006.png ; _0 [5 L5 n; S) |3 k5 Q. m

( V" S9 |$ j1 t7 |# b7 R9 Y
- V  R/ ^9 @/ E9 Q/ M8 B
Flash分区

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


& J$ Y9 N5 ^5 Y* B& j

微信图片_20230402174614.png


, v% L- I, F# U  O

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

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

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

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

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

    ) z- [) ~5 L5 p+ J: V+ e* ?
代码实现

硬件:

  • fallingstar-board(已开源,打板验证)
    - t+ u) ~# L+ j" P+ f. }

软件:

  • 内部flash读写

  • 串口DMA+空闲中断

    / r5 ^" E/ [( A: U: M+ f' W! p7 p' k
内部flash读写操作

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

读flash操作:

  1. /************************************************************
      b# A, f" p5 X( q) I# c
  2.   * @brief   读取2字节数据! g. J- Q( ?/ O( u
  3. * @param[in]   uint32_t faddr
    6 r* Y( `. L) ?/ v& h
  4.   * @return  NULL+ o; C/ T$ L0 o! o4 t
  5.   * @github  " S/ \/ e$ O* y5 z2 e
  6.   * @date    2021-xx-xx. c/ ^9 Y: H. y5 u( D
  7.   * @version v1.0& @/ ^9 C8 L1 n) l, K) f; ^
  8.   * @note    NULL
    + i4 R! x/ F' m0 i5 d
  9.   ***********************************************************/
    9 S7 t" \; Z% |9 Q) r) E# u
  10. uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)) P, S$ X6 w3 Y, n
  11. {0 U' J5 T/ u, F6 m: @1 D$ K
  12. return *(__IO uint16_t*)raddr;
    & |5 M' ], n, y$ N9 q. q
  13. }
    $ }* r. v6 \4 H; T' \6 r* w% P
  14. /************************************************************) Z' I2 G* O. S4 w5 P8 k5 u% E5 L
  15.   * @brief      读取n(uint16_t)字节数据
    $ {: l  q6 H( H/ F# e; g- z+ ^; y
  16. * @param[in]   uint32_t ReadAddr2 T( @4 n5 N( t0 v
  17. * @param[out]  uint16_t *pBuffer) _  \& J: e% i/ n
  18. * @param[in]   uint16_t len0 a' C$ C2 V1 `( c* k+ a
  19.   * @return  NULL# }: B4 d+ e0 T
  20.   * @github  
    5 {& S/ Y3 W& e+ M$ M# W
  21.   * @date    2021-xx-xx
    ; q* \( ~4 H% i$ H3 C+ z0 g- P, y
  22.   * @version v1.0
    2 h: p+ y2 X# p# W
  23.   * @note    NULL; @( k7 q2 E5 |- `9 d
  24.   ***********************************************************/' s& X; C4 W1 }+ C7 V! a# i
  25. void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )   
    9 l" p* {2 b# d0 y# A3 C
  26. {
    ! r! O7 Y/ X1 |* i: j
  27. uint16_t i;8 P' g6 D. F% e, Y/ F; ^
  28. ) M: s3 j; l8 S
  29. for(i=0;i<len;i++)
    ( V$ u7 Y$ I0 e& V
  30. {3 }% h0 f! H2 z  K$ y
  31.   pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr);   //读取2个字节.
    & W* e4 P# ~5 c5 z) Y! D; R
  32.   ReadAddr+=2;                   //偏移2个字节.
    7 o' N& o: ]1 E0 e0 h3 i( z, ?, o
  33. }
    $ _& K7 J: l( u6 Q8 f) U1 W
  34. }
复制代码
+ H) @1 p  |' t' Q4 I

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

  1. /************************************************************
    ! H0 h9 P( ~; G  c, Q
  2.   * @brief   写入n(uint16_t)字节数据
    ' z& D! M( f3 L2 i
  3. * @param[in]   uint32_t ReadAddr  n( z9 {: ~) y- W- M3 v1 j6 t; ~
  4. * @param[out]   uint16_t *pBuffer
    ( y6 a( u' Z; @4 s
  5. * @param[in]   uint16_t len' S& w2 v* t6 n+ @- c& E
  6.   * @return  NULL; h/ q  U% k! [8 k) F; ]" H& I  [
  7.   * @github  - f6 F- e+ u/ p
  8.   * @date    2021-xx-xx
      A9 x% b4 {0 F, m
  9.   * @version v1.0
    , ^# W: V& p3 i& i" p
  10.   * @note    NULL
    ( r. h0 Y# j, y8 ~: O# q
  11.   ***********************************************************/
    ) b; ?) f4 `+ P( h
  12. void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )   
    & y% Y' m( S4 E- |: m& ?
  13. {        , a: ^( }6 E, }% p9 |$ i
  14. uint16_t i;
    $ M2 t8 H( C8 R1 J
  15. 6 G* z+ s3 Q, `& l, b5 Y' R
  16. for(i=0;i<len;i++)
    1 ]5 b0 j: S$ |1 z# f3 ^& e
  17. {+ F' ^( E5 B! k3 j
  18.   HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
    0 y6 V" j# ^* G4 o9 l* @2 ?' S5 r# q
  19.    WriteAddr+=2;                                    //地址增加2.
    , H. v+ z0 x# ~0 q3 W# x: p* p1 J
  20. }  # T$ ~" v* B" @2 B6 Q/ U
  21. } 0 r+ D* h2 U4 C3 c$ i$ C6 g3 X0 h+ l
  22. /************************************************************9 R- i6 k) c% ~0 v
  23.   * @brief     写入n(uint16_t)字节数据
    & M4 E0 t- b9 b0 E0 \
  24. * @param[in]  uint32_t WriteAddr
    . q$ v( u  S$ Q
  25. * @param[in]  uint16_t *pBuffer
    / P' L; m3 ?. W- h7 M" H
  26. * @param[in]  uint16_t len) _3 |/ t) K" s& T4 N, e5 Q
  27.   * @return  NULL2 R, l% z) X$ b  t3 r$ g- s
  28.   * @github  1 @) h( d7 B+ `" w' I
  29.   * @date    2021-xx-xx
    $ z1 ^6 g6 q7 {% C* N: P1 k
  30.   * @version v1.0" L5 `; [- b8 [
  31.   * @note    NULL
    ( E0 v! J) h9 ^" D4 Q; i6 z
  32.   ***********************************************************/8 X1 w( L/ s; F3 x3 G  w
  33. void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
    % \1 `/ x5 {/ `! c  w5 z" N9 q
  34. {
    3 W* q9 ]5 |# q/ c" Q& p& `; [
  35.   uint32_t SECTORError = 0;6 _$ V  w& K3 g3 O6 e
  36. uint16_t sector_off;    //扇区内偏移地址(16位字计算)/ ?  e/ f. z8 _* n* i
  37. uint16_t sector_remain; //扇区内剩余地址(16位字计算)   
    * P8 w! U7 @, b2 u( `
  38.   uint16_t i;    / A9 X3 A3 t8 l9 W7 N( w
  39. uint32_t secor_pos;    //扇区地址
    3 h- C' }( w! \, C5 s
  40. uint32_t offaddr;   //去掉0X08000000后的地址
    - g5 O5 D9 l- E  @4 W' c- Y
  41. # [! L$ a& k4 ^: j1 m4 ^
  42. if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址# @0 b0 z3 E  Z
  43. - v- p9 ]  W8 N
  44. HAL_FLASH_Unlock();         //解锁$ M( t) d- _" o. ^

  45. & A- y3 V2 r  b5 C
  46. offaddr=WriteAddr-FLASH_BASE;    //实际偏移地址.$ [, {, L3 n4 p( I
  47. secor_pos=offaddr/STM_SECTOR_SIZE;   //扇区地址  0~127 for STM32F103RBT6# \( W  g# H( }! m3 K1 }9 ~
  48. sector_off=(offaddr%STM_SECTOR_SIZE)/2;  //在扇区内的偏移(2个字节为基本单位.)
    2 G3 Q- R/ p" c
  49. sector_remain=STM_SECTOR_SIZE/2-sector_off;  //扇区剩余空间大小   * B* i. J" v/ I
  50. if(len<=sector_remain)sector_remain=len;//不大于该扇区范围" W! _: M: G1 \# D# _6 L& L# a; u

  51. 5 O& {0 H- n7 N1 T8 r
  52. while(1) % Z: i& v/ q+ b" ~/ I1 {/ I- l. h4 x4 u
  53. {
    - q. ~% Y" c* L9 R. M0 K6 H
  54.   BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容% v! w  K+ p) _2 b( T- }( W2 \
  55.     1 W* f7 X5 }8 J0 q9 k0 L
  56.   for(i=0;i<sector_remain;i++)//校验数据  ]6 L- C) u# S* P2 u: X  K6 Z5 ]! O( p
  57.   {
    , {: R7 }7 l+ E$ f- V. K; d. \
  58.    if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)( ~/ |) _) z" v8 @
  59.     break;//需要擦除     
    6 }0 m3 Q# o9 {' |
  60.   }
    + _8 l- J, T, S3 q5 P% r
  61.   if(i<sector_remain)//需要擦除  X0 h' \2 j1 P/ x- e# F" A
  62.   {
    ; |% a* v3 k6 G3 b2 r( t
  63.    //擦除这个扇区
    $ r/ n7 B# D& M% ~
  64.       /* Fill EraseInit structure*/& v' s. |; G5 y
  65.       EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;
    ) n% \0 L9 E" I5 K
  66.       EraseInitStruct.PageAddress   = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;" k& k/ l- R* [; p: B" a5 e% h8 H
  67.       EraseInitStruct.NbPages       = 1;8 U- Q9 ^" M& |1 T' P9 o
  68.       HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);/ v' X, |9 A% G2 M/ Q4 {' m
  69.    for(i=0;i<sector_remain;i++)//复制- S  s5 s3 p  i, U% F
  70.    {) ^3 |5 h! _$ k* \- i+ D, ]& v( w
  71.     STMFLASH_BUF[i+sector_off]=pBuffer[i];   
    2 X' s9 B( l9 V  Q7 H
  72.    }
    6 U& K3 U# E2 W8 |; O
  73.    BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
    ; V  L% m" S& V6 \
  74.   }  Z$ }; Y) f, q; }
  75.   else : r7 X( K! _. D
  76.    BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.        
    & z" u7 h' o6 i# ^9 a
  77.   if(len==sector_remain)
    2 R3 ~/ B! N8 |
  78.    break;//写入结束了
    9 A; ~. U4 D3 h/ U% X( g' K
  79.   else//写入未结束
    7 ^, _9 _2 m6 M. S9 f- `6 ?: ?
  80.   {; `2 n- o6 [/ o2 c, e7 l
  81.    secor_pos++;    //扇区地址增1
    % V* h2 j7 m9 N  J
  82.    sector_off=0;    //偏移位置为0   ' W: n+ t" W- j0 W
  83.     pBuffer+=sector_remain;   //指针偏移* {$ s2 _; S- g& `4 e6 G1 X
  84.    WriteAddr+=sector_remain; //写地址偏移   
    6 r! p  k2 B1 h2 }" O' L8 y& B; k
  85.     len-=sector_remain; //字节(16位)数递减
    9 \. r* e# A" G9 B
  86.    2 E* H" j; p4 C5 T8 U( K
  87.    if(len>(STM_SECTOR_SIZE/2))) {1 x, `7 z& g4 }
  88.     sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    ( U4 ^# s% j, d2 A9 D& ^: u
  89.    else
    2 ^" j- o* H: m0 j2 `; A' T
  90.     sector_remain=len;//下一个扇区可以写完了" |1 Z: q+ q0 c2 f# _' j& `
  91.   }  5 ?$ G& n3 D3 J* r
  92. };
    7 }$ L  Z7 W1 k: F' B; {" z0 |3 N, y
  93. HAL_FLASH_Lock();//上锁
    . j6 ?9 J2 |" K+ H6 H3 _- c
  94. }
复制代码
3 l: n3 a  T) L( g
串口DMA+空闲中断接收不定长数据

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

  1. #define Max_RecLen 1024*3
    1 W7 |$ O% S$ b  V$ W6 h  H" o( f
  2. & J  }* \0 ^3 u& L( g6 v5 C1 `
  3. typedef struct{9 C, |5 U% W' k+ o" q& _
  4. uint8_t RxBuffer[Max_RecLen];  //DMA接收缓冲区# E7 s, M$ x0 ?8 ^  v
  5. uint16_t RecDat_len;      //单包数据长度
    $ k8 P, E: Y; |0 q& {, O; _
  6. uint32_t Cur_WriteAddr;     //APP缓冲区地址% N- N1 {7 u, ~  z2 h2 B
  7. uint16_t BinLen;        //bin文件数据长度5 x) r4 P" P1 w: m
  8. uint8_t rec_endFlag;      //单包数据接收结束标志
    + ~4 C( O7 \# L8 v1 m9 c
  9. uint8_t Binrec_endFlag;         //bin文件接收结束标志 ( S& p% c/ ^" r( N  O2 }
  10. uint16_t DMA_TIMCNT;       //bin文件下发超时计数器
    $ d, g  \/ c; J1 K

  11. : d  J  o3 _% s5 ]
  12. }UserUartDMA_Typedef;
复制代码
8 X) D+ F$ Y1 m. a

然后在串口中断中:

  1. /**8 h8 ^4 ^. S! }& N. n0 _
  2.   * @brief This function handles USART1 global interrupt./ X9 C6 F) B: f' s
  3.   */
    ! q) Z/ R* }' I: S" ]' x
  4. void USART1_IRQHandler(void)) U" M  s* P: b, ]
  5. {
      I) n. Q- ?# y. O( U1 S
  6.   /* USER CODE BEGIN USART1_IRQn 0 */
    % e1 s% p5 i1 o& `
  7. uint32_t idle_flag_temp = 0;
    0 I" N! a8 j2 N) r. g+ ?" l7 p
  8. uint16_t len_temp = 0;& ~; g9 P/ h+ ^* e; J: n
  9.   /* USER CODE END USART1_IRQn 0 */
    / C( b/ L7 g' r) I5 Y+ e( B
  10.   HAL_UART_IRQHandler(&huart1);! H+ F( t: a' J2 G
  11.   /* USER CODE BEGIN USART1_IRQn 1 */
    " A) s9 X: {* n2 h2 K, E" S
  12. idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
    2 h$ B- ~- Q9 T* i& u1 S& y  f6 y

  13. 5 E3 O0 B, R% v4 g
  14. if(idle_flag_temp)& o  e! [4 W" [" O0 p
  15. {
    9 x" L* G0 o; Y. U6 D6 ]) K
  16.   __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
    0 T4 K% e# {, A- X, x/ B# E
  17.   HAL_UART_DMAStop(&huart1);4 a) I" x  r7 \1 [
  18.   8 `  Z* e1 Q' C
  19.   UserUartDma.DMA_TIMCNT = 0;
    - U& v0 T! H5 {
  20.   UserUartDma.Binrec_endFlag=0;8 c; E9 A" S: i) v+ {5 N
  21.   len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
    ' R& _$ d8 O% n* P( Q' |6 c
  22.   0 t4 {0 P+ u. }9 I4 B, q) O
  23.   UserUartDma.RecDat_len = Max_RecLen - len_temp;  # |4 p, M. `, {6 B) j; s  h
  24.   UserUartDma.BinLen+=UserUartDma.RecDat_len;
    " e3 R: e. {) U/ ~* S& W
  25.   UserUartDma.rec_endFlag = 1;  K* q# F. ?% n7 u: @8 N, Q4 F
  26.   
    4 b. M& |  B4 |
  27. }4 W8 J. V( D4 O% ^) Z% a  v
  28.   __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);& c3 i6 B3 d( {% a; Y
  29.   HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
    & u) X, P4 @/ t1 M( }
  30.   /* USER CODE END USART1_IRQn 1 */9 m7 M* Q4 A. Y  E9 N
  31. }
复制代码
* d% ~% Q2 Q" Z+ X$ h- j; N
IAP代码设计
  • bootloader代码设计
    ! W& o* F2 O/ H: M0 G3 z* D

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

  1. /******************************************************
    / x9 J+ \: H, U  J
  2. * Brief     : 擦除APP区9 f7 \% j( g- O. t# }, L
  3. * Parameter :
    5 ?0 b: o( b+ k9 N- g- F% ~
  4. *           *startaddr:APP起始地址
    ( j) C- G9 G6 @* f' T4 O& a/ {
  5. *      *pages  :要擦除的page = APPSIZE/PAGESIZE
    + k% o! k2 P. K6 g+ A/ E" I
  6. * Return    :None.) A+ v6 l6 m5 W( O+ W( O1 C
  7. *******************************************************/
    " p) Y) A' E: T8 d
  8. void APPReigion_Erase(uint32_t startaddr,uint16_t pages)
    - i" x8 u2 N) J4 q
  9. {
    / b1 M, T' R7 }
  10.   uint32_t SECTORError = 0;
    6 e( y1 s# ?0 V8 J

  11. 9 B2 w% Q0 {- {2 h
  12.   //擦除APP区8 i- W$ b" U/ }  D2 ~% I) P
  13.   /* Fill EraseInit structure*/. X* d* B! T! g' ^: u% w1 K) ~
  14.   EraseInitStruct.TypeErase     = FLASH_TYPEERASE_PAGES;7 _" t0 l/ @8 ~9 V& m& H
  15.   EraseInitStruct.PageAddress   = startaddr;
    4 ^, U/ J" F* K6 F  ?
  16.   EraseInitStruct.NbPages       = pages;- C$ _6 ~! Q: ?! O/ V$ W; D2 h
  17.   HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
    - n! D5 _; L9 A& ~3 z( p/ A
  18. }
复制代码
& z" r/ x4 H. a& l1 A) \

升级标志获取与擦除

  1. /******************************************************
    5 n& J- B' A( \/ y7 \4 g. ]/ r
  2. * Brief     : 写入升级标志
    3 ?! n; e. Y& ^1 _
  3. * Parameter : 7 ~3 G' o* Y% t  }5 F" ]" d
  4. *           *addr:标志存放地址
    8 d+ `+ }2 i* l! e
  5. *      *pdata:写入的数据6 j7 x8 ]! b  R! a# B" I2 ]
  6. * Return    :None.
    ! s# ^+ }9 p  V9 F
  7. *******************************************************/+ ]$ M9 K2 c5 m4 z8 u9 `8 B  ?
  8. void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)% }1 z& s5 W7 B$ k
  9. {( o5 {7 a$ @; m6 B4 U
  10.   BSP_FLASH_Write(addr,pdata,1);# f1 |. T5 X6 N( p$ S9 E7 L
  11. }
    , q* E- {: ?( _2 p& n1 w7 C
  12. /******************************************************) C, v0 t5 r4 Z9 n5 ^; \
  13. * Brief     : 获取升级标志# C6 [$ g3 i3 x
  14. * Parameter : ) }) n9 a4 }1 \" t
  15. *           *addr:标志存放地址
    0 h3 f# y5 R& E/ ?) v- C" f- J
  16. * Return    :None." `& X8 f+ @& |
  17. *******************************************************/
    + K" \- y3 b7 k
  18. uint16_t APP_UpdateFlag_Read(uint32_t addr)$ K* Q! k! a' R. F+ X
  19. {6 E, O- c) `2 k8 g; u. \' i$ @
  20.   uint16_t flag_temp;
    $ S& a  {* w; ^( K& d2 d) R
  21.   BSP_FLASH_Read(addr,&flag_temp,1);( N  @2 @5 V9 K* `$ d

  22. 9 U3 C2 M- m) I3 h7 H  O/ j5 w* T% b
  23.   return flag_temp;% H# i1 e2 G0 S# @5 n
  24. }
复制代码
' o0 X% P! x+ L" }* Z2 \

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

  1. /******************************************************
    ) c( t; L' a( F! J' w$ P3 K
  2. * Brief     : Bin文件写入app缓冲区
    + N( `% x2 F- [5 n- P6 Q
  3. * Parameter : 1 p% f8 ]. ?& p) ]5 P; v0 i9 r
  4. *           StartAddr: 起始地址
    * c* @9 H0 }4 c( i: y
  5. *           *pBin_DataBuf: 要传输的数据* H" K( m0 G4 l" {4 \$ J
  6. *           packBufLength:单包数据长度
    $ H6 `/ I" \0 l6 L# ~
  7. * Return    : None.5 r, j! N) b/ F$ @! q
  8. *******************************************************/6 K6 I+ n8 ?4 C" d' n' P) {
  9. void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)& i# N# [$ M) m" B1 ]6 y3 q
  10. {- o( D3 f3 v+ J8 ~4 X
  11. uint16_t pack_len, packlen_Ctr=0, dataTemp;
    3 v8 @( l* h. h9 c1 x
  12. uint8_t * pData = pBin_DataBuf;
    / F* s0 [- F1 n- j' f) ~% B, e% i6 e' R
  13.   / G: J% @5 C# H* z( l7 j/ T: A
  14. for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
    2 M% M: [+ z. m" O* Z2 [/ u' u
  15. {         
    " h6 |: ]; |. i/ D  x& _! ?
  16.   dataTemp =  ( uint16_t ) pData[1]<<8;( [5 G4 \! e# R9 ^
  17.   dataTemp += ( uint16_t ) pData[0];   
    : Z7 r- Q; @" K7 c% _" {
  18.   pData += 2;                                                      //偏移2个字节  O5 E4 E0 r' r' X
  19.   ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;     
    8 W4 h: x2 B# s6 E+ [/ J" _8 m
  20. }
    , O" C  H$ M  e# P+ L" e' j

  21.   r" w" Z, w/ p/ z& J& V
  22.    BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr ); * p+ s3 o6 F* t. G! ~. X0 E5 x0 J
  23.    UserUartDma.Cur_WriteAddr += (packlen_Ctr*2);                                           //偏移packlen_Ctr  16=2*8.所以要乘以2.( b2 Y& q" T; Q4 x# P
  24.    packlen_Ctr = 0;; B. h6 z; M# G( @& S7 h
  25. }
    * O1 j. P0 \% `# N! \
  26. /******************************************************6 f9 p% E8 p; `! o7 l5 f& V' h( G
  27. * Brief     : Bin文件从app缓冲区写入app区  n) J' _" {! y# |4 }
  28. * Parameter : # J5 Q: w/ C7 _! n
  29. *           SrcStartAddr: app缓冲区起始地址* o* s4 |/ C; O/ ^, X& [+ T) P
  30. *           DstStartAddr: APP区起始地址
    5 V( W8 o' g3 c& h, {% {
  31. *           BinLength:bin文件长度! E8 |/ x& A  [2 P) S% `6 X$ @+ l

  32. 2 I; u% J# f8 k4 z4 d9 D
  33. * Return    : None.
    - C( H0 k! R% }, V
  34. *******************************************************/$ ?6 t9 N) Z# k3 }0 \
  35. void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
    $ T- L. F; F* z. h- ~
  36. {
    / v6 _" j( y/ z
  37. uint16_t data_temp = 0;
    % Y" g1 c" H# @; D: u
  38. uint32_t count=0;2 d% t6 U# ^7 }. d
  39. 7 l8 E% G/ X; r% W9 p. |: W$ E9 v
  40. 8 N6 c- i- I% Z& f9 S
  41. HAL_FLASH_Unlock();         //解锁) @2 D. e( V' w" Z

  42. ( s. S; M1 q# y. o
  43. APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
    / M1 o) i; m# y7 n0 e4 G  X: w! _
  44. HAL_Delay(10);
    1 X8 H+ D5 Q# P4 P. M: ]9 b2 Q

  45. 8 R/ e$ A3 U" C5 C
  46. for(count=0;count<BinLength;count=count+2)
    . P( K' P) x/ y8 G' ?
  47. {) B! d% d* r; p. z4 Z' w! N! ^& N
  48.    BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
    2 E$ J. _" S6 l( V! O7 q! N5 f
  49.    BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);8 D$ f+ R3 A/ o) `8 R) m
  50.    SrcStartAddr+=2;
    / y, [1 O+ w2 h& R+ a% @
  51.    DstStartAddr+=2;
    : j) m6 Q6 P, v) L$ P9 _' s
  52. }
    5 u( O2 _0 B# Q. o1 ^2 s0 X9 ?
  53. //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);- \( c/ `6 R; O% E  x  @
  54. HAL_FLASH_Lock();//上锁$ {' _- `% P6 S3 `* w2 a' e! A
  55. }
复制代码

* G- |% r' X7 Z9 A, ^' Z

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

  1. /******************************************************! z- A9 z7 m5 M! q2 `# f
  2. * Brief     : 设置栈顶指针
    - p: }* e4 ~+ M. V) ?
  3. * Parameter : , ]3 g! y+ F6 K% a
  4. *           ulAddr:
    " h% X! l  l- H0 o  F2 r
  5. * Return    : None.- r6 m6 C1 ?0 E: r- c5 I
  6. *******************************************************/
    5 H) p  C+ Q) P% A: E

  7. # i% @8 g6 `# q2 G  M" I
  8. __asm void MSR_MSP ( uint32_t ulAddr )
    0 w; Y, J/ m8 }
  9. {( a- q: X; }1 {" Z1 T: i8 L: G) v
  10.     MSR MSP, r0                       //set Main Stack value( X, m# w- A' @6 d+ X
  11.     BX r14
    ! q8 ^% q; B$ a. b  o
  12. }5 `6 L1 _/ D$ h7 }

  13. * p& A' _, K% q, b
  14. /******************************************************
    * W3 K- X! b( z) m" j/ F
  15. * Brief     : IAP执行4 B- z/ Q( s! I0 q7 w5 W. \
  16. * Parameter :
    ; B2 c+ i) J: z( |1 T& R
  17. *           ulAddr_App: APP起始地址
    0 S. h9 v& O, i  r1 X
  18. * Return    : None.
    . d4 v: J3 _9 a7 f* ^. g0 R
  19. *******************************************************/
    7 ]* ~. b+ f/ h; X
  20. void IAP_ExecuteApp ( uint32_t ulAddr_App ), |1 n- o# E: H) H) ]6 M
  21. {
    / U/ ~9 Q" Q( P6 r( G) T0 O! }' U
  22. pIapFun_TypeDef pJump2App;
    ( S' t1 e( P1 Z) r
  23. & o& G- O6 f1 r% t% K% Q. o5 t0 s1 N
  24. if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )   //检查栈顶地址是否合法." Y( z) h: X) F8 t& {* q. U# D
  25. { 7 `  z  i6 B+ m, A8 \" @' F' s
  26.   pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)  & i- J$ |2 e7 n2 O# h9 \8 Q
  27.   MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    * [! ~  S% t' y- [( p6 \5 P: V
  28.   pJump2App ();                                             //跳转到APP.5 F0 `' n& g  }2 p) T
  29. }* b0 D, l( F/ w/ T: r, z/ }
  30. }  
复制代码

6 ?6 U. X/ B3 p

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

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

  1. static void Systick_Config(void)
    + |0 ~7 m# k7 Y
  2. {
    3 K+ e( b/ C: f
  3.   HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
    # }" @! o* t. O1 U# `
  4.   HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
    % k/ L; _. s: Z
  5.   HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
    # k  j  ]  v  s# T
  6. }5 w8 U" m/ j1 {
  7. - _, b- r7 B! Y; t1 i1 ]* [0 q
  8. void HAL_SYSTICK_Callback(void)" W. a6 r* N0 Z; [/ i3 P+ ~: u
  9. {
    3 p( l3 B9 `9 _1 z8 B
  10. UserUartDma.DMA_TIMCNT++;5 w5 u; X2 F9 f3 N/ ?9 @
  11. if(UserUartDma.DMA_TIMCNT>5000)$ l  X4 c' x! m# Y5 z
  12. {
    ' s7 V5 j7 l9 c: t5 T$ k# n
  13.   UserUartDma.Binrec_endFlag = 1;
    * E$ T. n) L! w1 }/ \1 `1 d
  14.   UserUartDma.DMA_TIMCNT = 0;
      ?  ^" f4 i. r. k
  15. }* x0 D4 f5 t' ~0 z  g4 \' y
  16.   TIMX_IRQHandler_user(); 9 }7 M) q1 s+ \8 [4 {7 f1 c
  17. }
复制代码
$ @+ v* P$ u& @$ U0 F' q5 T

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

  1. ' Y+ ^. Z1 }- A
  2. uint32_t Task_02()# R9 W2 S+ n5 k6 q" X5 e. V
  3. {4 u7 n$ H* M( r* W& T
  4. 0 p" Q( c1 B7 q7 b; j# _
  5. if(UserUartDma.rec_endFlag)
    0 |( g7 D2 D/ A+ z9 }
  6. {
    : ~% }9 c5 {0 U1 a
  7.   PRINT_INFO("update firmware\n");
    + U. Z! W0 q0 _, b, h! [
  8.   PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);  3 T7 O/ w2 l3 t. g! U# {9 r' @! s
  9.   
    % `. t; U+ |& ^+ c) ]
  10.   UserUartDma.rec_endFlag = 0;
    9 B6 U# [2 S# S' `7 K6 ?( d
  11.   IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);5 }' V- _8 \9 ~/ O  x+ A8 U
  12.   / e* l6 f3 O; r- K+ b' u/ }
  13.   memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
    3 v& o4 O7 s8 e, a" G5 Z
  14.   UserUartDma.RecDat_len = 0;' ]$ C8 K  v3 f  h
  15. }
    9 l( o4 V0 s, ?5 @* g- T
  16. if(UserUartDma.BinLen!=0)6 U  w( ?  S& Q1 W7 D. H
  17. {
    7 H; |, K) W; h
  18.   if(UserUartDma.Binrec_endFlag)  D% h* g; R7 w  _% \
  19.   {3 h! Q, E  T: }- s! ~5 v4 c& P
  20.     UserUartDma.Binrec_endFlag = 0;
    0 M& z+ \( |$ M8 x( D0 _
  21.       P1 k1 L* w3 b
  22.     APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen);   //写入升级标志
    4 ?! y# Z1 L2 b4 O/ ^3 O
  23.     APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG);   //写入升级标志( R7 I: K1 s. x. D* t% A
  24.     UserUartDma.BinLen = 0;  1 N3 B+ s2 C. W5 A, P* a* W
  25.     IAP_ExecuteApp(FLASH_BASE);   //跳转8000000,重新启动; {; Z# z4 M, G1 D2 S
  26.     HAL_Delay(2000);: m" F! R: j' G' U
  27.   }
    $ T* V0 G7 E( J# x& I

  28. 6 D6 |  u/ o3 {% C
  29. }
    0 j) x* x; |6 N' S1 N$ z
  30. }
复制代码

# x4 r0 \) i" L( o- T

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

  1. PRINT_INFO("-----IAP Menu--------------\n");: w- k7 X# v2 W( B+ s  m
  2. PRINT_INFO("-----Download APP BIN------\n");
    - G, q) y; V, l7 g8 L. f
  3. PRINT_INFO("-----Restart To RUN APP----\n"); ! h0 N0 X; T0 o: M. b( O: V
  4. PRINT_INFO("\n\n\n");
    * B- |( [$ Z* Y% b. T% V

  5. * `! H( i. |7 W: X8 F- w- }
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR)). d4 W/ a! u3 X5 r) D: d/ U+ i9 F
  7. {
    3 B8 }$ ~( ~) ^3 F7 t8 p8 Q
  8.   PRINT_INFO("-----Restart.....5-------\n");" O+ |3 Q" K$ s* `8 v8 s& z2 y7 {
  9.   HAL_Delay(1000);+ k! s7 f5 e3 p6 L3 v1 `! m3 l+ u+ e, E
  10.   PRINT_INFO("-----Restart.....4-------\n");; w% |% d1 c! p% D; }
  11.   HAL_Delay(1000);
    ; s( T& _- L4 Y: Z2 `/ O
  12.   PRINT_INFO("-----Restart.....3-------\n");: H- N5 c' @0 B& o6 i
  13.   HAL_Delay(1000);
    - n3 N( a( j/ J& a
  14.   PRINT_INFO("-----Restart.....2-------\n");' j7 J" @0 |+ H" E- L: z" \
  15.   HAL_Delay(1000);  o9 n+ O9 c; u; R
  16.   PRINT_INFO("-----Restart.....1-------\n");
    - j% l) V; l' u- T. t
  17.   HAL_Delay(1000);
    7 o7 @' E4 g: q3 x4 U. I
  18.   
      s2 n/ S& w5 v
  19.   PRINT_INFO("the device need update\n");9 o) v* Q3 p* G
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
    # e: K2 ~' u' N! ~' r& N
  21.   PRINT_INFO("firmware write OK\n");
    ; ~" G! i6 h( ~  A/ `6 D! a
  22.   PRINT_INFO("please double click the button the execute the application\n");5 j  h. {( i& V
  23. 2 u" R0 B6 y6 [4 n- d
  24.   HAL_FLASH_Unlock();         //解锁
    & U$ ?7 r  Y- W0 R8 l4 @: E- B, \
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区
    - b* V2 |/ y+ p  V7 o
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     0 `3 x6 H; d, }" S' X* _
  27.   HAL_FLASH_Lock();          //上锁
    ' z( p$ J5 i- N  K3 f; {
  28.   
    / P9 n# F: }) v9 O1 u$ t
  29.   PRINT_INFO ( "开始执行 APP\n" );
    9 G! i0 ^2 q- }( V! ^9 g& R
  30.   //执行FLASH APP代码
    2 F* p+ U  Q5 T, k
  31.   IAP_ExecuteApp(APP_START_ADDR);
    9 r$ p& q) k+ M4 v/ f4 x$ M
  32. }
复制代码

6 M5 T( P/ d: B9 p# {4 @

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

  1. PRINT_INFO("-----IAP Menu--------------\n");
    # ~+ O& X* C7 X4 J
  2. PRINT_INFO("-----Download APP BIN------\n");# j' Z+ {2 |( I+ e4 L
  3. PRINT_INFO("-----Restart To RUN APP----\n");
    8 J+ a7 ?) {/ Q& L' S8 `
  4. PRINT_INFO("\n\n\n");5 `0 c* U# x! h
  5. 0 p0 y  y- u7 [7 Q0 @! V5 L; n; q
  6. if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
    6 ?! s# O' k8 l. k( E
  7. {
    , K$ M5 s2 M, ~
  8.   PRINT_INFO("-----Restart.....5-------\n");6 ]- p. i. s7 u+ a2 P& ]
  9.   HAL_Delay(1000);/ J7 ^: N/ i( e6 r3 P3 J
  10.   PRINT_INFO("-----Restart.....4-------\n");
    2 w2 C5 o3 Q! V$ e' I- _$ ]4 s9 W
  11.   HAL_Delay(1000);
    : {: C* t* M: g9 E
  12.   PRINT_INFO("-----Restart.....3-------\n");& X; x1 v1 K- M4 y; T
  13.   HAL_Delay(1000);
    ' s2 I6 |# E/ z( P
  14.   PRINT_INFO("-----Restart.....2-------\n");/ Q! o+ k8 f: W5 \4 [( H# F
  15.   HAL_Delay(1000);3 F7 g, f  O1 E9 {# Q. J- [  M4 o
  16.   PRINT_INFO("-----Restart.....1-------\n");
    ' [  d- E7 x: K# g4 y+ C  E
  17.   HAL_Delay(1000);
    5 b3 ?7 T6 ?. v/ G: L1 a+ W+ Y
  18.   . I% [- }+ |7 s" G) y0 |
  19.   PRINT_INFO("the device need update\n");
    # i* x. R" O. `. p: |
  20.   IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
    7 N! b0 R* l4 t
  21.   PRINT_INFO("firmware write OK\n");- J0 N7 H# C! U% A) t+ c/ b
  22.   PRINT_INFO("please double click the button the execute the application\n");
    . \  @, p+ M) r" ^( n

  23. 6 [$ M: {% ^. e: ]. S8 g3 F; ]
  24.   HAL_FLASH_Unlock();         //解锁; R- m, a# D' O1 f" R; T1 _
  25.   APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE);  //擦除APP缓冲区7 M  x6 v: r2 r  d  s4 j. q7 V" B6 g
  26.   APPReigion_Erase(Flash_UNUSED,1);          //清除APP更新标志     
    & Q! S* x/ T8 |* ~+ I6 {
  27.   HAL_FLASH_Lock();          //上锁
    / M/ |# Y7 ?7 Q
  28.   4 k& g  _! C2 B
  29.   PRINT_INFO ( "开始执行 APP\n" ); - P* e; r" g& k0 ^2 M- f5 u
  30.   //执行FLASH APP代码
    - I4 x( g  H8 n: z2 Y
  31.   IAP_ExecuteApp(APP_START_ADDR);% H% P+ p% i5 X' g
  32. } 6 g  V) O) W8 @6 q& O1 f0 K: g; n
  33. else//不需要更新,执行APP
    8 j5 ~% S$ K+ `9 H4 ^* F, J
  34. {
    " T0 A0 @4 \, ^8 @$ W
  35.   IAP_ExecuteApp(APP_START_ADDR);
    * a! {7 y  X  Z& W: i; q
  36. }
复制代码
% z4 `6 p7 W; J9 T

bin文件生成

8 ?- ^  [/ V+ W( D' `) `4 S8 ^' U

微信图片_20230402174611.png

: B* d2 S7 d$ z% [/ y* g9 \

输入:

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


( o0 w8 Q+ d' k) ~. @

参数意义:

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

  • --bin -o  IAP.bin :

    ! Z5 u! l; O1 |5 R5 B) v

输出bin文件名称

  • UART_CircleQueueTest\UART_CircleQueueTest.axf:$ n& M9 \0 e1 M. z

axf文件目录及文件

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


0 A; R% G, f/ F! C2 R* N, j 微信图片_20230402174607.png ' g1 D& @! m5 I6 p/ J+ m

  H: O  O5 t/ i, [* I- h: g 微信图片_20230402174604.png 4 u5 T2 h4 Y1 e2 {0 x# P
3 B: D5 |* w! h- v+ A! o
  • APP代码设计. s* ~& K* ^2 b' A/ J& |

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


4 q' e6 Q" i% X  C' \8 K

  • boot区代码执行效果
    6 [% p8 h) @* b0 P1 m. K

+ Y9 w; s! E0 b# r* v 微信图片_20230402174601.png ' y( i* F8 A; `2 P6 d8 X

! C, G+ Y% `" }  P5 b: H2 N1 Y; V- K
  • APP1代码执行效果
    ) \3 v3 K- J& b1 p

. m( ]9 _% M, S+ C) H' Q8 Q% \

9 e7 |" c- d- }# m1 B" D 微信图片_20230402174557.png ! U% n/ _, W8 _2 J! p

: f0 S0 k: ^3 W2 D; r

3 R% b5 x4 |# ?& l, V* {( m9 `
  • 接下来再进行一次更新
    . r# l, g' {$ z& ?0 v
1 x, P) W0 n7 c) e! O% }
" _7 C' E* C' {
微信图片_20230402174553.png
9 J" t) ~5 x7 ^4 [" \' h5 u6 O, k! s: `. _; m* F8 s3 {/ y

/ \: V3 w8 m0 l' S

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

; k4 l$ A7 c4 l$ }8 m% P- U" e9 S
微信图片_20230402174550.png
1 ~: t' {0 e% d% V/ T! r9 q+ p, z1 t% o. B. L) W

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

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

9 v* m4 L3 J# P0 h
转载自:Embedded小飞哥; N4 v. D& k, @( {
如有侵权请联系删除7 Q7 c6 H: [. w/ \
. Q9 {- Z' d: C/ H' c
收藏 评论0 发布时间:2023-4-2 17:48

举报

0个回答

所属标签

相似技术帖

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