펌웨어 수업을 하는 동안 계속 ‘워치독’이라는 키워드가 나왔는데, 내 머릿속에는 ‘아 그거 서비스 아닌가? 이전에 일 잠깐 할 때 슬랙으로 데이터나 에러 관련해서 알림 오는걸 본 것 같은데’ 라는 생각이 어렴풋이 계속 들었었다. 보라색의 귀여운 강아지 이미지가 연상되면서 ㅋㅋㅋ 근데 알고보니 얘는 데이터독이였다. 웁스.
ㅤ
다시봐도 이미지가 애기 진돗개나 골댕이 같기도 해서 좀 귀엽네 ㅎ 🐕🐶

ㅤ
워치독은 Independent WDG과 Window WDG로 나뉜다. 우선은 Independent WDG부터 살펴보자. 혹시 글이 길어진다면 WWDG는 따로 빠져서 작성될 수도! [당첨]
ㅤ
Independent Watch Dog Timer의 목적

- 시스템 장애 감지 및 자동 복구: 소프트웨어가 무한 루프에 빠지거나 멈추면 자동으로 시스템을 재부팅
- 임베디드 시스템의 신뢰성 향상: 외부 개입 없이 자동으로 정상 상태로 복구
- 차량용 ECU에서 필수: 모터, 센서 등의 장애로 인한 시스템 중단 상황에서 자동 복구
ㅤ
시스템이 돌다가 갑자기 반응을 하지 않거나 특정 동작을 무한히 수행하면서 진전이 없어 열심히 코드는 실행하지만 의미없는 동작을 반복하고 있을 때, 시스템의 복구를 위해 사용하는 친구이다. Independent 워치독은 프로그램와 완전히 별개로 동작하는 프로세스이다! (그래서 Independent 구나! 아하)
ㅤ
뭔가 문제가 있어서 뻗어있는 시스템을 다시 살려낼 수 있는 거의 유일한 방법!
ㅤ
Independent Watch Dog Timer의 HW 분석
메인 CLK을 사용하는 Peripheral 의 타이머와 달리, Independent 워치독(이하 IWDG)은 내부 RC 발진 클럭을 이용해 동작한다. 이 덕분에 메인 CLK이 멈추어서 CPU는 뻗었다고 하더라도 IWDG은 관계없이 동작할 수 있다. LSI는 크리스탈이 아니라 RC 회로를 통해서 만들기 때문에 타이밍이 정확하지는 않다. 다만 IWDG은 정확한 CLK 타이밍이 중요한 녀석은 아니라서 큰 관계는 없다.
ㅤ
CLK Tree 상에서도 보면 이 32,768Hz의 RC 발진 내부 CLK인 LSI와 연결된 것을 볼 수 있다. 메인 CLK은 주로 외부 크리스탈을 통해서 만들어지는 (최대) 168MHz의 고속 CLK을 사용한다.

ㅤ
지금 보니 LSE RC 32 kHz 이거 오타인 듯 ㅋㅋ
왼쪽에 이미 LSE 가 있어서, 얘는 LSI가 맞는 것 같다.
ㅤ
Independent Watch Dog Timer의 구조와 동작 이해

