1 前言
3 y4 q. C) B2 t, p: Y0 v3 e 客户反馈在使用STM32F412的时候,擦除sector 8~11发现时间过长,从而导致意外触发IWDG复位。0 Y4 W/ F; j; _# V, z5 m t5 s0 ?) f
/ S1 s) [2 q( Q9 A8 R( G7 m2 问题分析
* ?* R% h4 _. z1 c& D0 I8 ^8 {6 n6 E4 W2.1 问题详情
* T" C- E; z$ N- ^6 T 通过与客户邮件和电话沟通,了解到客户主要是想使用内部FLASH暂时保存IAP升级时的程序数据,在IAP升级的过程中,需要首先擦除内部FLASH中一块足够大的空间,然后再写入升级数据。客户的工程中有使用到IWDG,喂狗间隔大约1.5S,客户的通过SysTick的方式计算出擦除Sector8大约需要2ms,因此认为若一次擦除sector8~11大约需要8ms,于是在代码中一次性擦除sector8~11后最后再来喂狗,但是,这样会触发IWDG复位,这个与预期不一致,固此产生疑问。; ]' e0 H1 w7 B* X+ V. h3 l- Z. Q
7 I9 y& F% l @0 g7 `2.2 问题重现
* e v* k; S% n2 F# [6 Z 使用NUCLEO-F412ZG板尝试重现客户问题,主要代码如下:- int main(void)6 V [ u5 @8 }2 ]
- {
; t/ w# H7 H1 i3 [+ q - /* USER CODE BEGIN 1 */+ y) |1 _$ y. ?& C
- uint32_t beginTick =0,endTick =0;3 R; b; ?1 F. ?, v4 [9 t
- uint32_t curSysTick=0,endSysTick =0;
: O0 P% A( O% n- m8 L - /* USER CODE END 1 */
7 x3 V1 X' ^( [* H8 w! i - /* MCU Configuration----------------------------------------------------------*/: J" I% S7 l6 B5 J9 o2 s7 g
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
6 v {+ G1 i( C8 B& w - HAL_Init();
! P& m0 f7 M) O4 n. p - /* Configure the system clock */2 X& ]; E2 P8 }3 S T( g
- SystemClock_Config(); r& d+ Z/ m' A# O X) m
- /* Initialize all configured peripherals */ I9 `# I' W9 f2 D
- MX_GPIO_Init();
$ t9 {& J5 s7 \- u" e - MX_IWDG_Init();1 l1 w9 P9 U; M1 `
- /* USER CODE BEGIN 2 */" w, \( u9 E! h6 T/ x7 t
- if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) //如果是看门狗复位
; Q" o! R0 `' H4 n+ J1 l; O - {
0 u' V* v8 ?4 T6 s) t - /* Clear reset flags */
# V* s3 y" f3 R4 `# d* F* E - HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET); F3 J* N+ S" o L$ C( _7 x
- __HAL_RCC_CLEAR_RESET_FLAGS();3 |1 h% x( v4 J' i
- Error_Handler(); ~4 I9 B" v) ]( w' D
- }
& Y3 ]. `1 z+ P9 H6 n$ z5 _2 x - HAL_FLASH_Unlock();* i9 \" {6 |/ V: N% t" O$ F
- /* Fill EraseInit structure*/$ o2 s4 f. A4 f9 }
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;+ V4 {- l0 C, L" [& y6 S
- EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;$ l) q1 u. [8 j) |* ^( y
- EraseInitStruct.Sector = FLASH_SECTOR_8;) ^& x, x$ R) O: K! }" X i' Q
- EraseInitStruct.NbSectors = 1;
$ h; U% ]* n( n$ l - // if(HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)0 i( G" t, K9 f" \+ X
- // {% |, {; R! v* d( J* E
- // Error_Handler();) V* T) p& ~" p9 h
- // }
* Z9 o/ v; O1 c1 A0 l: U - beginTick =HAL_GetTick();0 Z9 D1 }3 {3 D
- HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);8 i1 a+ \2 ]9 u* W- n) s
- curSysTick =SysTick->VAL;+ Z3 B/ i: T+ ~( r6 ?$ u0 _
- if(HAL_FLASHEx_Erase_IT(&EraseInitStruct)!= HAL_OK) //擦除sector8
- v; ? Z& i6 U ^$ g+ D - {* w1 z' Q$ U6 B. I
- Error_Handler();
. o" I j: d; W# f2 y - }. R# ?, c' H6 |$ G
- endSysTick =SysTick->VAL; // curSysTick, endSysTick保存着SysTick寄存器的值
7 W/ F3 \; K3 n7 Z2 ^9 B0 l: ]* A" K - HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); //PC8波形表示擦除FLASH的时间间隔
' p6 ~2 e1 k* C - endTick =HAL_GetTick(); // beginTick, endTick保存着全局变量Tick的值 Z# ~( a4 E5 l4 g0 K2 a- z
- g_TickCount =endTick -beginTick; //变量Tick的时间差2 ]. T$ G. J% `8 ?# O9 T1 v) p4 @; p
- HAL_IWDG_Refresh(&hiwdg);
' ~$ M2 p9 q' Y* a D - /* USER CODE END 2 */" t& {) c- K+ i- P
- /* Infinite loop */
& G- A& l2 K; J9 `, B' [ - /* USER CODE BEGIN WHILE */
, ~1 k; ?/ V6 |8 G1 j' {2 \' g - while (1)
+ H/ H2 V& |/ k( R8 c0 h - {! F7 ?$ k0 A; [5 t" n: q6 t. j
- /* USER CODE END WHILE */
. S1 r2 O/ ]& N, { - /* USER CODE BEGIN 3 */
9 g$ t" x$ V& W) i* Y - if (HAL_IWDG_Refresh(&hiwdg) != HAL_OK)
+ b" m3 `5 y/ J - {- i4 j7 |$ f+ u( i! M
- /* Refresh Error */! o' a" n! X, L' p3 S
- Error_Handler();
) [7 O: y- D3 H4 L2 \- r# K2 Q - }9 j5 O) N3 X" k$ M4 B( q8 \
- HAL_Delay(10);3 ?: ^9 @) L- A) L
- }: i3 }! \0 x% b# o2 `7 j
- /* USER CODE END 3 */
* N+ ?9 d& r% M4 m - }
复制代码
6 A4 @7 i' R+ r( I& { 此外,同时在每个SysTick中断输出一个波形,用来检测SysTick是否正常:
. K2 X& |7 O% `: x- void HAL_SYSTICK_Callback(void)* |! ^! _5 |; o/ g5 O" U& h' g
- {
/ Z" ~( z0 C# m9 X3 _3 u' W/ K - HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_11);//用PA11来检测SysTick波形/ k7 p7 b" ~+ U2 f# m: a
- }
复制代码 & u' q' z9 f3 g W' n& i1 {* u$ [
' s- E& L1 n/ }7 w" \: o 最终得出如下波形* |+ a' ?% m" b! K0 y$ K
图1 擦除FLASH时间与SysTick输出波形 " Z+ Q" K( E$ u) G' w/ X$ Z" A, z+ P
如上图,黄色为PC8脚波形,表示擦除FLASH的时间,下面蓝色为PA11管脚波形,表示SysTick波形。! K7 t2 {6 G9 M. U- G. `
从上图可以看出擦除sector8所需要的时间是800ms,这个与客户认为的2ms是不一致的。查看STM32F412的数据手册,在第6.3.12节中可以看到如下信息:: n$ v4 }8 f1 E' [# R( F
1 r i/ X6 i( e: l1 C' I$ t8 }图2 擦除FLASH扇区所需要的时间
( G6 z; X6 V+ W+ n, J! p6 W h 如上图,在PSIZE=32时,擦除一个128K的扇区需要大概1S(典型值)的时间,而我们从图1中实际测出的为800ms,这个基本相差不大,单与客户认为的2ms相去甚远,基本上我们认为这里的800ms是正确的结果,但是这个又是什么原因导致客户通过SysTick测出的值是错误的呢?5 z; P* i7 j. L5 S) R0 z+ V
7 |: ^3 g" [6 S/ N% C# l- Y
实际上,从图1我们也可以看出,在擦除FLASH的期间,SysTick是没有波形的(见图1下面蓝色波形),同时在参考手册3.5节中有如下信息:2 _3 o* O- y& b: g' O3 j9 D! V* _% ~
( R3 @+ j+ |. f4 q' q) I; m2 Q' \
图3 参考手册中关于擦除扇区的描述
: j4 H" |$ ^( L. Y! N) r% B 这句话的意思是说,在擦除FLASH的期间,若尝试读取FLASH,则会被暂停,实际这个”读取”是指取指,我们都知道,程序的执行首先得通过从FLASH中通过I-BUS取出指令后才可以执行。这里SysTick之所以会被暂停掉,就是因为在擦除FLASH期间,为了执行SysTick中断例程,内核会尝试从FLASH取指,从而导致被暂停掉,进而全局变量uwTick的值没有机会增加。下图是调试界面:) f2 V& Y1 g! T* r4 V/ }( n! b* b
* u$ O1 l- y: S9 C# V" X
图4 调试 ) d5 A( B# X1 W, i6 W, z# E: `) E; ?
如上图,在执行擦除扇区后,SysTick的全局变量uwTick就增加了1,但SysTick在内核中的寄存器还是有变化的。这个与我们的预想一致。; F3 G K* i& j0 L* t
1 n- j K6 k, r" p* E 最后客户通过每擦除一个扇区喂一次狗的方式解决了问题,而在此期间不能依靠SysTick的值来计算时间。
+ x7 f" J/ t- P) o1 |% G& q' @# H9 j; r$ O0 m
3 结论( S! [' X/ L/ H! }
在擦除FLASH期间,取指操作会被暂停掉,且SysTick所对应的全局变量uwTick值是不会增加的。另外,通过函数HAL_FLASHEx_Erase_IT()来执行擦除FLASH和通过函数HAL_FLASHEx_Erase()所花费时间没有差别,只不过前者在擦除完成后会产生一个中断,而后者没有。可以通过外设RTC来计算擦除FLASH的时间,从而% B6 p0 B5 b, ?
3 G. E/ F" I) @6 i' v; S
|