1 前言
8 Z- ]3 n5 X$ s5 J9 P* ] 客户反馈在使用STM32F412的时候,擦除sector 8~11发现时间过长,从而导致意外触发IWDG复位。3 h9 [0 I {) B# K8 X" d, O
' f+ y, E0 o3 X1 U0 A/ O7 e2 问题分析
( Y3 q- z. h$ V& \) q9 @- r# ~2.1 问题详情
6 U* J. ?* L$ ]' ` 通过与客户邮件和电话沟通,了解到客户主要是想使用内部FLASH暂时保存IAP升级时的程序数据,在IAP升级的过程中,需要首先擦除内部FLASH中一块足够大的空间,然后再写入升级数据。客户的工程中有使用到IWDG,喂狗间隔大约1.5S,客户的通过SysTick的方式计算出擦除Sector8大约需要2ms,因此认为若一次擦除sector8~11大约需要8ms,于是在代码中一次性擦除sector8~11后最后再来喂狗,但是,这样会触发IWDG复位,这个与预期不一致,固此产生疑问。
( B" M9 E- \ a6 P" |. N3 c0 s) r& @0 t1 _) ]
2.2 问题重现
! d6 X4 D q3 n- K, c 使用NUCLEO-F412ZG板尝试重现客户问题,主要代码如下:- int main(void)) N% B$ t7 v8 r3 d4 ~
- {% H5 Q/ O3 z4 l5 v
- /* USER CODE BEGIN 1 */$ l4 C5 Z7 k2 ?" Y Z
- uint32_t beginTick =0,endTick =0;: C9 }6 m# {. Q( O3 E# |, z
- uint32_t curSysTick=0,endSysTick =0;
" @. i$ u" O# A* r$ [9 E - /* USER CODE END 1 *// G h3 U# Y' x" |* n9 m$ o; l! Z# c
- /* MCU Configuration----------------------------------------------------------*/5 n% ~( s. s$ x8 ]3 F j
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
8 j4 F4 K8 d$ s! {6 b( S, _$ l - HAL_Init();4 n. a9 B. J/ P2 g9 i3 Y! D' M
- /* Configure the system clock */
6 J; m8 V/ v$ c# r - SystemClock_Config();
. h" X( }" D7 z1 E L( F2 Y - /* Initialize all configured peripherals */
0 a7 ^1 M; o5 m! L1 B - MX_GPIO_Init();
2 |$ {5 d1 P# K4 F+ M7 W; m; P - MX_IWDG_Init();) S/ _; e; Q& \# r* Y6 Y0 h' r
- /* USER CODE BEGIN 2 */
) n ~+ L+ E% O5 H; { - if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) //如果是看门狗复位" @9 A& E( b, l
- {* Z3 R l# V2 I' c( `3 A- @8 v4 Y
- /* Clear reset flags */
& T/ r- j/ s/ u5 C# ] - HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET);, B# {3 q& r6 {. C _! \! r% |
- __HAL_RCC_CLEAR_RESET_FLAGS();: A2 X$ A/ O; D. d# x% k
- Error_Handler();' r5 Y7 n2 a7 D2 Y* f& N
- }& ~9 Y a$ D+ X8 g
- HAL_FLASH_Unlock();
7 Q$ ~2 t2 _! C* V8 I: F - /* Fill EraseInit structure*/
0 O2 U) \( j) n% X5 f8 h1 X - EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;$ T6 C8 \$ [& x1 y' o
- EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;2 f. [0 L D5 \; ]& }+ B
- EraseInitStruct.Sector = FLASH_SECTOR_8;
* J$ V6 W& [! H( Z - EraseInitStruct.NbSectors = 1;5 p; d" [: h* R: b& ?! ]' L) w+ V
- // if(HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)* }9 S/ S2 U8 i# v+ j! N
- // {- K9 l+ }1 N- f, q. h5 h
- // Error_Handler();( C- ^9 W3 v( K) U2 R" W
- // }
) b) G# A J$ Z ?/ c1 S/ P - beginTick =HAL_GetTick();
4 i9 `( V! r" i - HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);
0 g4 P& ^8 U2 T# C5 @ - curSysTick =SysTick->VAL;
8 D5 ?6 u9 a8 o$ p; |: q# b - if(HAL_FLASHEx_Erase_IT(&EraseInitStruct)!= HAL_OK) //擦除sector8/ x3 @4 P( z; z5 y- S7 T; T
- {
! O) h, B5 M. f! ?; L - Error_Handler();$ b. M: I6 O3 j
- }; a" {. {: q2 e3 r2 P4 O
- endSysTick =SysTick->VAL; // curSysTick, endSysTick保存着SysTick寄存器的值
K1 Y* F8 r. F* b& q - HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); //PC8波形表示擦除FLASH的时间间隔
6 s# I. a9 r, m. ]8 t# \/ Z - endTick =HAL_GetTick(); // beginTick, endTick保存着全局变量Tick的值) P/ S9 J. T* F2 ?' y j
- g_TickCount =endTick -beginTick; //变量Tick的时间差3 O8 [- T5 T4 C* h( j% B* q
- HAL_IWDG_Refresh(&hiwdg);5 D' g8 t5 \% M
- /* USER CODE END 2 */* _% M+ F" f: F) d5 h0 u# n
- /* Infinite loop */2 {# M. K( h3 I7 z5 J* l1 N+ z
- /* USER CODE BEGIN WHILE */
O: m2 ?) s* p9 S - while (1)
) y& L$ C0 s }: i/ O - {4 L$ K4 q5 N5 ~) D& J9 h3 Q9 H+ I
- /* USER CODE END WHILE */
: g% c3 h4 X* a U+ d/ |7 G - /* USER CODE BEGIN 3 */9 n. a6 W7 j- j+ [" h) x
- if (HAL_IWDG_Refresh(&hiwdg) != HAL_OK)& B8 m1 K2 y& R) M; A- @7 v( z2 Q* v
- {
% E' `# r5 S. y. C7 y - /* Refresh Error */
: Z) ]1 ?6 B. P3 t4 ? - Error_Handler();
# H) R2 b3 b8 @/ x$ ~ - }
8 I8 Y8 ^0 T" W: N; d1 I - HAL_Delay(10);3 y A/ @4 D* z& Z( o" a. K
- }( F* a* q* b8 F$ M' w/ s/ q
- /* USER CODE END 3 */& t; y, u; s, J% x* A
- }
复制代码
7 q: Q$ I( ~2 N P( {/ R" M 此外,同时在每个SysTick中断输出一个波形,用来检测SysTick是否正常:& G; D1 R3 M. x6 b/ S& R
- void HAL_SYSTICK_Callback(void)( X+ g0 \ k6 e: b' _. |
- {9 n9 P6 r% W4 v" v$ v; s( `8 S
- HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_11);//用PA11来检测SysTick波形9 E1 P* r! z& C0 y' S1 ~
- }
复制代码
' U- V6 x: `* B* D- ? 0 o6 D' ?/ _8 U C& H6 D6 |
最终得出如下波形: J% i1 U0 L9 j) F5 U+ a
图1 擦除FLASH时间与SysTick输出波形
0 f% y( e. u; |1 C* V" }- ? 如上图,黄色为PC8脚波形,表示擦除FLASH的时间,下面蓝色为PA11管脚波形,表示SysTick波形。 n9 U, Q) A; y3 C, q" d% B8 K
从上图可以看出擦除sector8所需要的时间是800ms,这个与客户认为的2ms是不一致的。查看STM32F412的数据手册,在第6.3.12节中可以看到如下信息:
% ?( E" C9 B2 I; y7 M7 f7 c
8 v2 g/ ]9 J6 N* o图2 擦除FLASH扇区所需要的时间 6 \7 E' H- U& C- C" s
如上图,在PSIZE=32时,擦除一个128K的扇区需要大概1S(典型值)的时间,而我们从图1中实际测出的为800ms,这个基本相差不大,单与客户认为的2ms相去甚远,基本上我们认为这里的800ms是正确的结果,但是这个又是什么原因导致客户通过SysTick测出的值是错误的呢?. ?. X! Z( y! G4 n. I0 K
. V, M& x \+ Y- Z% ` m
实际上,从图1我们也可以看出,在擦除FLASH的期间,SysTick是没有波形的(见图1下面蓝色波形),同时在参考手册3.5节中有如下信息:
$ j& O: T) h6 {9 T
2 X! A1 R3 I! e( M3 Z% B; B: A0 s图3 参考手册中关于擦除扇区的描述
+ k7 K6 Q' X" m: D3 { 这句话的意思是说,在擦除FLASH的期间,若尝试读取FLASH,则会被暂停,实际这个”读取”是指取指,我们都知道,程序的执行首先得通过从FLASH中通过I-BUS取出指令后才可以执行。这里SysTick之所以会被暂停掉,就是因为在擦除FLASH期间,为了执行SysTick中断例程,内核会尝试从FLASH取指,从而导致被暂停掉,进而全局变量uwTick的值没有机会增加。下图是调试界面:- T, v* n( U. B& z5 m9 P
7 b$ O, x' i' M3 t7 K* M
图4 调试
. U3 d. f( V, H& E* U 如上图,在执行擦除扇区后,SysTick的全局变量uwTick就增加了1,但SysTick在内核中的寄存器还是有变化的。这个与我们的预想一致。
X8 P" x5 ~8 K! q3 Z y o
2 e/ ] h2 h( Q. V! v+ { 最后客户通过每擦除一个扇区喂一次狗的方式解决了问题,而在此期间不能依靠SysTick的值来计算时间。
+ ~4 o2 p, }, L2 j, m) [
$ Z2 r+ w% x- h' a3 结论
% S+ R5 w( {$ n1 ^ 在擦除FLASH期间,取指操作会被暂停掉,且SysTick所对应的全局变量uwTick值是不会增加的。另外,通过函数HAL_FLASHEx_Erase_IT()来执行擦除FLASH和通过函数HAL_FLASHEx_Erase()所花费时间没有差别,只不过前者在擦除完成后会产生一个中断,而后者没有。可以通过外设RTC来计算擦除FLASH的时间,从而
& f( ?7 z8 v* J! C+ h0 V0 O! T1 i( @/ D/ F& o3 g0 s
|