기본적으로 IWDG는 다음의 과정으로 동작한다.
- LSI CLK이 IWDG에게 공급된다.
- Prescaler에 의해서 CLK이 분주된다.
- Reload 레지스터에서 지정한 값부터 0까지 CLK 마다 하나씩 감소한다.
- 0에 도달하면 IWDG Reset 으로 MCU 시스템을 리셋한다.
ㅤ
IWDG은 아주아주 단순한 타이머이다. 다만 타이머가 다 흘러갔을 때의 영향력이 매우 강력할 뿐…
ㅤ
Independent Watch Dog Timer는 어떻게 제어하는가
IWDG은 MCU에게 있어서는 정말정말 최후의 안전장치이다. MCU의 CPU가 동작을 못하고 있을 때(어찌보면 심정지 상태가 되었을 때) IWDG의 신호를 통해서 시스템을 다시 돌아가게 만드는 역할(제세동!)이므로 IWDG는 CPU의 상태나 실행 환경에 관계없이 안정적으로 돌아가야한다.
ㅤ
그런데 만약에 MCU가 배치된 곳이 전자기 노이즈가 가득하거나 우주방사선이 가득한 환경이라서 메모리의 비트가 의도치않게 수정되어버린다면? 또는 버그로 인해서 메모리 Write 과정에서 의도치않게 워치독의 레지스터 값이 수정되어버린다면? 그러면 IWDG으로서의 제 역할을 다 수행하지 못하게 될 수도 있다.
ㅤ
그래서 IWDG은 최대한 안전하게 유지될 수 있도록 여러 매커니즘들을 기존의 타이머(또는 주변장치)와 다르게 설계되어있다.
- IWDG가 내부 클럭인 LSI를 사용하여 CPU 쪽 메인 CLK이 멈추거나 크리스탈 파손, AMBA 버스 동작 멈춤 등으로 문제가 발생하더라도 IWDG는 동작한다. 심지어 LSI는 멈추는 상황을 대비해 자동 복구 기능까지 붙어있다.
- Key 레지스터에 특정한 값이 들어오면 IWDG를 시작하거나 카운터 값을 다시 채우거나 설정 값을 변경할 수 있게된다.
- 아래에 기술할 내용이지만, Prescaler 값이 2의 제곱수로 고정되어있다.
- 혹시 Bit 가 잘못 쓰여서 수정되는 경우를 막기 위해서 IWDG을 동작시키는 KR 레지스터는 특정 패턴으로 값이 쓰여야 동작한다. 하나의 Bit 수정으로는 패턴을 만들 수 없다.
- 일단 IWDG가 동작하기 시작하면 웬만해선 IWDG를 막을 수 없다. 코드로 IWDG의 동작을 멈추게 만드는 방법은 없다. 누가 시스템을 해킹해서 IWDG를 정지시켜버리면 IWDG가 무용지물이 되어버리니, 워치독의 동작을 막는 것을 금지시켜버렸다.
ㅤ

ㅤ
진짜 빡시게 관리하긴 하네. 분명 이것도 누군가의 피로 쓰여진 페리퍼럴과 메뉴얼이겠지…? 도대체 무슨 일이 있었기에 이정도의 하드한 장치를 추가하게 된 것일까 ㅋㅋ
ㅤ

Key 레지스터는 쓰기 전용 레지스터이다.
0x5555값이 써져있으면 PR(prescaler) 레지스터와 RLR(reload) 레지스터에 접근할 수 있다.0xCCCC값이 써져있으면 워치독을 시작시킨다.0xAAAA값을 주기적으로 써주지 않으면 카운터가 0이 되어 리셋을 발생시킨다.
ㅤ

굉장히 특이하다. 이때까지 다른 타이머들의 Prescaler는 2진수로 구성되어 있었는데, IWDG의 Prescaler는 $2^N$ 의 값으로 만들어진다. 이것 역시 간결한 동작을 위해 설계된 방식이다. 사실 LSI CLK 자체가 오차가 크기 때문에 IWDG의 정확한 시간 제어는 큰 의미가 없다. 오히려 “간단하고 확실하게 동작하는” 시스템이 더 의미가 있다. 안정적으로 적당한 범위 내에서만 설정할 수 있도록 8개의 설정만 제공한다.
ㅤ
Read/Write가 모두 가능한 레지스터이지만, 읽기 동작을 했을 때는 실제 Prescaler 값을 리턴한다고 되어있다. 아래에서 조금 더 상세하게 다이어그램과 함께 확인하자.
ㅤ
여기에서 110과 111에서의 Prescaler 값이 동일한 것은 버그가 아니라 기능인 것 같다.
진짜로 그런지는 좀 있다가 한 번 출력해보자.
ㅤ

IWDG의 카운터는 RLR 레지스터의 값에서부터 0까지 downcount 모드로 내려간다. 즉 Reset 까지 걸리는 시간은 RLR 레지스터의 값과 연관되어있다.
ㅤ
여기에서도 마찬가지로, 읽기 동작을 하면 실제 Prescaler 값을 리턴한다고 되어있다.
ㅤ

