1 前言 7 M1 G5 H1 G- w0 e' h/ V* M2 }& p
客户反馈在使用STM32F412的时候,擦除sector 8~11发现时间过长,从而导致意外触发IWDG复位。/ C( x# Z5 O% H) _$ X
8 V1 b7 R" ]( t1 v# t* `9 A+ U
2 问题分析+ a2 y- m: [8 m# A
2.1 问题详情( p7 s, Y. S+ D7 Y2 F
通过与客户邮件和电话沟通,了解到客户主要是想使用内部FLASH暂时保存IAP升级时的程序数据,在IAP升级的过程中,需要首先擦除内部FLASH中一块足够大的空间,然后再写入升级数据。客户的工程中有使用到IWDG,喂狗间隔大约1.5S,客户的通过SysTick的方式计算出擦除Sector8大约需要2ms,因此认为若一次擦除sector8~11大约需要8ms,于是在代码中一次性擦除sector8~11后最后再来喂狗,但是,这样会触发IWDG复位,这个与预期不一致,固此产生疑问。: ? u( c) s3 {2 t9 W k
# F4 d8 @% r: @4 h6 m7 F o2.2 问题重现
4 V! b0 s6 ?/ K$ z7 i- g 使用NUCLEO-F412ZG板尝试重现客户问题,主要代码如下:- int main(void)
" }; F: I2 o# d9 Q3 c - {
/ O2 }6 V# s% t) k/ G - /* USER CODE BEGIN 1 */
; K$ F6 H- K9 x; |( c3 F. I - uint32_t beginTick =0,endTick =0;. a3 U9 F' M3 i* _+ p
- uint32_t curSysTick=0,endSysTick =0;
; Z5 Q7 U- O. l0 ]% U - /* USER CODE END 1 */; [7 L7 v- T* a2 e
- /* MCU Configuration----------------------------------------------------------*/" a( b6 z1 k h9 Q B% n
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ ?: B9 i7 ]# |3 }4 i/ t
- HAL_Init();0 _6 ]" E+ W, d/ e) J! S% t" g5 j( e
- /* Configure the system clock */& o: ?. \3 ?9 k$ w- V/ f
- SystemClock_Config();
! g' g# j! l, z) P' a" q7 Z - /* Initialize all configured peripherals */
9 W- H) U* n: y Z( _& q* e - MX_GPIO_Init();
4 b7 S. a# L# n& }& { - MX_IWDG_Init();
1 x8 {5 j" K5 a+ e - /* USER CODE BEGIN 2 */+ D$ e' h' B9 R- j; ]- k' B
- if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) //如果是看门狗复位 O( m9 _) H. g7 ~2 G2 d5 R" n C9 ]: \
- {9 |+ {# }4 c; K& ~. [5 c) W
- /* Clear reset flags */. C' \8 y2 x9 G9 ]. _
- HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET);; x0 t; t" z( l. q2 k0 s
- __HAL_RCC_CLEAR_RESET_FLAGS();
. V2 \/ y4 Q7 j9 ?8 e4 S - Error_Handler();
+ @" r8 N! H. |5 ]# D, D. j - } f: J2 H& B5 |3 w
- HAL_FLASH_Unlock();
" W% V9 h7 b- z# b( |; Y: ^4 i - /* Fill EraseInit structure*// r0 X: q3 O4 Y8 t$ T
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;" p* [& x( W6 c
- EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
/ Y( v9 X5 }. \ - EraseInitStruct.Sector = FLASH_SECTOR_8;* Z; @) c% \# j$ e. y
- EraseInitStruct.NbSectors = 1;
. ]; S, G3 Q6 `2 C* H0 K$ x7 t# \ - // if(HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)
Y: J" w: l" D) G- [) G) R: o - // {2 k% d; e$ w7 |+ u! ~) w
- // Error_Handler();% p5 }( L: }' D1 ]8 V
- // }
( g. J/ E# s' a$ i' x2 R5 h1 S% _ - beginTick =HAL_GetTick();! V# H; K" |+ t, d+ R! {8 ^2 Q* b- C
- HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);
/ s% C) L5 {3 M2 b( E$ h - curSysTick =SysTick->VAL;
. o' I6 a: @$ t+ z/ N - if(HAL_FLASHEx_Erase_IT(&EraseInitStruct)!= HAL_OK) //擦除sector8
z) g+ q; y1 [) K - { P% [- L; n4 H1 H% w& O7 X
- Error_Handler();# S" `+ i$ z8 W% V& b' u1 ?
- }' W2 M$ }& [3 K5 J& r
- endSysTick =SysTick->VAL; // curSysTick, endSysTick保存着SysTick寄存器的值
( o( K% d; y$ X' Y& e6 n! V E$ Z - HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); //PC8波形表示擦除FLASH的时间间隔
: O9 n: ]! S6 c" }& {2 i4 i3 a; k - endTick =HAL_GetTick(); // beginTick, endTick保存着全局变量Tick的值, |% Y1 S0 j6 A$ e" ~$ b- k& n/ e
- g_TickCount =endTick -beginTick; //变量Tick的时间差
% ^% _2 x ^* h, j- e - HAL_IWDG_Refresh(&hiwdg);4 j6 z/ j( o7 Y. i, C7 f) w
- /* USER CODE END 2 */
9 T# ^7 i: l8 V( R$ j$ U9 ~/ `$ @ - /* Infinite loop */5 Q( d2 R) `: C1 Z
- /* USER CODE BEGIN WHILE */
. X: U' d& ^8 V. D# k - while (1)
7 H, j! a- t# S9 q+ u - {
( w6 U% [$ T! i7 v$ O1 `2 e4 Y - /* USER CODE END WHILE */7 P, t+ [' K7 c, }2 U' A6 D
- /* USER CODE BEGIN 3 */; n* a1 S r, ^4 R4 W
- if (HAL_IWDG_Refresh(&hiwdg) != HAL_OK)$ G7 g/ Z2 _5 j f3 |% v
- {9 { {4 W# B+ E1 V4 g
- /* Refresh Error */
2 e) s: Q: D9 { - Error_Handler();( O9 \: E) b n/ Y
- }) S/ h" S; s' C% L f, M6 b5 }
- HAL_Delay(10);
1 ?) F6 z0 _# X8 W& D - }
- I0 {# W9 J* C9 C7 G8 O5 ~ - /* USER CODE END 3 */
9 l( d; F0 ?- N, z' B) ~! g9 g1 r - }
复制代码 7 g8 j0 u4 G9 l0 j# n. g
此外,同时在每个SysTick中断输出一个波形,用来检测SysTick是否正常:
( }; q& D" I8 S5 K! ~; \- void HAL_SYSTICK_Callback(void)
, p' B& s& d4 m* f2 ] - {
9 s k2 u; y2 }3 u; p$ [! o$ c - HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_11);//用PA11来检测SysTick波形
2 E6 h& V; q2 H8 o# U/ l6 T* r - }
复制代码 . [5 m& _2 B0 v: |2 z4 U/ _7 W
. ?; d4 y! n6 d# }& \$ t7 U8 _ 最终得出如下波形
Z! _0 I E* ]0 O3 p# _图1 擦除FLASH时间与SysTick输出波形 - N8 b* y' t/ W; P, ^) o6 ?3 A
如上图,黄色为PC8脚波形,表示擦除FLASH的时间,下面蓝色为PA11管脚波形,表示SysTick波形。
7 q" F3 Z5 ^* v2 s- N 从上图可以看出擦除sector8所需要的时间是800ms,这个与客户认为的2ms是不一致的。查看STM32F412的数据手册,在第6.3.12节中可以看到如下信息:" L7 }7 ~8 p% m( f7 ~: S+ }
, S) s$ H2 j1 T; O) y
图2 擦除FLASH扇区所需要的时间
; k) {; _# i0 b/ w% k% P+ G7 @; e 如上图,在PSIZE=32时,擦除一个128K的扇区需要大概1S(典型值)的时间,而我们从图1中实际测出的为800ms,这个基本相差不大,单与客户认为的2ms相去甚远,基本上我们认为这里的800ms是正确的结果,但是这个又是什么原因导致客户通过SysTick测出的值是错误的呢?1 K; L% @5 d$ F4 J/ E1 L
4 r. e3 o: a3 q 实际上,从图1我们也可以看出,在擦除FLASH的期间,SysTick是没有波形的(见图1下面蓝色波形),同时在参考手册3.5节中有如下信息:
5 K. c+ \0 U/ N' b) ^! |+ i8 d- E2 r v4 Z% q, ~4 X& W. W
图3 参考手册中关于擦除扇区的描述 ' N# q$ ]" Z& m" x( B
这句话的意思是说,在擦除FLASH的期间,若尝试读取FLASH,则会被暂停,实际这个”读取”是指取指,我们都知道,程序的执行首先得通过从FLASH中通过I-BUS取出指令后才可以执行。这里SysTick之所以会被暂停掉,就是因为在擦除FLASH期间,为了执行SysTick中断例程,内核会尝试从FLASH取指,从而导致被暂停掉,进而全局变量uwTick的值没有机会增加。下图是调试界面:) z* _/ l, p5 V
, o: \ @+ m5 h& N4 H6 R
图4 调试 2 t+ e3 ~! P) n) m: h
如上图,在执行擦除扇区后,SysTick的全局变量uwTick就增加了1,但SysTick在内核中的寄存器还是有变化的。这个与我们的预想一致。
) g" U9 ^* B3 t- k. ]% C6 s% A8 h0 l0 l0 }2 M
最后客户通过每擦除一个扇区喂一次狗的方式解决了问题,而在此期间不能依靠SysTick的值来计算时间。* \, y/ G9 l3 U, q" p, M
6 i6 z8 z- t- [% N# T3 结论. m8 C4 y% O# C3 Z' F2 n
在擦除FLASH期间,取指操作会被暂停掉,且SysTick所对应的全局变量uwTick值是不会增加的。另外,通过函数HAL_FLASHEx_Erase_IT()来执行擦除FLASH和通过函数HAL_FLASHEx_Erase()所花费时间没有差别,只不过前者在擦除完成后会产生一个中断,而后者没有。可以通过外设RTC来计算擦除FLASH的时间,从而
4 m! g* g" d4 m3 f# S- q2 W
; b- W- I. r. ?* r# Y2 {# @ |