poniedziałek, 21 października 2024

STM32H7 - Watchdog w projekcie z FreeRtos

W tym poście chciałbym opisać w jaki sposób wprowadzić obsługę watchdoga w projekcie obsługującym systemem FreeRtos. 


Ustawienie watchdoga:


W STM32H7 można uruchomić IWDG lub WWDG. Watchdog ma za zadanie monitorowanie działania systemu i wygenerowanie resetu w przypadku awarii. 

IWDG działa niezależnie od głównego zegara. Resetuje system gdy licznik nie zostanie odświeżony przed jego zakończeniem. Taktowanie licznika następuje z LSI lub LSE o częstotliwości 32kHz. 

WWDG jest to watchdog okienkowy. Odświeżenie licznika musi nastąpić w odpowiednim oknie czasowym. Jeśli to nastąpi za wcześnie lub za późno to nastąpi reset systemu. Taktowanie licznika następuje z głównego zegara systemu. 

IWDG przez to, że działa niezależnie od głównego zegara systemowego, jest bardziej niezawodny w działaniu.  

IWDG:


Jeśli mamy do czynienia ze zwykłym programem, działającego w pętli, reset licznika najczęśniej będzie wywoływany w pętli głównej programu. 

W przypadku FreeRtos najlepszym rozwiązaniem będzie sprawdzanie wszystkich zadań, czy poprawnie zostały wywołane. I posiadanie dedykowanego zadania które będzie zajmowało się obsługą watchdoga. 

Inicjalizacja zegarów systemowych:

  1. void SystemClock_Config(void)
  2. {
  3.       RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  4.       RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  5.  
  6.       HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);

  7.       __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
  8.  
  9.       while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
  10.  
  11.       HAL_PWR_EnableBkUpAccess();
  12.       __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMLOW);
  13.       /** Initializes the RCC Oscillators according to the specified parameters
  14.       * in the RCC_OscInitTypeDef structure.
  15.       */
  16.       RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSE
  17.                                   |RCC_OSCILLATORTYPE_LSE;
  18.       RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  19.       RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  20.       RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  21.       RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  22.       RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  23.       RCC_OscInitStruct.PLL.PLLM = 2;
  24.       RCC_OscInitStruct.PLL.PLLN = 40;
  25.       RCC_OscInitStruct.PLL.PLLP = 1;
  26.       RCC_OscInitStruct.PLL.PLLQ = 2;
  27.       RCC_OscInitStruct.PLL.PLLR = 2;
  28.       RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
  29.       RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  30.       RCC_OscInitStruct.PLL.PLLFRACN = 0;
  31.       if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  32.       {
  33.         Error_Handler();
  34.       }
  35.  
  36.       /** Initializes the CPU, AHB and APB buses clocks
  37.       */
  38.       RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  39.                                   |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
  40.                                   |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  41.       RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  42.       RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  43.       RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
  44.       RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  45.       RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  46.       RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  47.       RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
  48.  
  49.       if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  50.       {
  51.         Error_Handler();
  52.       }
  53. }

Ustawienie i uruchomienie IWDG:

  1. static void MX_IWDG1_Init(void) {
  2.   /* USER CODE BEGIN IWDG1_Init 0 */
  3.   /* USER CODE END IWDG1_Init 0 */
  4.   /* USER CODE BEGIN IWDG1_Init 1 */
  5.  
  6.   /* USER CODE END IWDG1_Init 1 */
  7.   LL_IWDG_Enable(IWDG1);
  8.   LL_IWDG_EnableWriteAccess(IWDG1);
  9.   LL_IWDG_SetPrescaler(IWDG1, LL_IWDG_PRESCALER_32);
  10.   LL_IWDG_SetReloadCounter(IWDG1, 4095);
  11.   while (LL_IWDG_IsReady(IWDG1) != 1) { }
  12.   LL_IWDG_ReloadCounter(IWDG1);
  13.   /* USER CODE BEGIN IWDG1_Init 2 */
  14.   /* USER CODE END IWDG1_Init 2 */
  15. }

