STMCU小助手
发布时间:2023-8-13 14:06
|
01引言 在STM32的应用中,SPI算是用的比较多的外设了,也是单片机最常见外设之一。客户说它执行了关闭SPI的代码,竟然会导致Flash中的WRPERR标志置位,致使应用碰到一些问题。这就奇怪了,SPI和内部Flash看起来是风马牛不相及的事情,为什么会发生这种事呢?一起来看看吧。 ) G# }2 o- _% @! H 02问题2.1 问题起源 $ G/ I* d, y6 p' S2 D$ @ 客户在使用STM32L072RBT6的时候,使用STM32 CubeL0库,在程序编写时,发现执行关闭SPI代码时,会导致Flash的写保护错误标志WRPERR置位,导致其后面准备写EEPROM的时候,就无法对EEPROM写入了。 客户使用两个标志flag1和flag2,来观察WRPERR标志的变化。代码如图1所示。 5 y5 o$ n" Y. s# v
图1.用户测试代码 # F8 ?1 i8 d% h4 J/ x+ g/ \ 在执行这个代码时,前面flag1还等于0,执行到flag2那句,就变成flag2等于1了,同样地取了WRPERR标志位的值。所以客户就怀疑执行_HAL_SPI_DISABLE()会把Flash的WRPERR标志置1了。 因为在对EEPROM编程中,需要先调用位于stm32l0xx_hal_flash.c中的FLASH_WaitForLastOperation()函数,此函数中,将会对Flash所有错误标志进行检查,如果出现了错误,它则返回HAL_ERROR,导致后续对EEPROM的编程不会被执行。 2.2 问题重现 0 r3 s6 S8 n* A' g8 O% x+ L 使用NUCLEO-L053R8来验证客户的这个问题。在\STM32Cube_FW_L0_V1.10.0\Projects\STM32L052R8-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling例程中直接进行修改测试。 首先,把客户的测试代码加到例程中SPI初始化之后的位置。如图2所示。 w, ~2 o- u" l9 P# x
图2.测试代码1(位于SPI初始化之后) : A2 Q6 O; p; j( W, F 编译,并在线调试,发现并没有出现客户所描述的问题。如图3所示。
图3.测试代码1结果(位于SPI初始化之后) : m3 I+ w; u8 Q' l l# r; f 可以看到,WRPERR的值并没有被置1,Flag1和Flag2的值也都是0。那么,为什么客户说他那边会有这个问题呢? ( p$ i- O* u7 Q! \ 再回头仔细看一下客户的测试代码,发现客户的测试代码中并没有对SPI进行初始化,其_HAL_SPI_DISABLE()代码是放在其他外设初始化之后的。 ( [( v$ H: {) s! }* f 好,那么再来修改一下测试代码,把客户这三句测试代码挪动到SPI初始化之前,如图4所示。
图4.测试代码2(位于SPI初始化之前) 9 Q, Q1 n# ~. \- V% p: {5 W" I 编译,并在线调试,这时,会惊奇地发现客户所描述地问题来了。其结果如图5所示。 3 G% b0 B7 Z1 ^& c5 p1 ]4 a
图5.测试代码2结果(位于SPI初始化之前) 3 I; G$ s3 h; P 可以看到,这时Flash的WRPERR标志位置1了,测试代码中,flag2的值也跟flag1不同了。 3 K3 F1 Q; w2 f 再做一个实验,将此处的HAL库写法,改成直接操作寄存器,来试一下。测试代码变成是图6这样的。
图6.测试代码3(位于SPI初始化之前,直接操作寄存器) 编译,在线调试,这次又惊喜地发现,问题不见了。结果如图7所示。 7 K+ x" U: a U! Q& s8 x% l
图7.测试代码3结果(位于SPI初始化之前,直接操作寄存器) ; ]7 M+ L. R9 u$ g 三种操作,为什么只有第二种方式有问题呢?而且为什么错的偏偏是Flash的写保护错误标志WRPERR呢?接下来可以分析一下它们的反汇编代码,看看到底是哪里出问题了。 5 v. Z% X' t$ D/ S7 z$ b3 \- t 2.3 反汇编分析 + J( Z# ]! L: I! W. [9 Z8 n 对于三种情况,把反汇编拉出来看最清楚其操作过程了。 ; q/ B) d: h8 J6 P 先分析第一种情况——测试代码位于SPI初始化之后。其反汇编如图8所示。
图8.测试代码1的反汇编(位于SPI初始化之后) 8 c7 v8 l3 j* ^0 _6 W5 h @ 从之前的Watch窗口,知道flag1的地址为 0x2000000c,flag2的地址为0x2000000d。 / f3 o3 V* ^# V, L4 S& `1 z 现在对三句C语言测试语句的反汇编语句进行解析,如下: 7 P2 k' y/ L9 D3 C( V6 |
可以看到,这段汇编是一点问题都没有的。 ( X: k: w; t4 E" L 接下来,先分析第三种情况——也就是测试代码放在SPI初始化之前,但是使用直接操作寄存器的方式。其反汇编如图9所示。
图9.测试代码3的反汇编(位于SPI初始化之前,直接操作寄存器) - B( q i8 ^3 P7 H& h; p+ R 从之前的Watch窗口,知道flag1的地址为0x2000000c,flag2的地址为0x2000000d。 ! P. ?1 o9 |$ V# h4 D u 现在对三句C语言测试语句的反汇编语句进行解析,如下:
可以看到,这段汇编也是一点问题都没有的。 最后,再来分析一下有问题的第二种情况,也就是测试代码放在SPI初始化之前,但是使用_HAL_SPI_DISABLE()关闭SPI的情况。其反汇编如图10所示。
图10.测试代码2的反汇编(位于SPI初始化之前) 从之前的Watch窗口,知道flag1的地址为0x20000008,flag2的地址为0x20000009。 2 a9 j' ]4 q# g 现在对三句C语言测试语句的反汇编语句进行解析,如下:
可以看到,问题出在哪了?问题就出在“STR R3,[R 2]”这个语句上,这个语句在0x00000000这个位置写值,而0x00000000此时映射的是Flash的地址0x08000000,也就是Stack Pointer的位置。如图11和图12所示。 / _6 Y2 c, v- Z/ I
图11.0x00000000地址的数据
图12.0x08000000地址的数据 首先,这个位置本来就不应该被修改。 第二,因为没有对Flash程序存储器进行解锁,就往里边写值,就会造成写保护错误,导致WRPERR标志位置位。所以,可以明白为什么WRPERR会被置位了。 9 ]; m4 ]7 ~$ b9 k/ u o$ T1 W 可是关键的问题在哪儿呢?在执行“LDR R2,[R0,#4]”这条语句时,R2本来应该是SPI2_CR1的地址,但是它竟然是0x00000000!如图13所示。
图13.0x2000000c地址的数据 2 V% M; g+ k6 U 从Watch窗口来看一下SpiHandle的情况。如图14所示。 / c$ b% P" ~+ F
图14.SpiHandle(未初始化) 从图14可以看到,其实刚才的0x2000000c地址就是SpiHandle结构体的地址,也是SpiHandle.Instance的地址,而SpiHandle.Instance的值为0。SpiHandle.Ins tance.CR1的地址为0x0,导致显示它装载的值是Stack pointer的值0x20000468,这里本应该是SPI2_CR1的地址和SPI2_CR1的值。 也就是因为这里的问题,才会导致了后面的WRPERR错误。 . F- ?( P+ _* a9 c- o1 R/ [ 2.4 代码分析 再回到代码这边来看一下,有问题的代码究竟是有什么情况。客户的代码主要就是一句关闭SPI的语句“_HAL_SPI_DISABLE(&SpiHandle);”。 4 ^' n9 |& N8 M8 s. W1 w( Q$ L) E 这个语句是怎么解析的?它再stm32l0xx_hal_spi.h中解析,如图15所示。
图15._HAL_SPI_DISABLE函数 " D; ?3 B+ N( m7 n) H 看到这个函数时,看到了重要的字眼——“Instance”!就明白是什么问题了,因为这个SpiHandle.Instance还没有被初始化呢!这也说明了为什么在图14中,看到的SpiHandle.Instance的值为0x0,而SpiHandle.Instance. CR2的值为0x20000468。关键就在于这个SpiHandle. Instance还没有初始化。 9 [" M% c1 [; W0 p 所以,把客户的测试代码放在SPI初始化代码之后没有问题,就是因为这个SpiHandle.Instance已经被初始化过了。所以,它不会有问题。 问题解决 5 m/ G! p$ }3 ^+ x; }* k$ S; q4 y 本来客户的代码就没有必要这么写,因为SPI都没初始化,对它进行关闭并没有什么意义。 4 y g0 l( H9 m' K; h+ v$ v 如果非要在这里关闭SPI的话,那就要先对SpiHandle.Instance进行初始化才行。如图16所示。 ! l2 p; J5 B4 Z3 L6 j' G2 m
图16._HAL_SPI_DISABLE函数 加了“SpiHandle.Instance=SPIx;”初始化后,再跑这段代码,就不会出现客户所说的问题了。 现在再来看一下SpiHandle的情况。 : y' ^/ a5 D6 t/ q8 _ W. [6 y
图17.SpiHandle(SpiHandle.Instance已初始化) 经过对SpiHandle.Instance的初始化,这里就可以看到SpiHandle.Instance的值为0x40003800了,为SPI2外设寄存器的基地址,而且可以看到SpiHandle.Instance. CR1的地址就是SPI2_CR1的地址0x40003800,值也是SPI2_CR1的值0x0了。 & t8 S- n4 `$ ]) P; H 在用户代码中,SpiHandle只是定义了SPI_HandleTypeDef结构体,其各种参数并还没有进行实际初始化。在没有初始化的前提下,对其进行操作时不对的,也是危险的,应该在写代码的时候引起重视。 使用HAL库的时候,如果要对一个外设进行任何的操作,请务必记得它是被初始化过的。否则,出了问题可能都不一定知道。 转载自: STM32单片机 如有侵权请联系删除 - J+ Z+ ?2 z% z! E* n& y8 [' d |
基于stm32平台上的IC-MU磁绝对值编码器SPI通讯和码盘数据处理
SPI 32位宽DMA方式收发失败问题
SPI 高温读错最后一位?STM32F42xx 官方根治方案
经验分享 | LAT1470 SPI传输长度超过1024时出现异常的问题分析
经验分享 | 使用EXIT0同步触发SPI的DMA发送话题
经验分享 | STM32H723 SPI 通讯异常排查:实时观察窗口的 “隐形干扰” 解决方案
【STM32C0评测】4、驱动Lorasx126x,实现透传
经验分享 | STM32 SPI半工单线通信示例
经验分享 | 多个SPI的DMA传输为何仅1个有效?
经验分享 | STM32C0 HAL 库的 SPI 驱动导致的 Hardfault 问题分析
微信公众号
手机版