一个Flash编程错误标志的探析

1、问题描述

客户项目中使用的 MCU 型号是 STM32G0B1, 他们反馈在代码中尝试擦除并编程 FLASH时, 发现 FLASH 的状态寄存器显示编程错误(如图 1 所示). 问题是当前代码还没有开始擦除和编程, 怎么就有了编程错误标志了呢 ? 如果不将此错误标志清除, 后续的编程操作无法继续.客户对于每次想要操作 FLASH 之前这个清除动作既感觉多余也感觉别扭, 且还不得不做, 且做了也不知对整个产品的稳定性会有什么样的影响 ?
Figure 1 Flash 编程错误标志
访问客户时, 客户也曾私下里反馈, 经常在网络论坛上获取类似这种问题, 客户怀疑是不是STM32 本身就存在某些未曾公开的问题 ? 其实, STM32 的所有问题都已公开在勘误手册中, 如果客户的问题在勘误手册中没找到, 那么极有可能是自己代码哪里出了问题。

2、问题分析及测试

查看客户的工程, 由于客户的工程相当庞大, 各个模块和任务相互交叉, 一时半刻是很难从如此庞大的工程中找出问题, 更麻烦地是, 客户的电脑是有加密系统的, 导致在工程内查找任何字符和函数都相当痛苦. 好在是, 问题能够稳定地复现。

于是尽量精简客户的代码, 将所有不相关的任务,模块统统移除掉, 并且保持问题能够重现. 并使其能够在 ST 官方的 NUCLEO 板上重现. 这样一来, 就完全可以脱离客户原来的硬件环境进行测试. 由于客户的环境非常不利于查找问题, 效率事倍功半. 于是, 将客户的最小化工程提取出来(与软件泄密无关), 并拿到办公室进行测试. 很快就找到了问题所在。

原来客户的工程中有用到两个串口, 串口 2 和串口 3, 都是使用的 DMA 模式。客户不同的软件人员负责不同的模块, 最终在整合代码时, 串口 2 并没有使用, 所以串口 2 对应的初始化代码是删除掉的, 但由于串口 2 和串口 3 的 DMA 中断是共用一条中断线, 是相同的中断入口, 在中断处理时,串口 2 的 DMA 处理函数和串口 3 的处理函数都会一起处理. 问题就出在串口 2 的 DMA 中断处理并没有移除 。
如 stm32g0xx_it.c 文件 :

