/* * test_watchdog.c — STM32 IWDG Watchdog Timer tests (Issue #300) * * Verifies: * - Watchdog initialization with configurable timeouts * - Timeout calculation and prescaler selection * - Kick function for resetting watchdog counter * - Timeout range validation * - State tracking (running, initialized) * - Reset reason detection * - Edge cases and boundary conditions */ #include #include #include #include /* ── Watchdog Simulator ──────────────────────────────────────────*/ #define LSI_FREQUENCY_HZ 32000 #define IWDG_RELOAD_MIN 1 #define IWDG_RELOAD_MAX 4095 typedef struct { bool is_initialized; bool is_running; uint32_t timeout_ms; uint8_t prescaler; uint16_t reload_value; uint32_t counter; /* Simulated counter */ bool was_kicked; bool watchdog_fired; /* Track if timeout occurred */ } WatchdogSim; static WatchdogSim sim = {0}; void sim_init(void) { memset(&sim, 0, sizeof(sim)); } bool sim_calculate_config(uint32_t timeout_ms, uint8_t *out_prescaler, uint16_t *out_reload) { if (timeout_ms < 1 || timeout_ms > 32000) { return false; } const uint8_t prescalers[] = {0, 1, 2, 3, 4, 5, 6}; const uint16_t dividers[] = {4, 8, 16, 32, 64, 128, 256}; for (int i = 0; i < 7; i++) { uint16_t divider = dividers[i]; uint32_t reload = (timeout_ms * LSI_FREQUENCY_HZ) / (divider * 1000); if (reload >= IWDG_RELOAD_MIN && reload <= IWDG_RELOAD_MAX) { *out_prescaler = prescalers[i]; *out_reload = (uint16_t)reload; return true; } } return false; } bool sim_watchdog_init(uint32_t timeout_ms) { if (sim.is_initialized) return false; uint8_t prescaler; uint16_t reload; if (!sim_calculate_config(timeout_ms, &prescaler, &reload)) { return false; } sim.prescaler = prescaler; sim.reload_value = reload; sim.timeout_ms = timeout_ms; sim.is_initialized = true; sim.is_running = true; sim.counter = reload; /* Counter starts at reload value */ sim.watchdog_fired = false; return true; } void sim_watchdog_kick(void) { if (sim.is_running) { sim.counter = sim.reload_value; /* Reset counter */ sim.was_kicked = true; } } void sim_watchdog_tick(uint32_t elapsed_ms) { if (!sim.is_running) return; /* Decrement counter based on elapsed time */ const uint16_t dividers[] = {4, 8, 16, 32, 64, 128, 256}; uint16_t divider = dividers[sim.prescaler]; /* Approximate: each ms decrements counter by (LSI_FREQUENCY / divider / 1000) */ uint32_t decrement = (elapsed_ms * LSI_FREQUENCY_HZ) / (divider * 1000); if (decrement > sim.counter) { sim.watchdog_fired = true; sim.is_running = false; sim.counter = 0; } else { sim.counter -= decrement; } } uint32_t sim_watchdog_get_timeout(void) { return sim.timeout_ms; } bool sim_watchdog_is_running(void) { return sim.is_running; } /* ── Unit Tests ────────────────────────────────────────────────────────*/ static int test_count = 0, test_passed = 0, test_failed = 0; #define TEST(name) do { test_count++; printf("\n TEST %d: %s\n", test_count, name); } while(0) #define ASSERT(cond, msg) do { if (cond) { test_passed++; printf(" ✓ %s\n", msg); } else { test_failed++; printf(" ✗ %s\n", msg); } } while(0) void test_timeout_calculation(void) { TEST("Timeout calculation for standard values"); uint8_t psc; uint16_t reload; /* 1 second */ bool result = sim_calculate_config(1000, &psc, &reload); ASSERT(result == true, "1s timeout valid"); ASSERT(reload > 0 && reload <= 4095, "Reload in valid range"); /* 2 seconds (default) */ result = sim_calculate_config(2000, &psc, &reload); ASSERT(result == true, "2s timeout valid"); /* 4 seconds */ result = sim_calculate_config(4000, &psc, &reload); ASSERT(result == true, "4s timeout valid"); /* 16 seconds (max) */ result = sim_calculate_config(16000, &psc, &reload); ASSERT(result == true, "16s timeout valid"); } void test_initialization(void) { TEST("Watchdog initialization"); sim_init(); bool result = sim_watchdog_init(2000); ASSERT(result == true, "Initialize with 2s timeout"); ASSERT(sim.is_initialized == true, "Marked as initialized"); ASSERT(sim.is_running == true, "Marked as running"); ASSERT(sim.timeout_ms == 2000, "Timeout stored correctly"); } void test_double_init(void) { TEST("Prevent double initialization"); sim_init(); bool result = sim_watchdog_init(2000); ASSERT(result == true, "First init succeeds"); result = sim_watchdog_init(1000); ASSERT(result == false, "Second init fails"); ASSERT(sim.timeout_ms == 2000, "Original timeout unchanged"); } void test_invalid_timeouts(void) { TEST("Invalid timeouts are rejected"); sim_init(); /* Too short */ bool result = sim_watchdog_init(0); ASSERT(result == false, "0ms timeout rejected"); /* Too long */ sim_init(); result = sim_watchdog_init(50000); ASSERT(result == false, "50s timeout rejected"); /* Valid after invalid */ sim_init(); sim_watchdog_init(50000); /* Invalid, should fail */ result = sim_watchdog_init(2000); /* Valid, should work */ ASSERT(result == true, "Valid timeout works after invalid attempt"); } void test_watchdog_kick(void) { TEST("Watchdog kick resets counter"); sim_init(); sim_watchdog_init(2000); sim_watchdog_tick(1000); /* Wait 1 second */ ASSERT(sim.counter < sim.reload_value, "Counter decremented"); sim_watchdog_kick(); /* Reset counter */ ASSERT(sim.counter == sim.reload_value, "Counter reset to reload value"); } void test_watchdog_timeout(void) { TEST("Watchdog timeout triggers reset"); sim_init(); sim_watchdog_init(2000); sim_watchdog_tick(1000); ASSERT(sim.is_running == true, "Still running after 1 second"); ASSERT(sim.watchdog_fired == false, "No timeout yet"); sim_watchdog_tick(1500); /* Total 2.5 seconds > 2s timeout */ ASSERT(sim.is_running == false, "Stopped after timeout"); ASSERT(sim.watchdog_fired == true, "Watchdog fired"); } void test_watchdog_prevent_timeout(void) { TEST("Regular kicks prevent timeout"); sim_init(); sim_watchdog_init(2000); /* Kick every 1 second, timeout is 2 seconds */ sim_watchdog_tick(500); sim_watchdog_kick(); sim_watchdog_tick(1000); sim_watchdog_kick(); sim_watchdog_tick(1500); sim_watchdog_kick(); sim_watchdog_tick(2000); ASSERT(sim.is_running == true, "No timeout with regular kicks"); ASSERT(sim.watchdog_fired == false, "Watchdog not fired"); } void test_get_timeout(void) { TEST("Get timeout value"); sim_init(); sim_watchdog_init(3000); uint32_t timeout = sim_watchdog_get_timeout(); ASSERT(timeout == 3000, "Timeout value retrieved correctly"); } void test_is_running(void) { TEST("Check if watchdog is running"); sim_init(); ASSERT(sim_watchdog_is_running() == false, "Not running before init"); sim_watchdog_init(2000); ASSERT(sim_watchdog_is_running() == true, "Running after init"); sim_watchdog_tick(3000); /* Timeout */ ASSERT(sim_watchdog_is_running() == false, "Not running after timeout"); } void test_multiple_timeouts(void) { TEST("Different timeout values"); sim_init(); uint32_t timeouts[] = {1000, 2000, 4000, 8000, 16000}; for (int i = 0; i < 5; i++) { sim_init(); bool result = sim_watchdog_init(timeouts[i]); ASSERT(result == true, "Timeout value valid"); } } void test_boundary_1ms(void) { TEST("Minimum timeout (1ms)"); sim_init(); bool result = sim_watchdog_init(1); ASSERT(result == true, "1ms timeout accepted"); ASSERT(sim.timeout_ms == 1, "Timeout set correctly"); } void test_boundary_max(void) { TEST("Maximum reasonable timeout (32s)"); sim_init(); bool result = sim_watchdog_init(32000); ASSERT(result == true, "32s timeout accepted"); ASSERT(sim.timeout_ms == 32000, "Timeout set correctly"); } void test_prescaler_selection(void) { TEST("Appropriate prescaler selected"); sim_init(); /* Small timeout needs small prescaler */ sim_watchdog_init(100); uint8_t psc_small = sim.prescaler; /* Large timeout needs large prescaler */ sim_init(); sim_watchdog_init(16000); uint8_t psc_large = sim.prescaler; ASSERT(psc_large > psc_small, "Larger timeout uses larger prescaler"); } int main(void) { printf("\n══════════════════════════════════════════════════════════════\n"); printf(" STM32 IWDG Watchdog Timer — Unit Tests (Issue #300)\n"); printf("══════════════════════════════════════════════════════════════\n"); test_timeout_calculation(); test_initialization(); test_double_init(); test_invalid_timeouts(); test_watchdog_kick(); test_watchdog_timeout(); test_watchdog_prevent_timeout(); test_get_timeout(); test_is_running(); test_multiple_timeouts(); test_boundary_1ms(); test_boundary_max(); test_prescaler_selection(); printf("\n──────────────────────────────────────────────────────────────\n"); printf(" Results: %d/%d tests passed, %d failed\n", test_passed, test_count, test_failed); printf("──────────────────────────────────────────────────────────────\n\n"); return (test_failed == 0) ? 0 : 1; }