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

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

[复制链接]
STMCU小助手 发布时间:2023-2-14 19:28
1. 问题现象与分析客户项目中使用的 MCU 型号是 STM32G0B1, 他们反馈在代码中尝试擦除并编程 FLASH时, 发现 FLASH 的状态寄存器显示编程错误(如图 1 所示). 问题是当前代码还没有开始擦除和编程, 怎么就有了编程错误标志了呢 ? 如果不将此错误标志清除, 后续的编程操作无法继续.客户对于每次想要操作 FLASH 之前这个清除动作既感觉多余也感觉别扭, 且还不得不做, 且做了也不知对整个产品的稳定性会有什么样的影响 ?
  E; q5 A; M& P, M# v0 e2 k, g; j! V$ l
- g7 g+ v$ b! A. I* G$ }# X  U
~ZI}5V3EKYIV7]T(9OLMI@G.png
" n, j: R3 F, j" A/ K1 N
, B, t# {( U8 k8 g

- a+ z; f* F2 A0 O% h访问客户时, 客户也曾私下里反馈, 经常在网络论坛上获取类似这种问题, 客户怀疑是不是STM32 本身就存在某些未曾公开的问题 ? 其实, STM32 的所有问题都已公开在勘误手册中, 如果客户的问题在勘误手册中没找到, 那么极有可能是自己代码哪里出了问题。
0 f" p  O. _/ d0 b: w- v& D5 l0 K/ o" W& r% c, F2 r
; L/ U! H3 x$ A' B
问题分析及测试 9 i4 Z7 T& O8 j5 w' k
查看客户的工程, 由于客户的工程相当庞大, 各个模块和任务相互交叉, 一时半刻是很难从如此庞大的工程中找出问题, 更麻烦地是, 客户的电脑是有加密系统的, 导致在工程内查找任何字符和函数都相当痛苦. 好在是, 问题能够稳定地复现。 : L* G  q. G6 b; v! X6 j$ P3 M

( r" b) i2 y, x5 R- }5 l" g7 Z

0 u+ y) G' M: d2 `8 D% [' q1 u于是尽量精简客户的代码, 将所有不相关的任务,模块统统移除掉, 并且保持问题能够重现. 并使其能够在 ST 官方的 NUCLEO 板上重现. 这样一来, 就完全可以脱离客户原来的硬件环境进行 LAT1210   LAT1210 - Rev 1.0 page 2/8 测试. 由于客户的环境非常不利于查找问题, 效率事倍功半. 于是, 将客户的最小化工程提取出来(与软件泄密无关), 并拿到办公室进行测试. 很快就找到了问题所在。
) z" l" H3 c+ W! A/ y3 u
; z4 Q; o) e" N
. G- d0 p% L& s8 ]/ v* O! o
原来客户的工程中有用到两个串口, 串口 2 和串口 3, 都是使用的 DMA 模式。客户不同的软件人员负责不同的模块, 最终在整合代码时, 串口 2 并没有使用, 所以串口 2 对应的初始化代码是删除掉的, 但由于串口 2 和串口 3 的 DMA 中断是共用一条中断线, 是相同的中断入口, 在中断处理时,串口 2 的 DMA 处理函数和串口 3 的处理函数都会一起处理. 问题就出在串口 2 的 DMA 中断处理并没有移除 。如 stm32g0xx_it.c 文件 :* e1 U% W* H& D  ~( @& U7 {
  1. /**
    8 P+ J% B( t6 H
  2. * @brief This function handles DMA1 Ch4 to Ch7, DMA2 Ch1 to Ch5 and
    5 y9 c7 m  I; U+ S4 E* Q
  3. DMAMUX1 Overrun Interrupts.) O% q6 X- e1 c! C! q, ^$ S
  4. */
    0 I  G1 {/ f* j# ]# H. u. }
  5. void DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler(void)
    . {7 o) [2 i* F2 N: T5 A4 K
  6. {8 x& E: r$ P9 ]' S) b" t( S
  7. /* USER CODE BEGIN
    : J- _& }2 y4 ?% ^+ v+ D' P  I# ?5 U
  8. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
    2 E+ |  g% b# e' i* ]+ S
  9. /* USER CODE END
    9 D: _/ _; L% c# C  r; B
  10. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
    , \8 |% w! a4 i+ C0 S8 i  [. k
  11. // HAL_DMA_IRQHandler(&hdma_spi2_tx);+ E( o0 U( q  a" w: a
  12. // HAL_DMA_IRQHandler(&hdma_adc1);
    / h4 b/ }* X: p6 g6 ]8 z
  13. HAL_DMA_IRQHandler(&hdma_usart2_rx);
    ; k1 D+ W; o) S3 _9 U
  14. HAL_DMA_IRQHandler(&hdma_usart2_tx);
    ) x8 R1 M* W1 V) ^$ `. F
  15. HAL_DMA_IRQHandler(&hdma_usart3_rx);5 N9 f) z* U% ~) e1 _0 u: u6 L
  16. HAL_DMA_IRQHandler(&hdma_usart3_tx);
    2 `5 F3 y/ T: p( Z- {& q; c* ~% X
  17. /* USER CODE BEGIN ; W; ^* g: k1 {
  18. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */5 g, o4 |5 j0 v- R
  19. /* USER CODE END # [6 c  J) j" J5 @/ k- d! b
  20. DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
    / U2 D, y# Z" g2 G; a; L
  21. }. K) p2 u' [" J6 R; D
复制代码

2 F0 g7 h* N; m+ t3 P0 q5 ?, C2 I/ `( Q
% N& K5 X, b5 z1 f2 n
如上图,DMA 的通道 4~7 以及 DAM2 的通道 1~5 都是共用一个中断入口的。在这个中断处理函数内, 串口 2 并没有使用到, 但其对应处理代码由于疏忽仍然保留了下来。句柄hdma_usart2_rx, 和 hdma_usart2_tx 内的数据成员很多都是不定内容或为 0. 当代码运行到函数内部, 如下图所示出问题的代码行: $ k6 K- N+ k, `
  z( S( `1 ?; i; S/ [0 Q" K: u1 q
8 ]! g! E2 F1 J: E5 |# F3 D
B9Z9EJIIUWDE2F}P2C{IAUS.png - L' W) \: S) R) I3 V4 [" p
3 T8 Y0 x1 }5 Q0 u6 d% Q" M- ?( v
- f' b  |' j8 t* s, N
如上面代码所示, 代码运行到上图 866 行代码 hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << (hdma->ChannelIndex & 0x1CU));时, 实际上是给错误地址 0x0800 4109 赋值了, 此地址是内部 FLASH 地址, 这样相当于直接写 FLASH, 肯定会出错, 这也是为什么 FLASH->SR.PGSERR 置位的原因. 我们都知道, 写内部 FLASH, 必须先擦除, 才可以写入, 而且写也是调用对应的 HAL API 函数, 且还需要先写 key 解锁 FLASH 等操作, 有一套写操作流程. 并不是直接用赋值语句, 这样操作出现问题一点也不奇怪。 5 B. s. d1 c5 v* p- ?  _
当在中断中将串口 2 的 DMA 对应处理函数移除掉后功能就恢复正常, 这也佐证了结论的准确性。
+ Z% G% f6 m, p, J8 }. q3 ?另外, 客户反映, 这个最小化工程, 相同的代码, 使用 IAR 时测试会出错, 但使用 KEIL 时并没有出错. 这个很奇怪. 这就引出的另外一个问题. 相同代码, 不同编译器运行结果不一致的问题。 # b5 e( J( t0 W$ x5 d6 |
于是继续找原因, 对比 IAR 和 KEIL 的调试情况, 发现当代码运行到图 2 中 857 行代码 if 语句时其判断结果不相同. IAR 调试环境会进入到 if 语句内容, 从而导致错误的给内部 FLASH 地址赋值, 进行导致问题. 而 KEIL 调试环境并没有进入到 if 语句内部, 因此并没有触发问题. 那么为什么if 语句的判断结果不一样呢? ; `$ C- e# T  n5 L" ]
为了方便并避免不同编译器对长语句的执行顺序的差异, 将这个 if 长语句拆开:
% F) [3 W- I1 [9 b
  1. /* Transfer Error Interrupt management **************************************/, S+ Y1 G* W& H% }2 K
  2. // else if (((flag_it & (DMA_FLAG_TE1 << (hdma->ChannelIndex &
    . O/ T1 J6 |) K
  3. 0x1CU))) != 0U) && ((source_it & DMA_IT_TE) != 0U))3 D# L- u. [$ f" E* v5 J* p
  4. else1 z/ B# V0 s/ Z
  5. {. H: J" a1 `8 w4 ]

  6. * x. e) a; W) m6 |
  7. uint32_t tmp1, tmp2,tmp3;
    & c/ F( I+ M; n$ }, s$ K4 Z6 \

  8. & B4 B2 X9 z0 v9 r4 k: p
  9. tmp1 =DMA_FLAG_TE1 << (hdma->ChannelIndex & 0x1CU);5 g; _1 N" h0 y! `9 v
  10. tmp2 = flag_it&tmp1;& |9 D0 s) V4 W  U& e! |& }3 v
  11. tmp3 =source_it & DMA_IT_TE;
    6 v% C4 h2 r4 [( J
  12. if(tmp2 && tmp3!=0)
    - M9 X: k5 U- ~7 j
  13. {
    ( z; \: E; {( [* N" H0 h- Q
  14. /* When a DMA transfer error occurs */7 T. a" A5 y5 e) H
  15. /* A hardware clear of its EN bits is performed */2 {0 I4 \' Q" ~6 s+ l- ]/ `
  16. /* Disable ALL DMA IT */! n! I0 L5 U8 I$ }$ }
  17. __HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | 7 J' W& s3 Q8 `) N' `
  18. DMA_IT_TE));
    . m3 Z. E( g1 F# r
  19. /* Clear all flags */7 o, R- w( B8 C+ V5 W2 T
  20. #if defined(DMA2)5 v0 h& e; N, R
  21. hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 <<
    1 W; N, i, v0 G3 o7 {! z2 }
  22. (hdma->ChannelIndex & 0x1CU));& [) E9 {# M3 B, W7 w
  23. #else( o$ _7 _2 |4 t5 g5 u7 i
  24. __HAL_DMA_CLEAR_FLAG(hdma, (DMA_FLAG_GI1 << 5 T* V6 r7 B1 d  ?
  25. (hdma->ChannelIndex & 0x1CU)));
    * |5 Y8 c9 [! p3 N$ E+ w8 I2 D
  26. #endif /* DMA2 */# W. d/ r. _4 j4 Q. I7 p
  27. /* Update error code *// Y" T0 W$ T0 A+ O  R! C: ?: a' h' N
  28. hdma->ErrorCode = HAL_DMA_ERROR_TE;
    6 ^3 ]; B% Y% ?* ]1 L& S' w
  29. /* Change the DMA state */
    " N" d' W8 `& h
  30. hdma->State = HAL_DMA_STATE_READY
复制代码

8 j: g1 K1 @# ?& x
' f$ y# B, a0 [' h$ B$ x/ n如上红色代码, 用它替换原来的 if 判断语句. 结果发现 tmp1 在 IAR 和 KEIL 两个编译器环境中的值是一样的, 但是 tmp2 的值却不一样, 正是由于 tmp2 值的不一样, 导致 if 语句的最终判断结果不同。进一步发现, tmp2 的值主要是由于 flag_it 的值在两种编译器环境不一样所致。
& [; X/ v% H# L# K9 L" e$ s4 P! b" L1 t% g& K: t0 }8 N+ V- g

9 `' H7 H/ M& e4 j4 o' z3 O, c OY9IO8AM]H]E4EFBW20IXUY.png
! r7 X  E1 ?% B! A6 k7 }* c" n' ]5 f% {) f+ m
" q) w1 P" T  t
如上 IAR 编译器环境, flag_it 的值为 0x2000 10f8。  _& v3 v9 w( b" V

. O* }6 k$ u6 H0 C

2 U7 z, ?. n0 I" J 04WF7{2MB2JP32IYV7MR.png 2 X" \7 l  I# O) V8 v( c, k) v. r

: y5 Q/ j6 D# x  Q9 t

- c. x/ s2 x6 c如上 KEIL 编译器环境, flag_it 的值却是 0x2000 14F0。: J( d3 f0 y% C
那么 flag_it 的值又是如何来的呢? 从如下代码:& N) @; {9 n( J
  1. void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma). n3 x7 T2 Y5 ^# t" ^7 I, K. l; j
  2. {* v9 v6 x5 e  g6 f7 [9 ^4 X  o
  3. #if defined(DMA2)& B' u. W: h- L0 e
  4. uint32_t flag_it = hdma->DmaBaseAddress->ISR;
    % f* A5 |% J2 U
  5. #else
    + ]0 o; H: d) u3 K- R" m
  6. uint32_t flag_it = DMA1->ISR;# J3 Z+ U5 _/ r4 W- s' A
  7. #endif /* DMA2 */
    % ^/ z" {% u% A, u4 L, Y5 v
  8. uint32_t source_it = hdma->Instance->CCR;5 F, H6 J! z, w* |
复制代码

9 s5 j- Q( P3 d1 u. u
* v* E( M7 n' c. m' f6 H! a3 K

- H; I( [' {& J$ _: q& [2 \如上所示, flag_it 的值来自 hdma->DmaBaseAddress->ISR, 原来是 DMA 相关 ISR 寄存器的值, 但实际调试如下:9 t. ^* E: d5 _1 x- x9 F4 f. F
: v7 l& T# q, ?, W; ]

3 T: Z) g3 j" S) \3 X5 l }}]3Y6FD~GS))LKBDPESH}M.png
5 y1 K/ A" C2 p5 s: X9 v
/ P+ x$ g& ]# _  r4 T

$ Q$ z# C) [$ ^, E2 \2 j) w$ u, B如上 IAR 调试环境下, 出错时, hdma->DmaBaseAddress 实际指向的是地址 0, 其成员 ISR为其第一个成员, 实际也就是地址 0 上的数据. 我们都知道, 在默认情况下, MCU 的地址 0 默认是映射到内部 FLASH 的首地址 0x0800 0000 上的, 而此地址一般保存的是栈顶.。也就是说, IAR 编译环境下, 地址 0 指向栈顶地址 0x2000 10f8。
7 z, C+ _/ z# q2 X% i1 b" e& k9 `# Z: _% Q- u( W' ?. p0 [# r
, b5 ?1 U+ L( T: J, k/ ^
完整版请查看:附件 一个Flash编程错误标志的探析_v1.0.pdf (939.14 KB, 下载次数: 0)
赞 收藏 评论0 发布时间:2023-2-14 19:28

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版