/*** @brief This function handles DMA1 Ch4 to Ch7, DMA2 Ch1 to Ch5 and 
DMAMUX1 Overrun Interrupts.*/
void DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler(void)
{/* USER CODE BEGIN 
DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 *//* USER CODE END 
DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
// HAL_DMA_IRQHandler(&hdma_spi2_tx);
// HAL_DMA_IRQHandler(&hdma_adc1);HAL_DMA_IRQHandler(&hdma_usart2_rx);HAL_DMA_IRQHandler(&hdma_usart2_tx);HAL_DMA_IRQHandler(&hdma_usart3_rx);HAL_DMA_IRQHandler(&hdma_usart3_tx);/* USER CODE BEGIN 
DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 *//* USER CODE END 
DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
}

如上图,DMA 的通道 4~7 以及 DAM2 的通道 1~5 都是共用一个中断入口的。在这个中断处理函数内, 串口 2 并没有使用到, 但其对应处理代码由于疏忽仍然保留了下来。句柄hdma_usart2_rx, 和 hdma_usart2_tx 内的数据成员很多都是不定内容或为 0. 当代码运行到函数内部, 如下图所示出问题的代码行:
在这里插入图片描述
如上面代码所示, 代码运行到上图 866 行代码 hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << (hdma->ChannelIndex & 0x1CU));时, 实际上是给错误地址 0x0800 4109 赋值了, 此地址是内部 FLASH 地址, 这样相当于直接写 FLASH, 肯定会出错, 这也是为什么FLASH->SR.PGSERR 置位的原因. 我们都知道, 写内部 FLASH, 必须先擦除, 才可以写入, 而且写也是调用对应的 HAL API 函数, 且还需要先写 key 解锁 FLASH 等操作, 有一套写操作流程. 并不是直接用赋值语句, 这样操作出现问题一点也不奇怪。

当在中断中将串口 2 的 DMA 对应处理函数移除掉后功能就恢复正常, 这也佐证了结论的准确性。

另外, 客户反映, 这个最小化工程, 相同的代码, 使用 IAR 时测试会出错, 但使用 KEIL 时并没有出错. 这个很奇怪. 这就引出的另外一个问题. 相同代码, 不同编译器运行结果不一致的问题。

于是继续找原因, 对比 IAR 和 KEIL 的调试情况, 发现当代码运行到图 2 中 857 行代码 if 语句时其判断结果不相同. IAR 调试环境会进入到 if 语句内容, 从而导致错误的给内部 FLASH 地址赋值, 进行导致问题. 而 KEIL 调试环境并没有进入到 if 语句内部, 因此并没有触发问题. 那么为什么if 语句的判断结果不一样呢?
为了方便并避免不同编译器对长语句的执行顺序的差异, 将这个 if 长语句拆开:

/* Transfer Error Interrupt management **************************************/
// else if (((flag_it & (DMA_FLAG_TE1 << (hdma->ChannelIndex & 0x1CU))) != 0U) && ((source_it & DMA_IT_TE) != 0U))else{/*---ADD------*/uint32_t tmp1, tmp2,tmp3;tmp1 =DMA_FLAG_TE1 << (hdma->ChannelIndex & 0x1CU);tmp2 = flag_it&tmp1;tmp3 =source_it & DMA_IT_TE;if(tmp2 && tmp3!=0)
/*---END-----*/{/* When a DMA transfer error occurs *//* A hardware clear of its EN bits is performed *//* Disable ALL DMA IT */__HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE));/* Clear all flags */#if defined(DMA2)hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << (hdma->ChannelIndex & 0x1CU));#else__HAL_DMA_CLEAR_FLAG(hdma, (DMA_FLAG_GI1 << (hdma->ChannelIndex & 0x1CU)));#endif /* DMA2 *//* Update error code */hdma->ErrorCode = HAL_DMA_ERROR_TE;/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;}

如上标注的代码, 用它替换原来的 if 判断语句. 结果发现 tmp1 在 IAR 和 KEIL 两个编译器环境中的值是一样的, 但是 tmp2 的值却不一样, 正是由于 tmp2 值的不一样, 导致 if 语句的最终判断结果不同。
进一步发现, tmp2 的值主要是由于 flag_it 的值在两种编译器环境不一样所致。
在这里插入图片描述
如上 IAR 编译器环境, flag_it 的值为 0x2000 10f8。
在这里插入图片描述
如上 KEIL 编译器环境, flag_it 的值却是 0x2000 14F0。
那么 flag_it 的值又是如何来的呢? 从如下代码:

void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{#if defined(DMA2)uint32_t flag_it = hdma->DmaBaseAddress->ISR;#elseuint32_t flag_it = DMA1->ISR;#endif /* DMA2 */uint32_t source_it = hdma->Instance->CCR;
}

如上所示, flag_it 的值来自 hdma->DmaBaseAddress->ISR, 原来是 DMA 相关 ISR 寄存器的值, 但实际调试如下:
在这里插入图片描述
如上 IAR 调试环境下, 出错时, hdma->DmaBaseAddress 实际指向的是地址 0, 其成员 ISR为其第一个成员, 实际也就是地址 0 上的数据. 我们都知道, 在默认情况下, MCU 的地址 0 默认是映射到内部 FLASH 的首地址 0x0800 0000 上的, 而此地址一般保存的是栈顶.。也就是说, IAR 编译环境下, 地址 0 指向栈顶地址 0x2000 10f8。
对应地, 在 KEIL 调试环境下:
在这里插入图片描述
如上 KEIL 调试环境, hdma->DmaBaseAddress 同样地实际指向的是地址 0, 而地址 0 的上对应的数据为栈顶地址: 0x2000 14F0。

也就是说, 在不同的 编译器 IAR 和 KEIL 环境下, 地址 0 指向栈顶地址是未必相同的, 进而导致两种编译环境下运行相同的代码结果不一样。

