STM32在线升级(IAP)超详细图解 及 需要注意的问题解决 IAP 是啥
. J4 y! R6 @% ?( ` IAP( In Application Programming)即在应用编程,也就是用户可以使用自己的程序对MCU的中的运行程序进行更新,而无需借助于外部烧写器。其实ST官网也给出了IAP的示例程序,感兴趣的可以直接去官网搜索。
0 Q" m8 C$ U, t. b% q; T# `# w( w 这里有一点需要特殊注意,就是在MCU中,有一个特殊区域被称为 System memory。在这块区域中存放了ST公司自己的 bootloader 程序,它是在MCU出厂时,有ST固化到芯片中的,后续不能再更改。其中的 bootloader 程序也可以对MCU进行升级(DFU对芯片的编程应该就是用的这个Bootloader)。而且,芯片不同,BootLoader的功能也是有区别的。ST官网对于这些也是有详细文档的,后续再写篇文章介绍这一块。下图为部分芯片BootLoader版本及功能
8 p( r& z6 z6 m9 S% o在这里插入图片描述
! b9 [$ w) L/ J- TSTM32 MCU启动配置9 w* E5 h' ^% U0 V" o
要实现IAP,首先要了解一下MCU是如何启动的。这一点在芯片的参考手册中都有详细的说明,不同的芯片手册所在位置可能不同,但是一般在第二章会有单独一节叫Boot configuration。如下图:
" s- x3 a' o0 a J' f6 ^2 JBootConfig
7 V, W/ l. [+ T主要就是说,启动是通过管脚BOOT0和BOOT1的连接方式来控制的。这个是在硬件设计阶段设计好的。不同的配置决定了,MCU将何处映射到0x00000000。从这里又可以看到一点,MCU眼里只有0x00000000。至于为啥可以从Flash(0x08000000)启动,就是因为MCU内部做了映射。从其他位置启动时同理。
+ n$ i4 Y. H' @IAP 实现$ W. ?) p, M: j' ] I- G
要实现IAP,则整个程序实现分为大程序(APP)和小程序(IAP)两部分。其中,APP主要接收升级数据并存储,IAP处理擦除APP,并重新写入升级数据。此外,IAP还应该可以独立接收升级数据的情况。但是,由于Cortex-M0核是没有中断向量表偏移寄存器的,这就导致了在Cortex-M0核的MCU上实现在线升级比较麻烦。在实际产品中,整个程序的基本组成结构:
7 ]0 E9 {$ G: j% H/ p$ o0 n实际的IAP流程如下:
+ ^7 [+ C; m( r3 j) I就是这么简单!- ~- E7 G% V6 R0 g' R; ~9 f
0 k) F U* D8 F# y# j% ^
' [! _# ?% r9 U注意:) ?1 ]( S% i# f3 g9 F
(1)与 Cortex-M3 和 Cortex-M4 不同,Cortex-M0 没有中断向量表偏移寄存器(VTOR寄存器)' N' G, i% S* j1 Q; r
(2)Cortex-M3 r2p0 及其之前版本,中断向量表只能位于SRAM或者CODE区域,但是Cortex-M3 r2p1及之后,Cortex-M4 没有该限制!
4 v- J4 Z) l* Y# Q! I- b8 R(3)MCU根据Boot引脚配置将指定地址映射为0x地址!5 Y$ g; S) t; E( i7 x
IAP 启动
5 G0 J3 L; A$ L 启动网上有很多文章介绍,但是或多或少不是很完善,我只做了一张相对来说比较详细的图,如下:; @. K A, v* G8 a
Startup
( Z1 Z4 E8 w7 K6 L$ ?. B9 J5 F Cortex-M内核规定,中断向量表开始的4个字节存放的是堆栈栈顶的地址,其后是中断向量表各中断服务程序的地址。当发生中断后程序通过查找该表得到相应的中断服务程序入口地址,然后再跳到相应的中断服务程序中执行,中断服务程序中最终调用用户实现的各函数。例如:main函数就是复位中断服务函数中调用的!
8 H* v9 }9 A9 r6 A( f' N! z 在没有IAP时,上电后从0x08000004处取出复位中断向量的地址,然后跳转到复位中断程序的入口(标号①所示),执行结束后跳转到main函数中(标号②所示)。通常main函数是个死循环,不会退出。在执行main函数的过程中发生中断,则STM32强制将PC指针指回中断向量表处(标号④所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数(标号⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。
% ~$ K% X. Z. H! F- }, {8 `; {! K 在添加IAP后,上电后仍然从0x08000004处取出复位中断向量的地址,然后跳转到复位中断程序的入口(标号①所示),执行结束后跳转到小程序的main函数中(标号②所示)。在执行小程序main函数的过程中发生中断,则STM32强制将PC指针指回中断向量表处(标号④所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数(标号⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。而想要大程序执行,则必须在小程序中显示强制跳转(标号⑦)。# Z3 r4 v- a, U! W/ c/ ~1 U, j
在大程序的main函数的执行过程中,如果CPU得到一个中断请求,由于我们设置了中断向量表偏移量为N+M,因此PC指针被强制跳转到0x08000004+N+M处的中断向量表中得到相应的中断函数地址,再跳转到相应新的中断服务函数,执行结束后返回到main函数中来。
0 `7 X( R9 r. W }5 u 需要注意的是,复位中断比较特殊。产生复位后,PC的值会被硬件强制置为0x08000004。因为,在发生复位后,负责中断向量偏移的寄存器VTOR变为了0,因此,复位后的中断就变为了0x08000004。而其他中断发生时,VTOR为已经设置好的终端向量表偏移。
. ?0 `8 A- N9 _2 z0 h6 _
' K8 U) N! O' [) @% x/ F( C( j% c
5 r: F! j# {7 v5 B9 D. G程序实现
# m3 }& p9 B ~1 X8 J5 t! [! i 有了上面的介绍,实现就比较简单了!其实我有设计了一套适用于全部STM32芯片的IAP模板,但是属于公司产品,不方便对外公布!简单说几个重点:- Y! A7 [ W# G. r. P" R0 _
) B7 m: Q, {4 n
' X9 ^' h/ u. Y4 F5 T9 R使用 分散加载文件 实现起来会比较方便
$ F" k% z1 _% l# i% h8 X1 w对于没有中断向量表偏移寄存器的MCU(主要是Cortex-M0核),一般采用将中断向量表复制到指定位置的内存中的方式实现:; [4 w6 j9 X% X2 r% ]6 }' ?3 S- Y
使用分散加载文件在内存中指定一块区域:
7 B5 ]/ i: }: v; V* ]0 T- #if (defined ( __CC_ARM )) Z' l# P# f. M' \
- __IO uint32_t VectorTable[48] __attribute__((section("SECTION_APP_VECTOR")));* @& Q5 E$ m: V
- #elif (defined (__ICCARM__))
2 v, }. ]6 @( @1 E; s# K! s - #pragma location = 0x20000000
3 m6 s2 z# [, y7 G* w: j - __no_init __IO uint32_t VectorTable[48];
" G/ ?% m2 e" U+ |+ C - #elif defined ( __GNUC__ )
$ P; u& j7 x8 y% X& P* Q - __IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
$ ~; h* a# S5 I' f+ T& w D% X T - #elif defined ( __TASKING__ )
- R& Y. N7 \" i* L* F( [% W% [ - __IO uint32_t VectorTable[48] __at(0x20000000);7 \6 Z ^. m T. w
- #endif
复制代码 . C2 u) B- y$ f- a) M! S5 \
2.将APP的终端向量表复制到以上位置,设置中断向量表重映射
1 s8 r$ E; I1 L- static void SetVectorTable(void)* l8 Q7 y. S6 U0 l0 @6 O, Q
- {
8 U4 B, W: P- @, _0 F3 {3 z - int i;
$ M6 t' Y7 }9 W' U9 Z! _$ S8 t
, u7 a. G2 ?5 |: v# j- R- /*!< At this stage the microcontroller clock setting is already configured,
* N, `& p2 @9 i3 B# r: Y - this is done through SystemInit() function which is called from startup
$ ^- a6 v" h# k2 C - file (startup_stm32f0xx.s) before to branch to application main.
1 }% R1 G$ o( I - To reconfigure the default setting of SystemInit() function, refer to4 V% h7 O& ?7 g) J$ a( ?# m: M
- system_stm32f0xx.c file
$ S- L# u& c1 k; J# z! I - */ # V0 K, ]; Q1 R& b; G& e/ H9 P* `
- & Y% h& @9 `$ I: z+ h% ~
- /* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/
* F" T( i$ |7 h9 I, }" N2 k6 y' r2 A - /* Copy the vector table from the Flash (mapped at the base of the application load address 0x08003000) to the base address of the SRAM at 0x20000000. */
( \4 A. t, n9 e) V( S1 q$ J/ B - for(i = 0; i < 48; i++)
, A1 H+ I2 s& s5 F; r- W - {3 ^- d n; r# i+ o4 s
- VectorTable[i] = *(__IO uint32_t*)(APP_SPACE_ADDR + (i<<2));
S7 s& o* i& W! V6 v' N9 D - }4 R+ _2 z; P7 A' J
8 I; ?, r4 h. `- /* Enable the SYSCFG peripheral clock */
. s" M% _8 M3 ^# k - RCC_APB2PeriphResetCmd(RCC_APB2Periph_SYSCFG, ENABLE); /* 注意:ST官方例程使用 RCC_APB2PeriphResetCmd是不对的 */
% v `/ i Z8 P - /* Remap SRAM at 0x00000000 */% J9 H: H& E; ~9 }& |( P- W
- SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
, y! n/ {' T+ J# @2 k4 ^8 O - }
复制代码
: e* x7 W$ E1 J# `( ^! f3.在由 IAP 跳转到 APP 时,一定注意把 IAP 中开启的外设全部关闭,否则在刚进入 APP中时,如果产生中断将导致死机等问题。 包括 SysTic 中断!!!包括 SysTic 中断!!!包括 SysTic 中断!!!这里可以做测试:
7 F7 |; c4 c+ g7 ~6 I4 G 1.测试一:IAP 中开启串口,然后用上位机不停的发送数据,在发送数据过程中执行 IAP 跳转 APP
3 |7 _" g9 ^1 {0 c! A5 N 2.将 SysTick 中断 配置时间很短(微秒级别),当程序跳转到 APP 后,会出现 先产生 SysTick 中断,然后才会到 main 函数。此时如果 SysTick 中断中有相关代码,将导致出现错误!2 R- \" j# V2 l6 f8 F( b( W
# j6 A. C* p9 a8 @
4.STM32 的 back SRAM 在 IAP 中和 APP 中都初始化时,将导致 APP中的初始化不起作用。如果 IAP 中有使用,则在跳转 APP前必须反初始化。4 a1 k m5 g$ X. V0 B$ Y% j
4 L$ @% P4 P6 m5 P# H$ W. m
$ e$ D: `* A% a5 Z2 f9 x3 g! {( O: k* i* V9 Y8 h
2 H* S# ~& p# q/ s* Z9 @- u, z3 ?9 Z( `7 I$ H- h
2 g# X* x" E+ ^4 I% q3 \& ^# L. Z( I' ]% T* M6 U4 a3 h
|