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:
- void SystemClock_Config(void)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
- HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
- while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
- HAL_PWR_EnableBkUpAccess();
- __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMLOW);
- /** Initializes the RCC Oscillators according to the specified parameters
- * in the RCC_OscInitTypeDef structure.
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSE
- |RCC_OSCILLATORTYPE_LSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.LSEState = RCC_LSE_ON;
- RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLM = 2;
- RCC_OscInitStruct.PLL.PLLN = 40;
- RCC_OscInitStruct.PLL.PLLP = 1;
- RCC_OscInitStruct.PLL.PLLQ = 2;
- RCC_OscInitStruct.PLL.PLLR = 2;
- RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
- RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
- RCC_OscInitStruct.PLL.PLLFRACN = 0;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
- /** Initializes the CPU, AHB and APB buses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
- |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
- RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
- RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
- {
- Error_Handler();
- }
- }
Ustawienie i uruchomienie IWDG:
- static void MX_IWDG1_Init(void) {
- /* USER CODE BEGIN IWDG1_Init 0 */
- /* USER CODE END IWDG1_Init 0 */
- /* USER CODE BEGIN IWDG1_Init 1 */
- /* USER CODE END IWDG1_Init 1 */
- LL_IWDG_Enable(IWDG1);
- LL_IWDG_EnableWriteAccess(IWDG1);
- LL_IWDG_SetPrescaler(IWDG1, LL_IWDG_PRESCALER_32);
- LL_IWDG_SetReloadCounter(IWDG1, 4095);
- while (LL_IWDG_IsReady(IWDG1) != 1) { }
- LL_IWDG_ReloadCounter(IWDG1);
- /* USER CODE BEGIN IWDG1_Init 2 */
- /* USER CODE END IWDG1_Init 2 */
- }
Teraz w celu przeładowania licznika wywołujemy następującą funkcję:
- __STATIC_INLINE void LL_IWDG_ReloadCounter(IWDG_TypeDef *IWDGx)
- {
- WRITE_REG(IWDGx->KR, LL_IWDG_KEY_RELOAD);
- }
- 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:
- void IWDG_Task(void *argument)
- {
- for (;;)
- {
- HAL_IWDG_Refresh(&hiwdg);
- vTaskDelay(pdMS_TO_TICKS(1000));
- }
- vTaskDelete(NULL);
- }
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.
- if((1 == Task1FlagIWDG) &&
- (1 == Task2FlagIWDG) &&
- (1 == Task3FlagIWDG) &&
- (1 == Task4FlagIWDG) &&
- (1 == Task5FlagIWDG) &&
- (1 == Task6FlagIWDG )) {
- Task1FlagIWDG = 0;
- Task2FlagIWDG = 0;
- Task3FlagIWDG = 0;
- Task4FlagIWDG = 0;
- Task5FlagIWDG = 0;
- Task6FlagIWDG = 0;
- LL_IWDG_ReloadCounter(IWDG1);
- }
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.
- static void check_reset_cause(void) {
- if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDG1RST)) {
- ResetWDTFlag = 1;
- }
- if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDG1RST)) {
- ResetWDTFlag = 2;
- }
- __HAL_RCC_CLEAR_RESET_FLAGS();
- }
Flagi resetu muszą być wyczyszczone ręcznie.
WWDG
Jak już wspomniałem, reset WWDG musi nastąpić oknie czasowym.
Inicjalizacja i odświeżanie:
- //Uruchomienie zegara
- static void MX_WWDG1_Init(void) {
- __HAL_RCC_WWDG_CLK_ENABLE();
- WWDG_HandleTypeDef hwwdg;
- hwwdg.Instance = WWDG;
- hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
- hwwdg.Init.Window = 0x50; // Ustawienie okna
- hwwdg.Init.Counter = 0x7F; // Początkowa wartość licznika
- hwwdg.Init.EWIMode = WWDG_EWI_DISABLE; // Wyłączenie przerwania
- HAL_WWDG_Init(&hwwdg);
- }
- //Odświeżanie
- 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.