我们知道, 通常栈地址是由编译器来指定的, 在默认情况下, IAR 和 KEIL 都会将栈放在内存的所有静态变量之后来分配. 其具体的分配地址这两个编译器都会默认按自动填充地方式来. 实际分配的地址具有不确定性, 当然, 我们也可以通过链接配置文件(IAR 的.icf 文件, KEIL 的.sct 文件)来将栈地址指定某一固定地址, 但我们通常不会这么做, 且完全没有必要.

3、问题小结

至此,将问题稍作小结。给变量 flag_it 实际赋值栈顶地址, 不同的编译器环境下, 此栈顶地址的不一致导致变量 flag_it 的值不一致, 进而导致 if 语句的判断结果不同, 最终导致 IAR 和 KEIL 这两个编译器环境下运行相同代码而结果不一样的情形。

4、后记

有时会听到某某客户反馈说, 在网络上看到 STM32 某款 MCU 存在某某问题, 然后问是不是 ST 故意隐瞒 ?
不存在故意隐瞒的说法,芯片终究是要经过终端验证的。

正常来讲, 任何芯片存在应用局限是正常的。对于 ST,一方面会正式地将所有已知 bug或应用局限放入到勘误手册中公示, 大家需要注意使用最新版勘误手册;另一方面,对于 ST 量产芯片,因本身缺陷导致的问题的概率非常低。事实上,绝大多数问题都来自我们自身的应用,遇到问题若简单的基于芯片品质来回猜疑非常不利于开发者静下心来查找问题原因。 其实,面对问题时,我们很多人欠缺的并不是多么高深的水平,而是一颗冷静、自信并富有条理的心。

本文档参考ST官方的《【应用笔记】LAT1210+一个Flash编程错误标志的探析》文档。
参考下载地址:https://download.csdn.net/download/u014319604/88971311

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

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

相关文章

vue2 table 页面 + 功能 展示

首页代码 <!-- 首页展示页面 弹框展示 --> <template><div style><el-button type"text" size"small" click"dailys()">测试跳转</el-button><!-- <div class"dingwei"><a href"#…

【办公类-16-07-07】“2023下学期 大班户外游戏2(有场地和无场地版,每天不同场地)”(python 排班表系列)

作品展示 背景需求&#xff1a; 2024年2月教务组发放的是“每周五天内容相同&#xff0c;两周10天内容相同”的户外游戏安排 【办公类-16-07-05】合并版“2023下学期 大班户外游戏&#xff08;有场地和无场地版&#xff0c;两周一次&#xff09;”&#xff08;python 排班表系…

css预处理器scss的使用如何全局引入

目录 scss 基本功能 1、嵌套 2、变量 $ 3、mixin 和 include 4、extend 5、import scss 在项目中的使用 1、存放 scss 文件 2、引入 variables 和 mixins 2-1、局部引入 2-2、全局引入 3、入口文件中引入其他文件 项目中使用 css 预处理器&#xff0c;可以提高 cs…

Uibot6.0 (RPA财务机器人师资培训第1天 )RPA+AI、RPA基础语法

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北之前的几篇博客&#xff0c;友友们我们即将开展新课的学习~…

Vulnhub - Raven2

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Raven2 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/raven-2,269/ 0x01 信息收集 Nmap扫描…

spring-boot-devtools debug SilentExitException

spring-boot-devtools debug SilentExitException&#xff1a;springboot热部署debug模式进入SilentExitException /** Copyright 2012-2019 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use …

软考网工学习笔记(6) 广域通信网

公共交换电话网&#xff08;pstn&#xff09; 在pstn是为了语音通信而建立的网络。从20世纪60你年代开始用于数据传输 电话网有三个部分组成&#xff1a; 本地回路 &#xff0c;干线 和 交换机 。 干线 和 交换机 一般采用数字传输和交换技术 &#xff0c;而 本地回路基本采…

Ubutun部署docker,并使用docker部署springboot项目,关闭软件可继续访问

工具为xftp和xshell。 我这里使用的Ubuntu的版本是20.04的&#xff0c;话不多说&#xff0c;我们来直接上代码。 首先我们最好使用管理员权限进行操作&#xff0c;预防操作时遇到权限问题。 部署docker 登入管理员 不登入管理员也没关系。 su根据提示输入密码&#xff0c;进…

