Archive STM32 firmware to legacy/stm32/: - src/, include/, lib/USB_CDC/, platformio.ini, test stubs, flash_firmware.py - test/test_battery_adc.c, test_hw_button.c, test_pid_schedule.c, test_vesc_can.c, test_can_watchdog.c - USB_CDC_BUG.md Rename: stm32_protocol → esp32_protocol, mamba_protocol → balance_protocol, stm32_cmd_node → esp32_cmd_node, stm32_cmd_params → esp32_cmd_params, stm32_cmd.launch.py → esp32_cmd.launch.py, test_stm32_protocol → test_esp32_protocol, test_stm32_cmd_node → test_esp32_cmd_node Content cleanup across all files: - Mamba F722S → ESP32-S3 BALANCE - BlackPill → ESP32-S3 IO - STM32F722/F7xx → ESP32-S3 - stm32Mode/Version/Port → esp32Mode/Version/Port - STM32 State/Mode labels → ESP32 State/Mode - Jetson Nano → Jetson Orin Nano Super - /dev/stm32 → /dev/esp32 - stm32_bridge → esp32_bridge - STM32 HAL → ESP-IDF docs/SALTYLAB.md: - Update "Drone FC Details" to describe ESP32-S3 BALANCE board (Waveshare ESP32-S3 Touch LCD 1.28) - Replace verbose "Self-Balancing Control" STM32 section with brief note pointing to SAUL-TEE-SYSTEM-REFERENCE.md TEAM.md: Update Embedded Firmware Engineer role to ESP32-S3 / ESP-IDF No new functionality — cleanup only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
333 lines
10 KiB
C
333 lines
10 KiB
C
/*
|
|
* test_watchdog.c — ESP32-S3 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 <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
/* ── 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(" ESP32-S3 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;
|
|
}
|