/* ============================================================================= * watchdog.c - 독립 워치독(IWDG) (RPi systemd 자동재시작 대응) * * IWDG 는 LSI(저속 내부 RC, STM32F407 기준 약 32kHz) 로 구동된다. * timeout_ms 동안 watchdog_refresh() 호출이 없으면 MCU 가 하드 리셋된다. * * 타임아웃 계산: * t_IWDG[s] = (reload + 1) * prescaler / f_LSI * → reload = timeout_s * f_LSI / prescaler - 1 * prescaler 는 4,8,16,32,64,128,256 중 선택, reload 는 12비트(0..4095) 클램프. * * 주의: LSI 실주파수는 부품별로 17~47kHz 편차가 있다. 본 구현은 공칭 32kHz 로 * reload 를 계산하므로 실제 타임아웃은 ±오차가 있다. 안전을 위해 메인 * 헬스 루프의 refresh 주기는 설정 타임아웃의 1/2 이하로 둘 것. * ===========================================================================*/ #include "watchdog.h" #include "stm32f4xx_hal.h" #include /* size_t */ /* LSI 공칭 주파수(Hz). 데이터시트 typ. 32kHz. */ #define WDG_LSI_HZ 32000u /* IWDG reload 레지스터는 12비트. */ #define WDG_RELOAD_MAX 0x0FFFu /* 4095 */ /* IWDG 핸들(이 모듈 소유). */ static IWDG_HandleTypeDef hiwdg; /* prescaler 코드 → 분주값 매핑(인덱스 = HAL 의 IWDG_PRESCALER_*). */ typedef struct { uint32_t code; /* IWDG_PRESCALER_x */ uint32_t div; /* 실제 분주값 */ } wdg_presc_t; static const wdg_presc_t WDG_PRESC[] = { { IWDG_PRESCALER_4, 4u }, { IWDG_PRESCALER_8, 8u }, { IWDG_PRESCALER_16, 16u }, { IWDG_PRESCALER_32, 32u }, { IWDG_PRESCALER_64, 64u }, { IWDG_PRESCALER_128, 128u }, { IWDG_PRESCALER_256, 256u }, }; #define WDG_PRESC_COUNT (sizeof(WDG_PRESC) / sizeof(WDG_PRESC[0])) void watchdog_init(uint32_t timeout_ms) { uint32_t chosen_code = IWDG_PRESCALER_256; /* 안전 기본(최대 타임아웃) */ uint32_t chosen_reload = WDG_RELOAD_MAX; size_t i; if (timeout_ms == 0u) { timeout_ms = 1u; /* 0 방지 */ } /* 요청 타임아웃을 12비트 reload 안에 담을 수 있는 가장 작은 prescaler 선택 * (분해능을 최대화). 안 되면 가장 큰 prescaler(256)로 최대 범위 사용. */ for (i = 0; i < WDG_PRESC_COUNT; ++i) { uint32_t div = WDG_PRESC[i].div; /* reload = timeout_ms * f_LSI / (1000 * div) - 1 * 오버플로 방지를 위해 64비트 중간 계산. */ uint64_t ticks = ((uint64_t)timeout_ms * (uint64_t)WDG_LSI_HZ) / 1000u; uint64_t reload64; if (ticks == 0u) { reload64 = 0u; } else { reload64 = (ticks / div); if (reload64 > 0u) { reload64 -= 1u; /* reload 는 (값+1) 카운트 */ } } if (reload64 <= WDG_RELOAD_MAX) { chosen_code = WDG_PRESC[i].code; chosen_reload = (uint32_t)reload64; break; } } /* 루프가 끝까지 못 맞췄다면(타임아웃이 너무 큼) 256 분주 + 최대 reload 클램프. * (32kHz, /256, reload=4095 → 약 32.7초가 IWDG 최대 타임아웃) */ if (i >= WDG_PRESC_COUNT) { chosen_code = IWDG_PRESCALER_256; chosen_reload = WDG_RELOAD_MAX; } if (chosen_reload > WDG_RELOAD_MAX) { chosen_reload = WDG_RELOAD_MAX; } hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = chosen_code; hiwdg.Init.Reload = chosen_reload; /* IWDG_Init 은 카운터를 시작하고 즉시 한 번 refresh 한다. */ if (HAL_IWDG_Init(&hiwdg) != HAL_OK) { /* 워치독을 켜지 못하면 시스템 보호가 불가하므로 무한 대기 → 외부 관찰 가능. * (여기서 bsp_fatal 을 부르지 않는 이유: 의존성 최소화.) */ for (;;) { __NOP(); } } } void watchdog_refresh(void) { /* 카운터를 reload 값으로 재적재. 실패해도 다음 주기에 재시도. */ (void)HAL_IWDG_Refresh(&hiwdg); }