Teraz w celu przeładowania licznika wywołujemy następującą funkcję:

  1. __STATIC_INLINE void LL_IWDG_ReloadCounter(IWDG_TypeDef *IWDGx)
  2. {
  3.   WRITE_REG(IWDGx->KR, LL_IWDG_KEY_RELOAD);
  4. }
  5.  
  6. LL_IWDG_ReloadCounter(IWDG1);

Teraz jeśli chcemy go odświeżać, należy to robić w odpowiedni sposób. 

Jeśli poprostu stworzymy zadanie i będziemy w nim przeprowadzali odświeżanie w następujący sposób:

  1. void IWDG_Task(void *argument)
  2. {
  3.     for (;;)
  4.     {
  5.         HAL_IWDG_Refresh(&hiwdg);
  6.         vTaskDelay(pdMS_TO_TICKS(1000));
  7.     }
  8.     vTaskDelete(NULL);
  9. }
Takie rozwiązanie nie uchroni nas przed problemem zawieszenia pojedynczego zadania. Zapewni nam to jedynie ochronę gdy cały procesor się zawiesi. I żadne z zadań nie będzie wykonywane. 

Jednym ze sposobów na rozwiązanie tego problemu jest przygotowanie dedykowanego zadania  (lub użycie jednego z istniejących), w którym sprawdzamy czy zostały ustawione flagi przez każdy proces. Jeśli tak się stało to czyścimy licznik dla watchdoga i kasujemy ustawione flagi. 

  1. if((1 == Task1FlagIWDG) &&
  2.    (1 == Task2FlagIWDG) &&
  3.    (1 == Task3FlagIWDG) &&
  4.    (1 == Task4FlagIWDG) &&
  5.    (1 == Task5FlagIWDG) &&
  6.    (1 == Task6FlagIWDG )) {
  7.     Task1FlagIWDG = 0;
  8.     Task2FlagIWDG = 0;
  9.     Task3FlagIWDG = 0;
  10.     Task4FlagIWDG = 0;
  11.     Task5FlagIWDG = 0;
  12.     Task6FlagIWDG = 0;
  13.  
  14.     LL_IWDG_ReloadCounter(IWDG1);
  15. }

W taki sposób, jeśli któreś z zadań się nie wykona to nie będzie możliwości wyczyszczenie licznika watchdoga, co spowoduje reset procesora. 

Sprawdzanie z jakiego powodu został wywołany watchdog. W poniższym kodzie sprawdzam flagi dla IWDG oraz WWDG. Oczywiście, w zależności od tego króry watchdog jest skonfigurowany, wystarczy sprawdzać tylko jedną z nich. 

  1. static void check_reset_cause(void) {
  2.     if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDG1RST)) {
  3.         ResetWDTFlag = 1;
  4.     }
  5.  
  6.     if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDG1RST)) {
  7.         ResetWDTFlag = 2;
  8.     }
  9.  
  10.     __HAL_RCC_CLEAR_RESET_FLAGS();
  11. }

Flagi resetu muszą być wyczyszczone ręcznie.

WWDG


Jak już wspomniałem, reset WWDG musi nastąpić oknie czasowym.

Inicjalizacja i odświeżanie: 

  1. //Uruchomienie zegara
  2.  
  3. static void MX_WWDG1_Init(void) {
  4.     __HAL_RCC_WWDG_CLK_ENABLE();
  5.     WWDG_HandleTypeDef hwwdg;
  6.     hwwdg.Instance = WWDG;
  7.     hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
  8.     hwwdg.Init.Window = 0x50;  // Ustawienie okna
  9.     hwwdg.Init.Counter = 0x7F;  // Początkowa wartość licznika
  10.     hwwdg.Init.EWIMode = WWDG_EWI_DISABLE;  // Wyłączenie przerwania
  11.     HAL_WWDG_Init(&hwwdg);
  12. }
  13.  
  14. //Odświeżanie
  15. HAL_WWDG_Refresh(&hwwdg);
 
Tutaj możemy zadziałać jak poprzednio, czyli wybieramy jedno zadanie w którym nastąpi reset. I w tym samym zadaniu kontolujemy pracę pozostałych tasków.