poniedziałek, 26 września 2022

[4] STM32F4 - Biblioteka LL - IWDG, WWDG

W tym poście chciałbym opisać sposób obsługi IWDG oraz WWDG z wykorzystaniem bibliotek LL.

[Źródłohttp://www.st.com/en/evaluation-tools/stm32f4discovery.html]

IWDG (Independent watchdog)


IWDG jest taktowany z wewnętrznego zegara LSI RC o wartości 32kHz


Do wyliczenia częstotliwości pracy wykorzystuje następujący wzór:


Gdzie:
RL - wartość licznika (IWDG down-counter reload value)
PR - wartość dzielnika (IWDG counter clock prescaler). Tutaj przyjmowane są następujące wartości:
4 => PR = 0
8 => PR = 1
16 => PR=2
32 => PR=3 itp. itd

Oznacza to, że dla ustawienia dzielnika na 32 oraz licznika na 999 otrzymana zostanie następująca wartość:

  1. t(iwdg) = 1/32000 * 4 * 8 * (999 + 1) = 1[s]

Można ten wzór zmodyfikować tak aby można było z niego wyliczyć wartość RL:

  1. RL = ((1[s] * 32000)/(4*2^3)) - 1 = (32000 / 32) - 1 = 999
  2. lub
  3. RL = ((1000[ms] * 32000)/(4 * 2^3 * 1000)) - 1 = (32000000 / 32000) - 1 = 999

Następnym elementem jest zmiana bibliotek z wybranego domyślnie HAL na LL. Należy to wykonać w zakładce Project Manager -> Advanced Settings:


Wygenerowana automatycznie funkcja uruchamiająca IWDG wygląda następująco:

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

  15.   /* USER CODE BEGIN IWDG_Init 2 */
  16.   /* USER CODE END IWDG_Init 2 */
  17. }

Następnie po uruchomieniu mikrokontrolera należy sprawdzić czy ponowne uruchomienie układu zostało spowodowane przez IWDG. Do tego służy funkcja sprawdzająca ustawienie odpowiedniego bitu w rejestrze:

  1. /**
  2.   * @brief  Check if RCC flag Independent Watchdog reset is set or not.
  3.   * @rmtoll CSR          IWDGRSTF      LL_RCC_IsActiveFlag_IWDGRST
  4.   * @retval State of bit (1 or 0).
  5.   */
  6. __STATIC_INLINE uint32_t LL_RCC_IsActiveFlag_IWDGRST(void)
  7. {
  8.   return (READ_BIT(RCC->CSR, RCC_CSR_IWDGRSTF) == (RCC_CSR_IWDGRSTF));
  9. }

Do obserwowania zmian wykorzystałem interfejs UART z prostym przesyłaniem danych wykorzystując funkcję printf.  Zmiana polega na dodaniu wysyłania znaku do funkcji __io_putchar a dokładniej jej przygotowanie. 

  1. // W pliku syscalls.c
  2. extern int __io_putchar(int ch) __attribute__((weak));
  3.  
  4. //Należy dołożyć własną definicję funkcji
  5. int __io_putchar (int ch)
  6. {
  7.   while (!LL_USART_IsActiveFlag_TXE(USART2));
  8.   //funkcja z biblioteki LL
  9.   LL_USART_TransmitData8(USART2, (char)ch);
  10.   //Lub poprostu tak
  11.   //USART2->DR = ch;
  12.   return ch;
  13. }

Powyższa funkcja wywoływana jest przez funkcję _write, z której korzysta printf:

  1. __attribute__((weak)) int _write(int file, char *ptr, int len)
  2. {
  3.     int DataIdx;
  4.  
  5.     for (DataIdx = 0; DataIdx < len; DataIdx++)
  6.     {
  7.         __io_putchar(*ptr++);
  8.     }
  9.     return len;
  10. }

Po ponownym uruchomieniu układu należy sprawdzić czy reset został wywołany z IWDG. Jeśli tak należy wyczyścić flagę:

  1. if (LL_RCC_IsActiveFlag_IWDGRST())
  2. {
  3.   printf ("Reset wywolany z IWDG\r\n");
  4.   LL_RCC_ClearResetFlags();
  5. }
  6. else
  7. {
  8.   printf("Reset bez IWDG\r\n");
  9. }

