From b84aa6a6f33afa9b7ea4bc00f26bc75d77e13452 Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Mon, 4 May 2026 21:06:23 +0545 Subject: [PATCH] fix(mcu): F-3.1 Error_Handler reset + audit cleanup tail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F-3.1 (functional): Error_Handler() now calls NVIC_SystemReset() instead of __disable_irq(); while(1). Every MX_*_Init() helper invokes Error_Handler before MX_IWDG_Init() runs, so an infinite spin would brick the MCU on any transient boot-time glitch with no watchdog to recover. SystemReset turns a hard-to-debug brick into a visible reboot loop. F-3.3..F-3.8 (comment hygiene in main.cpp init helpers + post-init): - TIM3 init: clarify 1 MHz tick @ 72 MHz timer clock (APB1=36 MHz but RCC_TIMPRES_ACTIVATED forces TIMxCLK=HCLK) - GPIO init: fix EN_P_3V3_ADAR12EN_P_3V3_VDD_SW_Pin → EN_P_3V3_VDD_SW_Pin typo; correct PD8-11 → PD8-12 and PD12-15 → PD13-15 ranges - SystemClock_Config: add VOS3 + 72 MHz intent comment - MPU_Config: decode SubRegionDisable=0x87 bitmask D1/D6/D7 (ADAR cleanup tail): code was already deleted in a prior pass; this strips the residual tombstone comments per the no-tombstone feedback policy. - ADAR1000_Manager.h: 5 tombstone blocks removed (fastTXMode/etc, setBeamAngle/4-phase/BeamConfig, setADTR1107Control, Configuration section + setSwitchSettlingTime/setFastSwitchMode/setBeamDwellTime, setTRSwitchPosition) - ADAR1000_Manager.cpp: 6 tombstone comments removed; switchToRXMode Step 4→3, Step 5→4 renumbered after Step-3 gap - ADAR1000_AGC.cpp: stale "(matching the convention in setBeamAngle)" reference removed - main.cpp:556-557: redundant "setFastSwitchMode(true) call removed" tombstone removed D2 (comment-only): initializeBeamMatrices() and runRadarPulseSequence descriptions rewritten to describe array-math peak (matrix1 → NEGATIVE θ peak, matrix2 → POSITIVE θ peak) instead of the misleading "positive phase difference" framing. Sky/ground sign vs antenna mount explicitly flagged unverified — functional sign question remains hardware-blocked pending calibrated-source bench test. Regression: 86/0. --- .../9_1_1_C_Cpp_Libraries/ADAR1000_AGC.cpp | 3 +- .../ADAR1000_Manager.cpp | 278 +++------- .../9_1_1_C_Cpp_Libraries/ADAR1000_Manager.h | 75 +-- .../9_1_3_C_Cpp_Code/main.cpp | 485 +++++++++--------- 4 files changed, 315 insertions(+), 526 deletions(-) diff --git a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_AGC.cpp b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_AGC.cpp index 068b80b..a0c4fb2 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_AGC.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_AGC.cpp @@ -73,8 +73,7 @@ void ADAR1000_AGC::update(bool fpga_saturation) // --------------------------------------------------------------------------- // applyGain -- write effective gain to all 16 RX VGA channels // -// Uses the Manager's adarSetRxVgaGain which takes 1-based channel indices -// (matching the convention in setBeamAngle). +// Uses the Manager's adarSetRxVgaGain which takes 1-based channel indices. // --------------------------------------------------------------------------- void ADAR1000_AGC::applyGain(ADAR1000Manager &mgr) { diff --git a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp index d9eb47d..278b431 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.cpp @@ -163,19 +163,14 @@ void ADAR1000Manager::switchToTXMode() { DIAG("BF", "Step 3: PA bias ON"); setPABias(true); delayUs(50); - // Step 4 (former setADTR1107Control(true)) removed: TR pin is FPGA-owned. - // Chip follows adar_tr_x; TX path is asserted by the FPGA chirp FSM, not - // by SPI here. Write per-channel TX enables so the FPGA TR override has - // something to gate. - + /* TR pin is FPGA-owned (adar_tr_x); chirp FSM asserts TX path. We write + * per-channel TX enables so the FPGA TR override has something to gate. */ for (uint8_t dev = 0; dev < devices_.size(); ++dev) { adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF); adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF); adarSetTxBias(dev, BROADCAST_OFF); - devices_[dev]->current_mode = BeamDirection::TX; DIAG("BF", " dev[%u] TX enables=0x0F, TX bias set", dev); } - current_mode_ = BeamDirection::TX; DIAG("BF", "switchToTXMode() complete"); } @@ -187,119 +182,21 @@ void ADAR1000Manager::switchToRXMode() { DIAG("BF", "Step 2: Disable PA supplies"); disablePASupplies(); delayUs(10); - // Step 3 (former setADTR1107Control(false)) removed: FPGA owns TR pin. - DIAG("BF", "Step 4: Enable LNA supplies"); + DIAG("BF", "Step 3: Enable LNA supplies"); enableLNASupplies(); delayUs(50); - DIAG("BF", "Step 5: LNA bias ON"); + DIAG("BF", "Step 4: LNA bias ON"); setLNABias(true); delayUs(50); for (uint8_t dev = 0; dev < devices_.size(); ++dev) { adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF); adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF); - devices_[dev]->current_mode = BeamDirection::RX; DIAG("BF", " dev[%u] RX enables=0x0F", dev); } - current_mode_ = BeamDirection::RX; DIAG("BF", "switchToRXMode() complete"); } -// fastTXMode, fastRXMode, pulseTXMode, pulseRXMode: REMOVED. -// The chirp hot path owns T/R switching via the FPGA adar_tr_x pins -// (see 9_Firmware/9_2_FPGA/plfm_chirp_controller.v). The old SPI-RMW per -// chirp was architecturally redundant, raced the FPGA, and toggled the -// wrong bit of REG_SW_CONTROL (TR_SOURCE instead of TR_SPI). - -// Beam Steering -bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction) { - DIAG("BF", "setBeamAngle(%.1f deg, %s)", (double)angle_degrees, - direction == BeamDirection::TX ? "TX" : "RX"); - uint8_t phase_settings[4]; - calculatePhaseSettings(angle_degrees, phase_settings); - DIAG("BF", " phase[0..3] = %u, %u, %u, %u", - phase_settings[0], phase_settings[1], phase_settings[2], phase_settings[3]); - - if (direction == BeamDirection::TX) { - setAllDevicesTXMode(); - } else { - setAllDevicesRXMode(); - } - - for (uint8_t dev = 0; dev < devices_.size(); ++dev) { - for (uint8_t ch = 0; ch < 4; ++ch) { - if (direction == BeamDirection::TX) { - adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF); - adarSetTxVgaGain(dev, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF); - } else { - adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF); - adarSetRxVgaGain(dev, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF); - } - } - } - return true; -} - -bool ADAR1000Manager::setCustomBeamPattern(const uint8_t phase_settings[4], const uint8_t gain_settings[4], BeamDirection direction) { - for (uint8_t dev = 0; dev < devices_.size(); ++dev) { - for (uint8_t ch = 0; ch < 4; ++ch) { - if (direction == BeamDirection::TX) { - adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF); - adarSetTxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF); - } else { - adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF); - adarSetRxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF); - } - } - } - return true; -} - -// Beam Sweeping -void ADAR1000Manager::startBeamSweeping() { - beam_sweeping_active_ = true; - current_beam_index_ = 0; - last_beam_update_time_ = HAL_GetTick(); -} - -void ADAR1000Manager::stopBeamSweeping() { - beam_sweeping_active_ = false; -} - -void ADAR1000Manager::updateBeamPosition() { - if (!beam_sweeping_active_) return; - - uint32_t current_time = HAL_GetTick(); - const std::vector& sequence = - (current_mode_ == BeamDirection::TX) ? tx_beam_sequence_ : rx_beam_sequence_; - - if (sequence.empty()) return; - - if (current_time - last_beam_update_time_ >= beam_dwell_time_ms_) { - const BeamConfig& beam = sequence[current_beam_index_]; - setCustomBeamPattern(beam.phase_settings, beam.gain_settings, current_mode_); - - current_beam_index_ = (current_beam_index_ + 1) % sequence.size(); - last_beam_update_time_ = current_time; - } -} - -void ADAR1000Manager::setBeamSequence(const std::vector& sequence, BeamDirection direction) { - if (direction == BeamDirection::TX) { - tx_beam_sequence_ = sequence; - } else { - rx_beam_sequence_ = sequence; - } -} - -void ADAR1000Manager::clearBeamSequence(BeamDirection direction) { - if (direction == BeamDirection::TX) { - tx_beam_sequence_.clear(); - } else { - rx_beam_sequence_.clear(); - } -} - // Monitoring and Diagnostics float ADAR1000Manager::readTemperature(uint8_t deviceIndex) { if (deviceIndex >= devices_.size() || !devices_[deviceIndex]->initialized) { @@ -340,22 +237,6 @@ void ADAR1000Manager::writeRegister(uint8_t deviceIndex, uint32_t address, uint8 adarWrite(deviceIndex, address, value, BROADCAST_OFF); } -// Configuration -// setSwitchSettlingTime, setFastSwitchMode: REMOVED. -// Their only reader was the deleted setADTR1107Control; setFastSwitchMode(true) -// also violated the ADTR1107 datasheet bias sequence (PA + LNA biased to -// operational simultaneously). Per-chirp T/R is FPGA-owned now. - -void ADAR1000Manager::setBeamDwellTime(uint32_t ms) { - beam_dwell_time_ms_ = ms; -} - -// Private helper methods (implementation continues...) -// ... include all the private method implementations from your original file -// ============================================================================ -// PRIVATE HELPER METHODS - Add these to the end of ADAR1000_Manager.cpp -// ============================================================================ - bool ADAR1000Manager::initializeAllDevices() { DIAG_SECTION("BF INIT ALL DEVICES"); @@ -412,90 +293,113 @@ bool ADAR1000Manager::initializeSingleDevice(uint8_t deviceIndex) { return false; } + // Initialize per-channel VGA gains to known defaults. POR leaves these + // undefined; without an explicit write, TX channels would not radiate at + // their nominal level and the AGC loop would have no known RX baseline to + // stride from. AGC overwrites RX gain dynamically once enabled; TX gain + // stays at this baseline (production has no per-channel TX gain loop). + DIAG("BF", " dev[%u] init VGA gains (TX=0x%02X, RX=%u)", + deviceIndex, kDefaultTxVgaGain, kDefaultRxVgaGain); + for (uint8_t ch = 0; ch < 4; ++ch) { + adarSetTxVgaGain(deviceIndex, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF); + adarSetRxVgaGain(deviceIndex, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF); + } + devices_[deviceIndex]->initialized = true; return true; } bool ADAR1000Manager::initializeADTR1107Sequence() { - DIAG_SECTION("ADTR1107 POWER SEQUENCE (9-step)"); + /* GPIO + power-rail steps only. The original 9-step datasheet sequence + * also wrote ADAR1000 LNA/PA bias registers (Steps 5/7/9) before the + * ADAR soft reset later wiped them — those writes are now in + * applyADTRBiasDefaults(), called AFTER initializeAllDevices(). Step 8 + * (enablePASupplies) is left here so the PA rail tracks the original + * bring-up order; the soft-reset window with PA rail ON and bias regs + * at POR-default 0V is bounded by setAllDevicesTXMode() at the end of + * initializeAllDevices() writing kPaBiasOperational and the subsequent + * applyADTRBiasDefaults() trim. See F-1.7 in the startup audit memory. */ + DIAG_SECTION("ADTR1107 POWER SEQUENCE (rails + supplies)"); uint32_t t0 = HAL_GetTick(); - //Powering up ADTR1107 TX mode const uint8_t msg[] = "Starting ADTR1107 Power Sequence...\r\n"; HAL_UART_Transmit(&huart3, msg, sizeof(msg) - 1, 1000); - // Step 1: Connect all GND pins to ground (assumed in hardware) + // Step 1: GND pins assumed in hardware. DIAG("BF", "Step 1: GND pins (hardware -- assumed connected)"); - // Step 2: Set VDD_SW to 3.3V + // Step 2: VDD_SW -> 3.3V DIAG("BF", "Step 2: VDD_SW -> 3.3V"); HAL_GPIO_WritePin(EN_P_3V3_VDD_SW_GPIO_Port, EN_P_3V3_VDD_SW_Pin, GPIO_PIN_SET); HAL_Delay(1); - // Step 3: Set VSS_SW to -3.3V + // Step 3: VSS_SW -> -3.3V DIAG("BF", "Step 3: VSS_SW -> -3.3V"); HAL_GPIO_WritePin(EN_P_3V3_SW_GPIO_Port, EN_P_3V3_SW_Pin, GPIO_PIN_SET); HAL_Delay(1); - // Step 4: CTRL_SW safe-default is RX. - // FPGA-owned path: with TR_SOURCE=1 (set in initializeSingleDevice) the - // chip follows adar_tr_x, which is 0 in the FPGA FSM's IDLE state = RX. - // No SPI write needed here. - DIAG("BF", "Step 4: CTRL_SW -> RX (FPGA adar_tr_x idle-low == RX)"); + // Step 4: CTRL_SW. With TR_SOURCE=1 the chip will follow FPGA adar_tr_x + // once initializeSingleDevice has run; nothing to write here. + DIAG("BF", "Step 4: CTRL_SW -> follows FPGA adar_tr_x post-init (no SPI write)"); HAL_Delay(1); - // Step 5: Set VGG_LNA to 0 - DIAG("BF", "Step 5: VGG_LNA bias -> OFF (0x%02X)", kLnaBiasOff); - uint8_t lna_bias_voltage = kLnaBiasOff; - for (uint8_t dev = 0; dev < devices_.size(); ++dev) { - adarWrite(dev, REG_LNA_BIAS_ON, lna_bias_voltage, BROADCAST_OFF); - adarWrite(dev, REG_LNA_BIAS_OFF, kLnaBiasOff, BROADCAST_OFF); - } - - // Step 6: Set VDD_LNA to 0V for TX mode + // Step 6: VDD_LNA -> 0V (disable ADTR LNA supply for TX path). DIAG("BF", "Step 6: VDD_LNA -> 0V (disable ADTR LNA supply)"); HAL_GPIO_WritePin(EN_P_3V3_ADTR_GPIO_Port, EN_P_3V3_ADTR_Pin, GPIO_PIN_RESET); HAL_Delay(2); - // Step 7: Set VGG_PA to safe negative voltage (PA off for TX mode) - /*A 0x00 value in the - on or off bias registers, correspond to a 0 V output. A 0xFF in the - on or off bias registers correspond to a −4.8 V output.*/ - DIAG("BF", "Step 7: VGG_PA -> safe bias 0x%02X (~ -1.75V, PA off)", kPaBiasTxSafe); - uint8_t safe_pa_bias = kPaBiasTxSafe; // Safe negative voltage (-1.75V) to keep PA off - for (uint8_t dev = 0; dev < devices_.size(); ++dev) { - adarWrite(dev, REG_PA_CH1_BIAS_ON, safe_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH2_BIAS_ON, safe_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH3_BIAS_ON, safe_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH4_BIAS_ON, safe_pa_bias, BROADCAST_OFF); - } - HAL_Delay(10); - - // Step 8: Set VDD_PA to 0V (PA powered up for TX mode) + // Step 8: Enable PA supplies. Bias-register writes happen in + // applyADTRBiasDefaults() AFTER the soft-reset-driven init. DIAG("BF", "Step 8: Enable PA supplies (VDD_PA)"); enablePASupplies(); HAL_Delay(50); - // Step 9: Adjust VGG_PA voltage between −1.75 V and −0.25 V to achieve the desired IDQ_PA=220mA - //Set VGG_PA to safe negative voltage (PA off for TX mode) - /*A 0x00 value in the - on or off bias registers, correspond to a 0 V output. A 0xFF in the - on or off bias registers correspond to a −4.8 V output.*/ - DIAG("BF", "Step 9: VGG_PA -> Idq cal bias 0x%02X (~ -0.24V, target 220mA)", kPaBiasIdqCalibration); - uint8_t Idq_pa_bias = kPaBiasIdqCalibration; // Safe negative voltage (-0.2447V) to keep PA off + DIAG_ELAPSED("BF", "ADTR1107 power sequence (rails)", t0); + + const uint8_t success[] = "ADTR1107 power sequence (rails) completed.\r\n"; + HAL_UART_Transmit(&huart3, success, sizeof(success) - 1, 1000); + + return true; +} + +bool ADAR1000Manager::applyADTRBiasDefaults() { + /* F-1.7: re-emit the LNA-off + PA-safe + PA-Idq-cal bias values that the + * original 9-step ADTR1107 sequence wrote in Steps 5/7/9. Must be called + * AFTER initializeAllDevices() because adarSoftReset() in + * initializeSingleDevice wipes every register to POR-default 0V. */ + DIAG_SECTION("ADTR1107 BIAS DEFAULTS (post-init)"); + uint32_t t0 = HAL_GetTick(); + + // Step 5: VGG_LNA -> OFF (both ON and OFF bias registers). + DIAG("BF", "Step 5: VGG_LNA bias -> OFF (0x%02X)", kLnaBiasOff); for (uint8_t dev = 0; dev < devices_.size(); ++dev) { - adarWrite(dev, REG_PA_CH1_BIAS_ON, Idq_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH2_BIAS_ON, Idq_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH3_BIAS_ON, Idq_pa_bias, BROADCAST_OFF); - adarWrite(dev, REG_PA_CH4_BIAS_ON, Idq_pa_bias, BROADCAST_OFF); + adarWrite(dev, REG_LNA_BIAS_ON, kLnaBiasOff, BROADCAST_OFF); + adarWrite(dev, REG_LNA_BIAS_OFF, kLnaBiasOff, BROADCAST_OFF); + } + + // Step 7: VGG_PA -> safe negative voltage. ADAR1000 datasheet: + // 0x00 -> 0 V, 0xFF -> -4.8 V on bias output; kPaBiasTxSafe (~ -1.75 V) + // keeps the ADTR1107 PA off while we settle. + DIAG("BF", "Step 7: VGG_PA -> safe bias 0x%02X (~ -1.75V, PA off)", kPaBiasTxSafe); + for (uint8_t dev = 0; dev < devices_.size(); ++dev) { + adarWrite(dev, REG_PA_CH1_BIAS_ON, kPaBiasTxSafe, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH2_BIAS_ON, kPaBiasTxSafe, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH3_BIAS_ON, kPaBiasTxSafe, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH4_BIAS_ON, kPaBiasTxSafe, BROADCAST_OFF); } HAL_Delay(10); - DIAG_ELAPSED("BF", "ADTR1107 power sequence", t0); - - const uint8_t success[] = "ADTR1107 power sequence completed.\r\n"; - HAL_UART_Transmit(&huart3, success, sizeof(success) - 1, 1000); + // Step 9: VGG_PA -> Idq cal bias (~ -0.24 V, IDQ_PA = 220 mA target). + DIAG("BF", "Step 9: VGG_PA -> Idq cal bias 0x%02X (~ -0.24V, target 220mA)", kPaBiasIdqCalibration); + for (uint8_t dev = 0; dev < devices_.size(); ++dev) { + adarWrite(dev, REG_PA_CH1_BIAS_ON, kPaBiasIdqCalibration, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH2_BIAS_ON, kPaBiasIdqCalibration, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH3_BIAS_ON, kPaBiasIdqCalibration, BROADCAST_OFF); + adarWrite(dev, REG_PA_CH4_BIAS_ON, kPaBiasIdqCalibration, BROADCAST_OFF); + } + HAL_Delay(10); + DIAG_ELAPSED("BF", "ADTR1107 bias defaults", t0); return true; } @@ -513,10 +417,8 @@ bool ADAR1000Manager::setAllDevicesTXMode() { adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF); // Enable all 4 channels adarSetTxBias(dev, BROADCAST_OFF); - devices_[dev]->current_mode = BeamDirection::TX; DIAG("BF", " dev[%u] TX mode set (enables=0x0F, bias applied)", dev); } - current_mode_ = BeamDirection::TX; return true; } @@ -533,17 +435,14 @@ bool ADAR1000Manager::setAllDevicesRXMode() { // Enable RX channels adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF); // Enable all 4 channels - devices_[dev]->current_mode = BeamDirection::RX; DIAG("BF", " dev[%u] RX mode set (enables=0x0F)", dev); } - current_mode_ = BeamDirection::RX; return true; } void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) { if (direction == BeamDirection::TX) { DIAG_SECTION("ADTR1107 -> TX MODE"); - // setADTR1107Control(true) removed: TR pin is FPGA-driven. // Step 1: Disable LNA power first DIAG("BF", " Disable LNA supplies"); @@ -585,7 +484,6 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) { } else { // RECEIVE MODE: Enable LNA, Disable PA DIAG_SECTION("ADTR1107 -> RX MODE"); - // setADTR1107Control(false) removed: TR pin is FPGA-driven. // Step 1: Disable PA power first DIAG("BF", " Disable PA supplies"); @@ -626,13 +524,6 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) { } } -// setADTR1107Control, setTRSwitchPosition: REMOVED. -// The per-device SPI RMW of REG_SW_CONTROL bit 2 (TR_SOURCE) was both wrong -// (it toggled the *control source*, not the TX/RX state -- TR_SPI is bit 1) -// and redundant with the FPGA's plfm_chirp_controller adar_tr_x output. -// TR_SOURCE is now set to 1 exactly once in initializeSingleDevice. - -// Add the new public method bool ADAR1000Manager::setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction) { for (uint8_t dev = 0; dev < 4; ++dev) { for (uint8_t ch = 0; ch < 4; ++ch) { @@ -711,23 +602,6 @@ void ADAR1000Manager::delayUs(uint32_t microseconds) { } } -void ADAR1000Manager::calculatePhaseSettings(float angle_degrees, uint8_t phase_settings[4]) { - const float freq = 10.5e9; - const float c = 3e8; - const float wavelength = c / freq; - const float element_spacing = wavelength / 2; - - float angle_rad = angle_degrees * M_PI / 180.0; - float phase_shift = (2 * M_PI * element_spacing * sin(angle_rad)) / wavelength; - - for (int i = 0; i < 4; ++i) { - float element_phase = i * phase_shift; - while (element_phase < 0) element_phase += 2 * M_PI; - while (element_phase >= 2 * M_PI) element_phase -= 2 * M_PI; - phase_settings[i] = static_cast((element_phase / (2 * M_PI)) * 128); - } -} - bool ADAR1000Manager::performSystemCalibration() { DIAG_SECTION("BF SYSTEM CALIBRATION"); for (uint8_t i = 0; i < devices_.size(); ++i) { diff --git a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.h b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.h index bc1e991..d23b559 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.h +++ b/9_Firmware/9_1_Microcontroller/9_1_1_C_Cpp_Libraries/ADAR1000_Manager.h @@ -15,27 +15,6 @@ public: RX = 1 }; - struct BeamConfig { - float angle_degrees; - uint8_t phase_settings[4]; - uint8_t gain_settings[4]; - uint32_t dwell_time_ms; - - BeamConfig() : angle_degrees(0), dwell_time_ms(100) { - for(int i = 0; i < 4; i++) { - phase_settings[i] = 0; - gain_settings[i] = 0x7F; - } - } - - BeamConfig(float angle, uint32_t dwell = 100) : angle_degrees(angle), dwell_time_ms(dwell) { - for(int i = 0; i < 4; i++) { - phase_settings[i] = 0; - gain_settings[i] = 0x7F; - } - } - }; - ADAR1000Manager(); ~ADAR1000Manager(); @@ -48,30 +27,14 @@ public: // Mode Switching void switchToTXMode(); void switchToRXMode(); - // fastTXMode/fastRXMode/pulseTXMode/pulseRXMode were removed: per-chirp T/R - // switching is owned by the FPGA (plfm_chirp_controller -> adar_tr_x pins, - // requires TR_SOURCE=1 in REG_SW_CONTROL, set in initializeSingleDevice). - // The old SPI RMW path was architecturally redundant and also toggled the - // wrong bit (TR_SOURCE instead of TR_SPI). See PR for details. // Beam Steering - bool setBeamAngle(float angle_degrees, BeamDirection direction); - bool setCustomBeamPattern(const uint8_t phase_settings[16], const uint8_t gain_settings[4], BeamDirection direction); bool setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction); - // Beam Sweeping - void startBeamSweeping(); - void stopBeamSweeping(); - void updateBeamPosition(); - void setBeamSequence(const std::vector& sequence, BeamDirection direction); - void clearBeamSequence(BeamDirection direction); - // Device Control bool setAllDevicesTXMode(); bool setAllDevicesRXMode(); void setADTR1107Mode(BeamDirection direction); - // setADTR1107Control removed -- it only wrapped the now-deleted - // setTRSwitchPosition SPI path. FPGA drives the TR pin directly. // Monitoring and Diagnostics float readTemperature(uint8_t deviceIndex); @@ -79,47 +42,18 @@ public: uint8_t readRegister(uint8_t deviceIndex, uint32_t address); void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value); - // Configuration - // setSwitchSettlingTime / setFastSwitchMode removed: their only reader was - // the deleted setADTR1107Control SPI path, and setFastSwitchMode(true) - // also bundled a datasheet-violating PA+LNA-biased-simultaneously side - // effect. Per-chirp settling is now FPGA-owned. Callers that need a - // warm-up bias state should use switchToTXMode / switchToRXMode instead. - void setBeamDwellTime(uint32_t ms); - - // Getters - bool isBeamSweepingActive() const { return beam_sweeping_active_; } - uint8_t getCurrentBeamIndex() const { return current_beam_index_; } - BeamDirection getCurrentMode() const { return current_mode_; } - uint32_t getLastSwitchTime() const { return last_switch_time_us_; } - struct ADAR1000Device { uint8_t dev_addr; bool initialized; - BeamDirection current_mode; float temperature; ADAR1000Device(uint8_t addr) - : dev_addr(addr), initialized(false), current_mode(BeamDirection::RX), temperature(25.0f) { + : dev_addr(addr), initialized(false), temperature(25.0f) { } }; - // Configuration - // fast_switch_mode_ / switch_settling_time_us_ removed: both had no - // readers after the FPGA-owned TR refactor. - uint32_t beam_dwell_time_ms_ = 100; - uint32_t last_switch_time_us_ = 0; - // Device Management std::vector> devices_; - BeamDirection current_mode_ = BeamDirection::RX; - - // Beam Sweeping - std::vector tx_beam_sequence_; - std::vector rx_beam_sequence_; - uint8_t current_beam_index_ = 0; - bool beam_sweeping_active_ = false; - uint32_t last_beam_update_time_ = 0; // Vector Modulator lookup tables (see ADAR1000_Manager.cpp for provenance). // Indexed as VM_*[phase % 128] on a uniform 2.8125 deg grid. @@ -143,7 +77,11 @@ public: // Private Methods bool initializeSingleDevice(uint8_t deviceIndex); bool initializeADTR1107Sequence(); - void calculatePhaseSettings(float angle_degrees, uint8_t phase_settings[4]); + // Re-emit ADTR1107 LNA-off + PA-safe + PA-Idq-cal bias values AFTER + // initializeAllDevices() has run its soft-reset-then-init flow. The soft + // reset wipes any bias values set before it, so this MUST be called + // post-init for the ADTR1107 driver to land at the Idq-tuned bias point. + bool applyADTRBiasDefaults(); void delayUs(uint32_t microseconds); // Power Management @@ -172,7 +110,6 @@ public: void adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast); void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast); uint8_t adarAdcRead(uint8_t deviceIndex, uint8_t broadcast); - // setTRSwitchPosition removed -- FPGA owns TR pin. See PR. private: diff --git a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp index 0e0f813..b9edc50 100644 --- a/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp +++ b/9_Firmware/9_1_Microcontroller/9_1_3_C_Cpp_Code/main.cpp @@ -297,7 +297,11 @@ void systemPowerUpSequence(); void systemPowerDownSequence(); void initializeBeamMatrices(); void runRadarPulseSequence(); -void executeChirpSequence(int num_chirps, uint32_t T1, uint32_t PRI1, uint32_t T2, uint32_t PRI2); +/* F-2.1: executeChirpSequence() removed. The MCU is no longer in the chirp + * dispatch path -- production runs FPGA mode 2'b01 (auto-scan) where + * chirp_scheduler.v owns chirp timing and adar_tr_x. See runRadarPulseSequence + * header for the rationale and the chirp_scheduler.v RP_MODE_AUTO_3KM block + * (line ~317) for the FPGA-side behaviour. */ void printSystemStatus(); ////////////////////////////////////////////// @@ -376,17 +380,21 @@ void systemPowerUpSequence() { uint8_t msg[] = "Starting Power Up Sequence...\r\n"; HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000); - // Step 1: Initialize ADTR1107 power sequence - DIAG("PWR", "Step 1: initializeADTR1107Sequence()"); + // Step 1: ADTR1107 GPIO + power-rail bring-up. Bias-register writes are + // deferred to applyADTRBiasDefaults() because the next step's soft reset + // would wipe them. + DIAG("PWR", "Step 1: initializeADTR1107Sequence() (rails only)"); if (!adarManager.initializeADTR1107Sequence()) { DIAG_ERR("PWR", "ADTR1107 power sequence FAILED -- calling Error_Handler()"); uint8_t err[] = "ERROR: ADTR1107 power sequence failed!\r\n"; HAL_UART_Transmit(&huart3, err, sizeof(err)-1, 1000); Error_Handler(); } - DIAG("PWR", "Step 1 OK: ADTR1107 sequence complete"); + DIAG("PWR", "Step 1 OK: ADTR1107 rails up"); - // Step 2: Initialize all ADAR1000 devices + // Step 2: Soft-reset + per-device init (sets TR_SOURCE=1, ADC, VGA gains). + // Ends with setAllDevicesTXMode() inside initializeAllDevices, which sets + // PA bias to operational; the next step trims to the Idq-cal target. DIAG("PWR", "Step 2: initializeAllDevices()"); if (!adarManager.initializeAllDevices()) { DIAG_ERR("PWR", "ADAR1000 initialization FAILED -- calling Error_Handler()"); @@ -396,24 +404,32 @@ void systemPowerUpSequence() { } DIAG("PWR", "Step 2 OK: All ADAR1000 devices initialized"); - // Step 3: Perform system calibration - DIAG("PWR", "Step 3: performSystemCalibration()"); + // Step 3: Re-emit ADTR1107 LNA-off + PA-Idq-cal bias values (the Step 5/7/9 + // writes that the original 9-step sequence put before init, where they + // were getting wiped by the soft reset). F-1.7 in startup audit. + DIAG("PWR", "Step 3: applyADTRBiasDefaults() (F-1.7: post-soft-reset bias trim)"); + if (!adarManager.applyADTRBiasDefaults()) { + DIAG_WARN("PWR", "ADTR bias defaults returned issues (non-fatal)"); + } + + // Step 4: Verify ADAR1000 SPI scratchpad on every device. + DIAG("PWR", "Step 4: performSystemCalibration()"); if (!adarManager.performSystemCalibration()) { DIAG_WARN("PWR", "System calibration returned issues (non-fatal)"); uint8_t warn[] = "WARNING: System calibration issues\r\n"; HAL_UART_Transmit(&huart3, warn, sizeof(warn)-1, 1000); } else { - DIAG("PWR", "Step 3 OK: System calibration passed"); + DIAG("PWR", "Step 4 OK: System calibration passed"); } - // Step 4: Set to safe TX mode - DIAG("PWR", "Step 4: setAllDevicesTXMode()"); - adarManager.setAllDevicesTXMode(); - DIAG("PWR", "Step 4 OK: All devices set to TX mode"); + // (Former Step 4 setAllDevicesTXMode removed — F-1.8: it is already + // called at the end of initializeAllDevices(). The chip's TX/RX state is + // overridden per-chirp by the FPGA TR pin (TR_SOURCE=1), so the static + // mode chosen here is mostly cosmetic.) uint8_t success[] = "Power Up Sequence Completed Successfully\r\n"; HAL_UART_Transmit(&huart3, success, sizeof(success)-1, 1000); - DIAG("PWR", "systemPowerUpSequence COMPLETE"); + DIAG("PWR", "systemPowerUpSequence COMPLETE -- ready for radar loop"); } void systemPowerDownSequence() { @@ -468,7 +484,9 @@ void initializeBeamMatrices() { uint8_t msg[] = "Initializing Beam Matrices...\r\n"; HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000); - // Matrix1: Positions 1-15 (positive phase differences) + // Matrix1: phase_differences[0..14] (+160°..+0°). Array math gives beam + // peak at NEGATIVE θ (matrix1[0] → −62°, matrix1[14] → broadside). + // Physical sky/ground sign vs antenna mount is unverified (audit D2). for(int beam_pos = 0; beam_pos < 15; beam_pos++) { float phase_diff_degrees = phase_differences[beam_pos]; @@ -478,7 +496,9 @@ void initializeBeamMatrices() { } } - // Matrix2: Positions 17-31 (negative phase differences) + // Matrix2: phase_differences[16..30] (−10.67°..−160°). Array math gives + // beam peak at POSITIVE θ (matrix2[0] → +4°, matrix2[14] → +62°). + // Same orientation caveat as Matrix1. for(int beam_pos = 0; beam_pos < 15; beam_pos++) { float phase_diff_degrees = phase_differences[beam_pos + 16]; @@ -497,52 +517,36 @@ void initializeBeamMatrices() { HAL_UART_Transmit(&huart3, done, sizeof(done)-1, 1000); } -void executeChirpSequence(int num_chirps, float T1, float PRI1, float T2, float PRI2) { - // NOTE: No per-chirp DIAG — this is a us/ns timing-critical path. - // Only log entry params for post-mortem analysis. - DIAG("SYS", "executeChirpSequence: num_chirps=%d T1=%.2f PRI1=%.2f T2=%.2f PRI2=%.2f", - num_chirps, T1, PRI1, T2, PRI2); - // First chirp sequence (microsecond timing) - // T/R switching is owned by the FPGA plfm_chirp_controller: its chirp - // FSM drives adar_tr_x high during LONG_CHIRP/SHORT_CHIRP and low during - // listen/guard. new_chirp (GPIOD_8) triggers the FSM out of IDLE. - // The MCU's old pulseTXMode/pulseRXMode SPI path was redundant and raced - // the FPGA -- removed. - for(int i = 0; i < num_chirps; i++) { - HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA - delay_us((uint32_t)T1); - delay_us((uint32_t)(PRI1 - T1)); - } - - delay_us((uint32_t)Guard); - - // Second chirp sequence (nanosecond timing) - for(int i = 0; i < num_chirps; i++) { - HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA - delay_ns((uint32_t)(T2 * 1000)); - delay_ns((uint32_t)((PRI2 - T2) * 1000)); - } -} - -/* runRadarPulseSequence — beam-steering loop + STM32 pass-through GPIO toggles. +/* F-2.1 (delete branch): runRadarPulseSequence — per-frame beam-steering + * loop + stepper rotation. * - * IMPORTANT (P-5 / PR-Q.1 follow-up): in production the FPGA cold-resets to - * host_radar_mode = 2'b01 (auto-scan) — see radar_system_top.v:1045. In that - * mode the FPGA's chirp_scheduler owns chirp timing and IGNORES the GPIOD_8 - * (new_chirp), GPIOD_9 (new_elevation), GPIOD_10 (new_azimuth) toggles this - * function emits via executeChirpSequence. The MCU's only live job per frame - * is therefore beam steering (ADAR1000 pattern updates) + stepper rotation. + * The MCU does NOT drive chirp timing. Production runs FPGA mode 2'b01 + * (`RP_MODE_AUTO_3KM`, the cold-reset default at radar_system_top.v:1058). + * In that mode chirp_scheduler.v owns the SHORT/MEDIUM/LONG sub-frame walk + * and plfm_chirp_controller.v drives `adar_tr_x` per chirp. The MCU's only + * live per-frame work is: + * 1. Updating ADAR1000 beam patterns (matrix1 / vector_0 / matrix2) + * via setCustomBeamPattern16 — once per pattern, three patterns per + * beam position, 15 beam positions per frame = 90 ADAR SPI writes. + * 2. Rotating the stepper to the next azimuth slot at end-of-frame. * - * MODE 2'b00 (STM32 pass-through) requires the MCU to drive the 3-PRI ladder - * SHORT 175 us (T2=1 us chirp) - * MEDIUM 161 us (T_MEDIUM=5 us chirp) -- PR-Q.1 stagger - * LONG 167 us (T1=30 us chirp) - * for the host-side CRT Doppler unfolder (processing.py extract_targets_from_ - * frame_crt) to deliver unambiguous velocities. The current GPIO loop only - * emits two PRIs (PRI1=167 LONG, PRI2=175 SHORT) and is structured around - * the pre-PR-F m_max=32 frame, so pass-through mode is OPERATIONALLY - * UNSUPPORTED until this loop is rebuilt to dispatch 3 sub-frames in - * SHORT/MEDIUM/LONG order. Do not switch the FPGA into mode 00 until then. + * History: this function previously called executeChirpSequence() to emit + * GPIO toggles on PD8/PD9/PD10 intended for FPGA mode 2'b00 (STM32 + * pass-through). Mode 2'b00 was never end-to-end validated — the GPIO loop + * emitted only 2 PRIs (LONG, SHORT) and was structured for the pre-PR-F + * m_max=32 frame, while the production receiver chain (PR-F doppler/mti/ + * cfar) expects 3 sub-frames (SHORT/MEDIUM/LONG) for the CRT Doppler + * unfolder. Building out a real 3-PRI pass-through would require a hardware + * timer driving PD8 (software delay_us is 3% jittery, unacceptable for + * Doppler) and full agreement between MCU and FPGA on chirps_per_subframe. + * That work was decided not worth doing — see audit memory + * project_aeris10_startup_audit_2026-05-04.md F-2.1 rationale. + * + * The m / n / y globals are kept and incremented (they feed + * getSystemStatusForGUI as ChirpCount / BeamPos / Azimuth). m advances by + * the same per-frame amount (3 × m_max/2) it did before so the GUI counter + * stays continuous; the actual count of chirps fired is owned by the FPGA + * and surfaces via the bulk-frame status word, not via this counter. */ void runRadarPulseSequence() { static int sequence_count = 0; @@ -553,8 +557,6 @@ void runRadarPulseSequence() { DIAG("SYS", "runRadarPulseSequence #%d: m_max=%d n_max=%d y_max=%d", sequence_count, m_max, n_max, y_max); - // Fast per-chirp switching is now FPGA-owned (plfm_chirp_controller - // adar_tr_x), not MCU-driven. setFastSwitchMode(true) call removed. DIAG("BF", "Beam sweep start (FPGA owns per-chirp T/R switching)"); // MCU-N5/C4: do NOT redeclare m/n/y here — they are file-scope globals @@ -562,29 +564,43 @@ void runRadarPulseSequence() { // BeamPos/Azimuth/ChirpCount. Local declarations would shadow them and // freeze the telemetry at 1. - // Main beam steering sequence + /* Main beam steering sequence. Per beam position we push three ADAR1000 + * pattern updates: matrix1, vector_0 (broadside), matrix2 (see + * initializeBeamMatrices for the actual sim-peak directions and the + * sign-convention caveat). The FPGA's chirp_scheduler is firing the + * 3-PRI ladder against whichever pattern is currently loaded; we hold + * each pattern for one full SHORT/MEDIUM/LONG ladder before advancing. + * + * Per-pattern dwell: + * 16 × PRI_SHORT (175 us) + + * 16 × PRI_MEDIUM (161 us) + + * 16 × PRI_LONG (167 us) = 8048 us per ladder, rounded to 8 ms. + * HAL_Delay yields to SysTick / IRQs, so unlike the removed + * executeChirpSequence busy-loop the MCU stays responsive to USB CDC, + * UART, and I2C peripherals during the dwell. For drift-immune sync + * we'd wait on an FPGA `subframe_pulse` GPIO instead, but the bitstream + * does not currently bring that signal out to a pin. */ + static const uint32_t BEAM_PATTERN_DWELL_MS = 8u; + for(int beam_pos = 0; beam_pos < 15; beam_pos++) { - HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_9);// Notify FPGA of elevation change - DIAG("SYS", "Beam pos %d/15: elevation GPIO toggle, patterns matrix1/vector_0/matrix2", beam_pos); - // Pattern 1: matrix1 (positive steering angles) + HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_9);// new_elevation -- mode 2'b00 input only; no-op in production mode 2'b01 but harmless to keep + DIAG("SYS", "Beam pos %d/15: patterns matrix1/vector_0/matrix2", beam_pos); + // Pattern 1: matrix1 adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::TX); adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::RX); - - executeChirpSequence(m_max/2, T1, PRI1, T2, PRI2); + HAL_Delay(BEAM_PATTERN_DWELL_MS); m += m_max/2; // Pattern 2: vector_0 (broadside) adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::TX); adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::RX); - - executeChirpSequence(m_max/2, T1, PRI1, T2, PRI2); + HAL_Delay(BEAM_PATTERN_DWELL_MS); m += m_max/2; - // Pattern 3: matrix2 (negative steering angles) + // Pattern 3: matrix2 adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::TX); adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::RX); - - executeChirpSequence(m_max/2, T1, PRI1, T2, PRI2); + HAL_Delay(BEAM_PATTERN_DWELL_MS); m += m_max/2; // Reset chirp counter if needed @@ -1253,10 +1269,41 @@ bool checkSystemHealthStatus(void) { system_emergency_state = true; DIAG_ERR("SYS", "Latching system_emergency_state due to error_count > 10"); } + /* F-2.5 FIX: persist emergency state to BKPSRAM so the IWDG reset + * that follows the SAFE-MODE blink loop in main() boots straight + * back into safe-hold (Emergency_Stop on the persist-check at + * ~line 1630) instead of re-running init and re-energising the + * PA rails. The critical-error path through Emergency_Stop() + * already persists; the non-critical-escalation path (this one) + * was the missing leg, leaving the system in a reset cycle with + * intermittent PA-on time while the underlying fault is unfixed. + * Idempotent (just writes a magic word). */ + emergency_persist_set(); DIAG_ERR("SYS", "checkSystemHealthStatus returning FALSE (emergency=true error_count=%lu)", error_count); return false; } + } else { + /* F-2.7a FIX: telemetry hygiene — clear last_error / error_count + * after recovery is confirmed stable (5 consecutive clean health + * checks, ~1.3 s at 258 ms frame cadence). Without this, + * getSystemStatusForGUI keeps reporting the stale last_error + * indefinitely after a transient fault auto-recovers (e.g. an + * ADAR1000 SPI glitch resolved by attemptErrorRecovery). The 5-iter + * confirmation prevents an intermittently-failing peripheral from + * looking healed between flaps. error_count is also reset so the + * `error_count > 10` escalation gate measures *current* run-rate, + * not lifetime cumulative since boot. */ + static uint32_t clean_streak = 0; + if (last_error != ERROR_NONE) { + if (++clean_streak >= 5) { + DIAG("SYS", "Recovery confirmed stable -- clearing last_error=%d error_count=%lu", + (int)last_error, (unsigned long)error_count); + last_error = ERROR_NONE; + error_count = 0; + clean_streak = 0; + } + } } return true; @@ -1554,106 +1601,6 @@ static int configure_ad9523(void) return 0; } -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////ADAR1000////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// Alternative approach using the beam sequence feature -void setupBeamSequences(ADAR1000Manager& manager) { - std::vector tx_sequence; - std::vector rx_sequence; - - // Create beam configurations for each pattern - // Matrix1 sequences (14 steps) - for(int i = 0; i < 14; i++) { - ADAR1000Manager::BeamConfig tx_config; - ADAR1000Manager::BeamConfig rx_config; - - // Calculate angles or use your matrix directly - // For now, we'll use a placeholder angle and set phases manually later - tx_config.angle_degrees = 0; // This would be calculated from your matrix - rx_config.angle_degrees = 0; - - // Copy phase settings from matrix1 - for(int ch = 0; ch < 16; ch++) { - if(ch < 4) { - tx_config.phase_settings[ch] = matrix1[i][ch]; - rx_config.phase_settings[ch] = matrix1[i][ch]; - } - } - - tx_sequence.push_back(tx_config); - rx_sequence.push_back(rx_config); - } - - // You would similarly add the zero phase and matrix2 sequences - - // Note: The beam sequence feature in the manager currently uses calculated phases - // You might need to modify the manager to support pre-calculated phase arrays -} -void setCustomBeamPattern(ADAR1000Manager& manager, uint8_t phase_pattern[16]) { - // Set TX phases for all 4 ADAR1000 devices - for(int dev = 0; dev < 4; dev++) { - for(int ch = 0; ch < 4; ch++) { - uint8_t phase = phase_pattern[dev * 4 + ch]; - manager.adarSetTxPhase(dev, ch + 1, phase, BROADCAST_OFF); - } - } - - // Set RX phases for all 4 ADAR1000 devices - for(int dev = 0; dev < 4; dev++) { - for(int ch = 0; ch < 4; ch++) { - uint8_t phase = phase_pattern[dev * 4 + ch]; - manager.adarSetRxPhase(dev, ch + 1, phase, BROADCAST_OFF); - } - } -} - -void initializeBeamMatricesWithSteeringAngles() { - const float wavelength = 0.02857f; // 10.5 GHz wavelength in meters - const float element_spacing = wavelength / 2.0f; - - // Calculate steering angles from phase differences - float steering_angles[31]; - for(int i = 0; i < 31; i++) { - float phase_diff_rad = phase_differences[i] * M_PI / 180.0f; - steering_angles[i] = asin((phase_diff_rad * wavelength) / (2 * M_PI * element_spacing)) * 180.0f / M_PI; - } - - // Matrix1: Positive angles (positions 1-15) - for(int beam_pos = 0; beam_pos < 15; beam_pos++) { - float angle = steering_angles[beam_pos]; - - for(int element = 0; element < 16; element++) { - // Calculate phase shift for linear array - float phase_shift_rad = (2 * M_PI * element_spacing * element * sin(angle * M_PI / 180.0f)) / wavelength; - float phase_degrees = phase_shift_rad * 180.0f / M_PI; - - // Wrap to 0-360 degrees - while(phase_degrees < 0) phase_degrees += 360; - while(phase_degrees >= 360) phase_degrees -= 360; - - matrix1[beam_pos][element] = (uint8_t)((phase_degrees / 360.0f) * 128); - } - } - - // Matrix2: Negative angles (positions 17-31) - for(int beam_pos = 0; beam_pos < 15; beam_pos++) { - float angle = steering_angles[beam_pos + 16]; - - for(int element = 0; element < 16; element++) { - float phase_shift_rad = (2 * M_PI * element_spacing * element * sin(angle * M_PI / 180.0f)) / wavelength; - float phase_degrees = phase_shift_rad * 180.0f / M_PI; - - while(phase_degrees < 0) phase_degrees += 360; - while(phase_degrees >= 360) phase_degrees -= 360; - - matrix2[beam_pos][element] = (uint8_t)((phase_degrees / 360.0f) * 128); - } - } -} - - - /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ @@ -1768,10 +1715,7 @@ int main(void) DIAG("PWR", "Enabling 3.3V clock rail"); HAL_GPIO_WritePin(EN_P_3V3_CLOCK_GPIO_Port,EN_P_3V3_CLOCK_Pin,GPIO_PIN_SET); HAL_Delay(100); - DIAG("PWR", "Releasing AD9523 reset (pin HIGH)"); - HAL_GPIO_WritePin(AD9523_RESET_GPIO_Port,AD9523_RESET_Pin,GPIO_PIN_SET); - HAL_Delay(100); - DIAG("PWR", "AD9523 power sequencing complete -- all rails up, reset released"); + DIAG("PWR", "AD9523 rails up; reset will be released inside configure_ad9523()"); //Set planned Clocks on AD9523 /* @@ -1813,6 +1757,26 @@ int main(void) HAL_Delay(100); DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V"); + /* Pulse FPGA reset (PD12) immediately after rails are stable so the FPGA + * outputs (`adar_tr_x`, `mixers_enable`, `new_chirp/elevation/azimuth`) + * are driven to known defaults BEFORE any ADAR1000 SPI traffic looks at + * the TR pin. PA Vdd remains gated by `EN_DIS_RFPA_VDD` later, so PA + * protection is unchanged. */ + DIAG("FPGA", "Resetting FPGA (GPIOD pin 12: LOW -> 10ms -> HIGH)"); + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET); + HAL_Delay(10); + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET); + DIAG("FPGA", "FPGA reset complete -- adar_tr_x driven LOW (RX commanded)"); + + /* F-2.1: this firmware build supports only FPGA mode 2'b01 (RP_MODE_AUTO_3KM, + * the cold-reset default at radar_system_top.v:1058). The MCU does not + * dispatch chirps -- chirp_scheduler.v owns the SHORT/MEDIUM/LONG ladder. + * Mode 2'b00 (STM32 pass-through) is supported on the FPGA side but is + * NOT implemented in this firmware: a real pass-through would need a + * hardware-timer-driven PD8 emitter (software delay_us is too jittery + * for Doppler) plus MCU<->FPGA agreement on host_chirps_per_subframe. + * Operators must not change host_radar_mode away from 2'b01. */ + DIAG("FPGA", "Production mode: host_radar_mode = 2'b01 (auto-scan, FPGA-owned chirp dispatch)"); // Initialize module IMU DIAG_SECTION("IMU INIT (GY-85)"); @@ -2061,6 +2025,12 @@ int main(void) (unsigned long)lock_timeout, tx_locked ? "LOCKED" : "UNLOCKED", rx_locked ? "LOCKED" : "UNLOCKED"); + /* F-1.6: latch emergency state. PA Vdd enable + TX mixer enable + * later in this function, and the radar loop in main(), all gate + * on system_emergency_state — keying the PA into an unlocked + * synth would radiate spurious/free-running RF at full power. */ + last_error = (!tx_locked) ? ERROR_ADF4382_TX_UNLOCK : ERROR_ADF4382_RX_UNLOCK; + system_emergency_state = true; } // check if there is a lock via direct GPIO (independent of register read above) @@ -2192,28 +2162,14 @@ int main(void) * the MCU at boot indefinitely. The USB settings handshake (if ever * re-enabled) should be handled non-blocking in the main loop. */ - /***************************************************************/ - /************ FPGA reset (BEFORE PA Vdd enable) ****************/ - /***************************************************************/ - /* [MCU-N2/N11 FIX] Reset FPGA early — before any PA-rail enables — - * so `adar_tr_x` is driven LOW (RX commanded externally) when the PA Vdd - * rail later comes up to 22 V. Without this, PA could be energised while - * the FPGA is still in its implicit reset and `adar_tr_x` is undefined, - * with the ADAR1000 already commanded to TX (TR_SOURCE=1) — a glitch - * could key the PA into an undefined antenna load. Kept outside the - * `if (PowerAmplifier)` block so the FPGA always boots cleanly even when - * the PA path is disabled for bench testing. TX mixer enable (PD11) is - * still LOW (set by MX_GPIO_Init), so no chirps fire. */ - DIAG("FPGA", "Resetting FPGA (GPIOD pin 12: LOW -> 10ms -> HIGH)"); - HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET); - HAL_Delay(10); - HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET); - DIAG("FPGA", "FPGA reset complete -- adar_tr_x driven LOW (RX commanded)"); - /***************************************************************/ /************RF Power Amplifier Powering up sequence************/ /***************************************************************/ - if(PowerAmplifier){ + /* FPGA was reset right after its 3V3 rail came up — adar_tr_x has been + * driven LOW (RX) for the entire interval since then. PA Vdd is still + * gated by EN_DIS_RFPA_VDD below. F-1.6: PA enable also gates on + * !system_emergency_state — set true earlier on LO lock failure. */ + if(PowerAmplifier && !system_emergency_state){ DIAG_SECTION("PA: RF Power Amplifier power-up sequence"); /* Initialize DACs */ /* DAC1: Address 0b1001000 = 0x48 */ @@ -2267,10 +2223,9 @@ int main(void) HAL_GPIO_WritePin(DAC_2_VG_LDAC_GPIO_Port, DAC_2_VG_LDAC_Pin, GPIO_PIN_SET); //Enable RF Power Amplifier VDD = 22V - /* [MCU-N2/N11] FPGA has already been reset earlier (before this PA block) - * so `adar_tr_x` is now driven LOW (RX commanded). Safe to bring PA Vdd - * up to 22 V here. TX mixer enable (PD11) is still LOW until later, - * gating any FPGA-driven chirps. */ + /* FPGA reset was issued right after its 3V3 rail came up; adar_tr_x has + * been LOW (RX) ever since. TX mixer enable (PD11) is still LOW until + * later, gating any FPGA-driven chirps. Safe to bring PA Vdd to 22 V. */ DIAG("PA", "Enabling RFPA VDD=22V (EN_DIS_RFPA_VDD HIGH)"); HAL_GPIO_WritePin(EN_DIS_RFPA_VDD_GPIO_Port, EN_DIS_RFPA_VDD_Pin, GPIO_PIN_SET); @@ -2357,16 +2312,13 @@ int main(void) DIAG("PA", "PA IDQ calibration sequence COMPLETE"); } - /* [MCU-N2/N11] FPGA was already reset earlier in the boot sequence, - * before PA Vdd was energised, to avoid an undefined `adar_tr_x` window. - * No further reset needed here. Leaving the comment so future readers - * understand why this block looks like it should be present. */ - - - - //Tell FPGA to apply TX RF by enabling Mixers - DIAG("FPGA", "Enabling TX mixers (GPIOD pin 11 HIGH)"); - HAL_GPIO_WritePin(GPIOD, GPIO_PIN_11, GPIO_PIN_SET); + //Tell FPGA to apply TX RF by enabling Mixers (F-1.6: skip if emergency) + if (!system_emergency_state) { + DIAG("FPGA", "Enabling TX mixers (GPIOD pin 11 HIGH)"); + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_11, GPIO_PIN_SET); + } else { + DIAG_ERR("FPGA", "TX mixer enable SKIPPED -- system_emergency_state=true (LO lock failed?)"); + } /* T°C sensor TMP37 ADC3: Address 0x49, Single-Ended mode, Internal Ref ON + ADC ON */ DIAG("SYS", "Initializing temperature sensor ADC3 (I2C2, addr=0x49, TMP37)"); @@ -2381,11 +2333,19 @@ int main(void) /************************ERRORS HANDLERS************************/ /***************************************************************/ - // Initialize error handler system - DIAG("SYS", "Initializing error handler: clearing error_count, last_error, emergency_state"); + // Initialize error handler system. Only clear emergency_state / last_error + // when no fatal init error was latched — F-1.6 sets system_emergency_state + // on LO lock failure earlier in main(), and we must not clear that here or + // the PA / TX-mixer guards become a one-shot that's already been cleared + // by the time the main-loop health check runs. + DIAG("SYS", "Initializing error handler: clearing error_count; preserving any latched emergency state"); error_count = 0; - last_error = ERROR_NONE; - system_emergency_state = false; + if (last_error == ERROR_NONE) { + system_emergency_state = false; + } else { + DIAG_ERR("SYS", "Init left last_error=%d, system_emergency_state=%d -- preserving for main-loop SAFE handling", + (int)last_error, (int)system_emergency_state); + } /***************************************************************/ /*********************SEND TO GUI STATUS************************/ @@ -2429,9 +2389,18 @@ int main(void) char emergency_msg[] = "SYSTEM IN SAFE MODE - Manual intervention required\r\n"; HAL_UART_Transmit(&huart3, (uint8_t*)emergency_msg, strlen(emergency_msg), 1000); - DIAG_ERR("SYS", "SAFE MODE ACTIVE -- blinking all LEDs, waiting for system_emergency_state clear"); + DIAG_ERR("SYS", "SAFE MODE ACTIVE -- blinking all LEDs until IWDG resets the MCU"); - // Blink all LEDs to indicate safe mode (500ms period, visible to operator) + /* F-2.4 FIX: blink-then-reset semantics. No main-loop path clears + * system_emergency_state — the loop body deliberately omits + * HAL_IWDG_Refresh() so the watchdog fires (~4 s) and resets the + * MCU. checkSystemHealthStatus() has already called + * emergency_persist_set() (F-2.5), so the next boot sees the + * BKPSRAM flag at line ~1630 and routes straight into + * Emergency_Stop's safe-hold (PA rails cut, infinite IWDG- + * refreshed loop, power-cycle to clear). The previous comment + * "waiting for system_emergency_state clear" implied a runtime + * exit that never existed. */ while (system_emergency_state) { HAL_GPIO_TogglePin(LED_1_GPIO_Port, LED_1_Pin); HAL_GPIO_TogglePin(LED_2_GPIO_Port, LED_2_Pin); @@ -2439,7 +2408,9 @@ int main(void) HAL_GPIO_TogglePin(LED_4_GPIO_Port, LED_4_Pin); HAL_Delay(250); } - DIAG("SYS", "Exited safe mode blink loop -- system_emergency_state cleared"); + /* NOTREACHED — IWDG resets before this line. Kept as defensive + * marker for future refactors that might add a runtime clear. */ + DIAG("SYS", "Exited safe mode blink loop -- unexpected, system_emergency_state cleared"); } ////////////////////////////////////////////////////////////////////////////////////// @@ -2451,32 +2422,26 @@ int main(void) if (um982_is_position_valid(&um982)) { RADAR_Latitude = um982_get_latitude(&um982); RADAR_Longitude = um982_get_longitude(&um982); + } else if (um982_position_age(&um982) > 30000) { + /* F-2.7b FIX: GPS fix has aged past the 30 s health-check threshold. + * Invalidate the cached lat/lon globals so getSystemStatusForGUI() + * does not keep emitting the last-known fix as if it were current. + * NaN propagates through %.6f as "nan" which the GUI parser treats + * as missing. The 30 s window matches the ERROR_GPS_COMM gate in + * checkSystemHealth() at line 842. */ + RADAR_Latitude = NAN; + RADAR_Longitude = NAN; } - ////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////// Monitor ADF4382A lock status periodically////////////////// - ////////////////////////////////////////////////////////////////////////////////////// - - // Monitor lock status periodically - static uint32_t last_check = 0; - if (HAL_GetTick() - last_check > 5000) { - ADF4382A_CheckLockStatus(&lo_manager, &tx_locked, &rx_locked); - - if (!tx_locked || !rx_locked) { - printf("LO Lock Lost! TX: %s, RX: %s\n", - tx_locked ? "LOCKED" : "UNLOCKED", - rx_locked ? "LOCKED" : "UNLOCKED"); - DIAG_ERR("LO", "Lock LOST in main loop! TX=%s RX=%s", - tx_locked ? "LOCKED" : "UNLOCKED", - rx_locked ? "LOCKED" : "UNLOCKED"); - DIAG_GPIO("LO", "TX_LKDET (direct)", ADF4382_TX_LKDET_GPIO_Port, ADF4382_TX_LKDET_Pin); - DIAG_GPIO("LO", "RX_LKDET (direct)", ADF4382_RX_LKDET_GPIO_Port, ADF4382_RX_LKDET_Pin); - } else { - DIAG("LO", "Lock poll OK: TX=LOCKED RX=LOCKED"); - } - - last_check = HAL_GetTick(); - } + /* F-2.1b: 5 s LO lock poll deleted. checkSystemHealth() at line ~749 + * already calls ADF4382A_CheckLockStatus(&lo_manager, ...) every + * main-loop iteration (no rate-limit) and routes any unlock through + * handleSystemError -> attemptErrorRecovery. The 5 s poll only added + * DIAG noise -- it didn't feed the recovery path. (void)tx_locked / + * (void)rx_locked silences the unused-but-set warning on the init + * declaration at line ~1974, which only appears here in non-test builds. */ + (void)tx_locked; + (void)rx_locked; ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////// Monitor Temperature Sensors periodically////////////////// ////////////////////////////////////////////////////////////////////////////////////// @@ -2535,8 +2500,13 @@ int main(void) /* [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) { + * Re-read all 16 channels every 5 s alongside temperature. + * F-2.8: extend the gate with !system_emergency_state to mirror the + * F-1.6 pattern. If we entered safe mode mid-loop, systemPowerDown- + * Sequence has cut the PA rails but the PowerAmplifier flag may + * still be true — reading near-zero Idq values then would trip a + * spurious ERROR_RF_PA_BIAS on the next checkSystemHealth pass. */ + if (PowerAmplifier && !system_emergency_state) { 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); @@ -2601,8 +2571,10 @@ int main(void) * ~4 s, the IWDG resets the MCU automatically. */ HAL_IWDG_Refresh(&hiwdg); - // Optional: Add system monitoring here - // Check temperatures, power levels, etc. + /* F-2.3: removed stale "Optional: Add system monitoring here / Check + * temperatures, power levels, etc." TODO -- those checks are already + * performed above (temperature + IDQ at the 5 s tick, LO lock at the + * 5 s tick, ADF4382/ADAR/IMU/BMP/GPS/FPGA-DSP via checkSystemHealth). */ /* USER CODE END WHILE */ @@ -2631,6 +2603,8 @@ void SystemClock_Config(void) /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); + /* F-3.7: VOS3 + 72 MHz SYSCLK is intentional -- MCU is control-plane only, + * heavy DSP runs FPGA-side. F7 can hit 216 MHz @ VOS1 if MCU work demands. */ __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3); /** Initializes the RCC Oscillators according to the specified parameters @@ -2958,7 +2932,8 @@ static void MX_TIM1_Init(void) * @brief TIM3 Initialization Function — DELADJ PWM for ADF4382A phase shift * CH2 = TX DELADJ, CH3 = RX DELADJ * Period (ARR) = 999 → 1000 counts matching DELADJ_MAX_DUTY_CYCLE - * Prescaler = 71 → 1 MHz tick @ 72 MHz APB1 → 1 kHz PWM frequency + * Prescaler = 71 → 1 MHz tick @ 72 MHz timer clock (APB1=36 MHz, but + * RCC_TIMPRES_ACTIVATED forces TIMxCLK=HCLK) → 1 kHz PWM frequency * @param None * @retval None */ @@ -3168,7 +3143,7 @@ static void MX_GPIO_Init(void) /*Configure GPIO pins : EN_P_1V0_FPGA_Pin EN_P_1V8_FPGA_Pin EN_P_3V3_FPGA_Pin EN_P_5V0_ADAR_Pin EN_P_3V3_ADAR12_Pin EN_P_3V3_ADAR34_Pin EN_P_3V3_ADTR_Pin EN_P_3V3_SW_Pin - EN_P_3V3_ADAR12EN_P_3V3_VDD_SW_Pin */ + EN_P_3V3_VDD_SW_Pin */ GPIO_InitStruct.Pin = EN_P_1V0_FPGA_Pin|EN_P_1V8_FPGA_Pin|EN_P_3V3_FPGA_Pin|EN_P_5V0_ADAR_Pin |EN_P_3V3_ADAR12_Pin|EN_P_3V3_ADAR34_Pin|EN_P_3V3_ADTR_Pin|EN_P_3V3_SW_Pin |EN_P_3V3_VDD_SW_Pin; @@ -3177,7 +3152,7 @@ static void MX_GPIO_Init(void) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); - /*Configure GPIO pins : PD8 PD9 PD10 PD11 + /*Configure GPIO pins : PD8 PD9 PD10 PD11 PD12 STEPPER_CW_P_Pin STEPPER_CLK_P_Pin EN_DIS_RFPA_VDD_Pin EN_DIS_COOLING_Pin */ GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12 |STEPPER_CW_P_Pin|STEPPER_CLK_P_Pin|EN_DIS_RFPA_VDD_Pin|EN_DIS_COOLING_Pin; @@ -3186,7 +3161,7 @@ static void MX_GPIO_Init(void) GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); - /*Configure GPIO pins : PD12 PD13 PD14 PD15 */ + /*Configure GPIO pins : PD13 PD14 PD15 */ GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; @@ -3264,6 +3239,9 @@ void MPU_Config(void) MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.BaseAddress = 0x0; MPU_InitStruct.Size = MPU_REGION_SIZE_4GB; + /* F-3.8: SRD=0x87 -> subregions {0,1,2,7} bypass NO_ACCESS (code/SRAM/APB/ + * system region pass via background privileged default); subregions 3-6 + * (0x6000_0000-0xDFFF_FFFF, FMC/QSPI/AHB3 aliases unused here) get blocked. */ MPU_InitStruct.SubRegionDisable = 0x87; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.AccessPermission = MPU_REGION_NO_ACCESS; @@ -3285,11 +3263,12 @@ void MPU_Config(void) void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ - /* User can add his own implementation to report the HAL error return state */ - __disable_irq(); - while (1) - { - } + /* F-3.1: reset instead of hang. Error_Handler is invoked by every + * MX_*_Init() helper before MX_IWDG_Init() runs, so an infinite spin + * here would brick the MCU on any transient boot-time glitch with no + * watchdog to recover. SystemReset turns a hard-to-debug brick into a + * visible reboot loop. */ + NVIC_SystemReset(); /* USER CODE END Error_Handler_Debug */ }