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:
@@ -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 min−40 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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user