[Quick Summary (TL;DR) - For Global Developers]
- Symptom: 시스템이 정상 주기를 무시하고 무한히 재부팅(Infinite Reset Loop)되거나 특정 주변장치 응답 대기 시 MCU가 완전히 먹통(System Freeze)이 됨.
- Cause: 메인 루프 내부에 HAL_Delay() 같은 차단형(Blocking) 지연 함수를 사용하거나 외부 하드웨어의 응답을 무한 대기(Infinite polling)하여 독립형 워치독 타이머(IWDG) 레지스터를 제때 리프레시(Kicking)하지 못함.
- Solution: 차단형 대기 코드를 HAL_GetTick() 기반의 비차단(Non-blocking) 시간 비교 구조 및 상태 머신(State Machine) 아키텍처로 전면 수정하여 어떤 분기에서도 주기적으로 HAL_IWDG_Refresh()가 호출되도록 개선함.
독립형 워치독 타이머(IWDG) 무한 리셋 현상과 시스템 다운 증상 (Introduction)
임베디드 시스템 펌웨어 개발 및 필드 테스트 과정에서 하드웨어가 멈추거나 예기치 않게 무한 재부팅되는 무한 리셋 루프(Infinite Reset Loop) 버그는 자주 발생합니다.
시스템 안정성을 위해 탑재한 독립형 워치독 타이머(IWDG, Independent Watchdog)가 도리어 하드웨어를 지속적인 리셋 상태로 몰고 가는 현상입니다. 디버거 가동 중에는 정상 동작하다가도 단독 구동(Standalone) 상태만 되면 수 초 이내에 시스템이 셧다운되거나, 외부 센서 및 통신 칩의 응답이 지연될 때 HardFault_Handler 예외 벡터로 진입하지 않고 시스템이 초기화되어 버리는 증상을 동반합니다.
해외 포럼이나 Stack Overflow 등에서 주로 "MCU infinite reboot loop with IWDG", "Watchdog reset during blocking function", 혹은 "System freeze due to missed watchdog refresh" 같은 키워드로 검색되는 대표적인 메인 루프 설계 결함 구조입니다.
워치독 카운터 다운카운팅 인터벌 및 카운터 리프레시 누락의 근본 원인 (Why it happens)
독립형 워치독(IWDG)은 메인 CPU의 시스템 클럭(HCLK)과 분리된 내부 저속 RC 오실레이터(LSI)를 클럭 소스로 사용합니다. 따라서 메인 코어가 하드웨어 오류나 소프트웨어 데드락(Deadlock)으로 정지하더라도 다운카운터는 독립적으로 연산을 지속합니다.
문제는 펌웨어 아키텍처 내부에서 워치독 카운터를 리프레시하는 HAL_IWDG_Refresh() (Kicking the watchdog) 함수의 호출 주기가 하드웨어 카운터의 오버플로우 임계값보다 길어질 때 발생합니다.
- 차단형 지연 함수(Blocking Delay Execution): HAL_Delay()는 내부적으로 시스템 틱이 내부 카운터 값에 도달할 때까지 while 루프를 돌며 CPU 자원을 완전히 점유하는 구조입니다. 이 지연 시간이 워치독 타임아웃 설정값(예: 1초)을 초과하면 타임아웃 예외가 트리거되어 하드웨어 리셋 신호가 발생합니다.
- 주변장치 폴링 대기(Infinite Hardware Polling): I2C, SPI, UART 통신이나 외부 플래시 메모리 응답을 동기식 폴링 구조(Polling mode)로 대기할 때, 물리적 단선이나 슬레이브 장치의 전원 불량으로 인해 상태 레지스터의 비트 변화를 무한 대기하게 되면 메인 루프 하단에 배치된 워치독 리프레시 함수에 절대 도달할 수 없습니다.
무한 리셋을 유발하는 잘못된 IWDG 리프레시 C 코드 예시 (Bad Case)
아래 소스코드는 메인 루프 내부에서 하드웨어 폴링 및 차단형 지연 함수를 무분별하게 사용하여 워치독 리셋 루프를 유발하는 전형적인 오동작 코드입니다.
#include "stm32f4xx_hal.h"
IWDG_HandleTypeDef hiwdg;
/* Bad implementation causing watchdog timeout reset */
int main(void) {
HAL_Init();
MX_IWDG_Init(); // Watchdog timeout is set to 1000ms (1 second)
MX_I2C1_Init();
while (1) {
uint8_t rx_buf[2];
/* CRITICAL BUG: Infinite polling without timeout if hardware fails.
If I2C slave disconnects, execution traps here and watchdog resets MCU. */
while(__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_RXNE) == RESET) {
// Wait indefinitely for hardware response
}
HAL_I2C_Master_Receive(&hi2c1, 0xA0, rx_buf, 2, HAL_MAX_DELAY);
/* CRITICAL BUG: 1500ms blocking delay exceeds 1000ms watchdog threshold.
This statement guarantees a hardware watchdog reset every loop cycle. */
HAL_Delay(1500);
/* This refresh function is never reached due to the issues above */
HAL_IWDG_Refresh(&hiwdg);
}
}
비차단 시간 비교문 및 상태 머신을 적용한 방어적 C 코드 구조 (Good Case)
문제를 해결하기 위해서는 시스템을 완전히 정지시키는 차단형 구조를 타임스탬프 비교문(HAL_GetTick()) 체계로 변경하고, 하드웨어 응답 대기 로직을 상태 머신(State Machine) 아키텍처로 분리하여 어떤 상황에서도 메인 루프가 초당 수천 번 이상 공회전하며 워치독을 리프레시하도록 재설계해야 합니다.
#include "stm32f4xx_hal.h"
typedef enum {
SYS_STATE_IDLE,
SYS_STATE_WAIT_SENSOR,
SYS_STATE_PROCESS,
SYS_STATE_ERROR
} SystemState;
IWDG_HandleTypeDef hiwdg;
I2C_HandleTypeDef hi2c1;
int main(void) {
HAL_Init();
MX_IWDG_Init(); // Watchdog timeout set to 1000ms
MX_I2C1_Init();
SystemState current_state = SYS_STATE_IDLE;
uint32_t prev_tick = HAL_GetTick();
uint32_t sensor_timeout_cnt = 0;
while (1) {
/* Always refresh watchdog at the top of non-blocking main loop */
HAL_IWDG_Refresh(&hiwdg);
/* Task 1: Non-blocking LED toggle or interval function instead of HAL_Delay() */
if (HAL_GetTick() - prev_tick >= 1500) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
prev_tick = HAL_GetTick();
}
/* Task 2: Robust State Machine architecture for hardware I/O */
switch (current_state) {
case SYS_STATE_IDLE:
if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_RXNE) == RESET) {
sensor_timeout_cnt = HAL_GetTick();
current_state = SYS_STATE_WAIT_SENSOR;
} else {
current_state = SYS_STATE_PROCESS;
}
break;
case SYS_STATE_WAIT_SENSOR:
/* Software-level timeout handling before hardware watchdog threshold (800ms) */
if (HAL_GetTick() - sensor_timeout_cnt > 800) {
current_state = SYS_STATE_ERROR; // Handle hardware exception safely
} else if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_RXNE) != RESET) {
current_state = SYS_STATE_PROCESS;
}
break;
case SYS_STATE_PROCESS:
// Execute secure data processing here
current_state = SYS_STATE_IDLE;
break;
case SYS_STATE_ERROR:
/* Safe state execution: Disable components or run safe reboot sequence */
break;
}
}
}
핵심 개선 포인트
- 차단형 지연 함수 제거: 메인 루프를 멈추게 만들었던 HAL_Delay()를 제거하고, HAL_GetTick() 기반의 무차단 시간 비교문 구조로 수정하여 루프의 연속성을 확보했습니다.
- 상태 머신 아키텍처 도입: 외부 하드웨어 응답 대기 로직을 비차단 주기 점검 상태 머신 구조로 변경하여, 임의의 시점에서도 주기적으로 HAL_IWDG_Refresh() 함수가 무조건 실행될 수 있도록 구조적 안전성을 확보했습니다.
- 소프트웨어 레벨 타임아웃 처리: 하드웨어 워치독 타이머 한계 임계값인 1초가 도달하기 전인 800ms 시점에 소프트웨어 카운터 기반의 예외 분기 구조를 추가하여, 예기치 못한 하드웨어 오동작 시에도 먹통 현상 없이 안전 상태(SYS_STATE_ERROR)로 진입하도록 유도했습니다.
시니어 엔지니어의 레지스터 기반 워치독 디버깅 팁 (Debugging Tips)
시스템 리셋이 워치독에 의한 것인지 다른 비정상 코드로 인한 하드폴트(HardFault) 때문인지 디버거 프로브(J-Link, ST-LINK) 및 통합 개발 환경(IDE)을 활용해 정밀하게 추적하는 실무 트러블슈팅 가이드입니다.
- 리셋 소스 제어 레지스터(Reset Status Register) 검증: MCU가 부팅될 때 가장 먼저 제어 레지스터(STM32의 경우 RCC_CSR)의 플래그 비트를 확인해야 합니다. 독립형 워치독 리셋 플래그인 IWDGRSTF 비트가 1로 셋되어 있다면 시스템 전원 노이즈나 하드폴트가 아닌, 메인 루프 지연으로 인한 워치독 타임아웃 리셋임이 100% 입증됩니다. 이 플래그는 확인 후 소프트웨어적으로 RCC_CSR_RMVF를 호출해 클리어해야 다음 리셋 원인을 식별할 수 있습니다.
- 디버그 모드 시 워치독 카운터 동결(Freeze Watchdog Counter in Debug Mode): 디버거를 연결하고 브레이크포인트(Breakpoint)를 잡아 코드를 멈추었을 때 워치독 카운터가 계속 다운카운팅되면 즉시 리셋이 걸려 디버깅이 불가능해집니다. 이를 방지하기 위해 MCU 내부의 디버그 컴포넌트 레지스터를 제어하여 CPU가 멈췄을 때 워치독 타이머도 일시 정지하도록 설정해야 합니다. STM32 기준 하단 코드를 초기화 영역에 삽입하십시오.
__HAL_DBGMCU_FREEZE_IWDG(); // Stops IWDG counter when core is halted via JTAG/SWD
- 워치독 인터럽트(Early Wakeup Interrupt, EWI)의 활용: 물리적 리셋이 걸리기 직전 단계에서 시스템 상태와 스택 프레임을 덤프하기 위해, 하드웨어 스펙이 허용한다면 창 방식 워치독(WWDG) 또는 EWI 인터럽트 기능을 활성화하는 것이 좋습니다. 카운터가 제로가 되기 직전 예외 인터럽트 벡터가 실행되므로, 해당 핸들러 내부에서 현재 중단된 함수 주소(PC, Program Counter)를 로그로 남겨 메인 루프 내 어떤 차단형 기능이 병목을 유발했는지 추적할 수 있습니다.
'Troubleshooting' 카테고리의 다른 글
| [임베디드 C] volatile 키워드 누락으로 인한 컴파일러 최적화 오류 및 무한 루프 해결 (0) | 2026.06.10 |
|---|---|
| ARM Cortex-M 하드폴트(Hard Fault) 디버깅: 레지스터 추적으로 원인 코드 찾는 법 (0) | 2026.06.08 |
| [C언어 임베디드] 버퍼 오버플로우(Buffer Overflow) 에러 예방과 안전한 메모리 복사 (1) | 2026.06.07 |
| [MCU 디버깅] Wild Pointer 및 Dangling Pointer로 인한 시스템 다운 방지 대책 (0) | 2026.06.06 |
| [임베디드 C] 스택 오버플로우(Stack Overflow) 원인 분석과 MAP 파일 활용한 해결 방법 (0) | 2026.06.05 |