SR 레지스터에는 단 2개의 bit가 있다. 하나는 Reload Value Update, 하나는 Prescaler Value Update. 이 2개의 Bit는 PR 또는 RLR 레지스터에 작성한 값이 실제로 반영되는 동안에 SET되고, 반영이 완료되면 RESET 되는 값이다. PR 또는 RLR 레지스터에 값을 썼다면 SR 레지스터에서 RVU또는 PVU Bit의 값이 Reset 될 때 까지 기다려줘야한다. 이 과정은 LSI 기준으로 5 Cycle 정도 소요된다.
ㅤ
32768Hz 에게 5 CLK→ 168MHz를 사용하는 CPU 입장에서는 대략 25635 CLK 정도라고 생각하면 된다.
ㅤ

- 여기에서 PR 레지스터에 값을 쓰면 VDD 영역에 있는 prescaler로 값이 넘어오게되고, RLR 레지스터에 값을 쓰면 reload value로 값이 넘어오게 된다.
- 이 값들이 들어오는 중에는 SR 레지스터의 bit가 1이 되고, 값 전달이 완료되면 0으로 다시 내려간다.
- KR 레지스터로
0xAAAA를 넣어주면 Reload Value를 다시 Downcounter에게 넣어준다. - 만약 downcounter의 값이 0이 되면 IWDG Reset 이벤트가 발생한다.
ㅤ
위 그림에서 VDD voltage domain 이 뭘까 계속 고민하다가 검색해봤다.
이건 전원을 사용하는 유형에 따라 도메인을 분리한 것인데, CORE 의 도메인에서는 1.2V의 전원을 사용하고 VDD 도메인에서는 1.8~3.6V 의 전원을 사용한다. (CPU랑 별개의 전압과 전원을 사용한다!) 이 또한 CPU가 전원이 없어서 죽었을 때 / Sleep 모드에 들어갔을 때에도 IWDG은 아득바득 살아남아 CPU에게 심폐소생술을 하도록 하기 위한 설계인가보다.
이외에는 VDDA(아날로그), VBAT(전원 끊겨도 유지)이 있다.
ㅤ
한 번 써보자!
여러가지 테스트를 해봤는데, 생각보다 까다로운 녀석이다. 내가 생각했던 것과 전혀 다르게 동작해서, 뭔가뭔가였다. 이것도 설계상의 이유가 있을 것 같기는 한데… 버그가 아니라 기능이겠지?
ㅤ
박치기 공룡의 IWDG 와의 한 판 승부
아래가 가장 기본적인 IWDG를 시작하고 설정하는 코드이다.
// LSI 활성화
RCC->CSR |= RCC_CSR_LSION;
while (!(RCC->CSR & RCC_CSR_LSIRDY));
// IWDG 시작하기
IWDG->KR = 0xCCCC;
// 설정 변경
IWDG->KR = 0x5555;
IWDG->PR = 0x05;
IWDG->RLR = 2000;
uart_printf("SR BEFORE: %X\n\r", IWDG->SR);
uart_printf("SR BEFORE: %X\n\r", IWDG->SR);
uart_printf("SR BEFORE: %X\n\r", IWDG->SR);
IWDG->KR = 0xAAAA;
uart_printf("SR AFTER: %X\n\r", IWDG->SR);

ㅤ
이렇게 코드를 작성해서 UART로 SR 값이 들어오는 타이밍을 확인해보면, 첫 번째 print 에서는 3이였다가, 두 번째 print를 실행하는 시점에서는 0으로 변경된 것을 볼 수 있다.
ㅤ
코드를 작성했을 때, 가장 의아했던 부분이 바로 요것이다. “설정 후 시작이 아니라, 시작 후 설정해야함.” 왜이렇게 만들어졌을까 🤔🤔🤔
ㅤ
처음에는 IWDG의 설정을 하고, IWDG→SR 의 값이 0이 되면 (모든 설정 값이 제자리를 찾고나면) KR 레지스터에 0xCCCC를 쓰면서 워치독을 시작시키려고 했는데, 실제로 해보니깐 전혀 0으로 갈 생각이 없어보였다. 문서에 별다른 내용은 없었던 것 같은데, RCC 쪽 활성화도 다 해줬는데 이게 동작하지 않아서 다른 문서들을 뒤져보기도하고 검색으로 레딧을 뒤져보면서 낑낑댔다.
// 내가 시도한 코드
// LSI 활성화
RCC->CSR |= RCC_CSR_LSION;
while (!(RCC->CSR & RCC_CSR_LSIRDY));
// 설정 변경
IWDG->KR = 0x5555;
IWDG->PR = 0x05;
IWDG->RLR = 2000;
// 설정 반영 대기
while(IWDG->SR != 0x0);
// IWDG 시작하기
IWDG->KR = 0xCCCC;
여기에서 무한루프가 걸려버린다.

