1 前言
9 V$ F7 C5 D. n- l- p- n+ t- y 客户反馈在使用STM32F412的时候,擦除sector 8~11发现时间过长,从而导致意外触发IWDG复位。
$ w h! T4 N! ?. M9 O D6 v. B* n7 z1 H# d% ]4 y+ ]
2 问题分析/ @5 o4 A! o# |( f7 ]
2.1 问题详情
8 O; e+ E) W$ B: s( W 通过与客户邮件和电话沟通,了解到客户主要是想使用内部FLASH暂时保存IAP升级时的程序数据,在IAP升级的过程中,需要首先擦除内部FLASH中一块足够大的空间,然后再写入升级数据。客户的工程中有使用到IWDG,喂狗间隔大约1.5S,客户的通过SysTick的方式计算出擦除Sector8大约需要2ms,因此认为若一次擦除sector8~11大约需要8ms,于是在代码中一次性擦除sector8~11后最后再来喂狗,但是,这样会触发IWDG复位,这个与预期不一致,固此产生疑问。+ N* \! l) `( n! k7 W2 l
) `# ~! I! ~% C+ E
2.2 问题重现
+ Y; Y8 ^/ q, k 使用NUCLEO-F412ZG板尝试重现客户问题,主要代码如下:- int main(void)
6 D* P# r, R& X5 j* B$ v - {+ K4 k% L5 _0 } y/ L+ |
- /* USER CODE BEGIN 1 */; e% U, ~1 p5 ]% F; O* S
- uint32_t beginTick =0,endTick =0;. J( o7 s6 @' Y
- uint32_t curSysTick=0,endSysTick =0;3 s1 Z# f) T; z+ s$ s! w" m
- /* USER CODE END 1 */; q; V7 ]" [+ a2 |: L
- /* MCU Configuration----------------------------------------------------------*/- ]7 G* p- t- `1 N" C% {; A0 ~
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
" y* @4 {7 @/ p; l! Z3 R) A: |& W+ n - HAL_Init();
& t- e9 z# l3 @5 s+ \+ g - /* Configure the system clock */
% D$ y# }$ B" p7 o+ ? - SystemClock_Config();
# u" ^0 y8 E5 R, x; D - /* Initialize all configured peripherals */
+ u9 a; E; r# r1 \ - MX_GPIO_Init();, _. H: D3 L k* |5 n' ^: I2 C- O
- MX_IWDG_Init();" j/ n0 H" b& `4 C- H% v
- /* USER CODE BEGIN 2 */3 X% Y7 \; S' i9 @( B
- if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) //如果是看门狗复位8 k' t1 S; o- m: l" U4 O
- {
7 `6 p% O! W% c' T$ p4 A) j - /* Clear reset flags */
% w- |. V4 p, l0 N - HAL_GPIO_WritePin(GPIOB,GPIO_PIN_7,GPIO_PIN_SET);8 C3 Y8 l* P8 v6 O) P6 ^) ^- g8 y
- __HAL_RCC_CLEAR_RESET_FLAGS();( ~1 l0 [/ m5 v: b1 @8 J8 T) _
- Error_Handler();1 Q; U& s: o( i( [2 A7 M
- }
! }* M$ j8 e/ V5 E8 [ - HAL_FLASH_Unlock();7 b5 L4 H& f) n1 ^
- /* Fill EraseInit structure*/# _1 R- J8 u+ A- r* n
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;
/ M# V* k) x x$ G) w/ V. R: x - EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;
! B. w$ `# P* \6 \8 f; O - EraseInitStruct.Sector = FLASH_SECTOR_8; {. i( w5 l5 q, C
- EraseInitStruct.NbSectors = 1;
9 o. G1 x# P: H" \: f - // if(HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError) != HAL_OK)1 h1 k% M. [& i4 C& y4 `
- // {
( c: K {4 n6 [% {" E! Y - // Error_Handler();
$ A4 C# j5 W) W - // }- i0 B; W7 r* T: H) |0 E
- beginTick =HAL_GetTick();8 q$ }* v5 {) M2 H' C& @- N+ Z; G7 a, T
- HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_SET);* x4 {# b9 ]" ^( S, ?0 G1 ~0 W W; H
- curSysTick =SysTick->VAL; t8 A1 s/ U6 O4 x; F: J
- if(HAL_FLASHEx_Erase_IT(&EraseInitStruct)!= HAL_OK) //擦除sector8* N: `' r; s& p1 P7 O
- {
7 F2 _' w# q9 _) K9 x, ~ - Error_Handler();3 q' `' t) w% o" D# a
- }
0 q4 X8 F+ g6 y0 y+ P: c - endSysTick =SysTick->VAL; // curSysTick, endSysTick保存着SysTick寄存器的值" G0 i/ P3 a# I* c$ ?; Z7 {/ _
- HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); //PC8波形表示擦除FLASH的时间间隔2 C+ c. k/ g& s5 _! F
- endTick =HAL_GetTick(); // beginTick, endTick保存着全局变量Tick的值
0 E" b8 N' {% Z1 s1 [ - g_TickCount =endTick -beginTick; //变量Tick的时间差6 `' [/ ]( O, I1 d6 A8 }5 x1 S: X
- HAL_IWDG_Refresh(&hiwdg);
( D) o4 F5 m5 u, P' O \ m - /* USER CODE END 2 */
: ^1 K" Y9 k( Z- K1 h - /* Infinite loop */; N. V A6 o7 Y! t
- /* USER CODE BEGIN WHILE */
" w+ W+ a7 b6 E; L, I8 T* g' W - while (1)
& H( _( s! c; z- \ |$ v$ } - {
3 m) I* T7 o' S. v: x$ I6 m' S2 b - /* USER CODE END WHILE */
5 e7 d4 ]' n2 G" f( c - /* USER CODE BEGIN 3 */
; I2 `2 { E7 ^; H4 p. r: e - if (HAL_IWDG_Refresh(&hiwdg) != HAL_OK)
" g, f* s! y- M; Y' q' t - {" P) ]6 `- A% l& c
- /* Refresh Error */' N0 p% H9 T" ^
- Error_Handler();: T X5 k5 f( N6 J, N( e
- }0 u+ O) C/ a- g! ~! f
- HAL_Delay(10);
2 }% K0 e" h$ } B' d - }
5 K% x% R8 `' F. ~ - /* USER CODE END 3 */
G7 W. c# h+ a& C1 j/ @6 N" G - }
复制代码 {& i7 [9 u/ D
此外,同时在每个SysTick中断输出一个波形,用来检测SysTick是否正常:
' T& Q+ t/ B# j- void HAL_SYSTICK_Callback(void): p. T, _' e5 C
- {
+ _9 N! g/ ]8 K7 ?0 ^ - HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_11);//用PA11来检测SysTick波形
1 j/ B5 J; o) m7 D# L S0 _ - }
复制代码
) S3 T0 K8 f: ~! e3 k2 | % Q s2 o9 w8 z8 C
最终得出如下波形
; l& X. c1 V! N) P7 ^ O# h图1 擦除FLASH时间与SysTick输出波形 2 ]: O8 c4 X E( A
如上图,黄色为PC8脚波形,表示擦除FLASH的时间,下面蓝色为PA11管脚波形,表示SysTick波形。
8 D, _2 C! @* U6 A7 F4 _ 从上图可以看出擦除sector8所需要的时间是800ms,这个与客户认为的2ms是不一致的。查看STM32F412的数据手册,在第6.3.12节中可以看到如下信息:$ u- }) I3 @6 i# j# R/ O8 m
5 m8 Z M% _9 S) n7 @
图2 擦除FLASH扇区所需要的时间 ) W8 L$ |8 a" q+ j- _
如上图,在PSIZE=32时,擦除一个128K的扇区需要大概1S(典型值)的时间,而我们从图1中实际测出的为800ms,这个基本相差不大,单与客户认为的2ms相去甚远,基本上我们认为这里的800ms是正确的结果,但是这个又是什么原因导致客户通过SysTick测出的值是错误的呢?
2 L# e2 s- N; Y7 \4 B* G5 I1 k7 ?6 Y6 d
实际上,从图1我们也可以看出,在擦除FLASH的期间,SysTick是没有波形的(见图1下面蓝色波形),同时在参考手册3.5节中有如下信息:
; {* ~! _" x: \% s1 M" e7 v
2 W- Z' u Q7 C5 F& W+ P图3 参考手册中关于擦除扇区的描述
# Q; r# W4 C9 b2 l$ l 这句话的意思是说,在擦除FLASH的期间,若尝试读取FLASH,则会被暂停,实际这个”读取”是指取指,我们都知道,程序的执行首先得通过从FLASH中通过I-BUS取出指令后才可以执行。这里SysTick之所以会被暂停掉,就是因为在擦除FLASH期间,为了执行SysTick中断例程,内核会尝试从FLASH取指,从而导致被暂停掉,进而全局变量uwTick的值没有机会增加。下图是调试界面:8 z; i! I6 |6 \2 W2 H5 c5 c6 |
; M, M8 F. X) L
图4 调试 / ]" Y4 m* x/ Q+ B5 y* Q- v
如上图,在执行擦除扇区后,SysTick的全局变量uwTick就增加了1,但SysTick在内核中的寄存器还是有变化的。这个与我们的预想一致。* w& k2 t) T. t) K7 u" Y7 r
6 C$ A J3 G: I2 p4 p4 G
最后客户通过每擦除一个扇区喂一次狗的方式解决了问题,而在此期间不能依靠SysTick的值来计算时间。
* L# r3 x$ V) a! y$ Z- h* [' Q$ T0 E1 A. y
3 结论
! k' t) C. f; k- o2 _7 p! r+ { V 在擦除FLASH期间,取指操作会被暂停掉,且SysTick所对应的全局变量uwTick值是不会增加的。另外,通过函数HAL_FLASHEx_Erase_IT()来执行擦除FLASH和通过函数HAL_FLASHEx_Erase()所花费时间没有差别,只不过前者在擦除完成后会产生一个中断,而后者没有。可以通过外设RTC来计算擦除FLASH的时间,从而
+ y9 Z/ M9 `/ }2 e* d5 b9 ?6 V% [" r/ V+ n5 _0 z
|