Gap 3 Safety Architecture: IWDG watchdog, Emergency_Stop PA rail cutoff, temp max, periodic IDQ re-read, emergency state ordering + 5 tests (20/20 pass)

This commit is contained in:
Jason
2026-03-19 21:58:39 +02:00
parent c87dce0d41
commit f3bbf77ca1
8 changed files with 688 additions and 8 deletions
@@ -53,7 +53,8 @@
/* #define HAL_SDRAM_MODULE_ENABLED */
/* #define HAL_HASH_MODULE_ENABLED */
/* #define HAL_I2S_MODULE_ENABLED */
/* #define HAL_IWDG_MODULE_ENABLED */
/* [GAP-3 FIX 2] Enable IWDG hardware watchdog — resets MCU if main loop stalls */
#define HAL_IWDG_MODULE_ENABLED
/* #define HAL_LPTIM_MODULE_ENABLED */
/* #define HAL_LTDC_MODULE_ENABLED */
/* #define HAL_QSPI_MODULE_ENABLED */
@@ -211,6 +211,11 @@ uint8_t adc1_readings[8] = {0};
uint8_t adc2_readings[8] = {0};
float Idq_reading[16]={0.0f};
/* [GAP-3 FIX 2] Hardware IWDG watchdog handle
* Prescaler=256, Reload=500 → timeout ≈ 4.096 s from 32 kHz LSI.
* If the main loop stalls, the MCU resets automatically. */
IWDG_HandleTypeDef hiwdg;
// Global manager instance ADF4382A
ADF4382A_Manager lo_manager;
@@ -270,6 +275,7 @@ static void MX_SPI1_Init(void);
static void MX_SPI4_Init(void);
static void MX_UART5_Init(void);
static void MX_USART3_UART_Init(void);
static void MX_IWDG_Init(void); /* GAP-3 FIX 2: hardware watchdog */
/* USER CODE BEGIN PFP */
// Function prototypes
@@ -796,9 +802,34 @@ void Emergency_Stop(void) {
DIAG_ERR("PA", "Clearing DAC2 outputs via CLR pin");
DAC5578_ActivateClearPin(&hdac2);
DIAG_ERR("PA", "DACs cleared -- entering infinite hold loop (manual reset required)");
/* Keep outputs cleared until reset */
/* [GAP-3 FIX 1] Cut RF and PA power rails — DAC CLR alone is not enough.
* With gate voltage cleared but VDD still energized, PAs can self-bias
* or oscillate. Disable everything in fast-to-slow order:
* 1. TX mixers (stop RF immediately)
* 2. PA 5V per-element supplies
* 3. PA 5.5V bulk supply
* 4. RFPA VDD enable
*/
DIAG_ERR("PA", "Disabling TX mixers (GPIOD pin 11 LOW)");
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_11, GPIO_PIN_RESET);
DIAG_ERR("PA", "Cutting PA 5V supplies (PA1/PA2/PA3 LOW)");
HAL_GPIO_WritePin(EN_P_5V0_PA1_GPIO_Port, EN_P_5V0_PA1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(EN_P_5V0_PA2_GPIO_Port, EN_P_5V0_PA2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(EN_P_5V0_PA3_GPIO_Port, EN_P_5V0_PA3_Pin, GPIO_PIN_RESET);
DIAG_ERR("PA", "Cutting PA 5.5V supply (EN_P_5V5_PA LOW)");
HAL_GPIO_WritePin(EN_P_5V5_PA_GPIO_Port, EN_P_5V5_PA_Pin, GPIO_PIN_RESET);
DIAG_ERR("PA", "Disabling RFPA VDD (EN_DIS_RFPA_VDD LOW)");
HAL_GPIO_WritePin(EN_DIS_RFPA_VDD_GPIO_Port, EN_DIS_RFPA_VDD_Pin, GPIO_PIN_RESET);
DIAG_ERR("PA", "All PA rails cut -- entering infinite hold loop (manual reset required)");
/* Keep outputs cleared until reset.
* MUST refresh IWDG here — otherwise the watchdog would reset the MCU,
* re-running startup code which re-energizes PA rails. */
while (1) {
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(100);
}
}
@@ -851,8 +882,11 @@ void handleSystemError(SystemError_t error) {
"CRITICAL ERROR! Initiating emergency shutdown.\r\n");
HAL_UART_Transmit(&huart3, (uint8_t*)error_msg, strlen(error_msg), 1000);
Emergency_Stop();
/* [GAP-3 FIX 5] Set flag BEFORE Emergency_Stop() — the function
* never returns (infinite loop), so the line after it was dead code. */
system_emergency_state = true;
Emergency_Stop();
/* NOTREACHED — Emergency_Stop() loops forever */
}
// For non-critical errors, attempt recovery
@@ -1331,6 +1365,7 @@ int main(void)
MX_UART5_Init();
MX_USART3_UART_Init();
MX_USB_DEVICE_Init();
MX_IWDG_Init(); /* GAP-3 FIX 2: start hardware watchdog (~4 s timeout) */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim1);
@@ -1345,7 +1380,12 @@ int main(void)
//Wait for OCXO 3mn
DIAG("CLK", "OCXO warmup starting -- waiting 180 s (3 min)");
uint32_t ocxo_start = HAL_GetTick();
HAL_Delay(180000);
/* [GAP-3 FIX 2] Cannot use HAL_Delay(180000) — IWDG would reset MCU.
* Instead loop in 1-second steps, kicking the watchdog each iteration. */
for (int ocxo_sec = 0; ocxo_sec < 180; ocxo_sec++) {
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(1000);
}
DIAG_ELAPSED("CLK", "OCXO warmup", ocxo_start);
DIAG_SECTION("AD9523 POWER SEQUENCING");
@@ -2009,6 +2049,19 @@ int main(void)
(double)Temperature_1, (double)Temperature_2, (double)Temperature_3, (double)Temperature_4,
(double)Temperature_5, (double)Temperature_6, (double)Temperature_7, (double)Temperature_8);
/* [GAP-3 FIX 3] Populate `temperature` with hottest sensor reading.
* checkSystemHealth() uses `temperature` for the >75 °C overtemp check;
* previously it was uninitialized. */
{
float temps[8] = { Temperature_1, Temperature_2, Temperature_3, Temperature_4,
Temperature_5, Temperature_6, Temperature_7, Temperature_8 };
temperature = temps[0];
for (int ti = 1; ti < 8; ti++) {
if (temps[ti] > temperature) temperature = temps[ti];
}
DIAG("PA", "System temperature (max of 8 sensors) = %.1f C", (double)temperature);
}
//(20 mV/°C on TMP37) QPA2962 RF amplifier Operating Temp. Range, TBASE min40 normal+25 max+85 °C
int Max_Temp = 25;
if((Temperature_1>Max_Temp)||(Temperature_2>Max_Temp)||(Temperature_3>Max_Temp)||(Temperature_4>Max_Temp)
@@ -2021,6 +2074,32 @@ int main(void)
HAL_GPIO_WritePin(EN_DIS_COOLING_GPIO_Port, EN_DIS_COOLING_Pin, GPIO_PIN_RESET);
}
/* [GAP-3 FIX 4] Periodic IDQ re-read — the Idq_reading[] array was only
* populated during startup/calibration. checkSystemHealth() compares
* stale values for overcurrent (>2.5 A) and bias fault (<0.1 A) checks.
* Re-read all 16 channels every 5 s alongside temperature. */
if (PowerAmplifier) {
DIAG("PA", "Periodic IDQ re-read (ADC1 + ADC2, 16 channels)");
for (uint8_t ch = 0; ch < 8; ch++) {
adc1_readings[ch] = ADS7830_Measure_SingleEnded(&hadc1, ch);
Idq_reading[ch] = (3.3f/255.0f) * adc1_readings[ch] / (50.0f * 0.005f);
}
for (uint8_t ch = 0; ch < 8; ch++) {
adc2_readings[ch] = ADS7830_Measure_SingleEnded(&hadc2, ch);
Idq_reading[ch + 8] = (3.3f/255.0f) * adc2_readings[ch] / (50.0f * 0.005f);
}
DIAG("PA", "IDQ[0..3]=%.3f %.3f %.3f %.3f [4..7]=%.3f %.3f %.3f %.3f",
(double)Idq_reading[0], (double)Idq_reading[1],
(double)Idq_reading[2], (double)Idq_reading[3],
(double)Idq_reading[4], (double)Idq_reading[5],
(double)Idq_reading[6], (double)Idq_reading[7]);
DIAG("PA", "IDQ[8..11]=%.3f %.3f %.3f %.3f [12..15]=%.3f %.3f %.3f %.3f",
(double)Idq_reading[8], (double)Idq_reading[9],
(double)Idq_reading[10], (double)Idq_reading[11],
(double)Idq_reading[12], (double)Idq_reading[13],
(double)Idq_reading[14], (double)Idq_reading[15]);
}
/* [BUG #6 FIXED] Was 'last_check' — now correctly writes 'last_check1'
* so the temperature timer runs independently of the lock-check timer. */
@@ -2035,6 +2114,10 @@ int main(void)
runRadarPulseSequence();
/* [GAP-3 FIX 2] Kick hardware watchdog — if we don't reach here within
* ~4 s, the IWDG resets the MCU automatically. */
HAL_IWDG_Refresh(&hiwdg);
// Optional: Add system monitoring here
// Check temperatures, power levels, etc.
@@ -2655,6 +2738,30 @@ static void MX_GPIO_Init(void)
* DEVICE INITIALIZATION
* ============================================================ */
/**
* [GAP-3 FIX 2] Initialize Independent Watchdog (IWDG).
*
* LSI clock ≈ 32 kHz.
* Prescaler = 256 → IWDG counter clock ≈ 125 Hz (8 ms/tick).
* Reload = 500 → timeout ≈ 500 × 8 ms = 4.0 s.
*
* The main loop must call HAL_IWDG_Refresh() within this window
* or the MCU hard-resets — protecting against firmware hangs when
* PA rails may be energized.
*/
static void MX_IWDG_Init(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
hiwdg.Init.Reload = 500;
hiwdg.Init.Window = IWDG_WINDOW_DISABLE;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
DIAG_ERR("SYS", "IWDG init FAILED -- continuing without hardware watchdog");
} else {
DIAG("SYS", "IWDG hardware watchdog started (timeout ~4s)");
}
}
/* USER CODE END 4 */
+41 -3
View File
@@ -48,11 +48,16 @@ TESTS_MOCK_ONLY := test_bug2_ad9523_double_setup \
test_bug6_timer_variable_collision \
test_bug7_gpio_pin_conflict \
test_bug8_uart_commented_out \
test_bug14_diag_section_args
test_bug14_diag_section_args \
test_gap3_emergency_stop_rails
# Tests that are standalone (no mocks needed, pure logic)
TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \
test_bug13_dac2_adc_buffer_mismatch
test_bug13_dac2_adc_buffer_mismatch \
test_gap3_iwdg_config \
test_gap3_temperature_max \
test_gap3_idq_periodic_reread \
test_gap3_emergency_state_ordering
# Tests that need platform_noos_stm32.o + mocks
TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
@@ -60,7 +65,8 @@ TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) $(TESTS_STANDALONE) $(TESTS_WITH_PLATFORM)
.PHONY: all build test clean \
$(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11 bug12 bug13 bug14 bug15)
$(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11 bug12 bug13 bug14 bug15) \
test_gap3_estop test_gap3_iwdg test_gap3_temp test_gap3_idq test_gap3_order
all: build test
@@ -129,6 +135,23 @@ test_bug12_pa_cal_loop_inverted: test_bug12_pa_cal_loop_inverted.c
test_bug13_dac2_adc_buffer_mismatch: test_bug13_dac2_adc_buffer_mismatch.c
$(CC) $(CFLAGS) $< -lm -o $@
# Gap-3 safety tests -- mock-only (needs spy log for GPIO sequence)
test_gap3_emergency_stop_rails: test_gap3_emergency_stop_rails.c $(MOCK_OBJS)
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) -o $@
# Gap-3 safety tests -- standalone (pure logic)
test_gap3_iwdg_config: test_gap3_iwdg_config.c
$(CC) $(CFLAGS) $< -lm -o $@
test_gap3_temperature_max: test_gap3_temperature_max.c
$(CC) $(CFLAGS) $< -lm -o $@
test_gap3_idq_periodic_reread: test_gap3_idq_periodic_reread.c
$(CC) $(CFLAGS) $< -lm -o $@
test_gap3_emergency_state_ordering: test_gap3_emergency_state_ordering.c
$(CC) $(CFLAGS) $< -o $@
# Tests that need platform_noos_stm32.o + mocks
$(TESTS_WITH_PLATFORM): %: %.c $(MOCK_OBJS) $(PLATFORM_OBJ)
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) $(PLATFORM_OBJ) -o $@
@@ -180,6 +203,21 @@ test_bug14: test_bug14_diag_section_args
test_bug15: test_bug15_htim3_dangling_extern
./test_bug15_htim3_dangling_extern
test_gap3_estop: test_gap3_emergency_stop_rails
./test_gap3_emergency_stop_rails
test_gap3_iwdg: test_gap3_iwdg_config
./test_gap3_iwdg_config
test_gap3_temp: test_gap3_temperature_max
./test_gap3_temperature_max
test_gap3_idq: test_gap3_idq_periodic_reread
./test_gap3_idq_periodic_reread
test_gap3_order: test_gap3_emergency_state_ordering
./test_gap3_emergency_state_ordering
# --- Clean ---
clean:
@@ -0,0 +1,116 @@
/*******************************************************************************
* test_gap3_emergency_state_ordering.c
*
* Gap-3 Fix 5 (FIXED): system_emergency_state set BEFORE Emergency_Stop().
*
* Before fix: handleSystemError() called Emergency_Stop() first (line 854),
* then set system_emergency_state = true (line 855).
* Since Emergency_Stop() never returns (infinite loop), the flag
* was never set — dead code.
*
* After fix: system_emergency_state = true is set BEFORE Emergency_Stop().
* This ensures any interrupt or parallel check can see the
* emergency state flag is set even though Emergency_Stop blocks.
*
* Test strategy:
* Simulate the handleSystemError critical-error path and verify that
* system_emergency_state is set to true BEFORE the Emergency_Stop would
* be called (we use a flag to track ordering).
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
/* Simulated global state */
static bool system_emergency_state = false;
static bool emergency_stop_called = false;
static bool state_was_true_when_estop_called = false;
/* Simulated Emergency_Stop (doesn't loop — just records) */
static void Mock_Emergency_Stop(void)
{
emergency_stop_called = true;
/* Check: was system_emergency_state already true? */
state_was_true_when_estop_called = system_emergency_state;
}
/* Error codes (subset matching main.cpp) */
typedef enum {
ERROR_NONE = 0,
ERROR_RF_PA_OVERCURRENT = 9,
ERROR_RF_PA_BIAS = 10,
ERROR_STEPPER_FAULT = 11,
ERROR_FPGA_COMM = 12,
ERROR_POWER_SUPPLY = 13,
ERROR_TEMPERATURE_HIGH = 14,
} SystemError_t;
/* Extracted critical-error handling logic (post-fix ordering) */
static void simulate_handleSystemError_critical(SystemError_t error)
{
/* Only critical errors (PA overcurrent through power supply) trigger e-stop */
if (error >= ERROR_RF_PA_OVERCURRENT && error <= ERROR_POWER_SUPPLY) {
/* FIX 5: set flag BEFORE calling Emergency_Stop */
system_emergency_state = true;
Mock_Emergency_Stop();
/* NOTREACHED in real code */
}
}
int main(void)
{
printf("=== Gap-3 Fix 5: system_emergency_state ordering ===\n");
/* Test 1: PA overcurrent → flag set BEFORE Emergency_Stop */
printf(" Test 1: PA overcurrent path... ");
system_emergency_state = false;
emergency_stop_called = false;
state_was_true_when_estop_called = false;
simulate_handleSystemError_critical(ERROR_RF_PA_OVERCURRENT);
assert(emergency_stop_called == true);
assert(system_emergency_state == true);
assert(state_was_true_when_estop_called == true);
printf("PASS\n");
/* Test 2: Power supply fault → same ordering */
printf(" Test 2: Power supply fault path... ");
system_emergency_state = false;
emergency_stop_called = false;
state_was_true_when_estop_called = false;
simulate_handleSystemError_critical(ERROR_POWER_SUPPLY);
assert(emergency_stop_called == true);
assert(system_emergency_state == true);
assert(state_was_true_when_estop_called == true);
printf("PASS\n");
/* Test 3: PA bias fault → same ordering */
printf(" Test 3: PA bias fault path... ");
system_emergency_state = false;
emergency_stop_called = false;
state_was_true_when_estop_called = false;
simulate_handleSystemError_critical(ERROR_RF_PA_BIAS);
assert(emergency_stop_called == true);
assert(state_was_true_when_estop_called == true);
printf("PASS\n");
/* Test 4: Non-critical error → no e-stop, flag stays false */
printf(" Test 4: Non-critical error (no e-stop)... ");
system_emergency_state = false;
emergency_stop_called = false;
simulate_handleSystemError_critical(ERROR_TEMPERATURE_HIGH);
assert(emergency_stop_called == false);
assert(system_emergency_state == false);
printf("PASS\n");
/* Test 5: ERROR_NONE → no e-stop */
printf(" Test 5: ERROR_NONE (no action)... ");
system_emergency_state = false;
emergency_stop_called = false;
simulate_handleSystemError_critical(ERROR_NONE);
assert(emergency_stop_called == false);
assert(system_emergency_state == false);
printf("PASS\n");
printf("\n=== Gap-3 Fix 5: ALL TESTS PASSED ===\n\n");
return 0;
}
@@ -0,0 +1,100 @@
/*******************************************************************************
* test_gap3_emergency_stop_rails.c
*
* Gap-3 Fix 1 (FIXED): Emergency_Stop() now cuts PA power rails.
*
* Before fix: Emergency_Stop() only cleared DAC gate voltages via CLR pin.
* PA VDD rails (5V0_PA1/2/3, 5V5_PA, RFPA_VDD) stayed energized,
* allowing PAs to self-bias or oscillate.
*
* After fix: Emergency_Stop() also:
* 1. Disables TX mixers (GPIOD pin 11 LOW)
* 2. Cuts PA 5V0 supplies (GPIOG pins 0,1,2 LOW)
* 3. Cuts PA 5V5 supply (GPIOG pin 3 LOW)
* 4. Disables RFPA VDD (GPIOD pin 6 LOW)
*
* Test strategy:
* Simulate the Emergency_Stop GPIO sequence and verify all required pins
* are driven LOW via the spy log.
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include "stm32_hal_mock.h"
/* Pin definitions from main.h shim */
#include "main.h"
/*
* Simulate the Emergency_Stop GPIO write sequence (post-fix).
* We can't call the real function (it loops forever), so we replicate
* the GPIO write sequence and verify it in the spy log.
*/
static void simulate_emergency_stop_gpio_sequence(void)
{
/* TX mixers OFF */
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_11, GPIO_PIN_RESET);
/* PA 5V0 supplies OFF */
HAL_GPIO_WritePin(EN_P_5V0_PA1_GPIO_Port, EN_P_5V0_PA1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(EN_P_5V0_PA2_GPIO_Port, EN_P_5V0_PA2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(EN_P_5V0_PA3_GPIO_Port, EN_P_5V0_PA3_Pin, GPIO_PIN_RESET);
/* PA 5V5 supply OFF */
HAL_GPIO_WritePin(EN_P_5V5_PA_GPIO_Port, EN_P_5V5_PA_Pin, GPIO_PIN_RESET);
/* RFPA VDD OFF */
HAL_GPIO_WritePin(EN_DIS_RFPA_VDD_GPIO_Port, EN_DIS_RFPA_VDD_Pin, GPIO_PIN_RESET);
}
/* Helper: check that a GPIO_WRITE record matches expected port/pin/state */
static void assert_gpio_write(int idx, GPIO_TypeDef *port, uint16_t pin, GPIO_PinState state,
const char *label)
{
const SpyRecord *r = spy_get(idx);
assert(r != NULL);
assert(r->type == SPY_GPIO_WRITE);
if (r->port != port || r->pin != pin || (GPIO_PinState)r->value != state) {
printf("FAIL at spy_log[%d] (%s): port=%p pin=0x%04x state=%d "
"(expected port=%p pin=0x%04x state=%d)\n",
idx, label, r->port, r->pin, r->value, port, pin, state);
assert(0);
}
}
int main(void)
{
printf("=== Gap-3 Fix 1: Emergency_Stop() PA rail shutdown ===\n");
/* Test 1: All 6 required GPIO pins are driven LOW in correct order */
printf(" Test 1: GPIO sequence correctness... ");
spy_reset();
simulate_emergency_stop_gpio_sequence();
assert(spy_count == 6);
assert_gpio_write(0, GPIOD, GPIO_PIN_11, GPIO_PIN_RESET, "TX_MIXERS_OFF");
assert_gpio_write(1, GPIOG, GPIO_PIN_0, GPIO_PIN_RESET, "PA1_5V0_OFF");
assert_gpio_write(2, GPIOG, GPIO_PIN_1, GPIO_PIN_RESET, "PA2_5V0_OFF");
assert_gpio_write(3, GPIOG, GPIO_PIN_2, GPIO_PIN_RESET, "PA3_5V0_OFF");
assert_gpio_write(4, GPIOG, GPIO_PIN_3, GPIO_PIN_RESET, "PA_5V5_OFF");
assert_gpio_write(5, GPIOD, GPIO_PIN_6, GPIO_PIN_RESET, "RFPA_VDD_OFF");
printf("PASS\n");
/* Test 2: TX mixers are cut FIRST (before PA supplies) */
printf(" Test 2: TX mixers disabled before PA rails... ");
/* Already verified by order in Test 1: spy_log[0] is TX_MIXERS */
printf("PASS (by ordering in Test 1)\n");
/* Test 3: Pin definitions match expected hardware mapping */
printf(" Test 3: Pin define cross-check... ");
assert(EN_P_5V0_PA1_Pin == GPIO_PIN_0);
assert(EN_P_5V0_PA1_GPIO_Port == GPIOG);
assert(EN_P_5V0_PA2_Pin == GPIO_PIN_1);
assert(EN_P_5V0_PA2_GPIO_Port == GPIOG);
assert(EN_P_5V0_PA3_Pin == GPIO_PIN_2);
assert(EN_P_5V0_PA3_GPIO_Port == GPIOG);
assert(EN_P_5V5_PA_Pin == GPIO_PIN_3);
assert(EN_P_5V5_PA_GPIO_Port == GPIOG);
assert(EN_DIS_RFPA_VDD_Pin == GPIO_PIN_6);
assert(EN_DIS_RFPA_VDD_GPIO_Port == GPIOD);
printf("PASS\n");
printf("\n=== Gap-3 Fix 1: ALL TESTS PASSED ===\n\n");
return 0;
}
@@ -0,0 +1,129 @@
/*******************************************************************************
* test_gap3_idq_periodic_reread.c
*
* Gap-3 Fix 4 (FIXED): IDQ values now periodically re-read during operation.
*
* Before fix: Idq_reading[16] was only populated during startup/calibration.
* checkSystemHealth() compared stale values for overcurrent
* (>2.5A) and bias fault (<0.1A) checks.
*
* After fix: Every 5 seconds (in the temperature monitoring block),
* all 16 ADC channels are re-read and Idq_reading[] is updated.
*
* Test strategy:
* Verify the IDQ conversion formula and fault thresholds with known
* raw ADC values.
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <math.h>
/* IDQ conversion formula: Idq = (3.3/255) * raw / (G * Rshunt)
* where G = 50 (INA241A3 gain) and Rshunt = 5 mOhm = 0.005 Ohm.
* Denominator = 50 * 0.005 = 0.25
* So: Idq = (3.3/255) * raw / 0.25 = raw * (3.3 / (255 * 0.25))
* = raw * 0.051765... */
static float idq_from_raw(uint8_t raw)
{
return (3.3f / 255.0f) * raw / (50.0f * 0.005f);
}
/* Overcurrent threshold from checkSystemHealth() */
#define IDQ_OVERCURRENT_THRESHOLD 2.5f
/* Bias fault threshold from checkSystemHealth() */
#define IDQ_BIAS_FAULT_THRESHOLD 0.1f
int main(void)
{
printf("=== Gap-3 Fix 4: Periodic IDQ re-read ===\n");
/* Test 1: Raw=0 → Idq=0 (no current) → bias fault */
printf(" Test 1: raw=0 → Idq=0.000A (bias fault)... ");
{
float idq = idq_from_raw(0);
assert(fabsf(idq - 0.0f) < 0.001f);
assert(idq < IDQ_BIAS_FAULT_THRESHOLD);
printf("PASS\n");
}
/* Test 2: Normal operating point
* Target Idq=1.680A → raw = Idq * (50*0.005) * 255/3.3 = 1.680 * 0.25 * 77.27 ≈ 32.5
* Use raw=33 → Idq = (3.3/255)*33/0.25 ≈ 1.709A */
printf(" Test 2: raw=33 → Idq≈1.709A (normal)... ");
{
float idq = idq_from_raw(33);
printf("(%.3fA) ", idq);
assert(idq > IDQ_BIAS_FAULT_THRESHOLD);
assert(idq < IDQ_OVERCURRENT_THRESHOLD);
assert(fabsf(idq - 1.680f) < 0.1f); /* close to calibration target */
printf("PASS\n");
}
/* Test 3: Overcurrent detection (raw=255 → max Idq ≈ 13.2A) */
printf(" Test 3: raw=255 → Idq≈13.2A (overcurrent)... ");
{
float idq = idq_from_raw(255);
printf("(%.3fA) ", idq);
assert(idq > IDQ_OVERCURRENT_THRESHOLD);
printf("PASS\n");
}
/* Test 4: Edge case — just below overcurrent
* 2.5A → raw = 2.5*0.25*255/3.3 ≈ 48.3, so raw=48 → 2.48A (below) */
printf(" Test 4: raw=48 → just below 2.5A... ");
{
float idq = idq_from_raw(48);
printf("(%.3fA) ", idq);
assert(idq < IDQ_OVERCURRENT_THRESHOLD);
printf("PASS\n");
}
/* Test 5: Edge case — just above bias fault
* 0.1A → raw = 0.1*0.25*255/3.3 ≈ 1.93, so raw=2 → 0.103A (above) */
printf(" Test 5: raw=2 → just above 0.1A... ");
{
float idq = idq_from_raw(2);
printf("(%.3fA) ", idq);
assert(idq > IDQ_BIAS_FAULT_THRESHOLD);
printf("PASS\n");
}
/* Test 6: All 16 channels use same formula */
printf(" Test 6: Formula consistency across channels... ");
{
/* Simulate ADC1 ch0-7 + ADC2 ch0-7 all returning raw=33 */
float idq_readings[16];
for (int ch = 0; ch < 8; ch++) {
idq_readings[ch] = idq_from_raw(33); /* ADC1 */
idq_readings[ch + 8] = idq_from_raw(33); /* ADC2 */
}
for (int i = 0; i < 16; i++) {
assert(fabsf(idq_readings[i] - idq_readings[0]) < 0.001f);
}
printf("PASS\n");
}
/* Test 7: Health check would detect overcurrent in any channel */
printf(" Test 7: Single-channel overcurrent detection... ");
{
float idq_readings[16];
for (int i = 0; i < 16; i++) {
idq_readings[i] = 1.5f; /* normal */
}
idq_readings[7] = 3.0f; /* overcurrent on channel 7 */
int fault_detected = 0;
for (int i = 0; i < 16; i++) {
if (idq_readings[i] > IDQ_OVERCURRENT_THRESHOLD) {
fault_detected = 1;
printf("(ch%d=%.1fA) ", i, idq_readings[i]);
break;
}
}
assert(fault_detected);
printf("PASS\n");
}
printf("\n=== Gap-3 Fix 4: ALL TESTS PASSED ===\n\n");
return 0;
}
@@ -0,0 +1,88 @@
/*******************************************************************************
* test_gap3_iwdg_config.c
*
* Gap-3 Fix 2 (FIXED): Hardware IWDG watchdog enabled.
*
* Before fix: HAL_IWDG_MODULE_ENABLED was commented out in hal_conf.h.
* Software-only timestamp check in checkSystemHealth() was the
* only watchdog — if MCU hangs, nothing resets it.
*
* After fix:
* 1. HAL_IWDG_MODULE_ENABLED uncommented
* 2. IWDG_HandleTypeDef hiwdg declared
* 3. MX_IWDG_Init() called at startup (prescaler=256, reload=500 → ~4s)
* 4. HAL_IWDG_Refresh() called in main loop
* 5. OCXO warmup loop refreshes IWDG every 1s instead of blocking 180s
* 6. Emergency_Stop() infinite loop also refreshes IWDG to prevent reset
*
* Test strategy:
* Verify configuration constants and timeout calculation.
* Verify OCXO warmup loop structure avoids IWDG timeout.
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <math.h>
/* IWDG configuration constants (must match MX_IWDG_Init in main.cpp) */
#define IWDG_PRESCALER_VALUE 256
#define IWDG_RELOAD_VALUE 500
#define LSI_FREQ_HZ 32000 /* STM32F7 LSI typical */
int main(void)
{
printf("=== Gap-3 Fix 2: IWDG hardware watchdog configuration ===\n");
/* Test 1: Timeout calculation */
printf(" Test 1: IWDG timeout within 3-5 seconds... ");
double timeout_s = (double)IWDG_PRESCALER_VALUE * IWDG_RELOAD_VALUE / LSI_FREQ_HZ;
printf("(calculated %.3f s) ", timeout_s);
assert(timeout_s >= 3.0 && timeout_s <= 5.0);
printf("PASS\n");
/* Test 2: OCXO warmup loop wouldn't trigger IWDG */
printf(" Test 2: OCXO loop refresh interval < IWDG timeout... ");
/* OCXO warmup: 180 iterations × 1000 ms delay = 180 s total.
* Each iteration refreshes IWDG. 1.0 s << 4.0 s timeout. */
double ocxo_refresh_interval_s = 1.0;
assert(ocxo_refresh_interval_s < timeout_s);
printf("(1.0 s < %.1f s) PASS\n", timeout_s);
/* Test 3: Emergency_Stop loop wouldn't trigger IWDG */
printf(" Test 3: Emergency_Stop refresh interval < IWDG timeout... ");
/* Emergency_Stop: loops with HAL_Delay(100) + IWDG_Refresh.
* 0.1 s << 4.0 s timeout. */
double estop_refresh_interval_s = 0.1;
assert(estop_refresh_interval_s < timeout_s);
printf("(0.1 s < %.1f s) PASS\n", timeout_s);
/* Test 4: Main loop frequency check */
printf(" Test 4: Main loop must complete within timeout... ");
/* Radar pulse sequence + health checks + monitoring should complete
* well within 4 seconds. Max single-iteration budget: ~1 s
* (dominated by the radar pulse sequence itself). */
double estimated_loop_worst_case_s = 1.0;
assert(estimated_loop_worst_case_s < timeout_s);
printf("(est. %.1f s < %.1f s) PASS\n", estimated_loop_worst_case_s, timeout_s);
/* Test 5: Prescaler is power-of-2 and valid for STM32F7 */
printf(" Test 5: Prescaler is valid STM32F7 IWDG value... ");
/* Valid prescalers: 4, 8, 16, 32, 64, 128, 256 */
int valid_prescalers[] = {4, 8, 16, 32, 64, 128, 256};
int prescaler_valid = 0;
for (int i = 0; i < 7; i++) {
if (valid_prescalers[i] == IWDG_PRESCALER_VALUE) {
prescaler_valid = 1;
break;
}
}
assert(prescaler_valid);
printf("PASS\n");
/* Test 6: Reload within 12-bit range */
printf(" Test 6: Reload value within 12-bit range (0-4095)... ");
assert(IWDG_RELOAD_VALUE >= 0 && IWDG_RELOAD_VALUE <= 4095);
printf("(%d) PASS\n", IWDG_RELOAD_VALUE);
printf("\n=== Gap-3 Fix 2: ALL TESTS PASSED ===\n\n");
return 0;
}
@@ -0,0 +1,101 @@
/*******************************************************************************
* test_gap3_temperature_max.c
*
* Gap-3 Fix 3 (FIXED): `temperature` variable now assigned from sensors.
*
* Before fix: `float temperature;` was declared but NEVER assigned.
* checkSystemHealth() compared uninitialized value against 75°C.
*
* After fix: After reading Temperature_1..8, the code computes
* temperature = max(Temperature_1..Temperature_8).
*
* Test strategy:
* Extract the max-temperature logic and verify with known sensor values.
******************************************************************************/
#include <assert.h>
#include <stdio.h>
#include <math.h>
/* Extracted max-temperature logic (post-fix) */
static float compute_max_temperature(float temps[8])
{
float max_temp = temps[0];
for (int i = 1; i < 8; i++) {
if (temps[i] > max_temp) max_temp = temps[i];
}
return max_temp;
}
int main(void)
{
printf("=== Gap-3 Fix 3: temperature = max(Temperature_1..8) ===\n");
/* Test 1: Hottest sensor is in middle */
printf(" Test 1: Max in middle position... ");
{
float temps[8] = {20.0f, 25.0f, 30.0f, 80.0f, 40.0f, 35.0f, 22.0f, 18.0f};
float result = compute_max_temperature(temps);
assert(fabsf(result - 80.0f) < 0.001f);
printf("%.1f PASS\n", result);
}
/* Test 2: Hottest sensor is first */
printf(" Test 2: Max at index 0... ");
{
float temps[8] = {90.0f, 25.0f, 30.0f, 40.0f, 40.0f, 35.0f, 22.0f, 18.0f};
float result = compute_max_temperature(temps);
assert(fabsf(result - 90.0f) < 0.001f);
printf("%.1f PASS\n", result);
}
/* Test 3: Hottest sensor is last */
printf(" Test 3: Max at index 7... ");
{
float temps[8] = {20.0f, 25.0f, 30.0f, 40.0f, 40.0f, 35.0f, 22.0f, 85.5f};
float result = compute_max_temperature(temps);
assert(fabsf(result - 85.5f) < 0.001f);
printf("%.1f PASS\n", result);
}
/* Test 4: All sensors equal */
printf(" Test 4: All equal... ");
{
float temps[8] = {42.0f, 42.0f, 42.0f, 42.0f, 42.0f, 42.0f, 42.0f, 42.0f};
float result = compute_max_temperature(temps);
assert(fabsf(result - 42.0f) < 0.001f);
printf("%.1f PASS\n", result);
}
/* Test 5: Overtemp threshold check (>75°C triggers ERROR_TEMPERATURE_HIGH) */
printf(" Test 5: Overtemp detection at 75.1C... ");
{
float temps[8] = {20.0f, 25.0f, 30.0f, 40.0f, 75.1f, 35.0f, 22.0f, 18.0f};
float result = compute_max_temperature(temps);
assert(result > 75.0f); /* would trigger checkSystemHealth overtemp */
printf("%.1f > 75.0 → OVERTEMP DETECTED, PASS\n", result);
}
/* Test 6: Below overtemp threshold */
printf(" Test 6: Normal temp (all below 75C)... ");
{
float temps[8] = {20.0f, 25.0f, 30.0f, 40.0f, 74.9f, 35.0f, 22.0f, 18.0f};
float result = compute_max_temperature(temps);
assert(result <= 75.0f); /* would NOT trigger overtemp */
printf("%.1f <= 75.0 → OK, PASS\n", result);
}
/* Test 7: ADC scaling verification */
printf(" Test 7: ADC scaling: raw=116 → 75.1°C... ");
{
/* TMP37: 3.3V→165°C, ADS7830: 3.3V→255
* temp = raw * 165/255 = raw * 0.64705 */
float raw = 116.0f;
float temp = raw * 0.64705f;
printf("(%.2f°C) ", temp);
assert(temp > 75.0f); /* 116 * 0.64705 ≈ 75.06 */
printf("PASS\n");
}
printf("\n=== Gap-3 Fix 3: ALL TESTS PASSED ===\n\n");
return 0;
}