ㅤ
그런데 이게 HAL 쪽 코드를 뒤져봤을 때도 그렇고 내가 임상적으로 해봤을 때도 그렇고, 먼저 IWDG를 동작시킨 다음에 값을 설정해주고 있었다. (띠용) 원래 타이머라면 설정 후 시작이 기본 아니였던가…?
ㅤ

ㅤ
그래서 이게 IWDG을 사용할 때는 일단 시작시키고 → 설정값 변경하고 → 잘 변경되었는지 확인해야함을 확인할 수 있었다. (아까운 내 시간…)
ㅤ
ㅤ
ST 에서 올려준 IWDG에 대한 설명 문서와 ST 커뮤니티에 올라온 이 글에서도 일단 시작시키고, 그다음에 설정하라고 되어있다.

ㅤ
이게 뭔가 일단 IWDG를 시작시켜야 CLK이 돌아가면서 동기화 회로가 세팅 값들을 받을 수 있도록 하드웨어를 구현해둔 이유 때문인 것 같다. 아니면 이 값을 설정하는 것도 안전하게 지켜야하는 시스템의 일부로 봐서, 설정이 안되면 IWDG Reset을 발생시켜 버리려고 일부러 이렇게 만들었을지도?
ㅤ
IWDG 활용방법
그래서 이 IWDG는 어떻게 쓰냐?
ㅤ
PR 레지스터와 RLR 레지스터에 설정한 값에 따른 제한시간 안에 0xAAAA 를 KR 레지스터에 Write 해주지 않으면 시스템이 리셋되어버린다.
ㅤ
그렇다는 말은, 시스템이 안정적으로 돌아가는 환경에서 주기적으로 제한시간이 지나기 전에 0xAAAA를 KR 레지스터에 써주면서 프로그램을 실행시키면 ‘문제 없이 돌아가는 상황’이 되는 것이고, 뭔가 CPU가 무한루프에 빠지거나 Fault가 연달아 발생해서 제대로 CPU가 일처리를 하지 못하고 있는 상황이라 코드 실행흐름에 진전이 없는 경우에 0xAAAA를 써주지 못한다면 IWDG가 이걸 감지하고 시스템을 리셋시켜서 처음부터 다시 안정된 상태로 프로그램을 실행할 수 있도록 한다.
ㅤ
아래 예시 코드에서는 일부러 반복문을 진행할 때마다 점점 더 많은 연산이 필요해지도록 코드를 구성하였다. 그리고 IWDG의 값들을 세팅해서 1초 간격으로 Counter가 0으로 내려가도록 설정하였다.
// LSI 활성화
RCC->CSR |= RCC_CSR_LSION;
while (!(RCC->CSR & RCC_CSR_LSIRDY));
// IWDG 시작하기
IWDG->KR = 0xCCCC;
// 설정 변경
IWDG->KR = 0x5555;
IWDG->PR = 0x6;
IWDG->RLR = 0x80;
// 설정 반영 대기
while(IWDG->SR != 0x0);
IWDG->KR = 0xAAAA;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
int score = 1;
while (1)
{
for(int i = 0; i < 400000 * score; i++) {}
uart_printf("SCORE: %d\r\n", score);
score++;
IWDG->KR = 0xAAAA;
}
ㅤ
그러면 아래처럼 대략 23번째 Loop 까지는 1초 내에 연산을 하다가, 24번째 Loop 부터는 1초 이상 시간이 걸리면서 더 연산을 수행하는 대신에 IWDG의 reset 명령으로 처음부터 다시 프로세스를 실행하게 된다.

