refactor(mcu): remove redundant ADAR1000 T/R SPI paths (FPGA-owned)

Per-chirp T/R switching is owned by the FPGA plfm_chirp_controller
driving adar_tr_x pins (TR_SOURCE=1 in REG_SW_CONTROL, already set by
initializeSingleDevice). The MCU's SPI RMW path via fastTXMode/
fastRXMode/pulseTXMode/pulseRXMode/setADTR1107Control was:
  (a) architecturally redundant — raced the FPGA-driven TR line,
  (b) toggled the wrong bit (TR_SOURCE instead of TR_SPI),
  (c) in setFastSwitchMode(true) bundled a datasheet-violating
      PA+LNA-simultaneously-biased side effect.

Removed methods and their backing state (fast_switch_mode_,
switch_settling_time_us_). Call sites in executeChirpSequence /
runRadarPulseSequence updated to rely on the FPGA chirp FSM (GPIOD_8
new_chirp trigger unchanged).

Tests: adds CMSIS-Core DWT/CoreDebug/SystemCoreClock stubs to
stm32_hal_mock so F-4.7's DWT-based delayUs() compiles under the host
mock build. SystemCoreClock=0 makes the busy-wait exit immediately.
This commit is contained in:
Jason
2026-04-21 01:07:34 +05:45
parent 1a7bd7e971
commit 25a280c200
5 changed files with 193 additions and 202 deletions
@@ -163,8 +163,10 @@ void ADAR1000Manager::switchToTXMode() {
DIAG("BF", "Step 3: PA bias ON"); DIAG("BF", "Step 3: PA bias ON");
setPABias(true); setPABias(true);
delayUs(50); delayUs(50);
DIAG("BF", "Step 4: ADTR1107 -> TX"); // Step 4 (former setADTR1107Control(true)) removed: TR pin is FPGA-owned.
setADTR1107Control(true); // 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.
for (uint8_t dev = 0; dev < devices_.size(); ++dev) { for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF); adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF);
@@ -185,8 +187,7 @@ void ADAR1000Manager::switchToRXMode() {
DIAG("BF", "Step 2: Disable PA supplies"); DIAG("BF", "Step 2: Disable PA supplies");
disablePASupplies(); disablePASupplies();
delayUs(10); delayUs(10);
DIAG("BF", "Step 3: ADTR1107 -> RX"); // Step 3 (former setADTR1107Control(false)) removed: FPGA owns TR pin.
setADTR1107Control(false);
DIAG("BF", "Step 4: Enable LNA supplies"); DIAG("BF", "Step 4: Enable LNA supplies");
enableLNASupplies(); enableLNASupplies();
delayUs(50); delayUs(50);
@@ -204,39 +205,11 @@ void ADAR1000Manager::switchToRXMode() {
DIAG("BF", "switchToRXMode() complete"); DIAG("BF", "switchToRXMode() complete");
} }
void ADAR1000Manager::fastTXMode() { // fastTXMode, fastRXMode, pulseTXMode, pulseRXMode: REMOVED.
DIAG("BF", "fastTXMode(): ADTR1107 -> TX (no bias sequencing)"); // The chirp hot path owns T/R switching via the FPGA adar_tr_x pins
setADTR1107Control(true); // (see 9_Firmware/9_2_FPGA/plfm_chirp_controller.v). The old SPI-RMW per
for (uint8_t dev = 0; dev < devices_.size(); ++dev) { // chirp was architecturally redundant, raced the FPGA, and toggled the
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF); // wrong bit of REG_SW_CONTROL (TR_SOURCE instead of TR_SPI).
adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF);
devices_[dev]->current_mode = BeamDirection::TX;
}
current_mode_ = BeamDirection::TX;
}
void ADAR1000Manager::fastRXMode() {
DIAG("BF", "fastRXMode(): ADTR1107 -> RX (no bias sequencing)");
setADTR1107Control(false);
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;
}
current_mode_ = BeamDirection::RX;
}
void ADAR1000Manager::pulseTXMode() {
DIAG("BF", "pulseTXMode(): TR switch only");
setADTR1107Control(true);
last_switch_time_us_ = HAL_GetTick() * 1000;
}
void ADAR1000Manager::pulseRXMode() {
DIAG("BF", "pulseRXMode(): TR switch only");
setADTR1107Control(false);
last_switch_time_us_ = HAL_GetTick() * 1000;
}
// Beam Steering // Beam Steering
bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction) { bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction) {
@@ -368,25 +341,10 @@ void ADAR1000Manager::writeRegister(uint8_t deviceIndex, uint32_t address, uint8
} }
// Configuration // Configuration
void ADAR1000Manager::setSwitchSettlingTime(uint32_t us) { // setSwitchSettlingTime, setFastSwitchMode: REMOVED.
switch_settling_time_us_ = us; // 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::setFastSwitchMode(bool enable) {
DIAG("BF", "setFastSwitchMode(%s)", enable ? "ON" : "OFF");
fast_switch_mode_ = enable;
if (enable) {
switch_settling_time_us_ = 10;
DIAG("BF", " settling time = 10 us, enabling PA+LNA supplies and bias simultaneously");
enablePASupplies();
enableLNASupplies();
setPABias(true);
setLNABias(true);
} else {
switch_settling_time_us_ = 50;
DIAG("BF", " settling time = 50 us");
}
}
void ADAR1000Manager::setBeamDwellTime(uint32_t ms) { void ADAR1000Manager::setBeamDwellTime(uint32_t ms) {
beam_dwell_time_ms_ = ms; beam_dwell_time_ms_ = ms;
@@ -428,6 +386,16 @@ bool ADAR1000Manager::initializeSingleDevice(uint8_t deviceIndex) {
DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex); DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex);
adarSetRamBypass(deviceIndex, BROADCAST_OFF); adarSetRamBypass(deviceIndex, BROADCAST_OFF);
// Hand per-chirp T/R switching to the FPGA.
// Set TR_SOURCE (REG_SW_CONTROL bit 2) = 1 so the chip's internal
// RX_EN_OVERRIDE / TX_EN_OVERRIDE follow the external TR pin (driven by
// plfm_chirp_controller's adar_tr_x output). See ADAR1000 datasheet
// "Theory of Operation" -- SPI Control vs TR Pin Control.
// Without this write, the FPGA's TR pin is ignored and the chip stays
// in RX state (TR_SPI POR default).
DIAG("BF", " dev[%u] SW_CONTROL: TR_SOURCE=1 (FPGA owns TR pin)", deviceIndex);
adarWrite(deviceIndex, REG_SW_CONTROL, (1 << 2), BROADCAST_OFF);
// Initialize ADC // Initialize ADC
DIAG("BF", " dev[%u] enable ADC (2MHz clk)", deviceIndex); DIAG("BF", " dev[%u] enable ADC (2MHz clk)", deviceIndex);
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF); adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF);
@@ -469,9 +437,11 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
HAL_GPIO_WritePin(EN_P_3V3_SW_GPIO_Port, EN_P_3V3_SW_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(EN_P_3V3_SW_GPIO_Port, EN_P_3V3_SW_Pin, GPIO_PIN_SET);
HAL_Delay(1); HAL_Delay(1);
// Step 4: Set CTRL_SW to RX mode initially via GPIO // Step 4: CTRL_SW safe-default is RX.
DIAG("BF", "Step 4: CTRL_SW -> RX (initial safe mode)"); // FPGA-owned path: with TR_SOURCE=1 (set in initializeSingleDevice) the
setADTR1107Control(false); // RX mode // 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)");
HAL_Delay(1); HAL_Delay(1);
// Step 5: Set VGG_LNA to 0 // Step 5: Set VGG_LNA to 0
@@ -573,7 +543,7 @@ bool ADAR1000Manager::setAllDevicesRXMode() {
void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) { void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
if (direction == BeamDirection::TX) { if (direction == BeamDirection::TX) {
DIAG_SECTION("ADTR1107 -> TX MODE"); DIAG_SECTION("ADTR1107 -> TX MODE");
setADTR1107Control(true); // TX mode // setADTR1107Control(true) removed: TR pin is FPGA-driven.
// Step 1: Disable LNA power first // Step 1: Disable LNA power first
DIAG("BF", " Disable LNA supplies"); DIAG("BF", " Disable LNA supplies");
@@ -603,10 +573,11 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
} }
HAL_Delay(5); HAL_Delay(5);
// Step 5: Set TR switch to TX mode // Step 5: TR switch state is FPGA-driven. TR_SOURCE=1 is set once in
DIAG("BF", " TR switch -> TX (TR_SOURCE=1, BIAS_EN)"); // initializeSingleDevice, so the chip already follows adar_tr_x.
// Only BIAS_EN needs to be asserted here.
DIAG("BF", " BIAS_EN (TR source still = FPGA adar_tr_x)");
for (uint8_t dev = 0; dev < devices_.size(); ++dev) { for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
adarSetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF); // TR_SOURCE = 1 (TX)
adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF); // BIAS_EN adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF); // BIAS_EN
} }
DIAG("BF", " ADTR1107 TX mode complete"); DIAG("BF", " ADTR1107 TX mode complete");
@@ -614,7 +585,7 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
} else { } else {
// RECEIVE MODE: Enable LNA, Disable PA // RECEIVE MODE: Enable LNA, Disable PA
DIAG_SECTION("ADTR1107 -> RX MODE"); DIAG_SECTION("ADTR1107 -> RX MODE");
setADTR1107Control(false); // RX mode // setADTR1107Control(false) removed: TR pin is FPGA-driven.
// Step 1: Disable PA power first // Step 1: Disable PA power first
DIAG("BF", " Disable PA supplies"); DIAG("BF", " Disable PA supplies");
@@ -645,34 +616,21 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
} }
HAL_Delay(5); HAL_Delay(5);
// Step 5: Set TR switch to RX mode // Step 5: TR switch state is FPGA-driven (TR_SOURCE left at 1).
DIAG("BF", " TR switch -> RX (TR_SOURCE=0, LNA_BIAS_OUT_EN)"); // Only LNA_BIAS_OUT_EN needs to be asserted here.
DIAG("BF", " LNA_BIAS_OUT_EN (TR source still = FPGA adar_tr_x)");
for (uint8_t dev = 0; dev < devices_.size(); ++dev) { for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
adarResetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF); // TR_SOURCE = 0 (RX)
adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF); // LNA_BIAS_OUT_EN adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF); // LNA_BIAS_OUT_EN
} }
DIAG("BF", " ADTR1107 RX mode complete"); DIAG("BF", " ADTR1107 RX mode complete");
} }
} }
void ADAR1000Manager::setADTR1107Control(bool tx_mode) { // setADTR1107Control, setTRSwitchPosition: REMOVED.
DIAG("BF", "setADTR1107Control(%s): setting TR switch on all %u devices, settling %lu us", // The per-device SPI RMW of REG_SW_CONTROL bit 2 (TR_SOURCE) was both wrong
tx_mode ? "TX" : "RX", (unsigned)devices_.size(), (unsigned long)switch_settling_time_us_); // (it toggled the *control source*, not the TX/RX state -- TR_SPI is bit 1)
for (uint8_t dev = 0; dev < devices_.size(); ++dev) { // and redundant with the FPGA's plfm_chirp_controller adar_tr_x output.
setTRSwitchPosition(dev, tx_mode); // TR_SOURCE is now set to 1 exactly once in initializeSingleDevice.
}
delayUs(switch_settling_time_us_);
}
void ADAR1000Manager::setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode) {
if (tx_mode) {
// TX mode: Set TR_SOURCE = 1
adarSetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
} else {
// RX mode: Set TR_SOURCE = 0
adarResetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
}
}
// Add the new public method // Add the new public method
bool ADAR1000Manager::setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction) { bool ADAR1000Manager::setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction) {
@@ -48,10 +48,11 @@ public:
// Mode Switching // Mode Switching
void switchToTXMode(); void switchToTXMode();
void switchToRXMode(); void switchToRXMode();
void fastTXMode(); // fastTXMode/fastRXMode/pulseTXMode/pulseRXMode were removed: per-chirp T/R
void fastRXMode(); // switching is owned by the FPGA (plfm_chirp_controller -> adar_tr_x pins,
void pulseTXMode(); // requires TR_SOURCE=1 in REG_SW_CONTROL, set in initializeSingleDevice).
void pulseRXMode(); // 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 // Beam Steering
bool setBeamAngle(float angle_degrees, BeamDirection direction); bool setBeamAngle(float angle_degrees, BeamDirection direction);
@@ -69,7 +70,8 @@ public:
bool setAllDevicesTXMode(); bool setAllDevicesTXMode();
bool setAllDevicesRXMode(); bool setAllDevicesRXMode();
void setADTR1107Mode(BeamDirection direction); void setADTR1107Mode(BeamDirection direction);
void setADTR1107Control(bool tx_mode); // setADTR1107Control removed -- it only wrapped the now-deleted
// setTRSwitchPosition SPI path. FPGA drives the TR pin directly.
// Monitoring and Diagnostics // Monitoring and Diagnostics
float readTemperature(uint8_t deviceIndex); float readTemperature(uint8_t deviceIndex);
@@ -78,8 +80,11 @@ public:
void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value); void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value);
// Configuration // Configuration
void setSwitchSettlingTime(uint32_t us); // setSwitchSettlingTime / setFastSwitchMode removed: their only reader was
void setFastSwitchMode(bool enable); // 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); void setBeamDwellTime(uint32_t ms);
// Getters // Getters
@@ -100,8 +105,8 @@ public:
}; };
// Configuration // Configuration
bool fast_switch_mode_ = false; // fast_switch_mode_ / switch_settling_time_us_ removed: both had no
uint32_t switch_settling_time_us_ = 50; // readers after the FPGA-owned TR refactor.
uint32_t beam_dwell_time_ms_ = 100; uint32_t beam_dwell_time_ms_ = 100;
uint32_t last_switch_time_us_ = 0; uint32_t last_switch_time_us_ = 0;
@@ -121,22 +126,22 @@ public:
// No VM_GAIN[] table exists: VM magnitude is bits [4:0] of the I/Q bytes // No VM_GAIN[] table exists: VM magnitude is bits [4:0] of the I/Q bytes
// themselves; per-channel VGA gain uses a separate register. // themselves; per-channel VGA gain uses a separate register.
static const uint8_t VM_I[128]; static const uint8_t VM_I[128];
static const uint8_t VM_Q[128]; static const uint8_t VM_Q[128];
// Named defaults for the ADTR1107 and ADAR1000 power sequence. // Named defaults for the ADTR1107 and ADAR1000 power sequence.
static constexpr uint8_t kDefaultTxVgaGain = 0x7F; static constexpr uint8_t kDefaultTxVgaGain = 0x7F;
static constexpr uint8_t kDefaultRxVgaGain = 30; static constexpr uint8_t kDefaultRxVgaGain = 30;
static constexpr uint8_t kLnaBiasOff = 0x00; static constexpr uint8_t kLnaBiasOff = 0x00;
static constexpr uint8_t kLnaBiasOperational = 0x30; static constexpr uint8_t kLnaBiasOperational = 0x30;
static constexpr uint8_t kPaBiasTxSafe = 0x5D; static constexpr uint8_t kPaBiasTxSafe = 0x5D;
static constexpr uint8_t kPaBiasIdqCalibration = 0x0D; static constexpr uint8_t kPaBiasIdqCalibration = 0x0D;
static constexpr uint8_t kPaBiasOperational = 0x7F; static constexpr uint8_t kPaBiasOperational = 0x7F;
static constexpr uint8_t kPaBiasRxSafe = 0x20; static constexpr uint8_t kPaBiasRxSafe = 0x20;
static constexpr uint8_t kTxBiasCurrent = 0x2D; static constexpr uint8_t kTxBiasCurrent = 0x2D;
static constexpr uint8_t kTxDriverBiasCurrent = 0x06; static constexpr uint8_t kTxDriverBiasCurrent = 0x06;
// Private Methods // Private Methods
bool initializeSingleDevice(uint8_t deviceIndex); bool initializeSingleDevice(uint8_t deviceIndex);
bool initializeADTR1107Sequence(); bool initializeADTR1107Sequence();
void calculatePhaseSettings(float angle_degrees, uint8_t phase_settings[4]); void calculatePhaseSettings(float angle_degrees, uint8_t phase_settings[4]);
void delayUs(uint32_t microseconds); void delayUs(uint32_t microseconds);
@@ -167,7 +172,7 @@ public:
void adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast); void adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast);
void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast); void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast);
uint8_t adarAdcRead(uint8_t deviceIndex, uint8_t broadcast); uint8_t adarAdcRead(uint8_t deviceIndex, uint8_t broadcast);
void setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode); // setTRSwitchPosition removed -- FPGA owns TR pin. See PR.
private: private:
@@ -483,11 +483,14 @@ void executeChirpSequence(int num_chirps, float T1, float PRI1, float T2, float
DIAG("SYS", "executeChirpSequence: num_chirps=%d T1=%.2f PRI1=%.2f T2=%.2f PRI2=%.2f", DIAG("SYS", "executeChirpSequence: num_chirps=%d T1=%.2f PRI1=%.2f T2=%.2f PRI2=%.2f",
num_chirps, T1, PRI1, T2, PRI2); num_chirps, T1, PRI1, T2, PRI2);
// First chirp sequence (microsecond timing) // 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++) { for(int i = 0; i < num_chirps; i++) {
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
adarManager.pulseTXMode();
delay_us((uint32_t)T1); delay_us((uint32_t)T1);
adarManager.pulseRXMode();
delay_us((uint32_t)(PRI1 - T1)); delay_us((uint32_t)(PRI1 - T1));
} }
@@ -496,11 +499,8 @@ void executeChirpSequence(int num_chirps, float T1, float PRI1, float T2, float
// Second chirp sequence (nanosecond timing) // Second chirp sequence (nanosecond timing)
for(int i = 0; i < num_chirps; i++) { for(int i = 0; i < num_chirps; i++) {
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
adarManager.pulseTXMode();
delay_ns((uint32_t)(T2 * 1000)); delay_ns((uint32_t)(T2 * 1000));
adarManager.pulseRXMode();
delay_ns((uint32_t)((PRI2 - T2) * 1000)); delay_ns((uint32_t)((PRI2 - T2) * 1000));
} }
} }
@@ -513,9 +513,9 @@ void runRadarPulseSequence() {
DIAG("SYS", "runRadarPulseSequence #%d: m_max=%d n_max=%d y_max=%d", DIAG("SYS", "runRadarPulseSequence #%d: m_max=%d n_max=%d y_max=%d",
sequence_count, m_max, n_max, y_max); sequence_count, m_max, n_max, y_max);
// Configure for fast switching // Fast per-chirp switching is now FPGA-owned (plfm_chirp_controller
DIAG("BF", "Enabling fast-switch mode for beam sweep"); // adar_tr_x), not MCU-driven. setFastSwitchMode(true) call removed.
adarManager.setFastSwitchMode(true); DIAG("BF", "Beam sweep start (FPGA owns per-chirp T/R switching)");
int m = 1; // Chirp counter int m = 1; // Chirp counter
int n = 1; // Beam Elevation position counter int n = 1; // Beam Elevation position counter
@@ -656,18 +656,18 @@ SystemError_t checkSystemHealth(void) {
// 1. Check AD9523 Clock Generator // 1. Check AD9523 Clock Generator
static uint32_t last_clock_check = 0; static uint32_t last_clock_check = 0;
if (HAL_GetTick() - last_clock_check > 5000) { if (HAL_GetTick() - last_clock_check > 5000) {
GPIO_PinState s0 = HAL_GPIO_ReadPin(AD9523_STATUS0_GPIO_Port, AD9523_STATUS0_Pin); GPIO_PinState s0 = HAL_GPIO_ReadPin(AD9523_STATUS0_GPIO_Port, AD9523_STATUS0_Pin);
GPIO_PinState s1 = HAL_GPIO_ReadPin(AD9523_STATUS1_GPIO_Port, AD9523_STATUS1_Pin); GPIO_PinState s1 = HAL_GPIO_ReadPin(AD9523_STATUS1_GPIO_Port, AD9523_STATUS1_Pin);
DIAG_GPIO("CLK", "AD9523 STATUS0", s0); DIAG_GPIO("CLK", "AD9523 STATUS0", s0);
DIAG_GPIO("CLK", "AD9523 STATUS1", s1); DIAG_GPIO("CLK", "AD9523 STATUS1", s1);
if (s0 == GPIO_PIN_RESET || s1 == GPIO_PIN_RESET) { if (s0 == GPIO_PIN_RESET || s1 == GPIO_PIN_RESET) {
current_error = ERROR_AD9523_CLOCK; current_error = ERROR_AD9523_CLOCK;
DIAG_ERR("CLK", "AD9523 clock health check FAILED (STATUS0=%d STATUS1=%d)", s0, s1); DIAG_ERR("CLK", "AD9523 clock health check FAILED (STATUS0=%d STATUS1=%d)", s0, s1);
return current_error; return current_error;
} }
last_clock_check = HAL_GetTick(); last_clock_check = HAL_GetTick();
} }
// 2. Check ADF4382 Lock Status // 2. Check ADF4382 Lock Status
bool tx_locked, rx_locked; bool tx_locked, rx_locked;
@@ -702,34 +702,34 @@ SystemError_t checkSystemHealth(void) {
// 4. Check IMU Communication // 4. Check IMU Communication
static uint32_t last_imu_check = 0; static uint32_t last_imu_check = 0;
if (HAL_GetTick() - last_imu_check > 10000) { if (HAL_GetTick() - last_imu_check > 10000) {
if (!GY85_Update(&imu)) { if (!GY85_Update(&imu)) {
current_error = ERROR_IMU_COMM; current_error = ERROR_IMU_COMM;
DIAG_ERR("IMU", "Health check: GY85_Update() FAILED"); DIAG_ERR("IMU", "Health check: GY85_Update() FAILED");
return current_error; return current_error;
} }
last_imu_check = HAL_GetTick(); last_imu_check = HAL_GetTick();
} }
// 5. Check BMP180 Communication // 5. Check BMP180 Communication
static uint32_t last_bmp_check = 0; static uint32_t last_bmp_check = 0;
if (HAL_GetTick() - last_bmp_check > 15000) { if (HAL_GetTick() - last_bmp_check > 15000) {
double pressure = myBMP.getPressure(); double pressure = myBMP.getPressure();
if (pressure < 30000.0 || pressure > 110000.0 || isnan(pressure)) { if (pressure < 30000.0 || pressure > 110000.0 || isnan(pressure)) {
current_error = ERROR_BMP180_COMM; current_error = ERROR_BMP180_COMM;
DIAG_ERR("SYS", "Health check: BMP180 pressure out of range: %.0f", pressure); DIAG_ERR("SYS", "Health check: BMP180 pressure out of range: %.0f", pressure);
return current_error; return current_error;
} }
last_bmp_check = HAL_GetTick(); last_bmp_check = HAL_GetTick();
} }
// 6. Check GPS Communication (30s grace period from boot / last valid fix) // 6. Check GPS Communication (30s grace period from boot / last valid fix)
uint32_t gps_fix_age = um982_position_age(&um982); uint32_t gps_fix_age = um982_position_age(&um982);
if (gps_fix_age > 30000) { if (gps_fix_age > 30000) {
current_error = ERROR_GPS_COMM; current_error = ERROR_GPS_COMM;
DIAG_WARN("SYS", "Health check: GPS no fix for >30s (age=%lu ms)", (unsigned long)gps_fix_age); DIAG_WARN("SYS", "Health check: GPS no fix for >30s (age=%lu ms)", (unsigned long)gps_fix_age);
return current_error; return current_error;
} }
// 7. Check RF Power Amplifier Current // 7. Check RF Power Amplifier Current
if (PowerAmplifier) { if (PowerAmplifier) {
@@ -760,7 +760,7 @@ SystemError_t checkSystemHealth(void) {
DIAG_ERR("SYS", "checkSystemHealth returning error code %d", current_error); DIAG_ERR("SYS", "checkSystemHealth returning error code %d", current_error);
} }
return current_error; return current_error;
} }
// Error recovery function // Error recovery function
void attemptErrorRecovery(SystemError_t error) { void attemptErrorRecovery(SystemError_t error) {
@@ -905,22 +905,22 @@ void handleSystemError(SystemError_t error) {
HAL_Delay(200); HAL_Delay(200);
} }
// Critical errors trigger emergency shutdown. // Critical errors trigger emergency shutdown.
// //
// Safety-critical range: any fault that can damage the PAs or leave the // Safety-critical range: any fault that can damage the PAs or leave the
// system in an undefined state must cut the RF rails via Emergency_Stop(). // system in an undefined state must cut the RF rails via Emergency_Stop().
// This covers: // This covers:
// ERROR_RF_PA_OVERCURRENT .. ERROR_POWER_SUPPLY (9..13) -- PA/supply faults // ERROR_RF_PA_OVERCURRENT .. ERROR_POWER_SUPPLY (9..13) -- PA/supply faults
// ERROR_TEMPERATURE_HIGH (14) -- >75 C on the PA thermal sensors; // ERROR_TEMPERATURE_HIGH (14) -- >75 C on the PA thermal sensors;
// without cutting bias + 5V/5V5/RFPA rails // without cutting bias + 5V/5V5/RFPA rails
// the GaN QPA2962 stage can thermal-runaway. // the GaN QPA2962 stage can thermal-runaway.
// ERROR_WATCHDOG_TIMEOUT (16) -- health-check loop has stalled (>60 s); // ERROR_WATCHDOG_TIMEOUT (16) -- health-check loop has stalled (>60 s);
// transmitter state is unknown, safest to // transmitter state is unknown, safest to
// latch Emergency_Stop rather than rely on // latch Emergency_Stop rather than rely on
// IWDG reset (which re-energises the rails). // IWDG reset (which re-energises the rails).
if ((error >= ERROR_RF_PA_OVERCURRENT && error <= ERROR_POWER_SUPPLY) || if ((error >= ERROR_RF_PA_OVERCURRENT && error <= ERROR_POWER_SUPPLY) ||
error == ERROR_TEMPERATURE_HIGH || error == ERROR_TEMPERATURE_HIGH ||
error == ERROR_WATCHDOG_TIMEOUT) { error == ERROR_WATCHDOG_TIMEOUT) {
DIAG_ERR("SYS", "CRITICAL ERROR (code %d: %s) -- initiating Emergency_Stop()", error, err_name); DIAG_ERR("SYS", "CRITICAL ERROR (code %d: %s) -- initiating Emergency_Stop()", error, err_name);
snprintf(error_msg, sizeof(error_msg), snprintf(error_msg, sizeof(error_msg),
"CRITICAL ERROR! Initiating emergency shutdown.\r\n"); "CRITICAL ERROR! Initiating emergency shutdown.\r\n");
@@ -1483,8 +1483,8 @@ int main(void)
HAL_GPIO_WritePin(EN_P_3V3_FPGA_GPIO_Port,EN_P_3V3_FPGA_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(EN_P_3V3_FPGA_GPIO_Port,EN_P_3V3_FPGA_Pin,GPIO_PIN_SET);
HAL_Delay(100); HAL_Delay(100);
DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V"); DIAG("PWR", "FPGA power sequencing complete -- 1.0V -> 1.8V -> 3.3V");
// Initialize module IMU // Initialize module IMU
DIAG_SECTION("IMU INIT (GY-85)"); DIAG_SECTION("IMU INIT (GY-85)");
DIAG("IMU", "Initializing GY-85 IMU..."); DIAG("IMU", "Initializing GY-85 IMU...");
@@ -1493,12 +1493,12 @@ int main(void)
Error_Handler(); Error_Handler();
} }
DIAG("IMU", "GY-85 initialized OK, running 10 calibration samples"); DIAG("IMU", "GY-85 initialized OK, running 10 calibration samples");
for(int i=0; i<10;i++){ for(int i=0; i<10;i++){
if (!GY85_Update(&imu)) { if (!GY85_Update(&imu)) {
Error_Handler(); Error_Handler();
} }
ax = imu.ax; ax = imu.ax;
ay = imu.ay; ay = imu.ay;
az = imu.az; az = imu.az;
gx = -imu.gx; gx = -imu.gx;
@@ -1793,20 +1793,20 @@ int main(void)
HAL_Delay(10); HAL_Delay(10);
} }
} }
RADAR_Longitude = um982_get_longitude(&um982); RADAR_Longitude = um982_get_longitude(&um982);
RADAR_Latitude = um982_get_latitude(&um982); RADAR_Latitude = um982_get_latitude(&um982);
DIAG("GPS", "Initial position: lat=%.6f lon=%.6f fix=%d sats=%d", DIAG("GPS", "Initial position: lat=%.6f lon=%.6f fix=%d sats=%d",
RADAR_Latitude, RADAR_Longitude, RADAR_Latitude, RADAR_Longitude,
um982_get_fix_quality(&um982), um982_get_num_sats(&um982)); um982_get_fix_quality(&um982), um982_get_num_sats(&um982));
// Re-apply heading after GPS init so the north-alignment stepper move uses // Re-apply heading after GPS init so the north-alignment stepper move uses
// UM982 dual-antenna heading when available. // UM982 dual-antenna heading when available.
if (um982_is_heading_valid(&um982)) { if (um982_is_heading_valid(&um982)) {
Yaw_Sensor = um982_get_heading(&um982); Yaw_Sensor = um982_get_heading(&um982);
} }
//move Stepper to position 1 = 0° //move Stepper to position 1 = 0°
HAL_GPIO_WritePin(STEPPER_CW_P_GPIO_Port, STEPPER_CW_P_Pin, GPIO_PIN_RESET);//Set stepper motor spinning direction to CCW HAL_GPIO_WritePin(STEPPER_CW_P_GPIO_Port, STEPPER_CW_P_Pin, GPIO_PIN_RESET);//Set stepper motor spinning direction to CCW
//Point Stepper to North //Point Stepper to North
for(int i= 0;i<(int)(Yaw_Sensor*Stepper_steps/360);i++){ for(int i= 0;i<(int)(Yaw_Sensor*Stepper_steps/360);i++){
HAL_GPIO_WritePin(STEPPER_CLK_P_GPIO_Port, STEPPER_CLK_P_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(STEPPER_CLK_P_GPIO_Port, STEPPER_CLK_P_Pin, GPIO_PIN_SET);
@@ -1819,14 +1819,14 @@ int main(void)
/**********wait for GUI start flag and Send Lat/Long/alt********/ /**********wait for GUI start flag and Send Lat/Long/alt********/
/***************************************************************/ /***************************************************************/
GPS_Data_t gps_data; GPS_Data_t gps_data;
// Binary packet structure: // Binary packet structure:
// [Header 4 bytes][Latitude 8 bytes][Longitude 8 bytes][Altitude 4 bytes][Pitch 4 bytes][CRC 2 bytes] // [Header 4 bytes][Latitude 8 bytes][Longitude 8 bytes][Altitude 4 bytes][Pitch 4 bytes][CRC 2 bytes]
gps_data = {RADAR_Latitude, RADAR_Longitude, RADAR_Altitude, Pitch_Sensor, HAL_GetTick()}; gps_data = {RADAR_Latitude, RADAR_Longitude, RADAR_Altitude, Pitch_Sensor, HAL_GetTick()};
if (!GPS_SendBinaryToGUI(&gps_data)) { if (!GPS_SendBinaryToGUI(&gps_data)) {
const uint8_t gps_send_error[] = "GPS binary send failed\r\n"; const uint8_t gps_send_error[] = "GPS binary send failed\r\n";
HAL_UART_Transmit(&huart3, (uint8_t*)gps_send_error, sizeof(gps_send_error) - 1, 1000); HAL_UART_Transmit(&huart3, (uint8_t*)gps_send_error, sizeof(gps_send_error) - 1, 1000);
} }
/* [STM32-006 FIXED] Removed blocking do-while loop that waited for /* [STM32-006 FIXED] Removed blocking do-while loop that waited for
* usbHandler.isStartFlagReceived(). The production V7 PyQt GUI does not * usbHandler.isStartFlagReceived(). The production V7 PyQt GUI does not
@@ -406,3 +406,11 @@ static int mock_spi_init_stub(void) { return 0; }
const struct no_os_spi_platform_ops stm32_spi_ops = { const struct no_os_spi_platform_ops stm32_spi_ops = {
.init = mock_spi_init_stub, .init = mock_spi_init_stub,
}; };
/* ========================= CMSIS-Core stub storage ======================= */
/* See stm32_hal_mock.h for rationale. SystemCoreClock = 0 forces delayUs() to
* return immediately under host test builds. DWT->CTRL pre-enabled so the
* one-time-init branch is skipped deterministically. */
struct _DWT_Mock_Type _dwt_mock = { .CTRL = DWT_CTRL_CYCCNTENA_Msk, .CYCCNT = 0 };
struct _CoreDebug_Mock_Type _coredebug_mock = { .DEMCR = 0 };
uint32_t SystemCoreClock = 0U;
@@ -242,6 +242,26 @@ uint8_t ADS7830_Measure_SingleEnded(ADC_HandleTypeDef *hadc, uint8_t channel);
* if desired via a global flag. */ * if desired via a global flag. */
extern int mock_printf_enabled; extern int mock_printf_enabled;
/* ========================= CMSIS-Core stubs ======================= */
/* Minimum surface to let F-4.7's DWT-based delayUs() in ADAR1000_Manager.cpp
* compile under the host mock build. SystemCoreClock is intentionally 0 so
* target = microseconds * (SystemCoreClock / 1000000) is also 0, making the
* busy-wait loop exit immediately regardless of argument. Pre-setting
* DWT->CTRL with CYCCNTENA also skips the one-time init branch. */
#define DWT_CTRL_CYCCNTENA_Msk (1UL << 0)
#define CoreDebug_DEMCR_TRCENA_Msk (1UL << 24)
struct _DWT_Mock_Type { uint32_t CTRL; uint32_t CYCCNT; };
struct _CoreDebug_Mock_Type { uint32_t DEMCR; };
extern struct _DWT_Mock_Type _dwt_mock;
extern struct _CoreDebug_Mock_Type _coredebug_mock;
extern uint32_t SystemCoreClock;
#define DWT (&_dwt_mock)
#define CoreDebug (&_coredebug_mock)
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif