关闭 SPI 会导致 WRPERR 错误的问题分析

1. 引言

在 STM32 的应用中,SPI 算是用的比较多的外设了,也是单片机最常见外设之一。客户说它执行了关闭 SPI 的代码,竟然会导致 Flash 中的 WRPERR 标志置位,致使应用碰到一些问题。这就奇怪了,SPI 和内部 Flash 看起来是风马牛不相及的事情,为什么会发生这种事呢?一起来看看吧。

2. 问题

2.1. 问题起源

客户在使用 STM32L072RBT6 的时候,使用 STM32CubeL0 库,在程序编写时,发现执行关闭 SPI 代码时,会导致 Flash 的写保护错误标志 WRPERR 置位,导致其后面准备写 EEPROM 的时候,就无法对 EEPROM 写入了。

客户使用两个标志 flag1 和 flag2,来观察 WRPERR 标志的变化。代码如图 1 所示。

图1.用户测试代码
在这里插入图片描述
在执行这个代码时,前面 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. 问题重现

使用 NUCLEO-L053R8 来验证客户的这个问题。在\STM32Cube_FW_L0_V1.10.0\Projects\STM32L053R8-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling 例程中直接进行修改测试。

首先,把客户的测试代码加到例程中 SPI 初始化之后的位置。如图 2 所示。

图2.测试代码 1(位于 SPI 初始化之后)
在这里插入图片描述
编译,并在线调试,发现并没有出现客户所描述的问题。如图 3 所示。

图3.测试代码 1 结果(位于 SPI 初始化之后)
在这里插入图片描述
可以看到,WRPERR 的值并没有被置 1,flag1 和 flag2 的值也都是 0。那么,为什么客户说他那边会有这个问题呢?

再回头仔细看一下客户的测试代码,发现客户的测试代码中并没有对 SPI 进行初始化,其__HAL_SPI_DISABLE()代码是放在其他外设初始化之后的。

好,那么再来修改一下测试代码,把客户这三句测试代码挪动到 SPI 初始化之前,如图 4 所示。

图4.测试代码 2(位于 SPI 初始化之前)
在这里插入图片描述
编译,并在线调试,这时,会惊奇地发现客户所描述的问题来了。其结果如图 5 所示。

图5.测试代码 2 结果(位于 SPI 初始化之前)
图5.测试代码 2 结果(位于 SPI 初始化之前)
可以看到,这时 Flash 的 WRPERR 标志位置 1 了,测试代码中,flag2 的值也跟 flag1不同了。

再做一个实验,将此处的 HAL 库写法,改成直接操作寄存器,来试一下。测试代码变成是图 6 这样的。

图6.测试代码 3(位于 SPI 初始化之前,直接操作寄存器)
图6.测试代码 3(位于 SPI 初始化之前,直接操作寄存器)
编译,在线调试,这次又惊喜地发现,问题又不见了。结果如图 7 所示。

图7.测试代码 3 结果(位于 SPI 初始化之前,直接操作寄存器)
在这里插入图片描述
三种操作,为什么只有第二种方式有问题呢?而且为什么错的偏偏是 Flash 的写保护错误标志 WRPERR 呢?接下来可以分析一下它们的反汇编代码,看看到底是哪里出问题了。

2.3. 反汇编分析

对于三种情况,把反汇编拉出来看最清楚其操作过程了。

先分析第一种情况——测试代码位于 SPI 初始化之后。其反汇编如图 8 所示。

图8. 测试代码 1 的反汇编(位于 SPI 初始化之后)
在这里插入图片描述
从之前的 Watch 窗口,知道 flag1 的地址为 0x2000000c,flag2 的地址为0x2000000d。

现在对三句 C 语言测试语句的反汇编语句进行解析,如下:

LDR.N R0, 			??DataTable0_4 ; 将 Flash_SR 的地址赋值给 R0
LDR R1, [R0] 		; 取出 Flash_SR 中的值,赋值给 R1
LSLS R2, R1, #23 	; R1 值左移 23 位,赋值给 R2
LSRS R2, R2, #31 	; R2 值再右移 31 位,赋值给 R2,将 WRPERR 值挪到 Bit0
STRB R2, [R4] 		; R2 值写到 R4 指向的数据,此时 R4 的值为; 0x2000000c,正好是 flag1 的地址,所以此操作将; WRPERR 值写入 flag1
LDR R1, [R4, #0x4] 	; 将地址 0x20000010 的值 0x40003800 赋给 R1; 0x40003800 为 SPI2_CR1 的地址
LDR R2, [R1] 		; 取出 SPI2_CR1 的值,赋值给 R2,R2=0x0000033e
MOVS R3, #64 		; R3 = 0x40,Bit6 对应 SPE
BICS R2, R2, R3 	; 清除 R2 的 Bit6(准备关闭 SPI2)
STR R2, [R1] 		; 将 R2 的值写回 SPI2_CR1,关闭 SPI2
LDR R0, [R0] 		; 取出 Flash_SR 中的值,赋值给 R0
LSLS R1, R0, #23 	; R0 值左移 23 位,赋值给 R1
LSRS R1, R1, #31 	; R1 值再右移 31 位,赋值给 R1,将 WRPERR 值挪到 Bit0
STRB R1, [R4, #0x1] ; R1 值写到[R4+1](也就是地址 0x2000000d)指向的位置; 0x2000000d,正好是 flag2 的地址,所以此操作将; WRPERR 值写入 flag2

可以看到,这段汇编是一点问题都没有的。

接下来,先分析第三种情况——也就是测试代码放在 SPI 初始化之前,但是使用直接操作寄存器的方式。其反汇编如图 9 所示。

图9.测试代码 3 的反汇编(位于 SPI 初始化之前,直接操作寄存器)
在这里插入图片描述
从之前的 Watch 窗口,知道 flag1 的地址为 0x2000000c,flag2 的地址为0x2000000d。

现在对三句 C 语言测试语句的反汇编语句进行解析,如下:

LDR.N R0, ??DataTable0_2 	; 将 flag1 的地址赋值给 R0
LDR.N R1, ??DataTable0_3 	; 将 Flash_SR 的地址赋值给 R1
LDR R2, [R1] 				; 取出 Flash_SR 中的值,赋值给 R2
LSLS R3, R2, #23 			; R2 值左移 23 位,赋值给 R3
LSRS R3, R3, #31 			; R3 值再右移 31 位,赋值给 R3,将 WRPERR 值挪到 Bit0
STRB R3, [R0] 				; R3 值写到 R0 指向的数据,也就是 WRPERR 值写入 flag1
LDR R2, ??DataTable0_4 		; 将 SPI2_CR1 的地址 0x40003800 赋给 R2
LDR R3, [R2] 				; 取出 SPI2_CR1 的值,赋值给 R3,R3=0x00000000
MOVS R4, #64 				; R4 = 0x40,Bit6 对应 SPE
BICS R3, R3, R4 			; 清除 R3 的 Bit6(准备关闭 SPI2)
STR R3, [R2] 				; 将 R3 的值写回 SPI2_CR1,关闭 SPI2
LDR R1, [R1] 				; 取出 Flash_SR 中的值,赋值给 R1
LSLS R2, R1, #23 			; R1 值左移 23 位,赋值给 R2
LSRS R2, R2, #31 			; R2 值再右移 31 位,赋值给 R2,将 WRPERR 值挪到 Bit0
STRB R2, [R0, #0x1] 		; R1 值写到[R0+1](也就是地址 0x2000000d)指向的位置; 0x2000000d,正好是 flag2 的地址,所以此操作将; WRPERR 值写入 flag2

可以看到,这段汇编也是一点问题都没有的。

最后,再来分析一下有问题的第二种情况——也就是测试代码放在 SPI 初始化之前,但是使用__HAL_SPI_DISABLE()关闭 SPI 的情况。其反汇编如图 10 所示。

图10. 测试代码 2 的反汇编(位于 SPI 初始化之前)
在这里插入图片描述
从之前的 Watch 窗口,知道 flag1 的地址为 0x20000008,flag2 的地址为0x20000009。

现在对三句 C 语言测试语句的反汇编语句进行解析,如下:

LDR.N R0, ??DataTable0_2 	; 将 flag1 的地址 0x20000008 赋值给 R0
LDR.N R1, ??DataTable0_3 	; 将 Flash_SR 的地址赋值给 R1
LDR R2, [R1] 				; 取出 Flash_SR 中的值,赋值给 R2
LSLS R3, R2, #23 			; R2 值左移 23 位,赋值给 R3
LSRS R3, R3, #31 			; R3 值再右移 31 位,赋值给 R3,将 WRPERR 值挪到 Bit0
STRB R3, [R0] 				; R3 值写到 R0 指向的数据,也就是 WRPERR 值写入 flag1
LDR R2, [R0, #4] 			; 将地址 0x2000000c 中的值 0x00000000 取出,赋值给 R2
LDR R3, [R2] 				; 取出地址 0x00000000 中的值,赋值给 R3,; R3 值为 0x20000468
MOVS R4, #64 				; R4 = 0x40,Bit6 对应 SPE
BICS R3, R3, R4 			; 清除 R3 的 Bit6
STR R3, [R2] 				; 将 R3 的值写回[0x00000000],WRPERR 置位,出错了!
LDR R1, [R1] 				; 取出 Flash_SR 中的值,赋值给 R1
LSLS R2, R1, #23 			; R1 值左移 23 位,赋值给 R2
LSRS R2, R2, #31 			; R2 值再右移 31 位,赋值给 R2,将 WRPERR 值挪到 Bit0
STRB R2, [R0, #0x1] 		; R1 值写到[R0+1](也就是地址 0x20000009)指向的位置; 0x20000009,正好是 flag2 的地址,所以此操作将; WRPERR 值写入 flag2

可以看到,问题出在哪了?问题就出在“STR R3, [R 2]”这个语句上,这个语句在向 0x00000000 这个位置写值,而 0x00000000 此时映射的是 Flash 的地址0x08000000,也就是 Stack Pointer 的位置。如图 11 和图 12 所示。

图11. 0x00000000 地址的数据
在这里插入图片描述
图12. 0x08000000 地址的数据
图12. 0x08000000 地址的数据
首先,这个位置本来就不应该被修改。

第二,因为没有对 Flash 程序存储器进行解锁,就往里边写值,就会造成写保护错误,导致WRPERR 标志位置位。所以,可以明白为什么 WRPERR 会被置位了。

可是关键的问题在哪儿呢?在执行“LDR R2, [R0, #4]”这条语句时,R2 本来应该是 SPI2_CR1 的地址,但是它竟然是 0x00000000!如图 13 所示。

图13. 0x2000000c 地址的数据
在这里插入图片描述
从 Watch 窗口来看一下 SpiHandle 的情况。如图 14 所示。

图14. SpiHandle(未初始化)
在这里插入图片描述
从图 14 可以看到,其实刚才的 0x2000000c 地址就是 SpiHandle 结构体的地址,也是SpiHandle.Instance 的地址,而 SpiHandle.Instance 的值为 0。SpiHandle.Instance.CR1的地址为 0x0,导致显示它装载的值是 Stack pointer 的值 0x20000468,这里本应该是SPI2_CR1 的地址和 SPI2_CR1 的值。

也就是因为这里的问题,才会导致了后面的 WRPERR 错误。

2.4. 代码分析

再回到代码这边来看一下,有问题的代码究竟是有什么情况。客户的代码主要就是一句关闭 SPI 的语句“__HAL_SPI_DISABLE(&SpiHandle);”。

这个语句是怎么解析的?它在 stm32l0xx_hal_spi.h 中解析,如图 15 所示。

图15. __HAL_SPI_DISABLE 函数
在这里插入图片描述
看到这个函数时,看到了重要的字眼——“Instance” !就明白是什么问题了,因为这个 SpiHandle.Instance 还没有被初始化呢!这也说明了为什么在图 14 中,看到的SpiHandle.Instance 的值为 0x0,而 SpiHandle.Instance.CR2 的值为 0x20000468。关键就在于这个 SpiHandle.Instance 还没有初始化。

所以,把客户的测试代码放在 SPI 初始化代码之后没有问题,就是因为这个SpiHandle.Instance 已经被初始化过了。所以,它不会有问题。

3. 问题解决

本来客户的代码就没有必要这么写,因为 SPI 都没初始化,对它进行关闭并没有什么意义。

如果非要在这里关闭 SPI 的话,那就要先对 SpiHandle.Instance 进行初始化才行。如图 16 所示。

图16. __HAL_SPI_DISABLE 函数
在这里插入图片描述
加了 “SpiHandle.Instance=SPIx ;”初始化后,再跑这段代码,就不会出现客户所说的问题了。

现在再来看一下 SpiHandle 的情况。

图17. SpiHandle(SpiHandle.Instance 已初始化)
在这里插入图片描述
经过对 SpiHandle.Instance 的初始化,这里就可以看到 SpiHandle.Instance 的值为0x40003800 了,为 SPI2 外设寄存器的基地址,而且可以看到 SpiHandle.Instance.CR1的地址就是SPI2_CR1 的地址 0x40003800,值也是 SPI2_CR1 的值 0x0 了。

4. 小结

在用户代码中,SpiHandle 只是定义了 SPI_HandleTypeDef 结构体,其各种参数并还没有进行实际初始化。在没有初始化的前提下,对其进行操作是不对的,也是危险的,应该在写代码的时候引起重视。

使用 HAL 库的时候,如果要对一个外设进行任何的操作,请务必记得它是被初始化过的。否则,出了问题可能都不一定知道。

参考文献

在这里插入图片描述

文档中所用到的工具及版本

IAR EW for Arm 9.20.4


本文档参考ST官方的《【应用笔记】LAT1178+关闭SPI会导致WRPERR错误的问题分析》文档。
参考下载地址:https://download.csdn.net/download/u014319604/88971344

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/615097.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

生成式AI对UiPath来说是机遇还是挑战?

企业争相通过技术革新来领跑市场,机器人流程自动化(RPA)技术更是将企业的效率和成本控制推向了新的高度。但当人工智能(AI)的最新进展——生成式AI登上舞台时,它不仅带来了变革的可能,还提出了一…

前端大屏项目适配方法

要在F11全屏模式下查看 方法一,rem font-size 动态设置HTML根字体大小 和 body 字体大小(lib_flexible.js) 将设计稿的宽(1920)平均分成 24 等份, 每一份为 80px。HTML字体大小就设置为 80 px&#xff…

组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测

组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测 目录 组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测预测效果基本介绍程序设计参考资料预测效果 基本…

浅说深度优先搜索(上)——递归

好久没有讲算法了,今天我们就来谈谈“初学者”的第二个坑,深度优先搜索,其实也就是递归。 写在最前 相信很多人都和我一样刚开始的时候完全不知道怎么下手,甚至可以说是毫无头绪,那么我们来理一理递归到底要怎么写。…

R语言 多组堆砌图

目录 数据格式 普通绘图 添加比例 R语言 堆砌图_r语言堆砌图-CSDN博客 关键点在于数据转换步骤和数据比例计算步骤&#xff0c;然后个性化调整图。 ①data <- melt(dat, id.vars c("ID"))##根据分组变为长数据 ②#计算百分比## data2 <- ddply(data, …

记一次http访问超时服务器端调试

问题&#xff1a;http访问服务器时没有返回&#xff0c;没有超时&#xff0c;一直在阻塞 处理过程&#xff1a;telnet端口能连上&#xff0c;服务端程序也不存在处理时间过长的情况。 说明tcp连接没问题。推测是客户端连接后再发起请求&#xff0c;服务端阻塞了。因为很多客户…

前端webWorker 的介绍以及应用

文章目录 webWorker以下是关于 Web Workers 的一些关键概念&#xff1a;控制台查看使用注意事项消息传递创建subworkerwebWorker的具体使用 共享worker(SharedWorker)创建方法&#xff1a;与专用worker的主要区别&#xff1a; webWorker JavaScript是单线程的语言&#xff0c;…

OneFlow深度学习框架介绍:新手快速上手指南

深度学习已成为现代人工智能领域的核心技术&#xff0c;而选择一款合适的深度学习框架对于科研人员与开发者而言至关重要。OneFlow作为近年来崭露头角的一款高性能深度学习框架&#xff0c;以其独特的设计理念、卓越的性能表现和友好的社区生态吸引了大量关注。本篇博客将以新手…

朗致集团面试-Java架构师

总结 三轮面试&#xff0c;第一轮是逻辑测试性格测试&#xff0c;第二轮是技术面试&#xff08;面试官-刘老师&#xff09;&#xff0c;第三轮是CTO面试&#xff08;面试官-屠老师&#xff09;。如果第三轮面试通过&#xff0c;考官会问你薪资意向&#xff0c;如果满意的话HR就…

Java基础习题及参考代码(循环结构)

二白整理了一些关于循环结构的习题&#xff0c;本人也逐个完成&#xff0c;有需要的同学自取&#xff0c;答案仅供参考。 01&#xff1a;求10以内的偶数的和。 package practise;public class Demo01 {public static void main(String[] args) {// 01&#xff1a;求10以内…

Nginx转发请求错误

说明&#xff1a;记录一次使用Nginx转发请求的错误&#xff1b; 场景 公司内部有两台服务器都跑了后端项目&#xff0c;在使用Nginx做请求分发时&#xff0c;我发现其中有台服务器一直没有处理请求&#xff08;没打印相关的日志信息&#xff09;&#xff0c;于是我修改了下Ng…

在Windows下面的vscode配置cmake使用vcpkg包管理器

安装 vscode下载地址 cmake下载地址 vcpkg下载地址 创建CMake项目 // main.cpp #include <fmt/core.h>int main() {fmt::print("Hello World!\n");return 0; }// CMakeLists.txtcmake_minimum_required(VERSION 3.10)project(HelloWorld)find_package(fmt…