|
什么是IAP升级? IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级 4 A8 ?# [' M4 c1 z
UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级 ]& D P6 L- x
STM32的代码启动过程要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧
: } @% G* K7 C! w: w1 b6 j在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):
1 L+ \ a7 y6 t
' e$ S! N: Y+ q6 K8 l启动文件源代码分析- ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
" K5 w; R2 o8 F - ;* File Name : startup_stm32f10x_hd.s* O3 d8 L+ K7 e+ }" o5 Y, d
- ;* Author : MCD Application Team
`3 r: h7 J& {& ~& a3 J - ;* Version : V3.5.0
8 e4 w6 t' w) M4 [; ` - ;* Date : 11-March-2011" \# A9 `$ W/ L0 T; ?8 p' G- H
- ;* Description : STM32F10x High Density Devices vector table for MDK-ARM # r2 ^# Q/ \2 @" G( k: r3 ]7 p
- ;* toolchain.
3 L \- o: N; d8 w9 x - ;* This module performs:9 T( O2 X+ y) x- q8 H& X2 B' p* c- Z4 B
- ;* (上电复位后会做下面的几件事情)
0 y- ]$ y- M: k+ X2 z - ;* - Set the initial SP(设置堆栈,就是设置MSP的值)( |, r# d- Y0 N, {
- ;* - Set the initial PC == Reset_Handler(设置PC的值)
6 P3 j, O1 V+ y6 x( G& A: x3 k3 u - ;* - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
% B# L- z! Y! _+ E, A1 i3 i - ;* - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)3 B8 Z8 v, b5 }4 N1 n: e+ v( z
- ;* SRAM mounted on STM3210E-EVAL board to be used as data
5 w0 q2 C& ?+ u. E/ x8 a/ Q* y% F - ;* memory (optional, to be enabled by user)
5 V- W; U% s0 l5 d# r4 m - ;* - Branches to __main in the C library (which eventually (调用C库的__main函数,然后调用main函数执行用户的)" \& }- d2 i5 X3 B. L3 i
- ;* calls main()).6 D1 g }, f# d3 p- W
- ;* After Reset the CortexM3 processor is in Thread mode,
- c- K6 C8 h, O* V3 H' P - ;* priority is Privileged, and the Stack is set to Main.
- ?: p4 ] x- P! ^, C" T - ;* <<< Use Configuration Wizard in Context Menu >>> 5 d3 c0 {! Q' B
- ;*******************************************************************************8 {" U- U2 R# D( ?0 {
- 4 |. h' a. S4 k, E( W
' b6 R$ c: }. V& T- 1 z+ C* D( _. [) n- m* |
- ; ------------------分配栈空间----------------, S [6 x, V* F- e3 O
- Stack_Size EQU 0x00000400 ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小* O6 z4 h: t2 \
- AREA STACK, NOINIT, READWRITE, ALIGN=3 ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)" `5 z0 ]5 G( B
- Stack_Mem SPACE Stack_Size ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
( R, V: r) |4 u6 H) B - __initial_sp ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到" H; Z; f' X+ x. f+ w
- ) e4 c9 ^5 F. A$ m. ^
-
; @/ u+ Y7 I2 D! c+ e! I - ; <h> Heap Configuration; |1 \" E- F1 k4 x
- ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>; z9 A* `" t0 p
- ; </h>) s2 s# ]. N0 ?6 z
- ; ------------------分配堆空间----------------
9 R4 T; u% T& C T6 Z - ;和分配栈空间一样不过大小只是512字节
2 S& T7 D' _( I% T* i* g7 v+ v - Heap_Size EQU 0x000002008 B' t" R: {7 K1 s( q
- AREA HEAP, NOINIT, READWRITE, ALIGN=3; n) v0 D# o$ g, o$ O0 v6 [
- . D. {& U* {' d2 T* z: U
- __heap_base ;__heap_base堆的起始地址% r3 K4 A' H( X# F5 g
- Heap_Mem SPACE Heap_Size ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的! l! f4 s- G! T( n- A1 y
- __heap_limit ;__heap_base堆的结束地址
X" l. }! o0 l/ A6 r- M - & E8 G3 l: N! C/ K$ a' C
- PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐% w0 O6 `0 F+ F9 w% d, j$ [
- THUMB;THUMB作用是后面的指令使用Thumb指令集, s; u0 n( A, f! b
- & ~$ ~. b' \. c$ k, v k
- ; ------------------设置中断向量表----------------
+ C2 T' s3 K3 m - ; Vector Table Mapped to Address 0 at Reset
$ l1 W/ t# | N" [ - AREA RESET, DATA, READONLY ;定义一个段,段名是RESET的只读数据段
) T- f4 K" ^" e8 l+ { - ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性2 [( g5 l% [% E7 J. v0 {
- EXPORT __Vectors ;声明一个__Vectors标号允许其他文件引用
5 `; ~& \$ @$ M# F n% b - EXPORT __Vectors_End ;声明一个__Vectors_End标号允许其他文件引用6 E1 m3 f- Y' u) l$ H. m5 d" O! o
- EXPORT __Vectors_Size ;声明一个__Vectors_Size标号允许其他文件引用
% J4 P* s. \; t w* Z5 [8 [) S - - I: @; j$ ~' w, ]1 ^+ x: b
. i9 H& N, \7 q5 a$ X; ~8 V- 5 D; k% w- j9 r5 z ]
- ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化5 q4 X" i7 i, g, {3 n
2 X0 a, L0 a; _6 Y2 i- ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
/ b+ C' {# X: r$ z! K; S) W - __Vectors DCD __initial_sp ;* Top of Stack 定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
?$ G5 J0 `2 b- O' L3 z - ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp) 8 A3 R+ G+ ^9 ^% y+ y4 ?4 x
- ;* __initial_sp的值是链接后,由链接器生成( D h& u; t! F" p9 N. F
$ ~! v- ^# L* n0 [5 p Q- DCD Reset_Handler ;* Reset Handler 定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
; t. E {$ y. @ |: w+ J - ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
1 s) d; a7 E2 p; Q/ M$ C( `# `( U( O - ;* Reset_Handler是一个函数,在下面定义
% g% j' P6 [/ R4 A3 n - ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》, ^% L( R' i1 N' P3 S- U5 e. p
- DCD NMI_Handler ; NMI Handler
; Y: k! i; D& P8 F - DCD HardFault_Handler ; Hard Fault Handler x, y& B: }0 u$ z; A# [) w
- DCD MemManage_Handler ; MPU Fault Handler6 o0 g+ X$ R' l; }( {. ?, d( e
- DCD BusFault_Handler ; Bus Fault Handler" m0 s& o+ }3 h6 s$ m
- DCD UsageFault_Handler ; Usage Fault Handler
. U! q* d8 ] \' H4 y, S - 2 B0 S& s5 x- q
) ]- y5 e# s* i" F$ ?4 x+ o- .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件/ a% ?- p/ w' H2 m3 k0 N% O
- 3 N+ y t3 |/ w0 |: X7 h
- DCD DMA2_Channel1_IRQHandler ; DMA2 Channel14 v: q2 r; S& O
- DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2# ]6 ?, T# d! g' G. n
- DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
; ^4 e9 Y6 f/ |0 _# D - DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
$ L3 B3 i/ a% ?! b6 g' F - __Vectors_End ;__Vectors_End向量表的结束地址
( P$ |0 B9 D* i0 N- Y
5 ]1 v3 d H. D0 b; T) @- __Vectors_Size EQU __Vectors_End - __Vectors ;定义__Vectors_Size标号,值是向量表的大小
# ~6 f! V2 }! y
+ ^+ F; i4 _. R9 |# B- AREA |.text|, CODE, READONLY ;定义一个代码段,段名是|.text|,属性是只读
* L" P, F/ {, }- y - 3 S2 s$ L4 [% l! L- R7 j1 c
- ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)
/ S6 _4 }4 f! W7 L/ J - ; Reset handler
( j8 V' P' x f0 i$ \* {6 r - Reset_Handler PROC ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
u4 \) B/ t0 w
^0 N+ q2 t0 [# y! m4 x* X3 h' c, ~- EXPORT Reset_Handler [WEAK] ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
# e7 n% b, J6 b. |$ V$ x7 C - ;*编译时使用另一个地方的函数,否则使用这个函数9 }( T& V. M$ [( Z" Q
- 0 e! a: L9 b/ H# } w
- ;*IMPORT 表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
0 M; J g* e2 G+ @/ ] - IMPORT __main ;*__main 和 SystemInit 函数都是外部文件的标号/ @/ l5 L1 Y. _' q# o1 D @5 `
- IMPORT SystemInit ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟: v0 G9 u8 f( C! u. M' a: C5 g* K
- LDR R0, =SystemInit
0 Z2 ]8 K+ U1 f( T! N! t. s - BLX R0 3 l6 ]' T; @8 V. U: Y1 S @
- LDR R0, =__main ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
& j) q& ~, f: e7 A) v( m1 D* Z - BX R0 E8 W( j2 ]1 X& u/ K
- ENDP
" h8 m. D+ ^; y& j8 I -
0 q& W) E5 }+ E$ r" p; N4 H/ r. B - ; Dummy Exception Handlers (infinite loops which can be modified)
' c) K/ h# E8 O5 @. e& k - ;下面定义的都是异常服务函中断服务函数. }% f' p# ?4 v' W8 Z$ I' k
- NMI_Handler PROC
; e/ |* D5 {- T1 e; q& b - EXPORT NMI_Handler [WEAK]5 f3 o) U* {6 O( s7 H( W
- B .. ], [. c2 a6 R8 m H/ Q& B
- ENDP- c" i' L5 X5 z8 x3 M
- .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件% \4 Q9 r8 ?2 S4 v! ^: T/ ?
- SysTick_Handler PROC1 @# ?. r: ]2 z/ f
- EXPORT SysTick_Handler [WEAK]
$ O1 N% @( D, f - B .* q. c! K% d0 Y; f9 E/ D( m
- ENDP
3 [! ?4 s. [+ z' i9 L7 w8 R# Q
& ]* d2 u, L( |% Z- Default_Handler PROC
: P$ }9 c: p$ }% L' z! x2 k
4 B' N, E# j+ s) F" u- EXPORT WWDG_IRQHandler [WEAK]
5 C, e w/ B2 G0 o5 Q - EXPORT PVD_IRQHandler [WEAK]
, g: F' \1 G& m# J6 N% N - .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
( d4 E" c" b% q! u& c - EXPORT DMA2_Channel2_IRQHandler [WEAK]
: l" X) Z' |4 { - EXPORT DMA2_Channel3_IRQHandler [WEAK]
6 V& {, L( D9 B6 a - EXPORT DMA2_Channel4_5_IRQHandler [WEAK]& V2 A/ i% Y/ o" c0 V
/ ]/ Y$ y- n8 _+ \1 `4 C: k- WWDG_IRQHandler2 p8 W$ _+ }5 X% |7 k0 a! ~- b! T
- PVD_IRQHandler
7 p! ^8 k) R& @! C' q3 ^2 i7 L4 I - TAMPER_IRQHandler
8 \8 t6 w% P6 E, s! {" E - .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件" M2 g3 l$ W. Y$ E' }% U" Z
- DMA2_Channel1_IRQHandler: M" Q6 X. a% ? v% H
- DMA2_Channel2_IRQHandler% {) E: T2 ]( @; j7 E" J0 C+ L
- DMA2_Channel3_IRQHandler' ~8 I* I. z" Y% J( t. I# N
- DMA2_Channel4_5_IRQHandler
, F, W! f; u V' C$ Z) M; ]$ [6 O# \ - B .+ e8 E" N9 }3 A" Y, T8 b9 I
+ E2 w* E: o9 b. w- e0 d- ENDP
9 w: s$ I# r+ |- k
7 y# f/ [ I) Q$ i4 ?( l- ALIGN ;四字节对齐
- t2 ?# b- A6 j: g
- X! k' g, a& s- |- ;*******************************************************************************" y* h9 H' j) |" j$ q0 a
- ; User Stack and Heap initialization
: [; l3 d! E8 m. n- B( A - ;*******************************************************************************
# N1 n! M& _( r; |$ b% R - ;下面函数是初始化堆栈的代码' h+ m1 Y5 j, c# y6 g1 m$ c( V+ v
- IF :DEF:__MICROLIB
' S4 F! m+ c+ o3 d' j# D - ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义9 R1 A* U" H4 q: g ]9 l; q
- ;这种方式初始化堆栈是由 __main 初始化的3 X* H/ j9 [; m x' d8 b0 y
- EXPORT __initial_sp ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)4 Y9 ~) l4 @6 ]0 ^
- EXPORT __heap_base ;堆的起始地址
+ k* [+ z) Q3 C6 Y - EXPORT __heap_limit ;堆的结束地址9 H: r" V/ a; S7 p8 [" L
- / i0 H! u' f; q& s* L( F& @; ^
- ELSE
8 V' l- [2 E/ b8 o( b* D# ~ - ;由用户初始化堆
: R) h! Z: R+ }2 ? - ;否则编译下面的
7 O4 l2 j- Y' k2 b9 D( f - IMPORT __use_two_region_memory ;__use_two_region_memory 由用户实现
7 N5 l, v* ~, a' z. W* Y4 v - EXPORT __user_initial_stackheap
/ g; K4 y* \* S9 I1 k( ` -
E8 `7 k, L a$ e$ k$ b - __user_initial_stackheap
- g* ?0 n7 }+ H9 V1 } g- N - 9 a v/ R3 E0 e& A
- LDR R0, = Heap_Mem ;堆的起始地址
0 T9 g/ O' H" l$ T7 {2 i- ] - LDR R1, =(Stack_Mem + Stack_Size);栈顶地址
& y; L: { ?$ J4 S$ e2 y - LDR R2, = (Heap_Mem + Heap_Size);堆的结束地址 p8 ~7 q2 n3 M9 `! t" M
- LDR R3, = Stack_Mem ;栈的结束地址2 |' E; W* r5 K$ Z# b- y' T4 E; q
- BX LR) @8 Y$ L2 b- n, R( B- X1 u9 `2 x4 Z
% d* w+ U# q& c Z1 e- ALIGN
+ U9 s* O. j& I1 \) o! [
4 m' C* P: v1 X- ENDIF" l& T: _3 u& N" |
, o6 \( b. W4 a/ Z" z/ b# f0 G- END
/ [6 _; E, V8 G4 y6 U# S
. V6 `: L, a2 \0 I& A# y- ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码
3 F$ t* h. d+ G4 M6 X8 s: h& u
/ E% w% V2 ?) WSTM32的启动步骤如下: 1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的 2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟 3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序
- B/ `" r1 H# U9 } IAP设计思路大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序 调研了一下群里的小伙伴,下面这个流程比较通用一些,大概是下图所示升级流程:
! n7 C; B; w0 J8 u3 u) P 升级流程图
1 y, |9 t8 r9 c& _: g, g2 S0 N7 [# C+ _* e) Y
2 q5 H$ V: B z6 D4 ~+ w- l9 u
, t( y- }6 F8 W } M* D
/ ]) r! p. l) h5 U4 B1 A" w
$ s( u" U5 m: [6 r( C" G; C4 zFlash分区是以STM32F103RET6为主控做的flash分区,主要功能: : P: g5 R9 U5 ^7 ~
`7 n( e) C$ j! n; p6 r: f3 o
 boot区:0x0800 0000 到 0x0800 b7FF 地址的flash块划分给bootloader,用于升级固件,大小是46kb 用户参数区:0x0800 B800 到 0x0800 BFFF 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是2Kb APP区:0x0800 C000 到 0x0804 3FFF 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是224Kb APP缓存区:0x0804 4000 到 0x0807 BFFF 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 224kb 未定义:0x0807 C000 到 0x0807 FFFF 的flash块划分未定义区,可以根据具体用途定义,大小是16Kb * A& a! X7 s( `3 c7 Z
代码实现硬件: - fallingstar-board(已开源,打板验证)
8 t) j7 t2 p0 Y; K, k, ~
软件: 内部flash读写 串口DMA+空闲中断 * }; Z% w9 j+ G6 ?$ I8 A8 T. ?
内部flash读写操作这部分比较简单,直接上代码: 读flash操作: - /************************************************************6 c& L; w G0 y' h3 u
- * @brief 读取2字节数据3 S7 \6 u& L* }! C# q
- * @param[in] uint32_t faddr
" y# [6 k) w, o$ F, b# p - * @return NULL7 I& {0 s! n" l- p; K4 L
- * @github
7 V9 D4 P& w: x6 X0 g }& l& g - * @date 2021-xx-xx
" y1 `2 s$ ^' U3 q5 _( V" Y - * @version v1.0' V! j6 A }4 t. j
- * @note NULL
7 c& Q( w2 \% c% [- a5 r! ^ - ***********************************************************/) W: c' C7 O; D; f8 s
- uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)4 f; X' [0 w/ a3 h6 \% k, n
- {+ `& R: D" e" C N- Y
- return *(__IO uint16_t*)raddr;
* a9 B" @/ R7 {% C; F& f1 @ - }
p5 K- O' o F5 M3 \; }6 S - /************************************************************. v/ m. r/ v4 X( K9 r1 ~% A `
- * @brief 读取n(uint16_t)字节数据0 ?- C6 ^! j5 k
- * @param[in] uint32_t ReadAddr7 [. ^3 R. Q7 `* D9 {
- * @param[out] uint16_t *pBuffer- i, [0 u L1 }) b, O( @5 X4 ~
- * @param[in] uint16_t len a$ l4 g: ^1 D# Y( B _7 \
- * @return NULL8 X5 x' h% m u' J6 i5 R3 _
- * @github & Y5 z1 U. s& `" i
- * @date 2021-xx-xx) l0 P( y4 _, f0 k- W% q
- * @version v1.0- v$ w) n& R+ O2 @
- * @note NULL3 O% [/ J# X# w7 Q n
- ***********************************************************/ n, u$ m+ [3 @- J
- void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len ) $ S2 e( z$ @ S0 J* @0 v
- {% Z. x* v' S. C4 s1 b' ]4 [$ u
- uint16_t i;
5 u/ _) O# c8 {8 w5 ` -
# p$ @8 s" W' |' M9 ^/ ?5 j - for(i=0;i<len;i++)
$ c1 L5 D* @0 h z/ C- r - {
) R1 x) }: W& U4 s0 p1 b } - pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr); //读取2个字节.
+ }! }& u$ i5 } - ReadAddr+=2; //偏移2个字节.
0 X( f" {2 k/ i$ c' ^ - }, r* Y4 B5 [1 X/ P% t
- }
复制代码 4 F% F& s, }* e
写操作,注意写之前要保证是没有写过的区域即可: - /************************************************************3 H3 j! z2 O* i4 N
- * @brief 写入n(uint16_t)字节数据0 m `( j7 L% r& X$ R
- * @param[in] uint32_t ReadAddr& A' c/ r8 \( }* ?( F
- * @param[out] uint16_t *pBuffer2 g: N+ U" d |; }6 ^9 Z! q
- * @param[in] uint16_t len/ l9 A4 @1 F- ?" N% N: ^
- * @return NULL$ f: M/ Q Y, x5 \) U, k
- * @github
. B8 U7 _! z+ d+ x1 S, u - * @date 2021-xx-xx
' v, a. }; N s; P4 Z1 S1 z4 @2 { - * @version v1.0* ]" B: i4 c- h% [! `) ~
- * @note NULL5 w d6 b) H! u }. V, e
- ***********************************************************/+ }5 u7 d- l8 {) ~
- void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len ) # q$ _: n7 p1 V5 C
- { 1 d4 a7 A. _$ A
- uint16_t i;
5 A1 F+ n1 f. B+ X -
& p7 x) m5 V4 h5 y2 R - for(i=0;i<len;i++)
4 M6 Y0 X& ~, K7 ?! V8 p - {+ `( p7 J D; j: p
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
2 O' r9 T9 t& L( O7 {4 A2 G# H' ` - WriteAddr+=2; //地址增加2.
8 y; Y5 ^ v& }: j! ~1 k4 H# | - } ; R) g) }- C! C
- } - O, v' Y0 c/ A8 F# L+ [
- /************************************************************: t9 T) h/ e# u' K
- * @brief 写入n(uint16_t)字节数据
4 R6 P8 n) R/ d& ?9 U - * @param[in] uint32_t WriteAddr" W( O) d) r; Q' F, h) j6 ~
- * @param[in] uint16_t *pBuffer. w8 y9 H% `7 b! s
- * @param[in] uint16_t len
& ^: h8 J( a/ E% j) R - * @return NULL
4 X; V9 g% `( B+ ~7 d - * @github 4 r/ N9 [$ m1 f% I3 A& ^* |
- * @date 2021-xx-xx7 B! F' F( p* t; H4 f4 h
- * @version v1.06 X0 X2 e# y5 n+ w9 H- q; q T
- * @note NULL" u) b( l- l& n# l0 ~9 d
- ***********************************************************/; M1 V" D( D9 |# y# ~" {
- void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
0 i; y' u/ N" A% g# [ - {& C! J. }9 M# E6 I) p r% ?
- uint32_t SECTORError = 0;
, S# y6 D- q# K! u - uint16_t sector_off; //扇区内偏移地址(16位字计算)( o; S6 Y! ]- W _' i
- uint16_t sector_remain; //扇区内剩余地址(16位字计算)
2 y% u1 E4 M4 V2 E: p - uint16_t i; 1 O) K2 h9 q. \/ c2 ~" N; c
- uint32_t secor_pos; //扇区地址
7 J# j3 W& I/ G, ~: ], j - uint32_t offaddr; //去掉0X08000000后的地址
, ^, G/ G8 ]: A! m6 B/ H - " v" t0 ?' x; E, n* F
- if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址; k$ ^' } F% \9 ` D
- % {( A+ O6 n8 n
- HAL_FLASH_Unlock(); //解锁
8 o1 R% J: e3 y$ p- h7 G - 2 z7 ?* f' Y4 {2 H5 U
- offaddr=WriteAddr-FLASH_BASE; //实际偏移地址.0 _! k5 w% x c: i$ e
- secor_pos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6$ d* q: a; J2 R: z
- sector_off=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)( y) b1 k" ~8 Z0 h* r
- sector_remain=STM_SECTOR_SIZE/2-sector_off; //扇区剩余空间大小 8 v Y- k0 `3 B3 F. J
- if(len<=sector_remain)sector_remain=len;//不大于该扇区范围4 L7 U! t5 R: a6 E
- % D5 D0 P& M% I0 c# \* p0 b6 w- y
- while(1)
$ p2 U9 ]" _( w' w - { 7 L) D; v9 W! g
- BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
N( A; @ d5 X! {0 l -
/ c' G+ w. u x" W& r - for(i=0;i<sector_remain;i++)//校验数据
% e3 G- v: G. \$ v* H& v( B - {3 b* H6 r3 S- R4 i2 T
- if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)1 ^& J2 E! A6 I+ U0 t, f2 ^
- break;//需要擦除
. U7 r( ^9 a5 i- w! ^' L - }* D6 @. C( |6 I8 @, X# V% {
- if(i<sector_remain)//需要擦除) s' |& ^. A6 v! E
- {8 y1 |3 f/ N* k* M/ B% B$ a+ j
- //擦除这个扇区7 j& B9 _/ x R8 n- f
- /* Fill EraseInit structure*/
8 T" A% v, @# T - EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;+ N8 q) g4 O. R
- EraseInitStruct.PageAddress = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
/ T z% V- k1 K# J - EraseInitStruct.NbPages = 1;* ]+ W& w7 l# e6 N, ^6 z% L
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);: y" M' ?, A `" ]% i0 U
- for(i=0;i<sector_remain;i++)//复制
) M {: J' k. G* \) l5 X: ` - {+ Y$ L- Z6 n5 z8 X1 R8 F4 o. N
- STMFLASH_BUF[i+sector_off]=pBuffer[i];
4 x' ?* i% I$ m h: A' G" q2 v - }6 x9 E/ n: l* X6 Y: d
- BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区 * |8 k2 H0 W) t2 A. [9 f
- }
, L1 T1 G& d% d. w* R6 | - else
0 X; y4 Y) e4 I3 f; U - BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.
- R+ w$ r6 V& t& k - if(len==sector_remain)
1 n: A4 J. P; d" r* v - break;//写入结束了
3 ~: q2 M- s7 i- F - else//写入未结束
- d6 b9 }. k) C% i+ m - {# i2 U0 @/ T: y2 Y; o, X7 V
- secor_pos++; //扇区地址增1
' Q9 F, F+ {$ P# u- A8 n5 U - sector_off=0; //偏移位置为0 # u6 d3 K* K9 s; O: d
- pBuffer+=sector_remain; //指针偏移3 }. s/ v; o/ V6 W8 m3 T) f- q
- WriteAddr+=sector_remain; //写地址偏移
) G$ Z1 J6 [; l1 V" L4 H- ? - len-=sector_remain; //字节(16位)数递减' M/ P$ n6 c* {4 c7 p" P
-
( ?) F# E6 K0 B7 a' r - if(len>(STM_SECTOR_SIZE/2))3 d( z$ C/ z4 V# V
- sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
3 q' J$ l. f1 m* g7 z+ n8 D - else
! c) A/ k' k7 x( @! B' r - sector_remain=len;//下一个扇区可以写完了
0 ~* a8 I8 f4 L- ^ O - } , g- h/ f8 ~4 a1 ]8 p) m3 }9 v% E
- };
. N' R* F6 Z# j0 X7 O$ D2 ? - HAL_FLASH_Lock();//上锁/ y, E. b& G) i6 A$ f
- }
复制代码
k+ o. Y# e8 o1 `7 t串口DMA+空闲中断接收不定长数据在上篇文章的基础上,我们对结构体做点修改,增加bin文件数据总长度记录,以及bin文件接收完成标志(小伙伴们可以采用其他办法,不必拘泥于我教程中的方式): - #define Max_RecLen 1024*3
* F; d. s$ v/ A1 v, D - 9 m8 R* u% m& s5 ^$ O c! ]0 l! ]
- typedef struct{5 ?+ u0 ^5 T8 p1 A) O0 J$ h
- uint8_t RxBuffer[Max_RecLen]; //DMA接收缓冲区0 K- _( L7 I' X6 a) o) s1 G, R: x
- uint16_t RecDat_len; //单包数据长度
4 x' F3 |+ \8 i5 [, y - uint32_t Cur_WriteAddr; //APP缓冲区地址
, h: m M. f m& u9 w - uint16_t BinLen; //bin文件数据长度+ v6 e0 k: I; j1 S7 r' K
- uint8_t rec_endFlag; //单包数据接收结束标志
5 k: p7 y2 ~! D0 \ - uint8_t Binrec_endFlag; //bin文件接收结束标志 9 d C# k) V7 X! d6 e" n
- uint16_t DMA_TIMCNT; //bin文件下发超时计数器* k* X3 P+ b. }1 s& @0 t
-
! e# y# Q; f, r8 q; q# |; a2 F e - }UserUartDMA_Typedef;
复制代码 4 O8 e8 w: C. U4 ~& G J3 N
然后在串口中断中: - /**
7 i6 u/ j& d+ ^9 P! c2 S - * @brief This function handles USART1 global interrupt.
6 P% E7 f/ m! A* i$ I - */
2 y" l9 {8 d/ n0 S1 G2 T* h, ^ - void USART1_IRQHandler(void)
3 `$ h4 {9 `2 L+ A* z1 `8 z! r, |, B - {- }$ h/ d: T* D2 b. s. r9 r1 w5 W
- /* USER CODE BEGIN USART1_IRQn 0 */( @$ o3 t j5 h, x9 {
- uint32_t idle_flag_temp = 0;
/ D& E% ^1 j* H; N$ y& ?8 j - uint16_t len_temp = 0;
7 e1 n0 }1 g9 [" d - /* USER CODE END USART1_IRQn 0 */8 C+ |$ M7 H0 c2 q* |" C
- HAL_UART_IRQHandler(&huart1);
( p& ` W+ ^6 c. P/ I$ o - /* USER CODE BEGIN USART1_IRQn 1 */# j# \6 |! w A6 K7 U s
- idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);: H* q8 W5 u0 m7 R$ T" H
-
7 S: O2 c' P$ a+ c( P - if(idle_flag_temp)
7 v+ a: w7 k$ m f4 ?* C9 A$ F - {# x3 r* Z, }. P6 q# @. E
- __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);/ x/ N y) A' Q) {
- HAL_UART_DMAStop(&huart1);( u$ u2 k2 D# }/ c
-
& {5 i+ {. c6 z' ], d - UserUartDma.DMA_TIMCNT = 0;
+ j8 J: I5 \- E9 ]. v" J - UserUartDma.Binrec_endFlag=0;
5 ]3 F7 N: x, { - len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
( U k" Y& m3 ^( t - " u q$ n1 o5 n3 t6 V
- UserUartDma.RecDat_len = Max_RecLen - len_temp; 8 s6 Z+ _3 n) s9 t
- UserUartDma.BinLen+=UserUartDma.RecDat_len;
9 X, _* u% N) ^6 K" n - UserUartDma.rec_endFlag = 1;- l# H7 [' j" |( ]" x5 l5 `; }
- & s! ~ X7 N! X; [% K
- }& n; `7 X$ t7 v$ v/ [
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);: G1 k |! ~8 I# V; Q" M1 C
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
0 z. { C3 n- a3 z! i0 D - /* USER CODE END USART1_IRQn 1 */
5 d! T2 n4 _& \* B% I$ Q9 ` - }
复制代码 + S% S" n8 L2 F5 K! A
IAP代码设计- bootloader代码设计
\3 f4 T( T. G6 ?- t
扇区擦除,用于写入前擦除相应扇区 - /******************************************************
: v3 G8 v l. p O) H - * Brief : 擦除APP区
7 a, ]# f* ~; T! r" G - * Parameter :
$ _* ]4 w& x' V1 j1 G - * *startaddr:APP起始地址- c, R2 J# s( r7 Q+ C* |+ e! h
- * *pages :要擦除的page = APPSIZE/PAGESIZE- r; f+ w; f, y( q$ G' l! F( }7 `
- * Return :None.
) \2 I3 v) F, E* P/ _/ H) c - *******************************************************/
i$ z7 S8 G4 B2 r2 }1 w8 j - void APPReigion_Erase(uint32_t startaddr,uint16_t pages)9 N: f) I4 r. a5 i/ |; B
- {5 U$ V* T- r5 q
- uint32_t SECTORError = 0;5 q2 e# x/ c3 V3 B$ r( R% q7 |
- 4 l2 ]0 z7 q5 P7 Z, o$ s& O, y$ G) W
- //擦除APP区
, P4 U' K* b. ^+ v4 a% n* g1 o F - /* Fill EraseInit structure*/# J, @6 r$ b- C. }6 z
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
+ f6 r; n& V. q5 D2 p' Y9 M& X4 [ - EraseInitStruct.PageAddress = startaddr;
" q+ Z6 v" }7 n& A* r0 R - EraseInitStruct.NbPages = pages;( D2 t. A& Z. ]
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
# l( }9 n3 Z) i: A0 t- Y3 [ - }
复制代码
! L- W. X/ h0 C升级标志获取与擦除 - /******************************************************
0 ^- L+ X8 B! U/ M6 ]1 }( w8 P( U2 h. e - * Brief : 写入升级标志" M# P2 G. {: @9 g: |
- * Parameter : 6 N H9 p3 R& A: @
- * *addr:标志存放地址; @2 S9 G8 _+ l+ |& D
- * *pdata:写入的数据
( k" u4 L* e. T. b; I - * Return :None.
. Y! a4 q* {: K- j. { - *******************************************************/
: S1 t! @- x- }& S - void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata). c5 C) N2 m, j) j A7 o
- {' h: R& g$ Y& |7 B3 X
- BSP_FLASH_Write(addr,pdata,1);, d2 ?% Q9 N+ W1 } { a( M/ f0 N) z
- }
m2 B2 x8 S) C, f7 }2 [ - /******************************************************( z" t3 @: j0 t6 P/ W# ^' D
- * Brief : 获取升级标志! G4 A) _! n1 H$ b* ^, Q
- * Parameter :
9 g, c, R% m5 D9 N+ ~ - * *addr:标志存放地址8 T' F( J' |9 s, R) w
- * Return :None.
4 q o( G2 }& i1 @3 w - *******************************************************/- x/ W0 u/ g, P1 q/ h$ t
- uint16_t APP_UpdateFlag_Read(uint32_t addr)2 s K$ i- {2 j# h8 A
- {6 u& M" F, ^; G p& L
- uint16_t flag_temp;
8 J9 P1 v% A: Q/ ], ~9 D. _ L - BSP_FLASH_Read(addr,&flag_temp,1);& a! v: |( \ ?
-
& ]# r4 A) ~3 [+ h/ f& O - return flag_temp;2 |1 ?# A- x2 d4 Y k; |
- }
复制代码 7 Y( y: @! h- ^; Q+ c2 _0 F
接下来就是APP缓冲区数据写入,APP区与APP缓冲区数据倒腾了 - /******************************************************
5 W7 e# x- N, X* v5 b - * Brief : Bin文件写入app缓冲区
8 ?0 C1 m. o+ P7 m0 r - * Parameter :
' g0 e% u0 Q- ^) D' d+ v4 U ] - * StartAddr: 起始地址
" |8 c- v6 t- S) G, p" m& z - * *pBin_DataBuf: 要传输的数据3 T! C7 q* m0 X6 Q+ c* N. q+ k
- * packBufLength:单包数据长度+ [" A6 N9 C% w4 `- p
- * Return : None.- d; K/ ]' l, b
- *******************************************************/
" K8 Y* H" f. O$ d/ f2 M3 L: J - void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
; \" ^0 S0 E6 H; R& e - {
% G+ ]2 {% t. M( z7 y* d. x - uint16_t pack_len, packlen_Ctr=0, dataTemp;8 P6 g2 ^: _& [; n9 a
- uint8_t * pData = pBin_DataBuf;
1 {0 Q' c: e2 n6 v9 y) D -
* B3 f0 X1 ~) g0 f O3 w - for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )/ c; Z; h& l! W. u0 v& P+ p
- {
0 {+ s9 s' Y7 c3 k7 P - dataTemp = ( uint16_t ) pData[1]<<8;- f T, F2 }& u0 F
- dataTemp += ( uint16_t ) pData[0];
5 c) \8 ~. h2 C: H! z - pData += 2; //偏移2个字节
' C. e* \/ h( s- \, b/ a - ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;
0 }2 K6 K3 Z+ ~1 N3 w - } + x7 y- R7 \0 w5 I/ ^1 h; ]
- & N) x1 {* ?, c0 C
- BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
9 f _1 Q, Y- b - UserUartDma.Cur_WriteAddr += (packlen_Ctr*2); //偏移packlen_Ctr 16=2*8.所以要乘以2.
# D+ B+ c( d) j+ _; {& z - packlen_Ctr = 0;
5 B' d8 l2 J0 Q6 F - }
; J# r+ T. s1 p/ i7 V( S - /******************************************************6 F( Z, e/ @0 @: U4 Y8 m3 R
- * Brief : Bin文件从app缓冲区写入app区
$ u* m. A9 I8 }' h0 V4 n* @- j - * Parameter : 4 s7 \: v+ _8 M# r' {1 Z
- * SrcStartAddr: app缓冲区起始地址
+ K# [4 D: t2 o. P - * DstStartAddr: APP区起始地址3 r9 P% N/ @2 N5 o! {" Y1 f: ~- v
- * BinLength:bin文件长度
. f3 I* W: \0 g7 n+ X$ C. H
7 |4 T1 u3 R1 V$ B, W9 h& O! Y. O: _- * Return : None.
% \6 R3 f6 V' v# Q1 ?/ ~. J2 ?7 E - *******************************************************/
/ x% p) O5 J b p0 m9 ~& I - void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
$ L! N1 s2 R7 S - {8 P3 a5 t+ S9 X% [% D0 V
- uint16_t data_temp = 0;
, j+ S* E" H7 i% t$ ~0 e- n - uint32_t count=0;5 y4 w2 a0 ~/ L, p/ Y
- u! `. ]. ?' U: f* {
-
$ Q( c( k. S5 y - HAL_FLASH_Unlock(); //解锁6 z. L) S M$ c# { V @
- " X' o1 E: S' h4 ]
- APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);! B* u9 Y) c/ Y5 P
- HAL_Delay(10);
3 `2 B! y7 o( G, o% ?& o - ! M0 K `' ^8 C* K' V2 C; R
- for(count=0;count<BinLength;count=count+2)
# e5 v& K2 I! f. D' a$ K# a9 }0 p - {
9 ~* E# K F4 v: x$ [: S - BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
0 c' @% f6 B# l1 `' H. U9 b - BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
" o3 q0 ^1 B$ n2 d" h- X - SrcStartAddr+=2;2 }/ S E! {6 y" D. W2 p
- DstStartAddr+=2;
+ W% e+ ]& C/ p6 X& G- W - }! B! N2 k, m- ~/ H
- //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
1 U7 Q' i3 R1 z0 Q) u% b! p - HAL_FLASH_Lock();//上锁9 D, L* X+ s6 w
- }
复制代码
& P( M3 N5 Y1 v/ B( v
最后,要设置APP程序的运行地址 - /******************************************************
1 X, P) [) r9 r9 _7 Y$ p - * Brief : 设置栈顶指针0 I, u: g9 |% Z+ g: k% e4 G
- * Parameter :
$ D+ I* F# n9 z% C* B& x - * ulAddr: " c0 f8 c; U. F' m
- * Return : None.
$ ]4 A" l8 _' j, d6 x6 U( V - *******************************************************/ W. K( K7 R4 y6 {7 G0 S
- . V2 N9 `! a2 A j0 C% X
- __asm void MSR_MSP ( uint32_t ulAddr ) ; o K: u1 r T2 d9 }9 ]
- {
. I3 d4 j9 W: R1 g! | - MSR MSP, r0 //set Main Stack value
1 Z0 E8 H1 b! W% E9 w. C5 u - BX r14+ Y0 a( {" Z1 q
- }
9 T3 n8 @0 }- P
- L' E* j: z- |0 w2 x) `- /******************************************************" z- V; q1 _9 x* g
- * Brief : IAP执行
$ {0 h! v7 L+ U' w( X [ - * Parameter :
4 D- v j( ^( f! f7 T6 S2 [ - * ulAddr_App: APP起始地址
* Y+ g* }, e- i% u( G - * Return : None.$ N5 Y1 R2 c2 ?
- *******************************************************/. D9 I& X! x. x% Q; p+ c: q; s
- void IAP_ExecuteApp ( uint32_t ulAddr_App )$ p8 M" G5 F) B. Y
- {
% L( w9 y- ?: M. o, K; {! f8 x - pIapFun_TypeDef pJump2App; . i. g& K8 F+ a
-
7 n% x" H+ Q D' K2 t - if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.: [- E l, U. I7 X% j6 r
- { # k7 R9 j. T \
- pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址) r9 g8 T8 ]8 o6 V
- MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
, p5 j) d/ M7 }8 o6 q/ v& Y( h - pJump2App (); //跳转到APP.7 o( T: g1 M9 O7 x
- }
( z" b0 u. W0 o8 p: p - }
复制代码 ; d5 b: @& q6 q% e: m+ h& _+ y
IAP相关的代码就这些,主要是数据的倒腾,其他倒也没什么复杂的 目前小飞哥对整包bin文件传输完成判断,单包采用DMA+空闲中断的方式,整包文件传输采用的是串口中断在5s内没有数据过来,认为一包数据接收完成,置位接收标志,这部分小伙伴可以自行判断 - static void Systick_Config(void)
' S( `' z; b1 I, p - {& I/ L: t& E* K4 L: f8 X- L
- HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms4 c3 @- t4 x& Q) c$ Q4 E* d
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); v9 S% h0 v4 A. Y6 ?& J1 s( [+ g* b
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
' ~$ ], N$ F4 b0 E - }! K2 q7 u4 E+ T. _& {5 r
- 4 F- ^" f8 e+ P l) B; }
- void HAL_SYSTICK_Callback(void)
6 K* k- l8 K8 [6 Q - {
8 u u. P, Q% d( }/ O - UserUartDma.DMA_TIMCNT++;3 N2 m' y+ s5 H9 U
- if(UserUartDma.DMA_TIMCNT>5000)! z4 \5 }6 A: U! }: _0 a0 w/ j5 p
- {
0 B1 R1 w! d& n* o, b - UserUartDma.Binrec_endFlag = 1;* Q& Y# C$ z2 f, {
- UserUartDma.DMA_TIMCNT = 0;
E3 x+ u% |# ^0 P' o3 } - }5 _! U5 c. U; d0 p# n- H) U
- TIMX_IRQHandler_user(); Q; u$ X5 X# I3 |, K
- }
复制代码 % |# ?' a* E9 m; b9 _3 y9 f
bin文件传输完成后,写入需要更新标志,重新跳转至boot区,检查是否APP代码需要更新:
! M3 C( Y/ B& D2 s) c; m( ^* g9 B- uint32_t Task_02()
2 R4 D: j4 H! h9 e! U( K4 n - {4 W g8 t0 Q* I5 W/ o2 p
-
8 o7 n5 N$ d2 j) A/ O. n - if(UserUartDma.rec_endFlag)5 T" V$ \1 p2 A) g
- {$ x% I( q2 o/ W }
- PRINT_INFO("update firmware\n");
5 m8 ?$ G' n/ d# o, Z# D - PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);
# ?/ m V) C4 ?, }/ P -
* c: ] n" u- U# h2 c" c - UserUartDma.rec_endFlag = 0;
) z9 F3 @+ X$ ? - IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
- v# f% \& x; A4 e* C) Q, G/ h1 s - ! v9 F i4 k) ^8 p
- memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
7 g0 v4 N+ V) E% d5 ?3 i T! X - UserUartDma.RecDat_len = 0;4 }% J! j" Y$ e$ d* |# M5 B1 p
- }0 r# g( z d0 S4 ]' }' L9 I" Z% ~+ w
- if(UserUartDma.BinLen!=0)
7 y* S0 }- ~, H) a, I. y - {
5 a! h D; h2 r+ R: X5 ]. T - if(UserUartDma.Binrec_endFlag)8 X. N1 |- P7 p q2 I' e
- {" h$ e+ p+ ?" p" J9 p8 {
- UserUartDma.Binrec_endFlag = 0;
+ H4 x& r* l3 U/ v6 B5 d - + V8 E, O7 h2 L# X, h2 m( N4 t! l8 X
- APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen); //写入升级标志
7 d# R! d7 `6 i" s2 G/ [$ {: [9 z - APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG); //写入升级标志
5 o" p9 Z* u4 M2 P- l0 \' T8 p% u% B - UserUartDma.BinLen = 0; & j$ D, a, M* A9 x/ K9 I l
- IAP_ExecuteApp(FLASH_BASE); //跳转8000000,重新启动
$ B# s" ]) q4 }! X2 ] f. N& w* a) p - HAL_Delay(2000);+ ~, w, b7 S/ `7 z" T8 l
- }5 W, M# q9 b- h! X8 ]* q
-
0 F+ t0 O u# Z, R d2 R$ \7 U - }
9 @, e% ~) E3 y0 e/ S" O - }
复制代码
. M+ v1 `/ d5 i8 RMCU复位之后,初始化执行过程中,对APP升级标志进行检测: - PRINT_INFO("-----IAP Menu--------------\n");" t9 z/ W# N u/ F- M5 M
- PRINT_INFO("-----Download APP BIN------\n");
5 p5 \3 K3 E% L2 A - PRINT_INFO("-----Restart To RUN APP----\n"); # G$ n0 \7 a& F1 C" U: v; m+ [
- PRINT_INFO("\n\n\n");
4 ^5 z! E; r9 G4 g* B# }8 a) p -
8 f& k+ a; }/ |7 H/ c" d; ]2 k - if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))# F" o: ~+ m* }; A
- {! L: [5 S) p- [* t7 t
- PRINT_INFO("-----Restart.....5-------\n");
; n2 Y( m* w7 |9 T - HAL_Delay(1000);
" F7 D+ z& A6 z) n - PRINT_INFO("-----Restart.....4-------\n");
# ]- \8 N" m. q - HAL_Delay(1000);! |: y2 E/ @8 v" T
- PRINT_INFO("-----Restart.....3-------\n");$ X5 H2 k% \+ i4 h; {; Y/ Y
- HAL_Delay(1000);
8 b, x2 w& a$ S l: @* r - PRINT_INFO("-----Restart.....2-------\n");( o @. E; L0 ?/ q3 i C
- HAL_Delay(1000);( _/ V) E4 E/ F. r P2 Y# ^
- PRINT_INFO("-----Restart.....1-------\n");4 B" x1 t9 K- f% D: q
- HAL_Delay(1000);
# b+ x- w; q1 f E2 L% Z/ D -
- @, @2 v# i' |6 ~8 x - PRINT_INFO("the device need update\n");0 T0 p( t# U o
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 8 k* I( D6 g' ^7 x
- PRINT_INFO("firmware write OK\n");
* s0 E2 ?9 M$ w4 B a8 X m - PRINT_INFO("please double click the button the execute the application\n");
) [1 j! P2 L. S; [* M -
, t. M) V* _0 k2 c - HAL_FLASH_Unlock(); //解锁
3 v C. L6 I. s4 h - APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区1 v' i! ? h+ y
- APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志 / ~) m0 g" g9 ]- t+ _/ s
- HAL_FLASH_Lock(); //上锁) q2 s8 R2 K* m2 m, ?
-
* n, J, ^ |/ d2 K. | - PRINT_INFO ( "开始执行 APP\n" );
4 \* G O- ?: E- O+ \ - //执行FLASH APP代码
' y1 v% h8 k* C0 U# q3 k7 b' U& z - IAP_ExecuteApp(APP_START_ADDR);
4 a! f2 d& x4 Q8 ]% T( Z - }
复制代码
' }: Q, ?' G; z4 n$ f `: f. |倒计时5秒后,代码更新,并执行 - PRINT_INFO("-----IAP Menu--------------\n");" M) J: D& v# y! `: A- b% F: R! i' G
- PRINT_INFO("-----Download APP BIN------\n");* J1 A; [& ~! d) y8 P
- PRINT_INFO("-----Restart To RUN APP----\n"); * o! ~- [( F0 e2 A4 Y
- PRINT_INFO("\n\n\n");
5 y' ]4 P( B. B+ [) W0 L0 I -
' a k7 {( V# x0 g. W/ n1 l - if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
/ A' O. m& s5 p, B* x) B; R# ^ - {) a- q" F: f3 f1 A
- PRINT_INFO("-----Restart.....5-------\n"); w3 \1 R; C9 s" F6 m
- HAL_Delay(1000);4 W" q2 a* \4 n: R3 j; T& t
- PRINT_INFO("-----Restart.....4-------\n");
8 }9 | [8 S9 v c# s2 t" Q q* Z - HAL_Delay(1000);
8 u# V' v8 j$ s- c - PRINT_INFO("-----Restart.....3-------\n");
3 G1 ~2 N4 H" w! |+ j, h b6 e - HAL_Delay(1000);
4 |5 v; w- U9 U0 }# n - PRINT_INFO("-----Restart.....2-------\n");
: b; ?* }: m9 t! y - HAL_Delay(1000);
) W: M* e0 J) ^1 ` - PRINT_INFO("-----Restart.....1-------\n");( o$ P$ V8 b# H' X% |
- HAL_Delay(1000);
# X$ x3 x. I: P5 D" e# C - $ {' F9 d- h3 [) ^7 Z5 z
- PRINT_INFO("the device need update\n");0 }4 B8 A( t! C! J6 q* k
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR)); 5 h+ n# S. Z; l; O5 @& T
- PRINT_INFO("firmware write OK\n");5 r! Y3 C: j5 \& j j3 n% |
- PRINT_INFO("please double click the button the execute the application\n");1 j$ J; H2 F3 ?2 ^- z5 v
-
0 M0 [5 I. z+ K z - HAL_FLASH_Unlock(); //解锁
: e' x- P( h7 }! H. p: B5 k( W5 Z. f - APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区
) C: ?* `$ K' J2 F2 M$ K* }0 V - APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志
1 z5 B# Z4 k$ G5 n3 Q - HAL_FLASH_Lock(); //上锁
], n8 N( s2 f -
+ T- m* r/ _% p! k& A; K - PRINT_INFO ( "开始执行 APP\n" );
5 h* ?5 _# \5 m! i, p' Z9 \# c" N - //执行FLASH APP代码3 p* z( S+ ] r" u1 K0 b
- IAP_ExecuteApp(APP_START_ADDR);
0 e! y' Q' E0 e, I; z - }
* h4 @7 X+ c5 O0 [+ ^ - else//不需要更新,执行APP
^+ t' S R% `7 s/ F% o - {$ {1 U7 i+ ?" ?4 q/ u! S7 {
- IAP_ExecuteApp(APP_START_ADDR);# n5 K- ~ Y# x
- }
复制代码 , x; z9 X }! z$ Y
bin文件生成
5 A, g. _2 J# @
$ I+ I8 P: ]; M G% U1 ~% h; c
输入: C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o IAP.bin UART_CircleQueueTest\UART_CircleQueueTest.axf ! d) x. g% v+ ~$ g- N
参数意义: 输出bin文件名称 - UART_CircleQueueTest\UART_CircleQueueTest.axf:
, P, F y/ b0 B }1 l$ N
axf文件目录及文件 要注意boot区的地址范围,根据前面的设计,46K,拿出计算器.... 
; P9 B! I$ \1 W& P m7 c
- o/ o M% o$ \: [2 E) R' x
% ?8 G% O' B* O' M
( z9 Q {' X( t8 k; G- \
) [9 l2 _' u- Q+ `( }. h8 w% T- APP代码设计1 n' l" X& I5 w4 ?5 L6 T8 {
APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码,如下:
$ W: M4 U0 f" T5 d
- boot区代码执行效果' J1 L* ^# o! D4 n# x* q0 j
5 f) J" r- M* N6 _/ y
) _0 e! ~! D) l6 f% Q4 s! _& T/ V& D% V
- APP1代码执行效果5 @9 m' q( u) {7 R" j( |' N* R" ?( Z
) S1 J& F1 Z; m# P
- i& Z8 }7 d0 R1 l/ _ ~" Z0 P7 x; j
, W$ w7 d ` R% j5 z7 f
7 j, Z: ~8 M. X2 \ S
* [! j. M3 {, V" q4 N
 - 接下来再进行一次更新/ ^9 O V6 l: J6 J& J! j% x
2 B: }# D& e0 f/ }: B
" S2 R: S' a8 Y7 ]+ j; O
6 M( j: K/ L0 z: x" y" U
9 y" M1 q \9 G1 {$ V4 ]+ ]7 g' N y$ \
 注意APP BIN文件的flash地址设置
. C& x a7 j* O: I' ~
# [0 t. e; N ^6 _! V
" E$ y- ^* A* R5 b
OK,至此,设计部分就完成了,涉及的内容还是非常多的 本文仅仅用于原理性介绍及IAP功能演示,与工程中实际使用的还是有很大区别的,后面会继续给大家出工程应用方便的教程,会涉及到签名、验签、加解密、校验固件完整性等等
% [5 r* n( S# M" s/ S转载自:Embedded小飞哥
) U& c7 q3 j9 ?, e1 H2 W如有侵权请联系删除9 b- \" n# q/ K+ N o( U
0 O, z/ N3 n5 V9 W |