你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
STMCU小助手 发布时间:2023-2-14 19:28
1. 问题现象与分析客户项目中使用的 MCU 型号是 STM32G0B1, 他们反馈在代码中尝试擦除并编程 FLASH时, 发现 FLASH 的状态寄存器显示编程错误(如图 1 所示). 问题是当前代码还没有开始擦除和编程, 怎么就有了编程错误标志了呢 ? 如果不将此错误标志清除, 后续的编程操作无法继续.客户对于每次想要操作 FLASH 之前这个清除动作既感觉多余也感觉别扭, 且还不得不做, 且做了也不知对整个产品的稳定性会有什么样的影响 ?


~ZI}5V3EKYIV7]T(9OLMI@G.png


访问客户时, 客户也曾私下里反馈, 经常在网络论坛上获取类似这种问题, 客户怀疑是不是STM32 本身就存在某些未曾公开的问题 ? 其实, STM32 的所有问题都已公开在勘误手册中, 如果客户的问题在勘误手册中没找到, 那么极有可能是自己代码哪里出了问题。


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


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


原来客户的工程中有用到两个串口, 串口 2 和串口 3, 都是使用的 DMA 模式。客户不同的软件人员负责不同的模块, 最终在整合代码时, 串口 2 并没有使用, 所以串口 2 对应的初始化代码是删除掉的, 但由于串口 2 和串口 3 的 DMA 中断是共用一条中断线, 是相同的中断入口, 在中断处理时,串口 2 的 DMA 处理函数和串口 3 的处理函数都会一起处理. 问题就出在串口 2 的 DMA 中断处理并没有移除 。如 stm32g0xx_it.c 文件 :
  1. /**
  2. * @brief This function handles DMA1 Ch4 to Ch7, DMA2 Ch1 to Ch5 and
  3. DMAMUX1 Overrun Interrupts.
  4. */
  5. void DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler(void)
  6. {
  7. /* USER CODE BEGIN
  8. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
  9. /* USER CODE END
  10. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
  11. // HAL_DMA_IRQHandler(&hdma_spi2_tx);
  12. // HAL_DMA_IRQHandler(&hdma_adc1);
  13. HAL_DMA_IRQHandler(&hdma_usart2_rx);
  14. HAL_DMA_IRQHandler(&hdma_usart2_tx);
  15. HAL_DMA_IRQHandler(&hdma_usart3_rx);
  16. HAL_DMA_IRQHandler(&hdma_usart3_tx);
  17. /* USER CODE BEGIN
  18. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
  19. /* USER CODE END
  20. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
  21. }
复制代码



如上图,DMA 的通道 4~7 以及 DAM2 的通道 1~5 都是共用一个中断入口的。在这个中断处理函数内, 串口 2 并没有使用到, 但其对应处理代码由于疏忽仍然保留了下来。句柄hdma_usart2_rx, 和 hdma_usart2_tx 内的数据成员很多都是不定内容或为 0. 当代码运行到函数内部, 如下图所示出问题的代码行:


B9Z9EJIIUWDE2F}P2C{IAUS.png


如上面代码所示, 代码运行到上图 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 长语句拆开:
  1. /* Transfer Error Interrupt management **************************************/
  2. // else if (((flag_it & (DMA_FLAG_TE1 << (hdma->ChannelIndex &
  3. 0x1CU))) != 0U) && ((source_it & DMA_IT_TE) != 0U))
  4. else
  5. {

  6. uint32_t tmp1, tmp2,tmp3;

  7. tmp1 =DMA_FLAG_TE1 << (hdma->ChannelIndex & 0x1CU);
  8. tmp2 = flag_it&tmp1;
  9. tmp3 =source_it & DMA_IT_TE;
  10. if(tmp2 && tmp3!=0)
  11. {
  12. /* When a DMA transfer error occurs */
  13. /* A hardware clear of its EN bits is performed */
  14. /* Disable ALL DMA IT */
  15. __HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT |
  16. DMA_IT_TE));
  17. /* Clear all flags */
  18. #if defined(DMA2)
  19. hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 <<
  20. (hdma->ChannelIndex & 0x1CU));
  21. #else
  22. __HAL_DMA_CLEAR_FLAG(hdma, (DMA_FLAG_GI1 <<
  23. (hdma->ChannelIndex & 0x1CU)));
  24. #endif /* DMA2 */
  25. /* Update error code */
  26. hdma->ErrorCode = HAL_DMA_ERROR_TE;
  27. /* Change the DMA state */
  28. hdma->State = HAL_DMA_STATE_READY
复制代码


如上红色代码, 用它替换原来的 if 判断语句. 结果发现 tmp1 在 IAR 和 KEIL 两个编译器环境中的值是一样的, 但是 tmp2 的值却不一样, 正是由于 tmp2 值的不一样, 导致 if 语句的最终判断结果不同。进一步发现, tmp2 的值主要是由于 flag_it 的值在两种编译器环境不一样所致。


OY9IO8AM]H]E4EFBW20IXUY.png


如上 IAR 编译器环境, flag_it 的值为 0x2000 10f8。


04WF7{2MB2JP32IYV7MR.png


如上 KEIL 编译器环境, flag_it 的值却是 0x2000 14F0。
那么 flag_it 的值又是如何来的呢? 从如下代码:
  1. void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
  2. {
  3. #if defined(DMA2)
  4. uint32_t flag_it = hdma->DmaBaseAddress->ISR;
  5. #else
  6. uint32_t flag_it = DMA1->ISR;
  7. #endif /* DMA2 */
  8. uint32_t source_it = hdma->Instance->CCR;
复制代码



如上所示, flag_it 的值来自 hdma->DmaBaseAddress->ISR, 原来是 DMA 相关 ISR 寄存器的值, 但实际调试如下:


}}]3Y6FD~GS))LKBDPESH}M.png


如上 IAR 调试环境下, 出错时, hdma->DmaBaseAddress 实际指向的是地址 0, 其成员 ISR为其第一个成员, 实际也就是地址 0 上的数据. 我们都知道, 在默认情况下, MCU 的地址 0 默认是映射到内部 FLASH 的首地址 0x0800 0000 上的, 而此地址一般保存的是栈顶.。也就是说, IAR 编译环境下, 地址 0 指向栈顶地址 0x2000 10f8。


完整版请查看:附件 一个Flash编程错误标志的探析_v1.0.pdf (939.14 KB, 下载次数: 0)
收藏 评论0 发布时间:2023-2-14 19:28

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版