fix(mcu): F-3.1 Error_Handler reset + audit cleanup tail

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.
This commit is contained in:
Jason
2026-05-04 21:06:23 +05:45
parent 53f7d1e3ee
commit b84aa6a6f3
4 changed files with 315 additions and 526 deletions

View File

@@ -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)
{

View File

@@ -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<BeamConfig>& 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<BeamConfig>& 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<uint8_t>((element_phase / (2 * M_PI)) * 128);
}
}
bool ADAR1000Manager::performSystemCalibration() {
DIAG_SECTION("BF SYSTEM CALIBRATION");
for (uint8_t i = 0; i < devices_.size(); ++i) {

View File

@@ -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<BeamConfig>& 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<std::unique_ptr<ADAR1000Device>> devices_;
BeamDirection current_mode_ = BeamDirection::RX;
// Beam Sweeping
std::vector<BeamConfig> tx_beam_sequence_;
std::vector<BeamConfig> 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:

View File

@@ -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<ADAR1000Manager::BeamConfig> tx_sequence;
std::vector<ADAR1000Manager::BeamConfig> 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 */
}