W pętli przeładowuję licznik od IWDG. Dodatkowo po kliknięciu przycisku zostaje dodane dodatkowe opóźnienie 

  1. while (1)
  2. {
  3.     /* USER CODE END WHILE */
  4.  
  5.     /* USER CODE BEGIN 3 */
  6.     if (LL_GPIO_IsInputPinSet(GPIOA , LL_GPIO_PIN_0))
  7.     {
  8.         printf("Dodatkowe opoznienie\r\n");
  9.         LL_mDelay(2000) ;
  10.     }
  11.  
  12.     LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_12);
  13.     LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_13);
  14.     LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_14);
  15.     LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_15);
  16.     LL_mDelay(500);
  17.  
  18.     LL_IWDG_ReloadCounter(IWDG);
  19. }

IWDG może być aktywowany we wszystkich trybach oszczędzania energii.


WWDG (Window Watchdog)

WWDG jest taktowane przez ABP1. W konfiguracji zegarów ustawionych w screenie z początku strony, jego wartość wynosi 42MHz.

Wzór do obliczenia czasu odświeżenia jest następujący:


Jak już wspomniałem wcześniej licznik jest taktowany z ABP1. Wewnętrzny dzielnik częstotliwości wynosi 4096. Kolejny dzielnik zależy od ustawionego prescalera. W moim przypadku będzie to maksymalna wartość czyli 8.

  1. 42000000 / 4096 / 8 = 1281,738 [Hz] => 1/1281,738 = 780 [us]

Konfiguracja w CubeMx jest następująca:


Dodatkowo zostaje uruchomione przerwanie które należy wybrać w oknie NVIC:


Teraz trzeba wyliczyć okno w jakim możliwe będzie odświeżenie licznika z takimi ustawieniami. 

  1. Dolny licznik twwdg = tclkwwdg * (127 - 63) = 780us * (64) = => 49,920 ms
  2. Górny licznik, czas liczony od startu dolnego licznego
  3. twwdg = tclkwwdf * (127 - 90) = 780us * (37) = => 28,860 ms

Przerwanie zostaje wyzwolone gdy wartość w liczniku osiągnie wartość 0x40. Jest ono wykorzystywane do przeprowadzania niezbędnych zadań, które powinny zostać przeprowadzone przed resetem urządzenia. 

Wygenerowana funkcja uruchamiająca WWDG wygląda następująco:

  1. static void MX_WWDG_Init(void)
  2. {
  3.   /* USER CODE BEGIN WWDG_Init 0 */
  4.   /* USER CODE END WWDG_Init 0 */
  5.  
  6.   /* Peripheral clock enable */
  7.   LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_WWDG);
  8.  
  9.   /* USER CODE BEGIN WWDG_Init 1 */
  10.   /* USER CODE END WWDG_Init 1 */
  11.   LL_WWDG_SetCounter(WWDG, 127);
  12.   LL_WWDG_Enable(WWDG);
  13.   LL_WWDG_SetPrescaler(WWDG, LL_WWDG_PRESCALER_8);
  14.   LL_WWDG_SetWindow(WWDG, 90);
  15.   LL_WWDG_EnableIT_EWKUP(WWDG);

  16.   /* USER CODE BEGIN WWDG_Init 2 */
  17.   /* USER CODE END WWDG_Init 2 */
  18. }

Po wywołaniu przerwania zostaje ustawiona flaga w EWI w rejestrze CFR. Po wywołaniu przerwania należy wyczyścić flagę. 

  1. void WWDG_IRQHandler(void)
  2. {
  3.   if(LL_WWDG_IsActiveFlag_EWKUP(WWDG))
  4.   {
  5.     LL_WWDG_ClearFlag_EWKUP(WWDG);
  6.     printf("WWDG interrupt.\r\n");
  7.   }
  8. }

Przerwanie zostanie wywołane tylko w przypadku za późnego odświeżenia licznika, czyli po przekroczeniu zakresu okna w górę. Jeśli licznik zostanie odświeżony za wcześnie to nastąpi reset bez wywołania przerwania.

Podobnie jak poprzednio należy sprawdzić czy WWDG wywołał reset, jeśli tak to czyszczone są flagi resetu:

  1. if(LL_RCC_IsActiveFlag_WWDGRST())
  2. {
  3.     printf("Reset wywolany z WWDG\r\n");
  4.     LL_RCC_ClearResetFlags();
  5. }
  6. else
  7. {
  8.     printf("Reset bez WWDG\r\n");
  9. }