2024蓝桥杯每日一题(并查集)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;奶酪 试题二&#xff1a;合并集合 试题三&#xff1a;连通块中点的数量 试题四&#xff1a;网络分析 试题一&#xff1a;奶酪 【题目描述】 现有一块大奶酪&#xff0c;它的高度为 hℎ…

shell source脚本中如何读取另外一个脚本中的变量

目录 前言语法举例注意 前言 要在一个Shell脚本中读取另一个Shell脚本中的变量&#xff0c;可以使用source命令或者.命令。这些命令用于在当前Shell环境中运行指定的脚本&#xff0c;从而使得脚本中的变量在当前Shell中可用。 语法 #!/bin/bash # 读取另一个Shell脚本中的变…

金智维的务实主义,打响大模型落地“突围战”

今年以来&#xff0c;新质生产力成为全社会关注的焦点。新质生产力的特征之一&#xff0c;就是深化新技术应用&#xff0c;尤其是AI及大模型&#xff0c;要加速落地到实际业务场景中&#xff0c;为千行万业提质增效。 2024是大模型技术做深、价值做实的一年。3月20日&#xff0…

【C++】详解智能指针

目录 一、智能指针的作用二、内存泄露1、什么是内存泄露2、内存泄漏分类3、如何避免内存泄露 三、智能指针的使用及原理1、RAII2、智能指针的原理3、std::auto_ptr4、std::unique_ptr5、std::shared_ptr1、std::shared_ptr原理2、std::shared_ptr的线程安全问题4、std::shared_…

python爬虫学习第二天----类型转换

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

电脑如何录视频?进阶教程来了!

随着科技的飞速发展&#xff0c;视频录制已成为我们日常生活和工作中不可或缺的一部分。无论是进行在线教育、制作教学视频&#xff0c;还是记录游戏过程、直播分享&#xff0c;录屏都扮演着至关重要的角色。可是您知道电脑如何录视频吗&#xff1f;本文将介绍两种电脑录视频的…

稀碎从零算法笔记Day23-LeetCode:二叉树的最大深度

题型&#xff1a;链表、二叉树的遍历 链接&#xff1a;104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上…

vue2从基础到高级学习笔记

在实际的工作中,我常使用vue的用法去实现效果,但是你要是问我为什么这样写,它的原理是啥就答不上来了。对vue的认知一直停留在表面,写这篇文章主要是为了理清并弄透彻vue的原理。 学习目标 1 学会一些基本用法的原理 2 弄懂vue核心设计原理 3 掌握vue高级api的用法 一 vue…

环境安装篇 之 安装kubevela

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 环境安装 系列文章&#xff0c;介绍 oam规范标准实施项目 kubevela 的安装详细步骤kubevela 官方安装文档&#xff1a;https://kubevela.io/zh/docs/installation/kubernetes/ 1.CentOS 安装kubevela 1.1.前提…

电脑数据安全新篇章:备份文件,守护您的珍贵数据

备份文件&#xff0c;无疑是电脑使用中不可或缺的重要一环。在数字化时代&#xff0c;我们的工作、学习和生活都离不开电脑&#xff0c;而电脑中的数据更是我们宝贵的财富。一旦数据丢失或损坏&#xff0c;可能会带来无法估量的损失。因此&#xff0c;备份文件的重要性不言而喻…

Vue.js+SpringBoot开发高校宿舍调配管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统展示四、核心代码4.1 查询单条个人习惯4.2 查询我的室友4.3 查询宿舍4.4 查询指定性别全部宿舍4.5 初次分配宿舍 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的…

力扣hot100:4. 寻找两个正序数组的中位数(归并排序/二分/双指针)

目录 一、暴力排序 二、归并排序 三、双指针 四、二分查找 如果本题不说明时间复杂度应该为O(log(mn)&#xff0c;那么本题应该是一个简单题&#xff0c;解决的方法有很多。这里先列举出普通方法&#xff0c;再来讨论二分。 一、暴力排序 不管啦&#xff0c;直接纯暴力&…