W tym poście chciałbym opisać sposób obsługi IWDG oraz WWDG z wykorzystaniem bibliotek LL.
IWDG jest taktowany z wewnętrznego zegara LSI RC o wartości 32kHz
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ść:
Oznacza to, że dla ustawienia dzielnika na 32 oraz licznika na 999 otrzymana zostanie następująca wartość:
- 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:
- RL = ((1[s] * 32000)/(4*2^3)) - 1 = (32000 / 32) - 1 = 999
- lub
- 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:
- static void MX_IWDG_Init(void)
- {
- /* USER CODE BEGIN IWDG_Init 0 */
- /* USER CODE END IWDG_Init 0 */
- /* USER CODE BEGIN IWDG_Init 1 */
- /* USER CODE END IWDG_Init 1 */
- LL_IWDG_Enable(IWDG);
- LL_IWDG_EnableWriteAccess(IWDG);
- LL_IWDG_SetPrescaler(IWDG, LL_IWDG_PRESCALER_32);
- LL_IWDG_SetReloadCounter(IWDG, 999);
- while (LL_IWDG_IsReady(IWDG) != 1) { }
- LL_IWDG_ReloadCounter(IWDG);
- /* USER CODE BEGIN IWDG_Init 2 */
- /* USER CODE END IWDG_Init 2 */
- }
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:
- /**
- * @brief Check if RCC flag Independent Watchdog reset is set or not.
- * @rmtoll CSR IWDGRSTF LL_RCC_IsActiveFlag_IWDGRST
- * @retval State of bit (1 or 0).
- */
- __STATIC_INLINE uint32_t LL_RCC_IsActiveFlag_IWDGRST(void)
- {
- return (READ_BIT(RCC->CSR, RCC_CSR_IWDGRSTF) == (RCC_CSR_IWDGRSTF));
- }
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.
- // W pliku syscalls.c
- extern int __io_putchar(int ch) __attribute__((weak));
- //Należy dołożyć własną definicję funkcji
- int __io_putchar (int ch)
- {
- while (!LL_USART_IsActiveFlag_TXE(USART2));
- //funkcja z biblioteki LL
- LL_USART_TransmitData8(USART2, (char)ch);
- //Lub poprostu tak
- //USART2->DR = ch;
- return ch;
- }
Powyższa funkcja wywoływana jest przez funkcję _write, z której korzysta printf:
- __attribute__((weak)) int _write(int file, char *ptr, int len)
- {
- int DataIdx;
- for (DataIdx = 0; DataIdx < len; DataIdx++)
- {
- __io_putchar(*ptr++);
- }
- return len;
- }
Po ponownym uruchomieniu układu należy sprawdzić czy reset został wywołany z IWDG. Jeśli tak należy wyczyścić flagę:
- if (LL_RCC_IsActiveFlag_IWDGRST())
- {
- printf ("Reset wywolany z IWDG\r\n");
- LL_RCC_ClearResetFlags();
- }
- else
- {
- printf("Reset bez IWDG\r\n");
- }
W pętli przeładowuję licznik od IWDG. Dodatkowo po kliknięciu przycisku zostaje dodane dodatkowe opóźnienie
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- if (LL_GPIO_IsInputPinSet(GPIOA , LL_GPIO_PIN_0))
- {
- printf("Dodatkowe opoznienie\r\n");
- LL_mDelay(2000) ;
- }
- LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_12);
- LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_13);
- LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_14);
- LL_GPIO_TogglePin (GPIOD,LL_GPIO_PIN_15);
- LL_mDelay(500);
- LL_IWDG_ReloadCounter(IWDG);
- }
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.
- 42000000 / 4096 / 8 = 1281,738 [Hz] => 1/1281,738 = 780 [us]
Konfiguracja w CubeMx jest następująca:
Teraz trzeba wyliczyć okno w jakim możliwe będzie odświeżenie licznika z takimi ustawieniami.
- Dolny licznik twwdg = tclkwwdg * (127 - 63) = 780us * (64) = => 49,920 ms
- Górny licznik, czas liczony od startu dolnego licznego
- 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:
- static void MX_WWDG_Init(void)
- {
- /* USER CODE BEGIN WWDG_Init 0 */
- /* USER CODE END WWDG_Init 0 */
- /* Peripheral clock enable */
- LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_WWDG);
- /* USER CODE BEGIN WWDG_Init 1 */
- /* USER CODE END WWDG_Init 1 */
- LL_WWDG_SetCounter(WWDG, 127);
- LL_WWDG_Enable(WWDG);
- LL_WWDG_SetPrescaler(WWDG, LL_WWDG_PRESCALER_8);
- LL_WWDG_SetWindow(WWDG, 90);
- LL_WWDG_EnableIT_EWKUP(WWDG);
- /* USER CODE BEGIN WWDG_Init 2 */
- /* USER CODE END WWDG_Init 2 */
- }
Po wywołaniu przerwania zostaje ustawiona flaga w EWI w rejestrze CFR. Po wywołaniu przerwania należy wyczyścić flagę.
- void WWDG_IRQHandler(void)
- {
- if(LL_WWDG_IsActiveFlag_EWKUP(WWDG))
- {
- LL_WWDG_ClearFlag_EWKUP(WWDG);
- printf("WWDG interrupt.\r\n");
- }
- }
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:
- if(LL_RCC_IsActiveFlag_WWDGRST())
- {
- printf("Reset wywolany z WWDG\r\n");
- LL_RCC_ClearResetFlags();
- }
- else
- {
- printf("Reset bez WWDG\r\n");
- }
Funkcja main wygląda następująco:
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- /* USER CODE END 1 */
- /* MCU Configuration--------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
- LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
- NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
- /* System interrupt init*/
- /* SysTick_IRQn interrupt configuration */
- NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),15, 0));
- /* USER CODE BEGIN Init */
- /* USER CODE END Init */
- /* Configure the system clock */
- SystemClock_Config();
- /* USER CODE BEGIN SysInit */
- /* USER CODE END SysInit */
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_USART2_UART_Init();
- LL_mDelay(100);
- MX_WWDG_Init();
- /* USER CODE BEGIN 2 */
- if(LL_RCC_IsActiveFlag_WWDGRST())
- {
- printf ("Reset wywolany z WWDG\r\n");
- LL_RCC_ClearResetFlags();
- }
- else
- {
- printf("Reset bez WWDG\r\n");
- }
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- if (LL_GPIO_IsInputPinSet(GPIOA , LL_GPIO_PIN_0) && btnflag == 0)
- {
- btnflag = 1;
- LL_GPIO_TogglePin (GPIOD , LL_GPIO_PIN_15);
- LL_mDelay(60) ;
- }
- else
- {
- LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_12);
- LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_13);
- LL_GPIO_TogglePin(GPIOD, LL_GPIO_PIN_14);
- LL_mDelay(40) ;
- }
- LL_WWDG_SetCounter(WWDG , 127);
- }
- /* USER CODE END 3 */
- }
Po kliknięciu przycisku następuje zwiększenie czasu opóźnienia, co powoduje zbyt późne odświeżenie licznika:
- Reset bez WWDG
- //Po kliknięciu
- WWDG int
- Reset wywolany z WWDG
Działanie w różnych trybach oszczędzania energii wygląda następująco:
Do zatrzymywania i uruchamiania WWDG jak i IWDG zostały udostępnione odpowiednie funkcje oraz makra:
- //Biblioteka HAL
- #define __HAL_DBGMCU_FREEZE_WWDG() (DBGMCU->APB1FZ |= (DBGMCU_APB1_FZ_DBG_WWDG_STOP))
- #define __HAL_DBGMCU_FREEZE_IWDG() (DBGMCU->APB1FZ |= (DBGMCU_APB1_FZ_DBG_IWDG_STOP))
- //Biblioteka LL
- #define LL_DBGMCU_APB1_GRP1_WWDG_STOP DBGMCU_APB1_FZ_DBG_WWDG_STOP
- #define LL_DBGMCU_APB1_GRP1_IWDG_STOP DBGMCU_APB1_FZ_DBG_IWDG_STOP
- __STATIC_INLINE void LL_DBGMCU_APB1_GRP1_FreezePeriph(uint32_t Periphs)
- {
- SET_BIT(DBGMCU->APB1FZ, Periphs);
- }
- __STATIC_INLINE void LL_DBGMCU_APB1_GRP1_UnFreezePeriph(uint32_t Periphs)
- {
- CLEAR_BIT(DBGMCU->APB1FZ, Periphs);
- }
Jest to najbardziej przydatne zwłaszcza podczas pracy w trybie debugowania.
[1] STM32 IWDG
[2] STM32 WWDG