Funkcja main wygląda następująco:

  1. int main(void)
  2. {
  3.   /* USER CODE BEGIN 1 */
  4.  
  5.   /* USER CODE END 1 */
  6.  
  7.   /* MCU Configuration--------------------------------------------------------*/
  8.  
  9.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  10.  
  11.   LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
  12.   LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
  13.  
  14.   NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  15.  
  16.   /* System interrupt init*/
  17.   /* SysTick_IRQn interrupt configuration */
  18.   NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),15, 0));
  19.  
  20.   /* USER CODE BEGIN Init */
  21.  
  22.   /* USER CODE END Init */
  23.  
  24.   /* Configure the system clock */
  25.   SystemClock_Config();
  26.  
  27.   /* USER CODE BEGIN SysInit */
  28.  
  29.   /* USER CODE END SysInit */
  30.  
  31.   /* Initialize all configured peripherals */
  32.   MX_GPIO_Init();
  33.   MX_USART2_UART_Init();
  34.   LL_mDelay(100);
  35.   MX_WWDG_Init();
  36.   /* USER CODE BEGIN 2 */
  37.   if(LL_RCC_IsActiveFlag_WWDGRST())
  38.   {
  39.     printf ("Reset wywolany z WWDG\r\n");
  40.     LL_RCC_ClearResetFlags();
  41.   }
  42.   else
  43.   {
  44.     printf("Reset bez WWDG\r\n");
  45.   }
  46.   /* USER CODE END 2 */
  47.  
  48.   /* Infinite loop */
  49.   /* USER CODE BEGIN WHILE */
  50.   while (1)
  51.   {
  52.     /* USER CODE END WHILE */
  53.     /* USER CODE BEGIN 3 */
  54.         if (LL_GPIO_IsInputPinSet(GPIOA , LL_GPIO_PIN_0) && btnflag == 0)
  55.         {
  56.             btnflag = 1;
  57.             LL_GPIO_TogglePin (GPIOD ,  LL_GPIO_PIN_15);
  58.             LL_mDelay(60) ;
  59.         }
  60.         else
  61.         {
  62.             LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_12);
  63.             LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_13);
  64.             LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_14);
  65.             LL_mDelay(40) ;
  66.         }
  67.  
  68.         LL_WWDG_SetCounter(WWDG , 127);
  69.   }
  70.   /* USER CODE END 3 */
  71. }

Po kliknięciu przycisku następuje zwiększenie czasu opóźnienia, co powoduje zbyt późne odświeżenie licznika:

  1. Reset bez WWDG
  2. //Po kliknięciu
  3. WWDG int
  4. Reset wywolany z WWDG

Działanie w różnych trybach oszczędzania energii wygląda następująco:


WWDG dostępny jest w trybie Run oraz Sleep. W pozostałych trybach jest nieaktywny. 

Do zatrzymywania i uruchamiania WWDG jak i IWDG zostały udostępnione odpowiednie funkcje oraz makra:

  1. //Biblioteka HAL
  2. #define __HAL_DBGMCU_FREEZE_WWDG()           (DBGMCU->APB1FZ |= (DBGMCU_APB1_FZ_DBG_WWDG_STOP))
  3. #define __HAL_DBGMCU_FREEZE_IWDG()           (DBGMCU->APB1FZ |= (DBGMCU_APB1_FZ_DBG_IWDG_STOP))
  4.  
  5. //Biblioteka LL
  6. #define LL_DBGMCU_APB1_GRP1_WWDG_STOP      DBGMCU_APB1_FZ_DBG_WWDG_STOP  
  7. #define LL_DBGMCU_APB1_GRP1_IWDG_STOP      DBGMCU_APB1_FZ_DBG_IWDG_STOP
  8.  
  9. __STATIC_INLINE void LL_DBGMCU_APB1_GRP1_FreezePeriph(uint32_t Periphs)
  10. {
  11.   SET_BIT(DBGMCU->APB1FZ, Periphs);
  12. }
  13.  
  14. __STATIC_INLINE void LL_DBGMCU_APB1_GRP1_UnFreezePeriph(uint32_t Periphs)
  15. {
  16.   CLEAR_BIT(DBGMCU->APB1FZ, Periphs);
  17. }

Jest to najbardziej przydatne zwłaszcza podczas pracy w trybie debugowania.