关闭 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中仕公考:三支一扶期满后有编制吗?

三支一扶两年的期限到达之后,会自动获得编制吗? 完成三支一扶项目的服务期限后,参与人员必须通过正式的考试才能获得编制,而并不是期满后自动获得编制。但是,三支一扶服务期满人员在参加公务员考试中可依照其身份享受加分的优惠…

h5应用如何适配移动端(干货总结)

h5应用如何适配移动端总结 前言一、简单场景搭建二、从哪些方面进行适配?1.对html中的meta标签进行适配2.清除默认样式3.使用全局变量去控制采用css值4.绝对单位相对化 前端必备工具推荐网站(免费图床、API和ChatAI等实用工具): http://luckycola.com.cn/ 前言 H5应用的开发是…

基于springboot实现视频网站管理系统【项目源码+论文说明】计算机毕业设计

基于springboot实现视频网站管理系统演示 摘要 使用旧方法对视频信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在视频信息的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问…

AndroidAutomotive模块介绍(一)整体介绍

前言 Android Automotive 是一个基本 Android 平台,可运行 IVI 系统中预安装的 Android 应用以及可选的第二方和第三方 Android 应用。 本系列文档将会系统的介绍 Android Automotive 的功能、架构、逻辑等。模块逻辑将从 应用api接口、系统服务、底层服务&#x…

2024.4.12每日一题

今天上午参加了蓝桥杯,只会暴力,还需努力学习 LeetCode 找到冠军 || 题目链接:2924. 找到冠军 II - 力扣(LeetCode) 题目描述 一场比赛中共有 n 支队伍,按从 0 到 n - 1 编号。每支队伍也是 有向无环图…

泰坦尼克号幸存者预测

泰坦尼克号幸存者预测 1、特征工程概述2、数据预处理3、特征选择与提取4、建模与预测 1、特征工程概述 在上篇 泰坦尼克号幸存者数据分析 中,我们对泰坦尼克号的幸存者做了数据分析,通过性别、年龄、船舱等级等不同维度对幸存者进行了分类统计&#xff0…

Traefik的前世今生

Traefik 是一款现代的反向代理和负载均衡器,它的设计专门针对微服务架构和容器技术,如 Docker 🐳 和 Kubernetes 🎯。自从其首次发布以来,Traefik 已经迅速成为云原生生态系统中不可或缺的一部分。在这篇文章中&#x…

上位机图像处理和嵌入式模块部署(qmacvisual缺失的颜色检测)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 在图像处理当中,对颜色的处理是非常重要的。当然,这要建立在拍摄图片是彩色图片的基础之上。工业上面,虽然是黑…

第三次作业

创建了一个教务管理系统的登录页面,其中包含左侧的图片以及右侧的表单容器,当鼠标悬停在表单容器上时,会稍微变大,并且图片容器会相应的缩小,是通过css的transition以及fiex属性实现。 表单容器包含用户名和密码的输入…

基于linux进一步理解核间通讯

芯片架构分为同构和异构: 如下图TC397: 如下图TDA4: 如下图STM32MP157: 非对称多处理结构(AMP): AMP 结构是指每个内核运行自己的 OS 或同一 OS 的独立实例&#

番茄 abogus rpc调用

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!wx a15018601872 本文章…

生产者和消费者模型 | 阻塞队列 | 信号量 | 环形队列

文章目录 1.生产者和消费者模型2.生产者和消费者模型优点3.阻塞队列4.POSIX信号量5.基于环形队列的生产消费模型 本文完整的代码放在了这: Gitee链接 1.生产者和消费者模型 生产者和消费者模型,概括起来其实是一个321原则:3是:三…

如何恢复未保存或删除/丢失的Word文件?

关闭 Word 应用程序而不保存文档?误删Word文档?许多用户会在不同的情况下丢失Word文档。如果不幸遇到此类问题,如何恢复已删除或未保存的 Word 文档?有一些方法可以恢复未保存/删除的文档。此外,您还可以使用Word文件恢…

前端 接口返回来的照片太大 加载慢如何解决

现象 解决 1. 添加图片懒加载 背景图懒加载 对背景图懒加载做的解释 和图片懒加载不同&#xff0c;背景图懒加载需要使用 v-lazy:background-image&#xff0c;值设置为背景图片的地址&#xff0c;需要注意的是必须声明容器高度。 <div v-for"img in imageList&quo…

【论文笔记】PointMamba: A Simple State Space Model for Point Cloud Analysis

原文链接&#xff1a;https://arxiv.org/abs/2402.10739 1. 引言 基于Transformer的点云分析方法有二次时空复杂度&#xff0c;一些方法通过限制感受野降低计算。这引出了一个问题&#xff1a;如何设计方法实现线性复杂度并有全局感受野。 状态空间模型&#xff08;SSM&…

微服务-2 Eureka

Eureka 启动页面&#xff1a; 同理再注册完order-service后&#xff0c;刷新启动页面&#xff1a; userservice 启动多台服务&#xff1a; [ 代码 ]&#xff1a;orderService.java&#xff08;用 RestTemplate 调其他服务&#xff0c;用 userservice 代替 localhost:8081&…

二叉树——存储结构

二叉树的存储结构 二叉树一般可以使用两种结构存储&#xff0c;一种是顺序结构&#xff0c;另一种是链式结构。 一、顺序存储 二叉树的顺序存储是指用一组连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素&#xff0c;即将完全二叉树上编号为i的结点元素存储…

LeetCode 142.环形链表II(数学公式推导)

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整…

python图书馆图书借阅系统含网上商城管理系统7d538

&#xff0c;python语言&#xff0c;django框架进行开发&#xff0c;后台使用MySQL数据库进行信息管理&#xff0c;设计开发的图书管理系统。通过调研和分析&#xff0c;系统拥有管理员和用户两个角色&#xff0c;主要具备注册登录、个人信息修改、用户、图书分类、图书信息、借…

多模块项目使用springboot框架进行业务处理

项目目录 1、在Result定义返回结果 package com.edu.result;import lombok.Data;import java.io.Serializable;/*** 后端统一返回结果* param <T>*/ Data public class Result<T> implements Serializable {private Integer code; //编码&#xff1a;1成功&#xf…