와치독은 학부생때 배웠는데 기억이 나질않는건지 안배운 것인지 생소하게 느껴졌다. 이전에 타이머 종류에서 와치독 타이머를 봤는데 이번 파트에 좀 더 깊게 다뤄보자.
Watchdog이란 단어 뜻은 집 지키는/감시하는 개를 뜻하며, 이 뜻을 생각해보면 임베디드에서의 Watchdog이 어떤건지 좀 쉽게 와닿을 듯하다.
임베디드에서의 Watchdog은 시스템이 무한루프에 빠지거나 중단되는 현상을 감시하는 것을 뜻한다. 임베디드시스템은 특수 목적을 가지고 작업을 수행하는 시스템이기 때문에, 물리적인 리셋에만 의존할 수 없다. 그래서 자동으로 리셋하여 에러를 복구하는 Watchdog이 필요한 것이다.
STM32 시리즈에서는 IWDG와 WWDG 두가지가 있다고 하는데 차이는 어느 클럭을 제공받는지 차이가 있다.
LSI(Low-Speed Internal oscillator)라는 내장클럭인데 시스템에 영향을 받지 않아 느리지만 독립적으로 동작한다. 이는 Independent-WDG와 연관있으며 왜 독립적 WDG인지 알 수 있는 특성이다. 코딩이나 소프트웨어쪽의 에러보단 전체적인 시스템, 하드웨어쪽 문제가 발생했을 경우를 대비하는 것이다.
반면 WWDG는 시스템 클럭(PLCK1)에 영향을 받아 동작하기에 빠르고 정확하지만 독립적이지 못하다.(시스템이 멈추면 같이 멈출 수 있음) 그래서 주어진 시간 안에서의 소프트웨어적인 동작을 감시하는 것이고, 이 주어진 시간 범위(타이밍 범위)를 '윈도우'라고 불러서 Window-WDG라고 일컫는 것 같다.
이번 실습에는 LED가 깜빡이는 도중에 사용자 버튼을 눌러 무한루프에 빠지게 만들면 무한루프에 벗어나 리셋되는 것을 눈으로 확인할 것이다.(WWDG)
프로젝트 생성과 외부클럭 비활성화는 이전과 동일하게 설정해준다. 그리고 WWDG를 선택하고 Activated를 체크해준다.

그러면 하단에 Parameter Settings이 생기는데 WWDG counter clock prescaler = 8, WWDG window value = 80, WWDG free-running downcounter value = 127로 설정한다.

이후에 클럭 설정에 들어가보면 PCLK1이 32MHz로 설정되어 있는 것을 볼 수 있는데 이것이 최적의 상태로 설정되어있는 것이라 한다. 왜 최적의 상태라는 것인지 이유가 나와있지 않지만 뒷부분에서 다뤄보겠다.

그리고나서 코드 생성을 해주면 MX_WWDG_Init()이라는 새로운 코드를 볼 수 있다. 역시 여기서도 WWDG의 초기화 코드를 확인할 수 있다.

아까 설정해준 것을 기준으로 와치독 갱신 가능 구간은 49.152~66.56ms이다. 이 시간은 뒤에서 설명하도록하고 갱신 구간에 맞게 딜레이를 줄 것이다. 딜레이를 50ms로 주고, 20번의 사이클마다 LED를 토글시켜 1초마다 깜빡이도록 만들어준다. HAL_WWDG_Refresh()함수를 호출해 갱신해주는데 갱신 주기가 틀어져버리면(오류로 인해) 리셋되는 것이다.
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint8_t i = 0;
while (1)
{
if(i == 20) {
i = 0;
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}
HAL_Delay(50);
i++;
if(HAL_WWDG_Refresh(&hwwdg) != HAL_OK) {
Error_Handler();
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
여기다가 외부 인터럽트를 활용해 무한루프에 진입하게 만들고, 이 때 리셋을 하는지 육안으로 확인할 수 있도록 한다. 이전에 배운 사용자 버튼으로 외부 인터럽트를 발생시킬 때 호출되는 콜백함수를 활용할 것이다. 콜백함수에는 간단하게 무한루프에 진입하는 코드를 작성한다.

그리고 리셋을 확인하는 방법은 부팅 시에 __HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)를 통해 와치독에 의한 리셋 유무를 확인할 수 있다고 한다. 원래는 코드 작성할 때 USER~로 시작하는 사용자 작성란에 했는데 이번에는 MX_USART2_UART_Init(); 과 MX_WWDG_Init(); 사이에 코드를 작성한다. 간단한 방법을 찾다보니 예외적인 상황이라고 하는데, Init 순서 때문이지 않을까 싶다.
__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)를 통해 와치독에 의한 리셋유무를 확인하는데 WWDG초기화가 먼저 와버리면 리셋유무 판단이 불가능하기 때문일 것이다. 그리고 GPIO초기화를 해야 LED를 사용할 수 있기 때문이기도 하다.
아무튼 와치독 리셋일 경우 LED를 4초간 점등 시켜주는 코드를 짜면
MX_GPIO_Init();
MX_USART2_UART_Init();
if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET) {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
HAL_Delay(4000);
__HAL_RCC_CLEAR_RESET_FLAGS();
} else {
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
MX_WWDG_Init();
__HAL_RCC_CLEAR_RESET_FLAGS()함수는 리셋 플래그를 지워주는 함수라고 한다. 만약 지워주지 않으면 보드에 리셋 버튼을 눌러도 와치독에 의한 리셋으로 판별해 LED가 4초간 점등될 것이다. 동작을 확인해보면 사용자 버튼을 눌렀을 때 LED가 4초간 점등된 후 다시 정상적으로 깜빡이는 것을 볼 수 있다.

아까 최적의 상태라는 것과 와치독 시간에 대한 것은 지금부터 알아보겠다.
데이터 시트를 확인하면 와치독 시간을 계산하는 공식이 나와있다.


여기서 공식을 보고 계산을 해보면
1/32,000,000 * 4096 * 8 * (127 - 80 + 1) = 49.152ms
1/32,000,000 * 4096 * 8 * (127 - 63 + 1) = 66.56ms
49.152 ~ 66.56ms가 갱신 가능 시간인 것을 확인할 수 있다. 그래서 50ms의 딜레이를 줘 갱신 시간 범위 내에 실행되게끔 설정한 것이다. 32MHz가 최적의 상태라는 이유는 IDE자체에서 최적의 값을 잡아준다고 하는데, MCU입장에서 너무 빠르거나 느리지않는 리프레쉬 주기를 설정해주게끔 클럭 주기를 잡아준다고 한다.(GPT피셜)

127부터 다운 카운터를 한다는데 여기서 최대값기준이 왜 0x3F인지 궁금했다. WWDG_CR(컨트롤러 레지스터)를 보면 T5~T0, 즉 T[6:0] -> 0x7F ~ 0x40까지 카운트 동작하고 T6이 0이 되는 순간 리셋이 발생한다고 한다. 고로 window value를 127로 설정해주면 이론상 설정가능한 갱신 최소 시간부터 최대시간까지 셋팅되는 것이다.