ㅤ
IWDG 리셋으로 다시 시작한다면
만약 IWDG 에 의해 리셋이 발생했다면 뭔가 예상치못한 문제가 발생한 상황일 것이다. 이때 일단 시스템을 다시 실행시켜서 CPU를 살려낸 다음에 에러 로그를 찍든 안전모드 부팅을 하든 처리를 해주어야 한다. 현재 프로세스가 어떤 이유로 리셋되었는지는 RCC의 CSR 레지스터를 통해 확인할 수 있다.
int main(void)
{
/* USER CODE BEGIN 1 */
if(RCC->CSR & RCC_CSR_IWDGRSTF) {
// IWDG로 인한 리셋
uart_printf("Watchdog Reset detected!\n\r");
// **<<< 여기에서 필요한 일 하기 >>>**
} else if(RCC->CSR & RCC_CSR_PORRSTF) {
// Power-On Reset
uart_printf("Power-On Reset\n\r");
} else if(RCC->CSR & RCC_CSR_SFTRSTF) {
// Software Reset
uart_printf("Software Reset\n\r");
}
// 플래그 클리어 (필수!)
RCC->CSR |= RCC_CSR_RMVF;
main init 부분 작성하기
...
ㅤ
뭔가 Fault가 나서 실행이 안되고 있었는데, 생각해보니깐 main 시작하자마자 저 if 문을 넣었더니 USART3 쪽이 CLK이나 레지스터 세팅이 되지 않아서 출력하지 못하는 문제였다 ㅎ
ㅤ
IWDG를 통해 리셋된 경우에, 디버거로 Step을 찍어보면 이렇게 IWDG 쪽으로 부팅되는 것을 확인할 수 있다!

ㅤ
그리고 이 RCC_CSR 레지스터에는 다양한 부팅 원인들이 담겨있다. 그 중에서 29번 Bit 인 IWDGRSTF가 IWDG에 의해서 부팅되었을 때 SET 되는 플래그이다. 이외에도 여럿 있는데, 곧 공부할 WWDG에 의한 리셋과 PIN 리셋 (보드에 붙어있는 검은색 버튼)에 의해 SET 되는 Flag도 있는 것을 볼 수 있다.


ㅤ
워치독은 디버그 모드도 예외없다. 디버거도 개조심.
디버깅 중에 다음 코드로 진행못하게 브레이크포인트를 박아두더라도, IWDG는 CPU와 관계없이 동작하는 녀석이기 때문에 그냥 “어라 멈췄네? 리셋시켜” 하고 시스템을 리셋하고 다시 실행시켜버린다.

시스템 시작을 하고나서 브레이크 걸었는데, 그냥 다시 시스템을 리셋해서 시작시켜버리는 카리스마 넘치는 워치독의 모습…
ㅤ
이러한 경우에 만약 IWDG도 같이 정지하기를 원한다면, 디버그 모드에서 IWDG의 동작을 중지시키는 설정을 따로 해줘야한다. 놀랍게도 이 방법 역시 레지스터로 제공한다…

CMSIS에서는 DBGMCU 라는 분류 안에서 APB1FZ라는 이름으로 레지스터가 제공된다. 여기에서 DBG_IWDG_STOP Bit를 SET 해주면 된다.
DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_IWDG_STOP;
// 또는
DBGMCU->APB1FZ |= 0x1 << 12;
ㅤ
이걸 코드로 하는건 죽도록 싫다면, STM32CubeIDE에서 Debug Configuration 메뉴에 들어가서 ‘Suspend watchdog counters while halted’를 Enable로 설정해주면 된다.

ㅤ
그러면 디버거가 동작할 때 지멋대로 리셋해버리지 않고 야물딱지게 잘 잡혀있다.

'Embedded System > MCU' 카테고리의 다른 글
| [MCU] CAN 통신 - 빠른 고속 CAN vs 안전한 저속 CAN (0) | 2025.12.25 |
|---|---|
| [MCU] General Purpose Timer (0) | 2025.11.09 |
| [MCU] STM32F429ZIT6의 타이머들 (0) | 2025.11.07 |
| [MCU] Basic Timer (2) | 2025.11.07 |
| [MCU] SysTick 타이머 (2) | 2025.11.04 |