$ h1 C/ ~6 o: M+ h- f$ \/ W 越来越多的应用为了解决出厂后产品软件故障修复和应用升级的问题,增加了Bootloader 程序,这其中也有不少功能安全的应用。ST 提供的功能安全软件的例程都是从 0x08000000 地址启动的,所以这就需要用户自己做一些修改,将应用程序放到偏移地址,并通过 Bootloader 程序进行跳转。其实接下来要介绍的很大一部分和功能安全检测代码不直接相关,但因为也给我们的用户或多或少带来了一些困扰,所以在此做一个总结。 ! ^; s' q0 M0 s: E- q6 ` 这些问题你遇到过吗 ?& `3 I1 h$ W& J6 j ·本来调试好的程序,加了 bootloader 程序后无法正常启动了 + y$ @1 [5 K! }" C' P0 T ·加了 bootloader 后 Flash 校验失败 ·程序一跳转就进入 hardfault …… - \6 f* m( l) R3 o 我们先来看看跳转中出现的问题,这也是遇到最多的情况。 如何保证跳转成功 程序跳转的过程 我们先来看看下面这张图,了解一下从 bootloader (也就是图中的 IAP 程序)跳转到应用程序的过程。 芯片上电后,先从 bootloader 开始执行,在跳转到应用程序之前,它做了两件事情(第3步): 1、从应用程序的起始地址处取出应用程序的栈顶地址,并将其赋给 CPU 的堆栈指针(SP)。 4 E3 f2 s: t( ^$ a" v 2、接着从(起始地址+4)处取出应用程序的复位向量地址,也就是 ResetHandler 函数的地址,然后直接跳转执行该函数。 ; E; R/ W7 ^ P8 F# q! Z / c% V# F2 H) t) x 具体的代码实现请参考 STM32Cube 包中的 IAP 例程,例如 STM32Cube_FW_H7中的 ProjectsSTM32H743I-EVALApplicationsIAPIAP_Main 例程。 + a; V& ^, w. L- v/ G- u - s- o5 N6 O6 ? 从这个时候开始,执行的就不是 bootloader,而是应用程序了。但是此时的程序跳转还未完全完成,我们接着往下看。 ' S! @$ d# f0 ~, |1 n! R 正确配置中断向量偏移地址 1 q8 Y- q, {, u0 e% M 修改了 SP 指针,程序执行也跳转到应用程序部分了,这时顺序执行没有问题了,但是处理中断还有问题,因为应用程序和 bootloader 程序的中断向量表在不同的位置,这时候如果来了中断,CPU 还是会去 bootloader 的中断向量表取中断函数地址。 ' F# }' L" |% p3 t' w 这里也是新手们容易忽略的地方:在应用程序中,需要修改 VTOR 的值。 : @1 W. B4 F. e8 w 一般我们在 SystemInit 函数中完成这个修改操作。 0 r$ J8 x, ]# i+ H+ P3 q 关闭中断和打开中断可以调用 __disable_irq() 和 __enable_irq() 函数。 小心出入栈操作 程序跳转后,除了中断向量表的位置变了,堆栈指针的指向地址也发生了变化。因为出入栈的操作都是编译器处理,所以这也是另一个容易忽视的地方。我们来看下面这个案例。 7 ]+ l# f! b- g; z9 @) u 然后程序就在这里出错进入了 hardfault,无法正常跳转。 6 T& ?9 m1 ], m' b6 s9 D" k 3 c8 J5 S% ^; d7 K% F" J) ^ 为什么这里执行 POP 指令就会出错呢? 6 P6 c: w6 G7 f" U2 d# h' v$ |& _ " ~+ L2 [$ Y. Z9 g ( v# G. \1 N" H# B+ p5 P4 T5 b6 V 我们来回顾一下前面 CPU 的动作。这段代码是将跳转的程序封进了一个函数,在函数里还有一些其他变量的使用。所以程序进入该跳转函数的时候,先执行了一次入栈的操作(PUSH)。请注意,这时的堆栈位置还是 bootloader 程序的堆栈位置,假设是 0x20000680。 7 ]9 h4 h# X1 `" R- Q9 j6 t8 f 1 ~+ N8 O+ ]) u- P: \# M, `, H 接下来,bootloader 程序从应用程序的位置取出新的堆栈的栈顶地址(假设是0x20020000),赋值给了 SP 指针。也就是 “MSR MSP,r0” 这条指令做的事情。这时 SP 指向的堆栈位置已经变了,指针这时指向了 0x20020000。然后当执行到了POP 指令的时候,CPU 就会尝试访问 0x20020000 地址,而这时 0x20020000 地址里的内容是不正确的,所以就出错了。 ! M# Q" P. {9 Q1 M1 a6 \, e9 u0 C 这种情况遇到的不多,但如果我们跳转时遇到了 hardfault,并且在其他的地方没有找到问题,也可以检查一下是否跟堆栈的栈顶地址改变有关系。如果确实是因为SP指针改变引起的 hardfault,可以考虑以下解决办法: 1 W; p1 q2 Z) ~. c8 I# y 不要在执行跳转代码的子函数里使用局部变量; ) t- ?5 P& P; X' L1 S5 H 1 H4 b3 w- m2 w0 c& W8 u 使用 IAR,可以用关键字__stackless 告诉编译器不使用堆栈; 1 e; \& a+ n8 M$ Y9 p 避免将跳转代码放在子函数里执行; 5 E+ a" z$ Z; r: F 正确配置Flash检测参数 M: u: s1 {' P" f. M 除了跳转的问题,Flash 检测也是添加 Bootloader 后经常会出错的一个测试模块。 $ d# \- E. r! n4 l5 L 在集成功能安全软件库的时候,Flash 检测失败是经常碰到的问题,其实如果把 Flash 检测的原理和过程搞清楚了,找到问题一点也不难。 + K0 V+ u2 F* j/ L 总得来说,Flash 检测是通过添加 CRC 值来校验 Flash 的完整性。实现的时候,会涉及到代码开发的三个阶段:编译,烧录和执行。在编译阶段,计算 CRC 参考值;烧录阶段,确保带有 CRC 参考值的执行代码被正确烧录到芯片中;执行阶段,软件重新计算 CRC 值,并与烧录在 Flash 中的 CRC 参考值进行比较。这三个阶段,如果计算的 Flash 范围,使用的 CRC 算法或者保存 CRC 值的位置,任何一点不一致,都会导致 Flash 检测失败。 , x; h- w, K8 `) i b4 E2 |/ A0 W 这里主要想提醒大家的是,如果在使用了 bootloader 程序的情况下,还有哪里需要注意。 6 x0 a/ ]5 H) D0 R S; M' M: L 使用了 bootloader 程序,应用程序的起始地址都不再是 0x08000000,而是有一个偏移。相信大家都不会忘记去修改 linker 文件,也就是 IAR 的 icf,KEIL 的 sct 文件和 CubeIDE 的 ld 文件。 3 n: [# {( w/ O5 } 2 `2 @# H/ c/ D" h. C" I: d 除了修改 linker 文件,MCU 程序中涉及到 Flash CRC 检测部分的代码也需要进行相应调整: ) L8 [4 n1 P% B; P8 {2 r 上图中的编译阶段,需要根据实际应用程序的起始地址来修改 IDE 或者第三方工具计算 CRC 值范围的起始地址。 执行阶段,需要确认 MCU 程序执行时计算 CRC 的起始地址和实际应用程序的起始地址是一致的。 / t0 N; D1 X D c% U4 o- J 如果以 X-CUBE-CLASSB 代码为例,那就还需要修改 stm32xx_STLparam.h 文件中ROM_START 的定义。 3 v9 C* O3 T. X* W" z 7 Q6 ]# G! M0 h+ C( ~% d2 ? 注意好上面的这几点,相信会减少在添加 Bootloader 过程中遇到的不少问题。4 A6 z& g+ l: l" D6 r5 y 9 u$ K0 s8 D, j: u8 W" A" ` |
基于STM32代码的启动过程经验分享
基于STM32 GPIO 经验分享
ClassB在STM32CubeIDE上的移植可能遇到的问题
基于STM32看似无法唤醒的一种异常现象经验分享
【我的STM32U5 项目秀】+02-STM32U5利用LL库点灯
【STM32小技巧】主函数循环&烧录
【STM32小技巧】STM32硬件错误的调试技巧
【STM32小技巧】+输入捕获的io口配置
【STM32小技巧】STM32移植FAFTS文件系统遍历创建文件夹
【我心中的STM32H7A3】+内容