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