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