mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-11 07:51:17 +00:00
fix: propagate SPI/ADC communication failures in ADAR1000_Manager
This commit is contained in:
@@ -124,7 +124,10 @@ bool ADAR1000Manager::powerUpSystem() {
|
|||||||
|
|
||||||
// Start in RX mode
|
// Start in RX mode
|
||||||
DIAG("BF", "Setting initial mode to RX");
|
DIAG("BF", "Setting initial mode to RX");
|
||||||
switchToRXMode();
|
if (!switchToRXMode()) {
|
||||||
|
DIAG_ERR("BF", "Initial switchToRXMode() FAILED -- leaving in last-known mode");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
DIAG_ELAPSED("BF", "powerUpSystem() total", t0);
|
DIAG_ELAPSED("BF", "powerUpSystem() total", t0);
|
||||||
const uint8_t success[] = "System Power-Up Sequence Completed Successfully.\r\n";
|
const uint8_t success[] = "System Power-Up Sequence Completed Successfully.\r\n";
|
||||||
@@ -135,7 +138,11 @@ bool ADAR1000Manager::powerUpSystem() {
|
|||||||
bool ADAR1000Manager::powerDownSystem() {
|
bool ADAR1000Manager::powerDownSystem() {
|
||||||
DIAG_SECTION("BF POWER-DOWN SEQUENCE");
|
DIAG_SECTION("BF POWER-DOWN SEQUENCE");
|
||||||
DIAG("BF", "Switching to RX mode before power-down");
|
DIAG("BF", "Switching to RX mode before power-down");
|
||||||
switchToRXMode();
|
// Note: even if RX-mode switch fails partially, we still cut the rails
|
||||||
|
// below. Power-down must always proceed -- a stuck PA bias would be
|
||||||
|
// worse than losing the RX-mode telemetry. We capture the status to
|
||||||
|
// return at the end.
|
||||||
|
bool rx_ok = switchToRXMode();
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
DIAG("BF", "Disabling PA supplies");
|
DIAG("BF", "Disabling PA supplies");
|
||||||
@@ -147,95 +154,119 @@ bool ADAR1000Manager::powerDownSystem() {
|
|||||||
DIAG("BF", "Disabling VDD_SW rail");
|
DIAG("BF", "Disabling VDD_SW rail");
|
||||||
HAL_GPIO_WritePin(EN_P_3V3_VDD_SW_GPIO_Port, EN_P_3V3_VDD_SW_Pin, GPIO_PIN_RESET);
|
HAL_GPIO_WritePin(EN_P_3V3_VDD_SW_GPIO_Port, EN_P_3V3_VDD_SW_Pin, GPIO_PIN_RESET);
|
||||||
|
|
||||||
DIAG("BF", "powerDownSystem() complete");
|
DIAG("BF", "powerDownSystem() %s", rx_ok ? "complete" : "complete (RX-mode setup had failures, rails cut anyway)");
|
||||||
return true;
|
return rx_ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode Switching
|
// Mode Switching
|
||||||
void ADAR1000Manager::switchToTXMode() {
|
bool ADAR1000Manager::switchToTXMode() {
|
||||||
DIAG_SECTION("BF SWITCH TO TX MODE");
|
DIAG_SECTION("BF SWITCH TO TX MODE");
|
||||||
|
bool ok = true;
|
||||||
DIAG("BF", "Step 1: LNA bias OFF");
|
DIAG("BF", "Step 1: LNA bias OFF");
|
||||||
setLNABias(false);
|
ok = setLNABias(false) && ok;
|
||||||
delayUs(10);
|
delayUs(10);
|
||||||
DIAG("BF", "Step 2: Enable PA supplies");
|
DIAG("BF", "Step 2: Enable PA supplies");
|
||||||
enablePASupplies();
|
enablePASupplies();
|
||||||
delayUs(100);
|
delayUs(100);
|
||||||
DIAG("BF", "Step 3: PA bias ON");
|
DIAG("BF", "Step 3: PA bias ON");
|
||||||
setPABias(true);
|
ok = setPABias(true) && ok;
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
DIAG("BF", "Step 4: ADTR1107 -> TX");
|
DIAG("BF", "Step 4: ADTR1107 -> TX");
|
||||||
setADTR1107Control(true);
|
ok = setADTR1107Control(true) && ok;
|
||||||
|
|
||||||
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);
|
bool dev_ok = true;
|
||||||
adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF);
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
adarSetTxBias(dev, BROADCAST_OFF);
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
devices_[dev]->current_mode = BeamDirection::TX;
|
dev_ok = adarSetTxBias(dev, BROADCAST_OFF) && dev_ok;
|
||||||
DIAG("BF", " dev[%u] TX enables=0x0F, TX bias set", dev);
|
if (dev_ok) {
|
||||||
|
devices_[dev]->current_mode = BeamDirection::TX;
|
||||||
|
DIAG("BF", " dev[%u] TX enables=0x0F, TX bias set", dev);
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", " dev[%u] TX setup FAILED -- per-device current_mode unchanged", dev);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
current_mode_ = BeamDirection::TX;
|
if (ok) current_mode_ = BeamDirection::TX;
|
||||||
DIAG("BF", "switchToTXMode() complete");
|
DIAG("BF", "switchToTXMode() %s", ok ? "complete" : "completed WITH FAILURES (mode unchanged)");
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::switchToRXMode() {
|
bool ADAR1000Manager::switchToRXMode() {
|
||||||
DIAG_SECTION("BF SWITCH TO RX MODE");
|
DIAG_SECTION("BF SWITCH TO RX MODE");
|
||||||
|
bool ok = true;
|
||||||
DIAG("BF", "Step 1: PA bias OFF");
|
DIAG("BF", "Step 1: PA bias OFF");
|
||||||
setPABias(false);
|
ok = setPABias(false) && ok;
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
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");
|
DIAG("BF", "Step 3: ADTR1107 -> RX");
|
||||||
setADTR1107Control(false);
|
ok = setADTR1107Control(false) && ok;
|
||||||
DIAG("BF", "Step 4: Enable LNA supplies");
|
DIAG("BF", "Step 4: Enable LNA supplies");
|
||||||
enableLNASupplies();
|
enableLNASupplies();
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
DIAG("BF", "Step 5: LNA bias ON");
|
DIAG("BF", "Step 5: LNA bias ON");
|
||||||
setLNABias(true);
|
ok = setLNABias(true) && ok;
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF);
|
bool dev_ok = true;
|
||||||
adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF);
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
devices_[dev]->current_mode = BeamDirection::RX;
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
DIAG("BF", " dev[%u] RX enables=0x0F", dev);
|
if (dev_ok) {
|
||||||
|
devices_[dev]->current_mode = BeamDirection::RX;
|
||||||
|
DIAG("BF", " dev[%u] RX enables=0x0F", dev);
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", " dev[%u] RX setup FAILED -- per-device current_mode unchanged", dev);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
current_mode_ = BeamDirection::RX;
|
if (ok) current_mode_ = BeamDirection::RX;
|
||||||
DIAG("BF", "switchToRXMode() complete");
|
DIAG("BF", "switchToRXMode() %s", ok ? "complete" : "completed WITH FAILURES (mode unchanged)");
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::fastTXMode() {
|
bool ADAR1000Manager::fastTXMode() {
|
||||||
DIAG("BF", "fastTXMode(): ADTR1107 -> TX (no bias sequencing)");
|
DIAG("BF", "fastTXMode(): ADTR1107 -> TX (no bias sequencing)");
|
||||||
setADTR1107Control(true);
|
bool ok = setADTR1107Control(true);
|
||||||
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);
|
bool dev_ok = true;
|
||||||
adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF);
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
devices_[dev]->current_mode = BeamDirection::TX;
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
|
if (dev_ok) devices_[dev]->current_mode = BeamDirection::TX;
|
||||||
|
ok = dev_ok && ok;
|
||||||
}
|
}
|
||||||
current_mode_ = BeamDirection::TX;
|
if (ok) current_mode_ = BeamDirection::TX;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::fastRXMode() {
|
bool ADAR1000Manager::fastRXMode() {
|
||||||
DIAG("BF", "fastRXMode(): ADTR1107 -> RX (no bias sequencing)");
|
DIAG("BF", "fastRXMode(): ADTR1107 -> RX (no bias sequencing)");
|
||||||
setADTR1107Control(false);
|
bool ok = setADTR1107Control(false);
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF);
|
bool dev_ok = true;
|
||||||
adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF);
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
devices_[dev]->current_mode = BeamDirection::RX;
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
|
if (dev_ok) devices_[dev]->current_mode = BeamDirection::RX;
|
||||||
|
ok = dev_ok && ok;
|
||||||
}
|
}
|
||||||
current_mode_ = BeamDirection::RX;
|
if (ok) current_mode_ = BeamDirection::RX;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::pulseTXMode() {
|
bool ADAR1000Manager::pulseTXMode() {
|
||||||
DIAG("BF", "pulseTXMode(): TR switch only");
|
DIAG("BF", "pulseTXMode(): TR switch only");
|
||||||
setADTR1107Control(true);
|
bool ok = setADTR1107Control(true);
|
||||||
last_switch_time_us_ = HAL_GetTick() * 1000;
|
last_switch_time_us_ = HAL_GetTick() * 1000;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::pulseRXMode() {
|
bool ADAR1000Manager::pulseRXMode() {
|
||||||
DIAG("BF", "pulseRXMode(): TR switch only");
|
DIAG("BF", "pulseRXMode(): TR switch only");
|
||||||
setADTR1107Control(false);
|
bool ok = setADTR1107Control(false);
|
||||||
last_switch_time_us_ = HAL_GetTick() * 1000;
|
last_switch_time_us_ = HAL_GetTick() * 1000;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beam Steering
|
// Beam Steering
|
||||||
@@ -247,39 +278,36 @@ bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction)
|
|||||||
DIAG("BF", " phase[0..3] = %u, %u, %u, %u",
|
DIAG("BF", " phase[0..3] = %u, %u, %u, %u",
|
||||||
phase_settings[0], phase_settings[1], phase_settings[2], phase_settings[3]);
|
phase_settings[0], phase_settings[1], phase_settings[2], phase_settings[3]);
|
||||||
|
|
||||||
if (direction == BeamDirection::TX) {
|
bool ok = (direction == BeamDirection::TX) ? setAllDevicesTXMode() : setAllDevicesRXMode();
|
||||||
setAllDevicesTXMode();
|
|
||||||
} else {
|
|
||||||
setAllDevicesRXMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
for (uint8_t ch = 0; ch < 4; ++ch) {
|
for (uint8_t ch = 0; ch < 4; ++ch) {
|
||||||
if (direction == BeamDirection::TX) {
|
if (direction == BeamDirection::TX) {
|
||||||
adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
|
ok = adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF) && ok;
|
||||||
adarSetTxVgaGain(dev, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF);
|
ok = adarSetTxVgaGain(dev, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF) && ok;
|
||||||
} else {
|
} else {
|
||||||
adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
|
ok = adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF) && ok;
|
||||||
adarSetRxVgaGain(dev, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF);
|
ok = adarSetRxVgaGain(dev, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ADAR1000Manager::setCustomBeamPattern(const uint8_t phase_settings[4], const uint8_t gain_settings[4], BeamDirection direction) {
|
bool ADAR1000Manager::setCustomBeamPattern(const uint8_t phase_settings[4], const uint8_t gain_settings[4], BeamDirection direction) {
|
||||||
|
bool ok = true;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
for (uint8_t ch = 0; ch < 4; ++ch) {
|
for (uint8_t ch = 0; ch < 4; ++ch) {
|
||||||
if (direction == BeamDirection::TX) {
|
if (direction == BeamDirection::TX) {
|
||||||
adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
|
ok = adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF) && ok;
|
||||||
adarSetTxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF);
|
ok = adarSetTxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF) && ok;
|
||||||
} else {
|
} else {
|
||||||
adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
|
ok = adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF) && ok;
|
||||||
adarSetRxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF);
|
ok = adarSetRxVgaGain(dev, ch + 1, gain_settings[ch], BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beam Sweeping
|
// Beam Sweeping
|
||||||
@@ -334,7 +362,17 @@ float ADAR1000Manager::readTemperature(uint8_t deviceIndex) {
|
|||||||
return -273.15f;
|
return -273.15f;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t temp_raw = adarAdcRead(deviceIndex, BROADCAST_OFF);
|
// Snapshot the timeout counter so we can detect a timeout that occurred
|
||||||
|
// anywhere inside adarAdcRead (start-conv write, polling, output read).
|
||||||
|
// This keeps the float signature while still giving callers an explicit
|
||||||
|
// "this reading is invalid" channel via std::isnan().
|
||||||
|
uint32_t timeouts_before = comm_stats_.adc_timeouts;
|
||||||
|
uint8_t temp_raw = adarAdcRead(deviceIndex, BROADCAST_OFF);
|
||||||
|
if (comm_stats_.adc_timeouts > timeouts_before) {
|
||||||
|
DIAG_WARN("BF", "readTemperature(dev[%u]): ADC timeout/comm-fail -- returning NaN", deviceIndex);
|
||||||
|
return std::nanf("");
|
||||||
|
}
|
||||||
|
|
||||||
float temp_c = (temp_raw * 0.5f) - 50.0f;
|
float temp_c = (temp_raw * 0.5f) - 50.0f;
|
||||||
DIAG("BF", "readTemperature(dev[%u]): raw=0x%02X => %.1f C", deviceIndex, temp_raw, (double)temp_c);
|
DIAG("BF", "readTemperature(dev[%u]): raw=0x%02X => %.1f C", deviceIndex, temp_raw, (double)temp_c);
|
||||||
return temp_c;
|
return temp_c;
|
||||||
@@ -346,10 +384,21 @@ bool ADAR1000Manager::verifyDeviceCommunication(uint8_t deviceIndex) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t test_value = 0xA5;
|
// Distinguish three failure modes that previously all looked the same:
|
||||||
adarWrite(deviceIndex, REG_SCRATCHPAD, test_value, BROADCAST_OFF);
|
// 1. scratchpad write failed at the SPI layer
|
||||||
|
// 2. scratchpad read failed at the SPI layer
|
||||||
|
// 3. value round-tripped but didn't match (real chip mismatch)
|
||||||
|
constexpr uint8_t test_value = 0xA5;
|
||||||
|
if (!adarWrite(deviceIndex, REG_SCRATCHPAD, test_value, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", "verifyDeviceComm(dev[%u]): scratchpad WRITE failed", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
HAL_Delay(1);
|
HAL_Delay(1);
|
||||||
uint8_t readback = adarRead(deviceIndex, REG_SCRATCHPAD);
|
uint8_t readback = 0;
|
||||||
|
if (!adarReadChecked(deviceIndex, REG_SCRATCHPAD, &readback)) {
|
||||||
|
DIAG_ERR("BF", "verifyDeviceComm(dev[%u]): scratchpad READ failed", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool pass = (readback == test_value);
|
bool pass = (readback == test_value);
|
||||||
if (pass) {
|
if (pass) {
|
||||||
DIAG("BF", "verifyDeviceComm(dev[%u]): scratchpad 0xA5 -> 0x%02X OK", deviceIndex, readback);
|
DIAG("BF", "verifyDeviceComm(dev[%u]): scratchpad 0xA5 -> 0x%02X OK", deviceIndex, readback);
|
||||||
@@ -363,8 +412,8 @@ uint8_t ADAR1000Manager::readRegister(uint8_t deviceIndex, uint32_t address) {
|
|||||||
return adarRead(deviceIndex, address);
|
return adarRead(deviceIndex, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value) {
|
bool ADAR1000Manager::writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value) {
|
||||||
adarWrite(deviceIndex, address, value, BROADCAST_OFF);
|
return adarWrite(deviceIndex, address, value, BROADCAST_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
@@ -412,7 +461,10 @@ bool ADAR1000Manager::initializeAllDevices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DIAG("BF", "All 4 ADAR1000 devices initialized, setting TX mode");
|
DIAG("BF", "All 4 ADAR1000 devices initialized, setting TX mode");
|
||||||
setAllDevicesTXMode();
|
if (!setAllDevicesTXMode()) {
|
||||||
|
DIAG_ERR("BF", "initializeAllDevices: setAllDevicesTXMode() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,23 +472,38 @@ bool ADAR1000Manager::initializeSingleDevice(uint8_t deviceIndex) {
|
|||||||
if (deviceIndex >= devices_.size()) return false;
|
if (deviceIndex >= devices_.size()) return false;
|
||||||
|
|
||||||
DIAG("BF", " dev[%u] soft reset", deviceIndex);
|
DIAG("BF", " dev[%u] soft reset", deviceIndex);
|
||||||
adarSoftReset(deviceIndex);
|
if (!adarSoftReset(deviceIndex)) {
|
||||||
|
DIAG_ERR("BF", " dev[%u] soft reset FAILED -- aborting init", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
DIAG("BF", " dev[%u] write ConfigA (SDO_ACTIVE)", deviceIndex);
|
DIAG("BF", " dev[%u] write ConfigA (SDO_ACTIVE)", deviceIndex);
|
||||||
adarWriteConfigA(deviceIndex, INTERFACE_CONFIG_A_SDO_ACTIVE, BROADCAST_OFF);
|
if (!adarWriteConfigA(deviceIndex, INTERFACE_CONFIG_A_SDO_ACTIVE, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", " dev[%u] ConfigA write FAILED -- aborting init", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex);
|
DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex);
|
||||||
adarSetRamBypass(deviceIndex, BROADCAST_OFF);
|
if (!adarSetRamBypass(deviceIndex, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", " dev[%u] RAM bypass write FAILED -- aborting init", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 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);
|
if (!adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", " dev[%u] ADC enable write FAILED -- aborting init", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify communication with scratchpad test
|
// Verify communication with scratchpad test. Previous behavior was to log
|
||||||
|
// a warning and mark the device initialized anyway -- that hid completely
|
||||||
|
// dead chips behind a green init. Now this is a hard failure.
|
||||||
DIAG("BF", " dev[%u] verifying SPI communication...", deviceIndex);
|
DIAG("BF", " dev[%u] verifying SPI communication...", deviceIndex);
|
||||||
bool comms_ok = verifyDeviceCommunication(deviceIndex);
|
if (!verifyDeviceCommunication(deviceIndex)) {
|
||||||
if (!comms_ok) {
|
DIAG_ERR("BF", " dev[%u] scratchpad verify FAILED -- NOT marking initialized", deviceIndex);
|
||||||
DIAG_WARN("BF", " dev[%u] scratchpad verify FAILED but marking initialized anyway", deviceIndex);
|
devices_[deviceIndex]->initialized = false;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
devices_[deviceIndex]->initialized = true;
|
devices_[deviceIndex]->initialized = true;
|
||||||
@@ -466,15 +533,21 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
|
|||||||
|
|
||||||
// Step 4: Set CTRL_SW to RX mode initially via GPIO
|
// Step 4: Set CTRL_SW to RX mode initially via GPIO
|
||||||
DIAG("BF", "Step 4: CTRL_SW -> RX (initial safe mode)");
|
DIAG("BF", "Step 4: CTRL_SW -> RX (initial safe mode)");
|
||||||
setADTR1107Control(false); // RX mode
|
if (!setADTR1107Control(false)) {
|
||||||
|
DIAG_ERR("BF", "ADTR1107 step 4 FAILED -- aborting power sequence");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
HAL_Delay(1);
|
HAL_Delay(1);
|
||||||
|
|
||||||
// Step 5: Set VGG_LNA to 0
|
// Step 5: Set VGG_LNA to 0
|
||||||
DIAG("BF", "Step 5: VGG_LNA bias -> OFF (0x%02X)", kLnaBiasOff);
|
DIAG("BF", "Step 5: VGG_LNA bias -> OFF (0x%02X)", kLnaBiasOff);
|
||||||
uint8_t lna_bias_voltage = kLnaBiasOff;
|
uint8_t lna_bias_voltage = kLnaBiasOff;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_LNA_BIAS_ON, lna_bias_voltage, BROADCAST_OFF);
|
if (!adarWrite(dev, REG_LNA_BIAS_ON, lna_bias_voltage, BROADCAST_OFF) ||
|
||||||
adarWrite(dev, REG_LNA_BIAS_OFF, kLnaBiasOff, BROADCAST_OFF);
|
!adarWrite(dev, REG_LNA_BIAS_OFF, kLnaBiasOff, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", "ADTR1107 step 5 dev[%u] LNA bias write FAILED", dev);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Set VDD_LNA to 0V for TX mode
|
// Step 6: Set VDD_LNA to 0V for TX mode
|
||||||
@@ -489,10 +562,14 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
|
|||||||
DIAG("BF", "Step 7: VGG_PA -> safe bias 0x%02X (~ -1.75V, PA off)", kPaBiasTxSafe);
|
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
|
uint8_t safe_pa_bias = kPaBiasTxSafe; // Safe negative voltage (-1.75V) to keep PA off
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_PA_CH1_BIAS_ON, safe_pa_bias, BROADCAST_OFF);
|
if (!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_CH2_BIAS_ON, safe_pa_bias, BROADCAST_OFF) ||
|
||||||
adarWrite(dev, REG_PA_CH3_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);
|
!adarWrite(dev, REG_PA_CH4_BIAS_ON, safe_pa_bias, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", "ADTR1107 step 7 dev[%u] safe PA bias write FAILED -- aborting before enabling supplies",
|
||||||
|
dev);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
@@ -509,10 +586,13 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
|
|||||||
DIAG("BF", "Step 9: VGG_PA -> Idq cal bias 0x%02X (~ -0.24V, target 220mA)", kPaBiasIdqCalibration);
|
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
|
uint8_t Idq_pa_bias = kPaBiasIdqCalibration; // Safe negative voltage (-0.2447V) to keep PA off
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_PA_CH1_BIAS_ON, Idq_pa_bias, BROADCAST_OFF);
|
if (!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_CH2_BIAS_ON, Idq_pa_bias, BROADCAST_OFF) ||
|
||||||
adarWrite(dev, REG_PA_CH3_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_PA_CH4_BIAS_ON, Idq_pa_bias, BROADCAST_OFF)) {
|
||||||
|
DIAG_ERR("BF", "ADTR1107 step 9 dev[%u] Idq cal bias write FAILED", dev);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
@@ -526,162 +606,175 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
|
|||||||
|
|
||||||
bool ADAR1000Manager::setAllDevicesTXMode() {
|
bool ADAR1000Manager::setAllDevicesTXMode() {
|
||||||
DIAG("BF", "setAllDevicesTXMode(): ADTR1107 -> TX, then configure ADAR1000s");
|
DIAG("BF", "setAllDevicesTXMode(): ADTR1107 -> TX, then configure ADAR1000s");
|
||||||
// Set ADTR1107 to TX mode first
|
// Set ADTR1107 to TX mode first. If this fails, do NOT advance state --
|
||||||
setADTR1107Mode(BeamDirection::TX);
|
// software was previously claiming TX mode while hardware stayed in RX.
|
||||||
|
if (!setADTR1107Mode(BeamDirection::TX)) {
|
||||||
// Then configure ADAR1000 for TX
|
DIAG_ERR("BF", "setAllDevicesTXMode: ADTR1107 TX setup FAILED -- not updating mode flags");
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
return false;
|
||||||
// Disable RX first
|
|
||||||
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF);
|
|
||||||
|
|
||||||
// Enable TX channels and set bias
|
|
||||||
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;
|
bool ok = true;
|
||||||
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
|
bool dev_ok = true;
|
||||||
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
|
dev_ok = adarSetTxBias(dev, BROADCAST_OFF) && dev_ok;
|
||||||
|
|
||||||
|
if (dev_ok) {
|
||||||
|
devices_[dev]->current_mode = BeamDirection::TX;
|
||||||
|
DIAG("BF", " dev[%u] TX mode set (enables=0x0F, bias applied)", dev);
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", " dev[%u] TX mode setup FAILED -- per-device current_mode unchanged", dev);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
current_mode_ = BeamDirection::TX;
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", "setAllDevicesTXMode: at least one device failed -- global current_mode unchanged");
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ADAR1000Manager::setAllDevicesRXMode() {
|
bool ADAR1000Manager::setAllDevicesRXMode() {
|
||||||
DIAG("BF", "setAllDevicesRXMode(): ADTR1107 -> RX, then configure ADAR1000s");
|
DIAG("BF", "setAllDevicesRXMode(): ADTR1107 -> RX, then configure ADAR1000s");
|
||||||
// Set ADTR1107 to RX mode first
|
if (!setADTR1107Mode(BeamDirection::RX)) {
|
||||||
setADTR1107Mode(BeamDirection::RX);
|
DIAG_ERR("BF", "setAllDevicesRXMode: ADTR1107 RX setup FAILED -- not updating mode flags");
|
||||||
|
return false;
|
||||||
// Then configure ADAR1000 for RX
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
|
||||||
// Disable TX first
|
|
||||||
adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF);
|
|
||||||
|
|
||||||
// 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;
|
bool ok = true;
|
||||||
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
|
bool dev_ok = true;
|
||||||
|
dev_ok = adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF) && dev_ok;
|
||||||
|
dev_ok = adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF) && dev_ok;
|
||||||
|
|
||||||
|
if (dev_ok) {
|
||||||
|
devices_[dev]->current_mode = BeamDirection::RX;
|
||||||
|
DIAG("BF", " dev[%u] RX mode set (enables=0x0F)", dev);
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", " dev[%u] RX mode setup FAILED -- per-device current_mode unchanged", dev);
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
current_mode_ = BeamDirection::RX;
|
||||||
|
} else {
|
||||||
|
DIAG_ERR("BF", "setAllDevicesRXMode: at least one device failed -- global current_mode unchanged");
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
bool ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
||||||
|
bool ok = true;
|
||||||
if (direction == BeamDirection::TX) {
|
if (direction == BeamDirection::TX) {
|
||||||
DIAG_SECTION("ADTR1107 -> TX MODE");
|
DIAG_SECTION("ADTR1107 -> TX MODE");
|
||||||
setADTR1107Control(true); // TX mode
|
ok = setADTR1107Control(true) && ok;
|
||||||
|
|
||||||
// Step 1: Disable LNA power first
|
|
||||||
DIAG("BF", " Disable LNA supplies");
|
DIAG("BF", " Disable LNA supplies");
|
||||||
disableLNASupplies();
|
disableLNASupplies();
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 2: Set LNA bias to safe off value
|
|
||||||
DIAG("BF", " LNA bias -> OFF (0x%02X)", kLnaBiasOff);
|
DIAG("BF", " LNA bias -> OFF (0x%02X)", kLnaBiasOff);
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_LNA_BIAS_ON, kLnaBiasOff, BROADCAST_OFF); // Turn off LNA bias
|
ok = adarWrite(dev, REG_LNA_BIAS_ON, kLnaBiasOff, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 3: Enable PA power
|
|
||||||
DIAG("BF", " Enable PA supplies");
|
DIAG("BF", " Enable PA supplies");
|
||||||
enablePASupplies();
|
enablePASupplies();
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
// Step 4: Set PA bias to operational value
|
|
||||||
DIAG("BF", " PA bias -> operational (0x%02X)", kPaBiasOperational);
|
DIAG("BF", " PA bias -> operational (0x%02X)", kPaBiasOperational);
|
||||||
uint8_t operational_pa_bias = kPaBiasOperational; // Maximum bias for full power
|
uint8_t operational_pa_bias = kPaBiasOperational;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_PA_CH1_BIAS_ON, operational_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH1_BIAS_ON, operational_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH2_BIAS_ON, operational_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH2_BIAS_ON, operational_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH3_BIAS_ON, operational_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH3_BIAS_ON, operational_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH4_BIAS_ON, operational_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH4_BIAS_ON, operational_pa_bias, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 5: Set TR switch to TX mode
|
|
||||||
DIAG("BF", " TR switch -> TX (TR_SOURCE=1, BIAS_EN)");
|
DIAG("BF", " TR switch -> TX (TR_SOURCE=1, BIAS_EN)");
|
||||||
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)
|
ok = adarSetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF) && ok;
|
||||||
adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF); // BIAS_EN
|
ok = adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
DIAG("BF", " ADTR1107 TX mode complete");
|
DIAG("BF", " ADTR1107 TX mode %s", ok ? "complete" : "completed WITH FAILURES");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// RECEIVE MODE: Enable LNA, Disable PA
|
|
||||||
DIAG_SECTION("ADTR1107 -> RX MODE");
|
DIAG_SECTION("ADTR1107 -> RX MODE");
|
||||||
setADTR1107Control(false); // RX mode
|
ok = setADTR1107Control(false) && ok;
|
||||||
|
|
||||||
// Step 1: Disable PA power first
|
|
||||||
DIAG("BF", " Disable PA supplies");
|
DIAG("BF", " Disable PA supplies");
|
||||||
disablePASupplies();
|
disablePASupplies();
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 2: Set PA bias to safe negative voltage
|
|
||||||
DIAG("BF", " PA bias -> safe (0x%02X)", kPaBiasRxSafe);
|
DIAG("BF", " PA bias -> safe (0x%02X)", kPaBiasRxSafe);
|
||||||
uint8_t safe_pa_bias = kPaBiasRxSafe;
|
uint8_t safe_pa_bias = kPaBiasRxSafe;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_PA_CH1_BIAS_ON, safe_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH1_BIAS_ON, safe_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH2_BIAS_ON, safe_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH2_BIAS_ON, safe_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH3_BIAS_ON, safe_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH3_BIAS_ON, safe_pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH4_BIAS_ON, safe_pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH4_BIAS_ON, safe_pa_bias, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 3: Enable LNA power
|
|
||||||
DIAG("BF", " Enable LNA supplies");
|
DIAG("BF", " Enable LNA supplies");
|
||||||
enableLNASupplies();
|
enableLNASupplies();
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
// Step 4: Set LNA bias to operational value
|
|
||||||
DIAG("BF", " LNA bias -> operational (0x%02X)", kLnaBiasOperational);
|
DIAG("BF", " LNA bias -> operational (0x%02X)", kLnaBiasOperational);
|
||||||
uint8_t operational_lna_bias = kLnaBiasOperational;
|
uint8_t operational_lna_bias = kLnaBiasOperational;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_LNA_BIAS_ON, operational_lna_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_LNA_BIAS_ON, operational_lna_bias, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 5: Set TR switch to RX mode
|
|
||||||
DIAG("BF", " TR switch -> RX (TR_SOURCE=0, LNA_BIAS_OUT_EN)");
|
DIAG("BF", " TR switch -> RX (TR_SOURCE=0, LNA_BIAS_OUT_EN)");
|
||||||
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)
|
ok = adarResetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF) && ok;
|
||||||
adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF); // LNA_BIAS_OUT_EN
|
ok = adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
DIAG("BF", " ADTR1107 RX mode complete");
|
DIAG("BF", " ADTR1107 RX mode %s", ok ? "complete" : "completed WITH FAILURES");
|
||||||
}
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setADTR1107Control(bool tx_mode) {
|
bool ADAR1000Manager::setADTR1107Control(bool tx_mode) {
|
||||||
DIAG("BF", "setADTR1107Control(%s): setting TR switch on all %u devices, settling %lu us",
|
DIAG("BF", "setADTR1107Control(%s): setting TR switch on all %u devices, settling %lu us",
|
||||||
tx_mode ? "TX" : "RX", (unsigned)devices_.size(), (unsigned long)switch_settling_time_us_);
|
tx_mode ? "TX" : "RX", (unsigned)devices_.size(), (unsigned long)switch_settling_time_us_);
|
||||||
|
bool ok = true;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
setTRSwitchPosition(dev, tx_mode);
|
ok = setTRSwitchPosition(dev, tx_mode) && ok;
|
||||||
}
|
}
|
||||||
delayUs(switch_settling_time_us_);
|
delayUs(switch_settling_time_us_);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode) {
|
bool ADAR1000Manager::setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode) {
|
||||||
if (tx_mode) {
|
if (tx_mode) {
|
||||||
// TX mode: Set TR_SOURCE = 1
|
// TX mode: Set TR_SOURCE = 1
|
||||||
adarSetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
|
return adarSetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
|
||||||
} else {
|
|
||||||
// RX mode: Set TR_SOURCE = 0
|
|
||||||
adarResetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
|
|
||||||
}
|
}
|
||||||
|
// RX mode: Set TR_SOURCE = 0
|
||||||
|
return 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) {
|
||||||
|
bool ok = true;
|
||||||
for (uint8_t dev = 0; dev < 4; ++dev) {
|
for (uint8_t dev = 0; dev < 4; ++dev) {
|
||||||
for (uint8_t ch = 0; ch < 4; ++ch) {
|
for (uint8_t ch = 0; ch < 4; ++ch) {
|
||||||
uint8_t phase = phase_pattern[dev * 4 + ch];
|
uint8_t phase = phase_pattern[dev * 4 + ch];
|
||||||
if (direction == BeamDirection::TX) {
|
if (direction == BeamDirection::TX) {
|
||||||
adarSetTxPhase(dev, ch + 1, phase, BROADCAST_OFF);
|
ok = adarSetTxPhase(dev, ch + 1, phase, BROADCAST_OFF) && ok;
|
||||||
} else {
|
} else {
|
||||||
adarSetRxPhase(dev, ch + 1, phase, BROADCAST_OFF);
|
ok = adarSetRxPhase(dev, ch + 1, phase, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::enablePASupplies() {
|
void ADAR1000Manager::enablePASupplies() {
|
||||||
@@ -708,25 +801,29 @@ void ADAR1000Manager::disableLNASupplies() {
|
|||||||
HAL_GPIO_WritePin(EN_P_3V3_ADTR_GPIO_Port, EN_P_3V3_ADTR_Pin, GPIO_PIN_RESET);
|
HAL_GPIO_WritePin(EN_P_3V3_ADTR_GPIO_Port, EN_P_3V3_ADTR_Pin, GPIO_PIN_RESET);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setPABias(bool enable) {
|
bool ADAR1000Manager::setPABias(bool enable) {
|
||||||
uint8_t pa_bias = enable ? kPaBiasOperational : kPaBiasRxSafe; // Operational vs safe bias
|
uint8_t pa_bias = enable ? kPaBiasOperational : kPaBiasRxSafe; // Operational vs safe bias
|
||||||
DIAG("BF", "setPABias(%s): bias=0x%02X", enable ? "ON" : "OFF", pa_bias);
|
DIAG("BF", "setPABias(%s): bias=0x%02X", enable ? "ON" : "OFF", pa_bias);
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_PA_CH1_BIAS_ON, pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH1_BIAS_ON, pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH2_BIAS_ON, pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH2_BIAS_ON, pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH3_BIAS_ON, pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH3_BIAS_ON, pa_bias, BROADCAST_OFF) && ok;
|
||||||
adarWrite(dev, REG_PA_CH4_BIAS_ON, pa_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_PA_CH4_BIAS_ON, pa_bias, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setLNABias(bool enable) {
|
bool ADAR1000Manager::setLNABias(bool enable) {
|
||||||
uint8_t lna_bias = enable ? kLnaBiasOperational : kLnaBiasOff; // Operational vs off
|
uint8_t lna_bias = enable ? kLnaBiasOperational : kLnaBiasOff; // Operational vs off
|
||||||
DIAG("BF", "setLNABias(%s): bias=0x%02X", enable ? "ON" : "OFF", lna_bias);
|
DIAG("BF", "setLNABias(%s): bias=0x%02X", enable ? "ON" : "OFF", lna_bias);
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_LNA_BIAS_ON, lna_bias, BROADCAST_OFF);
|
ok = adarWrite(dev, REG_LNA_BIAS_ON, lna_bias, BROADCAST_OFF) && ok;
|
||||||
}
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::delayUs(uint32_t microseconds) {
|
void ADAR1000Manager::delayUs(uint32_t microseconds) {
|
||||||
@@ -771,7 +868,11 @@ bool ADAR1000Manager::performSystemCalibration() {
|
|||||||
// LOW-LEVEL SPI COMMUNICATION METHODS
|
// LOW-LEVEL SPI COMMUNICATION METHODS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
uint32_t ADAR1000Manager::spiTransfer(uint8_t* txData, uint8_t* rxData, uint32_t size) {
|
void ADAR1000Manager::resetCommStats() {
|
||||||
|
comm_stats_ = {0, 0, 0, 0, 0, 0xFF};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADAR1000Manager::spiTransfer(uint8_t* txData, uint8_t* rxData, uint32_t size) {
|
||||||
HAL_StatusTypeDef status;
|
HAL_StatusTypeDef status;
|
||||||
|
|
||||||
if (rxData) {
|
if (rxData) {
|
||||||
@@ -782,9 +883,9 @@ uint32_t ADAR1000Manager::spiTransfer(uint8_t* txData, uint8_t* rxData, uint32_t
|
|||||||
|
|
||||||
if (status != HAL_OK) {
|
if (status != HAL_OK) {
|
||||||
DIAG_ERR("BF", "SPI1 transfer FAILED: HAL status=%d, size=%lu", (int)status, (unsigned long)size);
|
DIAG_ERR("BF", "SPI1 transfer FAILED: HAL status=%d, size=%lu", (int)status, (unsigned long)size);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
return (status == HAL_OK) ? size : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setChipSelect(uint8_t deviceIndex, bool state) {
|
void ADAR1000Manager::setChipSelect(uint8_t deviceIndex, bool state) {
|
||||||
@@ -794,7 +895,14 @@ void ADAR1000Manager::setChipSelect(uint8_t deviceIndex, bool state) {
|
|||||||
state ? GPIO_PIN_RESET : GPIO_PIN_SET);
|
state ? GPIO_PIN_RESET : GPIO_PIN_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast) {
|
bool ADAR1000Manager::adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast) {
|
||||||
|
if (deviceIndex >= devices_.size()) {
|
||||||
|
comm_stats_.writes_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
DIAG_ERR("BF", "adarWrite(dev[%u]): index out of range", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t instruction[3];
|
uint8_t instruction[3];
|
||||||
|
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
@@ -808,16 +916,38 @@ void ADAR1000Manager::adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t
|
|||||||
instruction[2] = data;
|
instruction[2] = data;
|
||||||
|
|
||||||
setChipSelect(deviceIndex, true);
|
setChipSelect(deviceIndex, true);
|
||||||
spiTransfer(instruction, nullptr, sizeof(instruction));
|
bool ok = spiTransfer(instruction, nullptr, sizeof(instruction));
|
||||||
setChipSelect(deviceIndex, false);
|
setChipSelect(deviceIndex, false);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
comm_stats_.writes_ok++;
|
||||||
|
} else {
|
||||||
|
comm_stats_.writes_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ADAR1000Manager::adarRead(uint8_t deviceIndex, uint32_t mem_addr) {
|
bool ADAR1000Manager::adarReadChecked(uint8_t deviceIndex, uint32_t mem_addr, uint8_t* out) {
|
||||||
uint8_t instruction[3] = {0};
|
if (out == nullptr) return false;
|
||||||
uint8_t rx_buffer[3] = {0};
|
*out = 0;
|
||||||
|
|
||||||
// Set SDO active
|
if (deviceIndex >= devices_.size()) {
|
||||||
adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, INTERFACE_CONFIG_A_SDO_ACTIVE, 0);
|
comm_stats_.reads_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
DIAG_ERR("BF", "adarRead(dev[%u]): index out of range", deviceIndex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t instruction[3] = {0};
|
||||||
|
uint8_t rx_buffer[3] = {0};
|
||||||
|
|
||||||
|
// Set SDO active. Failure here means we cannot trust the readback that follows.
|
||||||
|
if (!adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, INTERFACE_CONFIG_A_SDO_ACTIVE, 0)) {
|
||||||
|
comm_stats_.reads_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
instruction[0] = 0x80 | ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
instruction[0] = 0x80 | ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
||||||
instruction[0] |= ((0xff00 & mem_addr) >> 8);
|
instruction[0] |= ((0xff00 & mem_addr) >> 8);
|
||||||
@@ -825,28 +955,57 @@ uint8_t ADAR1000Manager::adarRead(uint8_t deviceIndex, uint32_t mem_addr) {
|
|||||||
instruction[2] = 0x00;
|
instruction[2] = 0x00;
|
||||||
|
|
||||||
setChipSelect(deviceIndex, true);
|
setChipSelect(deviceIndex, true);
|
||||||
spiTransfer(instruction, rx_buffer, sizeof(instruction));
|
bool ok = spiTransfer(instruction, rx_buffer, sizeof(instruction));
|
||||||
setChipSelect(deviceIndex, false);
|
setChipSelect(deviceIndex, false);
|
||||||
|
|
||||||
// Set SDO Inactive
|
// Best-effort: clear SDO active even if the read above failed. Don't let a
|
||||||
adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, 0, 0);
|
// failure on the trailing write override the read failure status.
|
||||||
|
bool sdo_off_ok = adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, 0, 0);
|
||||||
|
(void)sdo_off_ok; // already counted in writes_*; don't double-count as a read failure.
|
||||||
|
|
||||||
return rx_buffer[2];
|
if (!ok) {
|
||||||
|
comm_stats_.reads_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = rx_buffer[2];
|
||||||
|
comm_stats_.reads_ok++;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
uint8_t ADAR1000Manager::adarRead(uint8_t deviceIndex, uint32_t mem_addr) {
|
||||||
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
uint8_t value = 0;
|
||||||
|
(void)adarReadChecked(deviceIndex, mem_addr, &value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADAR1000Manager::adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
||||||
|
uint8_t temp = 0;
|
||||||
|
// Critical: read-modify-write must NOT proceed on a failed read, otherwise we
|
||||||
|
// would write back (0 | mask) and clobber every other bit in the register.
|
||||||
|
if (!adarReadChecked(deviceIndex, mem_addr, &temp)) {
|
||||||
|
DIAG_ERR("BF", "adarSetBit(dev[%u], 0x%03lX, bit %u): read failed -- skipping write to avoid corruption",
|
||||||
|
deviceIndex, (unsigned long)mem_addr, bit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
uint8_t data = temp | (1 << bit);
|
uint8_t data = temp | (1 << bit);
|
||||||
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
return adarWrite(deviceIndex, mem_addr, data, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
bool ADAR1000Manager::adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
||||||
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
uint8_t temp = 0;
|
||||||
|
if (!adarReadChecked(deviceIndex, mem_addr, &temp)) {
|
||||||
|
DIAG_ERR("BF", "adarResetBit(dev[%u], 0x%03lX, bit %u): read failed -- skipping write to avoid corruption",
|
||||||
|
deviceIndex, (unsigned long)mem_addr, bit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
uint8_t data = temp & ~(1 << bit);
|
uint8_t data = temp & ~(1 << bit);
|
||||||
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
return adarWrite(deviceIndex, mem_addr, data, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSoftReset(uint8_t deviceIndex) {
|
bool ADAR1000Manager::adarSoftReset(uint8_t deviceIndex) {
|
||||||
|
if (deviceIndex >= devices_.size()) return false;
|
||||||
DIAG("BF", "adarSoftReset(dev[%u]): addr=0x%02X", deviceIndex, devices_[deviceIndex]->dev_addr);
|
DIAG("BF", "adarSoftReset(dev[%u]): addr=0x%02X", deviceIndex, devices_[deviceIndex]->dev_addr);
|
||||||
uint8_t instruction[3];
|
uint8_t instruction[3];
|
||||||
instruction[0] = ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
instruction[0] = ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
||||||
@@ -854,20 +1013,28 @@ void ADAR1000Manager::adarSoftReset(uint8_t deviceIndex) {
|
|||||||
instruction[2] = 0x81;
|
instruction[2] = 0x81;
|
||||||
|
|
||||||
setChipSelect(deviceIndex, true);
|
setChipSelect(deviceIndex, true);
|
||||||
spiTransfer(instruction, nullptr, sizeof(instruction));
|
bool ok = spiTransfer(instruction, nullptr, sizeof(instruction));
|
||||||
setChipSelect(deviceIndex, false);
|
setChipSelect(deviceIndex, false);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
comm_stats_.writes_ok++;
|
||||||
|
} else {
|
||||||
|
comm_stats_.writes_fail++;
|
||||||
|
comm_stats_.last_fail_dev = deviceIndex;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarWriteConfigA(uint8_t deviceIndex, uint8_t flags, uint8_t broadcast) {
|
bool ADAR1000Manager::adarWriteConfigA(uint8_t deviceIndex, uint8_t flags, uint8_t broadcast) {
|
||||||
adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, flags, broadcast);
|
return adarWrite(deviceIndex, REG_INTERFACE_CONFIG_A, flags, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRamBypass(uint8_t deviceIndex, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetRamBypass(uint8_t deviceIndex, uint8_t broadcast) {
|
||||||
uint8_t data = (MEM_CTRL_BIAS_RAM_BYPASS | MEM_CTRL_BEAM_RAM_BYPASS);
|
uint8_t data = (MEM_CTRL_BIAS_RAM_BYPASS | MEM_CTRL_BEAM_RAM_BYPASS);
|
||||||
adarWrite(deviceIndex, REG_MEM_CTL, data, broadcast);
|
return adarWrite(deviceIndex, REG_MEM_CTL, data, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4) per API contract documented in
|
// channel is 1-based (CH1..CH4) per API contract documented in
|
||||||
// ADAR1000_AGC.cpp and matching ADI datasheet terminology.
|
// ADAR1000_AGC.cpp and matching ADI datasheet terminology.
|
||||||
// Reject out-of-range early so a stale 0-based caller does not
|
// Reject out-of-range early so a stale 0-based caller does not
|
||||||
@@ -875,7 +1042,7 @@ void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
// See issue #90.
|
// See issue #90.
|
||||||
if (channel < 1 || channel > 4) {
|
if (channel < 1 || channel > 4) {
|
||||||
DIAG("BF", "adarSetRxPhase: channel %u out of range [1..4], ignored", channel);
|
DIAG("BF", "adarSetRxPhase: channel %u out of range [1..4], ignored", channel);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t i_val = VM_I[phase % 128];
|
uint8_t i_val = VM_I[phase % 128];
|
||||||
uint8_t q_val = VM_Q[phase % 128];
|
uint8_t q_val = VM_Q[phase % 128];
|
||||||
@@ -885,16 +1052,17 @@ void ADAR1000Manager::adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
uint32_t mem_addr_i = REG_CH1_RX_PHS_I + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_i = REG_CH1_RX_PHS_I + ((channel - 1) & 0x03) * 2;
|
||||||
uint32_t mem_addr_q = REG_CH1_RX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_q = REG_CH1_RX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
||||||
|
|
||||||
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
bool ok = adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
||||||
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
ok = adarWrite(deviceIndex, mem_addr_q, q_val, broadcast) && ok;
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
ok = adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast) && ok;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
// channel is 1-based (CH1..CH4). See issue #90.
|
||||||
if (channel < 1 || channel > 4) {
|
if (channel < 1 || channel > 4) {
|
||||||
DIAG("BF", "adarSetTxPhase: channel %u out of range [1..4], ignored", channel);
|
DIAG("BF", "adarSetTxPhase: channel %u out of range [1..4], ignored", channel);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t i_val = VM_I[phase % 128];
|
uint8_t i_val = VM_I[phase % 128];
|
||||||
uint8_t q_val = VM_Q[phase % 128];
|
uint8_t q_val = VM_Q[phase % 128];
|
||||||
@@ -902,55 +1070,76 @@ void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
uint32_t mem_addr_i = REG_CH1_TX_PHS_I + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_i = REG_CH1_TX_PHS_I + ((channel - 1) & 0x03) * 2;
|
||||||
uint32_t mem_addr_q = REG_CH1_TX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
uint32_t mem_addr_q = REG_CH1_TX_PHS_Q + ((channel - 1) & 0x03) * 2;
|
||||||
|
|
||||||
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
bool ok = adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
||||||
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
ok = adarWrite(deviceIndex, mem_addr_q, q_val, broadcast) && ok;
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
ok = adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast) && ok;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
// channel is 1-based (CH1..CH4). See issue #90.
|
||||||
if (channel < 1 || channel > 4) {
|
if (channel < 1 || channel > 4) {
|
||||||
DIAG("BF", "adarSetRxVgaGain: channel %u out of range [1..4], ignored", channel);
|
DIAG("BF", "adarSetRxVgaGain: channel %u out of range [1..4], ignored", channel);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t mem_addr = REG_CH1_RX_GAIN + ((channel - 1) & 0x03);
|
uint32_t mem_addr = REG_CH1_RX_GAIN + ((channel - 1) & 0x03);
|
||||||
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
bool ok = adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
ok = adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast) && ok;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
||||||
// channel is 1-based (CH1..CH4). See issue #90.
|
// channel is 1-based (CH1..CH4). See issue #90.
|
||||||
if (channel < 1 || channel > 4) {
|
if (channel < 1 || channel > 4) {
|
||||||
DIAG("BF", "adarSetTxVgaGain: channel %u out of range [1..4], ignored", channel);
|
DIAG("BF", "adarSetTxVgaGain: channel %u out of range [1..4], ignored", channel);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
uint32_t mem_addr = REG_CH1_TX_GAIN + ((channel - 1) & 0x03);
|
uint32_t mem_addr = REG_CH1_TX_GAIN + ((channel - 1) & 0x03);
|
||||||
adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
bool ok = adarWrite(deviceIndex, mem_addr, gain, broadcast);
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
|
ok = adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast) && ok;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast) {
|
bool ADAR1000Manager::adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast) {
|
||||||
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX, kTxBiasCurrent, broadcast);
|
bool ok = adarWrite(deviceIndex, REG_BIAS_CURRENT_TX, kTxBiasCurrent, broadcast);
|
||||||
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX_DRV, kTxDriverBiasCurrent, broadcast);
|
ok = adarWrite(deviceIndex, REG_BIAS_CURRENT_TX_DRV, kTxDriverBiasCurrent, broadcast) && ok;
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x2, broadcast);
|
ok = adarWrite(deviceIndex, REG_LOAD_WORKING, 0x2, broadcast) && ok;
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ADAR1000Manager::adarAdcRead(uint8_t deviceIndex, uint8_t broadcast) {
|
uint8_t ADAR1000Manager::adarAdcRead(uint8_t deviceIndex, uint8_t broadcast) {
|
||||||
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_ST_CONV, broadcast);
|
if (!adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_ST_CONV, broadcast)) {
|
||||||
|
DIAG_ERR("BF", "adarAdcRead(dev[%u]): ADC start-conversion write failed", deviceIndex);
|
||||||
|
comm_stats_.adc_timeouts++; // treat as a "no-result" event for caller observability
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for conversion -- WARNING: no timeout, can hang if ADC never completes
|
|
||||||
uint32_t t0 = HAL_GetTick();
|
uint32_t t0 = HAL_GetTick();
|
||||||
uint32_t polls = 0;
|
uint32_t polls = 0;
|
||||||
while (!(adarRead(deviceIndex, REG_ADC_CONTROL) & 0x01)) {
|
uint8_t ctrl = 0;
|
||||||
|
while (true) {
|
||||||
|
if (!adarReadChecked(deviceIndex, REG_ADC_CONTROL, &ctrl)) {
|
||||||
|
DIAG_ERR("BF", "adarAdcRead(dev[%u]): ADC poll read failed", deviceIndex);
|
||||||
|
comm_stats_.adc_timeouts++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ctrl & 0x01) break;
|
||||||
polls++;
|
polls++;
|
||||||
if (HAL_GetTick() - t0 > 100) {
|
if (HAL_GetTick() - t0 > 100) {
|
||||||
DIAG_ERR("BF", "adarAdcRead(dev[%u]): ADC conversion TIMEOUT after %lu ms, %lu polls",
|
DIAG_ERR("BF", "adarAdcRead(dev[%u]): ADC conversion TIMEOUT after %lu ms, %lu polls",
|
||||||
deviceIndex, (unsigned long)(HAL_GetTick() - t0), (unsigned long)polls);
|
deviceIndex, (unsigned long)(HAL_GetTick() - t0), (unsigned long)polls);
|
||||||
|
comm_stats_.adc_timeouts++;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DIAG("BF", "adarAdcRead(dev[%u]): conversion done in %lu ms (%lu polls)",
|
DIAG("BF", "adarAdcRead(dev[%u]): conversion done in %lu ms (%lu polls)",
|
||||||
deviceIndex, (unsigned long)(HAL_GetTick() - t0), (unsigned long)polls);
|
deviceIndex, (unsigned long)(HAL_GetTick() - t0), (unsigned long)polls);
|
||||||
|
|
||||||
return adarRead(deviceIndex, REG_ADC_OUT);
|
uint8_t out = 0;
|
||||||
|
if (!adarReadChecked(deviceIndex, REG_ADC_OUT, &out)) {
|
||||||
|
DIAG_ERR("BF", "adarAdcRead(dev[%u]): ADC output read failed", deviceIndex);
|
||||||
|
comm_stats_.adc_timeouts++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,19 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Communication health counters. Incremented by checked SPI helpers; queryable
|
||||||
|
// for telemetry/observability without changing the boolean public contract.
|
||||||
|
// PR2 may promote a richer OpStatus enum; today the policy is bool returns
|
||||||
|
// for control flow + this struct for trends.
|
||||||
|
struct CommStats {
|
||||||
|
uint32_t writes_ok;
|
||||||
|
uint32_t writes_fail;
|
||||||
|
uint32_t reads_ok;
|
||||||
|
uint32_t reads_fail;
|
||||||
|
uint32_t adc_timeouts;
|
||||||
|
uint8_t last_fail_dev; // device index of most recent failure (0xFF if none)
|
||||||
|
};
|
||||||
|
|
||||||
ADAR1000Manager();
|
ADAR1000Manager();
|
||||||
~ADAR1000Manager();
|
~ADAR1000Manager();
|
||||||
|
|
||||||
@@ -46,12 +59,12 @@ public:
|
|||||||
bool performSystemCalibration();
|
bool performSystemCalibration();
|
||||||
|
|
||||||
// Mode Switching
|
// Mode Switching
|
||||||
void switchToTXMode();
|
bool switchToTXMode();
|
||||||
void switchToRXMode();
|
bool switchToRXMode();
|
||||||
void fastTXMode();
|
bool fastTXMode();
|
||||||
void fastRXMode();
|
bool fastRXMode();
|
||||||
void pulseTXMode();
|
bool pulseTXMode();
|
||||||
void pulseRXMode();
|
bool pulseRXMode();
|
||||||
|
|
||||||
// Beam Steering
|
// Beam Steering
|
||||||
bool setBeamAngle(float angle_degrees, BeamDirection direction);
|
bool setBeamAngle(float angle_degrees, BeamDirection direction);
|
||||||
@@ -68,14 +81,20 @@ public:
|
|||||||
// Device Control
|
// Device Control
|
||||||
bool setAllDevicesTXMode();
|
bool setAllDevicesTXMode();
|
||||||
bool setAllDevicesRXMode();
|
bool setAllDevicesRXMode();
|
||||||
void setADTR1107Mode(BeamDirection direction);
|
bool setADTR1107Mode(BeamDirection direction);
|
||||||
void setADTR1107Control(bool tx_mode);
|
bool setADTR1107Control(bool tx_mode);
|
||||||
|
|
||||||
// Monitoring and Diagnostics
|
// Monitoring and Diagnostics
|
||||||
|
// readTemperature returns NaN when the on-chip ADC times out, so callers
|
||||||
|
// can distinguish a hung chip from a real cold reading via std::isnan().
|
||||||
float readTemperature(uint8_t deviceIndex);
|
float readTemperature(uint8_t deviceIndex);
|
||||||
bool verifyDeviceCommunication(uint8_t deviceIndex);
|
bool verifyDeviceCommunication(uint8_t deviceIndex);
|
||||||
uint8_t readRegister(uint8_t deviceIndex, uint32_t address);
|
uint8_t readRegister(uint8_t deviceIndex, uint32_t address);
|
||||||
void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value);
|
bool writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value);
|
||||||
|
|
||||||
|
// Communication health observability
|
||||||
|
const CommStats& getCommStats() const { return comm_stats_; }
|
||||||
|
void resetCommStats();
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
void setSwitchSettlingTime(uint32_t us);
|
void setSwitchSettlingTime(uint32_t us);
|
||||||
@@ -109,6 +128,9 @@ public:
|
|||||||
std::vector<std::unique_ptr<ADAR1000Device>> devices_;
|
std::vector<std::unique_ptr<ADAR1000Device>> devices_;
|
||||||
BeamDirection current_mode_ = BeamDirection::RX;
|
BeamDirection current_mode_ = BeamDirection::RX;
|
||||||
|
|
||||||
|
// Comm health counters (zeroed in resetCommStats()).
|
||||||
|
CommStats comm_stats_ = {0, 0, 0, 0, 0, 0xFF};
|
||||||
|
|
||||||
// Beam Sweeping
|
// Beam Sweeping
|
||||||
std::vector<BeamConfig> tx_beam_sequence_;
|
std::vector<BeamConfig> tx_beam_sequence_;
|
||||||
std::vector<BeamConfig> rx_beam_sequence_;
|
std::vector<BeamConfig> rx_beam_sequence_;
|
||||||
@@ -142,32 +164,42 @@ public:
|
|||||||
void delayUs(uint32_t microseconds);
|
void delayUs(uint32_t microseconds);
|
||||||
|
|
||||||
// Power Management
|
// Power Management
|
||||||
|
// PA/LNA supply rails are pure GPIO toggles -- those stay void.
|
||||||
|
// setPABias/setLNABias issue per-device SPI writes, so they propagate.
|
||||||
void enablePASupplies();
|
void enablePASupplies();
|
||||||
void disablePASupplies();
|
void disablePASupplies();
|
||||||
void enableLNASupplies();
|
void enableLNASupplies();
|
||||||
void disableLNASupplies();
|
void disableLNASupplies();
|
||||||
void setPABias(bool enable);
|
bool setPABias(bool enable);
|
||||||
void setLNABias(bool enable);
|
bool setLNABias(bool enable);
|
||||||
|
|
||||||
// SPI Communication
|
// SPI Communication
|
||||||
|
// setChipSelect is a pure GPIO toggle (no failure mode in HAL_GPIO_WritePin).
|
||||||
|
// Everything else returns true on success, false on SPI failure or invalid index.
|
||||||
void setChipSelect(uint8_t deviceIndex, bool state);
|
void setChipSelect(uint8_t deviceIndex, bool state);
|
||||||
uint32_t spiTransfer(uint8_t* txData, uint8_t* rxData, uint32_t size);
|
bool spiTransfer(uint8_t* txData, uint8_t* rxData, uint32_t size);
|
||||||
void adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast);
|
bool adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast);
|
||||||
|
// adarRead returns the register byte; on SPI failure it returns 0 and the
|
||||||
|
// failure is reflected in comm_stats_.reads_fail (callers wanting an
|
||||||
|
// explicit ok/fail signal should use adarReadChecked below).
|
||||||
uint8_t adarRead(uint8_t deviceIndex, uint32_t mem_addr);
|
uint8_t adarRead(uint8_t deviceIndex, uint32_t mem_addr);
|
||||||
void adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
bool adarReadChecked(uint8_t deviceIndex, uint32_t mem_addr, uint8_t* out);
|
||||||
void adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
bool adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
||||||
void adarSoftReset(uint8_t deviceIndex);
|
bool adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast);
|
||||||
void adarWriteConfigA(uint8_t deviceIndex, uint8_t flags, uint8_t broadcast);
|
bool adarSoftReset(uint8_t deviceIndex);
|
||||||
void adarSetRamBypass(uint8_t deviceIndex, uint8_t broadcast);
|
bool adarWriteConfigA(uint8_t deviceIndex, uint8_t flags, uint8_t broadcast);
|
||||||
|
bool adarSetRamBypass(uint8_t deviceIndex, uint8_t broadcast);
|
||||||
|
|
||||||
// Channel Configuration
|
// Channel Configuration
|
||||||
void adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast);
|
bool adarSetRxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast);
|
||||||
void adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast);
|
bool adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8_t phase, uint8_t broadcast);
|
||||||
void adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast);
|
bool adarSetRxVgaGain(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);
|
bool adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast);
|
||||||
void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast);
|
bool adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast);
|
||||||
|
// adarAdcRead returns 0 on timeout AND increments comm_stats_.adc_timeouts.
|
||||||
|
// readTemperature() detects the timeout via that counter delta.
|
||||||
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);
|
bool setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|||||||
@@ -388,7 +388,12 @@ void systemPowerUpSequence() {
|
|||||||
|
|
||||||
// Step 4: Set to safe TX mode
|
// Step 4: Set to safe TX mode
|
||||||
DIAG("PWR", "Step 4: setAllDevicesTXMode()");
|
DIAG("PWR", "Step 4: setAllDevicesTXMode()");
|
||||||
adarManager.setAllDevicesTXMode();
|
if (!adarManager.setAllDevicesTXMode()) {
|
||||||
|
DIAG_ERR("PWR", "setAllDevicesTXMode() FAILED -- calling Error_Handler()");
|
||||||
|
uint8_t err[] = "ERROR: ADAR1000 TX-mode setup failed!\r\n";
|
||||||
|
HAL_UART_Transmit(&huart3, err, sizeof(err)-1, 1000);
|
||||||
|
Error_Handler();
|
||||||
|
}
|
||||||
DIAG("PWR", "Step 4 OK: All devices set to TX mode");
|
DIAG("PWR", "Step 4 OK: All devices set to TX mode");
|
||||||
|
|
||||||
uint8_t success[] = "Power Up Sequence Completed Successfully\r\n";
|
uint8_t success[] = "Power Up Sequence Completed Successfully\r\n";
|
||||||
@@ -401,9 +406,15 @@ void systemPowerDownSequence() {
|
|||||||
uint8_t msg[] = "Starting Power Down Sequence...\r\n";
|
uint8_t msg[] = "Starting Power Down Sequence...\r\n";
|
||||||
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
|
HAL_UART_Transmit(&huart3, msg, sizeof(msg)-1, 1000);
|
||||||
|
|
||||||
// Step 1: Set all devices to RX mode (safest state)
|
// Step 1: Set all devices to RX mode (safest state). Failure here is logged
|
||||||
|
// but NOT fatal -- power-down must always proceed to cut the rails below.
|
||||||
|
// Leaving a stuck PA bias would be more dangerous than losing RX-mode telemetry.
|
||||||
DIAG("PWR", "Step 1: setAllDevicesRXMode()");
|
DIAG("PWR", "Step 1: setAllDevicesRXMode()");
|
||||||
adarManager.setAllDevicesRXMode();
|
if (!adarManager.setAllDevicesRXMode()) {
|
||||||
|
DIAG_ERR("PWR", "setAllDevicesRXMode() FAILED during power-down -- continuing to cut rails");
|
||||||
|
uint8_t warn[] = "WARNING: RX-mode setup failed during power-down, cutting rails anyway\r\n";
|
||||||
|
HAL_UART_Transmit(&huart3, warn, sizeof(warn)-1, 1000);
|
||||||
|
}
|
||||||
HAL_Delay(10);
|
HAL_Delay(10);
|
||||||
|
|
||||||
// Step 2: Disable PA power supplies
|
// Step 2: Disable PA power supplies
|
||||||
@@ -693,6 +704,14 @@ SystemError_t checkSystemHealth(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float temp = adarManager.readTemperature(i);
|
float temp = adarManager.readTemperature(i);
|
||||||
|
// NaN signals an ADC timeout / comm failure inside the manager. Previously
|
||||||
|
// a hung ADC returned 0, which mapped to -50 C and looked healthy. Map
|
||||||
|
// it to the comm-error bucket so attemptErrorRecovery re-inits the chip.
|
||||||
|
if (isnan(temp)) {
|
||||||
|
current_error = ERROR_ADAR1000_COMM;
|
||||||
|
DIAG_ERR("BF", "Health check: ADAR1000 #%d temperature read returned NaN (ADC timeout)", i);
|
||||||
|
return current_error;
|
||||||
|
}
|
||||||
if (temp > 85.0f) {
|
if (temp > 85.0f) {
|
||||||
current_error = ERROR_ADAR1000_TEMP;
|
current_error = ERROR_ADAR1000_TEMP;
|
||||||
DIAG_ERR("BF", "Health check: ADAR1000 #%d OVERTEMP %.1fC > 85C", i, temp);
|
DIAG_ERR("BF", "Health check: ADAR1000 #%d OVERTEMP %.1fC > 85C", i, temp);
|
||||||
@@ -782,11 +801,20 @@ void attemptErrorRecovery(SystemError_t error) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ERROR_ADAR1000_COMM:
|
case ERROR_ADAR1000_COMM:
|
||||||
// Reset ADAR1000 communication
|
// Reset ADAR1000 communication. Previously this discarded the bool
|
||||||
|
// return, so a re-init that failed silently looked like recovery
|
||||||
|
// succeeded -- the next health check would loop right back here.
|
||||||
DIAG("BF", "Recovery: Re-initializing all ADAR1000 devices");
|
DIAG("BF", "Recovery: Re-initializing all ADAR1000 devices");
|
||||||
adarManager.initializeAllDevices();
|
if (!adarManager.initializeAllDevices()) {
|
||||||
HAL_Delay(50);
|
DIAG_ERR("BF", "Recovery FAILED: ADAR1000 re-init still failing -- escalating to emergency state");
|
||||||
DIAG("BF", "Recovery: ADAR1000 re-init complete");
|
uint8_t err[] = "ERROR: ADAR1000 recovery failed, entering emergency state\r\n";
|
||||||
|
HAL_UART_Transmit(&huart3, err, sizeof(err)-1, 1000);
|
||||||
|
system_emergency_state = true;
|
||||||
|
error_count++;
|
||||||
|
} else {
|
||||||
|
HAL_Delay(50);
|
||||||
|
DIAG("BF", "Recovery: ADAR1000 re-init complete");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ERROR_IMU_COMM:
|
case ERROR_IMU_COMM:
|
||||||
|
|||||||
@@ -79,10 +79,17 @@ TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
|
|||||||
# C++ tests (AGC outer loop)
|
# C++ tests (AGC outer loop)
|
||||||
TESTS_WITH_CXX := test_agc_outer_loop
|
TESTS_WITH_CXX := test_agc_outer_loop
|
||||||
|
|
||||||
|
# ADAR1000 error/status propagation tests -- link real ADAR1000_Manager.o + mocks
|
||||||
|
TESTS_ADAR_STATUS := test_adar_init_aborts_on_scratchpad_mismatch \
|
||||||
|
test_adar_spi_write_failure_propagates \
|
||||||
|
test_adar_adc_timeout_returns_nan \
|
||||||
|
test_adar_mode_switch_does_not_lie \
|
||||||
|
test_adar_comm_stats_increment
|
||||||
|
|
||||||
# GPS driver tests (need mocks + GPS source + -lm)
|
# GPS driver tests (need mocks + GPS source + -lm)
|
||||||
TESTS_GPS := test_um982_gps
|
TESTS_GPS := test_um982_gps
|
||||||
|
|
||||||
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) $(TESTS_STANDALONE) $(TESTS_WITH_PLATFORM) $(TESTS_WITH_CXX) $(TESTS_GPS)
|
ALL_TESTS := $(TESTS_WITH_REAL) $(TESTS_MOCK_ONLY) $(TESTS_STANDALONE) $(TESTS_WITH_PLATFORM) $(TESTS_WITH_CXX) $(TESTS_ADAR_STATUS) $(TESTS_GPS)
|
||||||
|
|
||||||
.PHONY: all build test clean \
|
.PHONY: all build test clean \
|
||||||
$(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11 bug12 bug13 bug14 bug15) \
|
$(addprefix test_,bug1 bug2 bug3 bug4 bug5 bug6 bug7 bug8 bug9 bug10 bug11 bug12 bug13 bug14 bug15) \
|
||||||
@@ -204,6 +211,16 @@ test_agc_outer_loop: test_agc_outer_loop.cpp $(CXX_OBJS) $(MOCK_OBJS)
|
|||||||
test_agc: test_agc_outer_loop
|
test_agc: test_agc_outer_loop
|
||||||
./test_agc_outer_loop
|
./test_agc_outer_loop
|
||||||
|
|
||||||
|
# --- ADAR1000 status-propagation test rules ---
|
||||||
|
# Each test links the real ADAR1000_Manager.cpp (compiled as ADAR1000_Manager.o)
|
||||||
|
# against the HAL mock so failure injection drives the production code paths.
|
||||||
|
$(TESTS_ADAR_STATUS): %: %.cpp ADAR1000_Manager.o $(MOCK_OBJS)
|
||||||
|
$(CXX) $(CXXFLAGS) $(INCLUDES) $< ADAR1000_Manager.o $(MOCK_OBJS) -o $@
|
||||||
|
|
||||||
|
.PHONY: test_adar_status
|
||||||
|
test_adar_status: $(TESTS_ADAR_STATUS)
|
||||||
|
@for t in $(TESTS_ADAR_STATUS); do echo "--- $$t ---"; ./$$t || exit 1; done
|
||||||
|
|
||||||
# --- GPS driver rules ---
|
# --- GPS driver rules ---
|
||||||
|
|
||||||
$(GPS_OBJ): $(GPS_SRC)
|
$(GPS_OBJ): $(GPS_SRC)
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ static struct {
|
|||||||
GPIO_PinState val;
|
GPIO_PinState val;
|
||||||
} gpio_read_table[GPIO_READ_TABLE_SIZE];
|
} gpio_read_table[GPIO_READ_TABLE_SIZE];
|
||||||
|
|
||||||
|
/* SPI failure-injection state */
|
||||||
|
static int mock_spi_fail_remaining = 0;
|
||||||
|
static HAL_StatusTypeDef mock_spi_fail_status = HAL_OK;
|
||||||
|
static uint8_t mock_spi_rx_byte = 0;
|
||||||
|
static uint32_t mock_tick_auto_advance = 0;
|
||||||
|
|
||||||
void spy_reset(void)
|
void spy_reset(void)
|
||||||
{
|
{
|
||||||
spy_count = 0;
|
spy_count = 0;
|
||||||
@@ -73,6 +79,26 @@ void spy_reset(void)
|
|||||||
memset(mock_uart_rx, 0, sizeof(mock_uart_rx));
|
memset(mock_uart_rx, 0, sizeof(mock_uart_rx));
|
||||||
mock_uart_tx_len = 0;
|
mock_uart_tx_len = 0;
|
||||||
memset(mock_uart_tx_buf, 0, sizeof(mock_uart_tx_buf));
|
memset(mock_uart_tx_buf, 0, sizeof(mock_uart_tx_buf));
|
||||||
|
mock_spi_fail_remaining = 0;
|
||||||
|
mock_spi_fail_status = HAL_OK;
|
||||||
|
mock_spi_rx_byte = 0;
|
||||||
|
mock_tick_auto_advance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mock_spi_queue_failure(int call_count, HAL_StatusTypeDef status)
|
||||||
|
{
|
||||||
|
mock_spi_fail_remaining = call_count;
|
||||||
|
mock_spi_fail_status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mock_spi_set_rx_byte(uint8_t value)
|
||||||
|
{
|
||||||
|
mock_spi_rx_byte = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mock_set_tick_auto_advance(uint32_t delta)
|
||||||
|
{
|
||||||
|
mock_tick_auto_advance = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpyRecord *spy_get(int index)
|
const SpyRecord *spy_get(int index)
|
||||||
@@ -177,14 +203,16 @@ void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
|
|||||||
|
|
||||||
uint32_t HAL_GetTick(void)
|
uint32_t HAL_GetTick(void)
|
||||||
{
|
{
|
||||||
|
uint32_t result = mock_tick;
|
||||||
spy_push((SpyRecord){
|
spy_push((SpyRecord){
|
||||||
.type = SPY_HAL_GET_TICK,
|
.type = SPY_HAL_GET_TICK,
|
||||||
.port = NULL,
|
.port = NULL,
|
||||||
.pin = 0,
|
.pin = 0,
|
||||||
.value = mock_tick,
|
.value = result,
|
||||||
.extra = NULL
|
.extra = NULL
|
||||||
});
|
});
|
||||||
return mock_tick;
|
mock_tick += mock_tick_auto_advance;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HAL_Delay(uint32_t Delay)
|
void HAL_Delay(uint32_t Delay)
|
||||||
@@ -369,6 +397,7 @@ void mock_tim_set_compare(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t Co
|
|||||||
|
|
||||||
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
|
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
|
||||||
{
|
{
|
||||||
|
(void)pTxData;
|
||||||
spy_push((SpyRecord){
|
spy_push((SpyRecord){
|
||||||
.type = SPY_SPI_TRANSMIT_RECEIVE,
|
.type = SPY_SPI_TRANSMIT_RECEIVE,
|
||||||
.port = NULL,
|
.port = NULL,
|
||||||
@@ -376,11 +405,19 @@ HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxD
|
|||||||
.value = Timeout,
|
.value = Timeout,
|
||||||
.extra = hspi
|
.extra = hspi
|
||||||
});
|
});
|
||||||
|
if (mock_spi_fail_remaining > 0) {
|
||||||
|
mock_spi_fail_remaining--;
|
||||||
|
return mock_spi_fail_status;
|
||||||
|
}
|
||||||
|
if (pRxData && Size > 0) {
|
||||||
|
pRxData[Size - 1] = mock_spi_rx_byte;
|
||||||
|
}
|
||||||
return HAL_OK;
|
return HAL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
|
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
|
||||||
{
|
{
|
||||||
|
(void)pData;
|
||||||
spy_push((SpyRecord){
|
spy_push((SpyRecord){
|
||||||
.type = SPY_SPI_TRANSMIT,
|
.type = SPY_SPI_TRANSMIT,
|
||||||
.port = NULL,
|
.port = NULL,
|
||||||
@@ -388,6 +425,10 @@ HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint
|
|||||||
.value = Timeout,
|
.value = Timeout,
|
||||||
.extra = hspi
|
.extra = hspi
|
||||||
});
|
});
|
||||||
|
if (mock_spi_fail_remaining > 0) {
|
||||||
|
mock_spi_fail_remaining--;
|
||||||
|
return mock_spi_fail_status;
|
||||||
|
}
|
||||||
return HAL_OK;
|
return HAL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,11 @@ void mock_set_tick(uint32_t tick);
|
|||||||
/* Advance the mock tick by `delta` ms */
|
/* Advance the mock tick by `delta` ms */
|
||||||
void mock_advance_tick(uint32_t delta);
|
void mock_advance_tick(uint32_t delta);
|
||||||
|
|
||||||
|
/* Each subsequent HAL_GetTick() call advances the mock tick by `delta` ms after
|
||||||
|
* returning the current value. Use to drive timeout loops without wall-clock
|
||||||
|
* waits. Set to 0 (default) for stable tick. */
|
||||||
|
void mock_set_tick_auto_advance(uint32_t delta);
|
||||||
|
|
||||||
/* ========================= Mock GPIO read returns ================= */
|
/* ========================= Mock GPIO read returns ================= */
|
||||||
|
|
||||||
/* Set the value HAL_GPIO_ReadPin will return for a specific port/pin */
|
/* Set the value HAL_GPIO_ReadPin will return for a specific port/pin */
|
||||||
@@ -212,6 +217,15 @@ void mock_uart_tx_clear(void);
|
|||||||
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
|
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
|
||||||
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
|
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
|
||||||
|
|
||||||
|
/* Queue the next N SPI calls (Transmit and TransmitReceive) to return `status`
|
||||||
|
* instead of HAL_OK. Decremented on each call. Use for failure-propagation tests. */
|
||||||
|
void mock_spi_queue_failure(int call_count, HAL_StatusTypeDef status);
|
||||||
|
|
||||||
|
/* Set the byte that HAL_SPI_TransmitReceive will write at pRxData[Size-1] for
|
||||||
|
* subsequent calls. ADAR1000 reads land at index 2 of a 3-byte transfer, so
|
||||||
|
* this lets tests inject scratchpad / register readback values. */
|
||||||
|
void mock_spi_set_rx_byte(uint8_t value);
|
||||||
|
|
||||||
/* ========================= no_os compat layer ===================== */
|
/* ========================= no_os compat layer ===================== */
|
||||||
|
|
||||||
void no_os_udelay(uint32_t usecs);
|
void no_os_udelay(uint32_t usecs);
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
// test_adar_adc_timeout_returns_nan.cpp
|
||||||
|
//
|
||||||
|
// readTemperature() previously returned -50.0 C silently when the on-chip
|
||||||
|
// ADC never completed a conversion (raw=0 timeout sentinel mapped through
|
||||||
|
// (raw * 0.5) - 50). A hung chip looked like a cold radar.
|
||||||
|
//
|
||||||
|
// New: on ADC timeout or any comm failure inside adarAdcRead(),
|
||||||
|
// readTemperature returns NaN, and comm_stats_.adc_timeouts increments.
|
||||||
|
// checkSystemHealth() in main.cpp uses isnan() to route NaN to the comm-error
|
||||||
|
// bucket (which triggers attemptErrorRecovery).
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "stm32_hal_mock.h"
|
||||||
|
#include "ADAR1000_Manager.h"
|
||||||
|
|
||||||
|
uint8_t GUI_start_flag_received = 0;
|
||||||
|
uint8_t USB_Buffer[64] = {0};
|
||||||
|
extern "C" void Error_Handler(void) {}
|
||||||
|
|
||||||
|
static int tests_passed = 0;
|
||||||
|
static int tests_total = 0;
|
||||||
|
|
||||||
|
#define RUN_TEST(fn) \
|
||||||
|
do { \
|
||||||
|
tests_total++; \
|
||||||
|
printf(" [%2d] %-65s ", tests_total, #fn); \
|
||||||
|
fn(); \
|
||||||
|
tests_passed++; \
|
||||||
|
printf("PASS\n"); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// Helper: bring all 4 devices through init successfully so readTemperature()
|
||||||
|
// gets past its "not initialized" guard.
|
||||||
|
static void init_devices_clean(ADAR1000Manager& mgr)
|
||||||
|
{
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default mock returns 0x00 to every read after init. Since bit 0 of the ADC
|
||||||
|
// status register never goes high, the polling loop in adarAdcRead is supposed
|
||||||
|
// to time out after 100 ms. Auto-advancing the tick on every HAL_GetTick()
|
||||||
|
// drives that timer forward without wall-clock waits.
|
||||||
|
static void test_polling_timeout_returns_nan()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
|
||||||
|
// Switch the rx-byte back to 0x00 so the ADC status bit stays low forever
|
||||||
|
// and the polling loop runs to its 100 ms watchdog.
|
||||||
|
mock_spi_set_rx_byte(0x00);
|
||||||
|
mock_set_tick_auto_advance(150); // each HAL_GetTick() advances 150 ms
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
float t = mgr.readTemperature(0);
|
||||||
|
|
||||||
|
assert(std::isnan(t));
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.adc_timeouts >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPI failure during adarAdcRead's start-conversion write must also count as
|
||||||
|
// a timeout (caller has no valid ADC reading) and produce NaN.
|
||||||
|
static void test_start_conv_spi_failure_returns_nan()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
// Fail the very next SPI call -- the start-conversion write inside
|
||||||
|
// adarAdcRead. Following polling reads will also fail, but the function
|
||||||
|
// bails on the start-conv write first.
|
||||||
|
mock_spi_queue_failure(100, HAL_ERROR);
|
||||||
|
|
||||||
|
float t = mgr.readTemperature(0);
|
||||||
|
assert(std::isnan(t));
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.adc_timeouts >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healthy path: ADC bit 0 is high on the first poll (rx_byte = 0xA5 after init),
|
||||||
|
// so adarAdcRead exits the loop immediately, and readTemperature returns a
|
||||||
|
// finite number. This guards against false-positive NaN from the new code path.
|
||||||
|
static void test_healthy_adc_returns_finite_temp()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
float t = mgr.readTemperature(0);
|
||||||
|
|
||||||
|
assert(!std::isnan(t));
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.adc_timeouts == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("=== ADAR1000 ADC timeout -> NaN propagation tests ===\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_polling_timeout_returns_nan);
|
||||||
|
RUN_TEST(test_start_conv_spi_failure_returns_nan);
|
||||||
|
RUN_TEST(test_healthy_adc_returns_finite_temp);
|
||||||
|
|
||||||
|
printf("=== Results: %d/%d passed ===\n", tests_passed, tests_total);
|
||||||
|
return (tests_passed == tests_total) ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
// test_adar_comm_stats_increment.cpp
|
||||||
|
//
|
||||||
|
// Documents and locks the CommStats observability surface added in this PR.
|
||||||
|
// Without this test, a future "simplification" could quietly remove the
|
||||||
|
// counters and nothing else would catch it.
|
||||||
|
//
|
||||||
|
// Contract:
|
||||||
|
// - Every successful adarWrite increments writes_ok.
|
||||||
|
// - Every failed adarWrite increments writes_fail and updates last_fail_dev.
|
||||||
|
// - Every successful adarRead increments reads_ok.
|
||||||
|
// - Every failed adarRead increments reads_fail and updates last_fail_dev.
|
||||||
|
// - resetCommStats() zeroes all counters and resets last_fail_dev to 0xFF.
|
||||||
|
// - PR2 may promote a richer OpStatus enum return type; this struct is the
|
||||||
|
// forward-compatible observability hook either way.
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "stm32_hal_mock.h"
|
||||||
|
#include "ADAR1000_Manager.h"
|
||||||
|
|
||||||
|
uint8_t GUI_start_flag_received = 0;
|
||||||
|
uint8_t USB_Buffer[64] = {0};
|
||||||
|
extern "C" void Error_Handler(void) {}
|
||||||
|
|
||||||
|
static int tests_passed = 0;
|
||||||
|
static int tests_total = 0;
|
||||||
|
|
||||||
|
#define RUN_TEST(fn) \
|
||||||
|
do { \
|
||||||
|
tests_total++; \
|
||||||
|
printf(" [%2d] %-65s ", tests_total, #fn); \
|
||||||
|
fn(); \
|
||||||
|
tests_passed++; \
|
||||||
|
printf("PASS\n"); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static void test_default_stats_are_zero()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.writes_ok == 0);
|
||||||
|
assert(s.writes_fail == 0);
|
||||||
|
assert(s.reads_ok == 0);
|
||||||
|
assert(s.reads_fail == 0);
|
||||||
|
assert(s.adc_timeouts == 0);
|
||||||
|
assert(s.last_fail_dev == 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_successful_write_increments_writes_ok()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
bool ok = mgr.writeRegister(0, 0x010, 0x42);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.writes_ok == 1);
|
||||||
|
assert(s.writes_fail == 0);
|
||||||
|
assert(s.last_fail_dev == 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_failed_write_increments_writes_fail_and_records_dev()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
mock_spi_queue_failure(1, HAL_ERROR);
|
||||||
|
bool ok = mgr.writeRegister(2, 0x010, 0x42); // dev[2]
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.writes_ok == 0);
|
||||||
|
assert(s.writes_fail == 1);
|
||||||
|
assert(s.last_fail_dev == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_successful_read_increments_reads_ok()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
uint8_t value = 0;
|
||||||
|
bool ok = mgr.adarReadChecked(1, 0x010, &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 0xA5);
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.reads_ok == 1);
|
||||||
|
assert(s.reads_fail == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_failed_read_increments_reads_fail_and_records_dev()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
// adarReadChecked sequence:
|
||||||
|
// 1. adarWrite(SDO active) -- HAL_SPI_Transmit
|
||||||
|
// 2. HAL_SPI_TransmitReceive (the actual read) <-- we want this to fail
|
||||||
|
// 3. adarWrite(SDO inactive)
|
||||||
|
// Letting calls 1+2 fail (queue 2 failures) covers the case where SDO-active
|
||||||
|
// also fails -- adarReadChecked aborts after #1 and bumps reads_fail.
|
||||||
|
mock_spi_queue_failure(2, HAL_ERROR);
|
||||||
|
|
||||||
|
uint8_t value = 0xCC; // pre-poison to confirm out param gets cleared
|
||||||
|
bool ok = mgr.adarReadChecked(3, 0x010, &value);
|
||||||
|
assert(ok == false);
|
||||||
|
assert(value == 0); // adarReadChecked must zero the out param on failure
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.reads_fail >= 1);
|
||||||
|
assert(s.last_fail_dev == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset must clear everything to defaults, including last_fail_dev back to 0xFF.
|
||||||
|
static void test_reset_clears_all_counters()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
// Generate some non-zero state in every field.
|
||||||
|
mgr.writeRegister(0, 0x010, 0x42); // writes_ok++
|
||||||
|
mock_spi_queue_failure(1, HAL_ERROR);
|
||||||
|
mgr.writeRegister(1, 0x010, 0x42); // writes_fail++, last_fail_dev=1
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
uint8_t v = 0;
|
||||||
|
mgr.adarReadChecked(0, 0x010, &v); // reads_ok++
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.writes_ok > 0);
|
||||||
|
assert(s.writes_fail > 0);
|
||||||
|
assert(s.reads_ok > 0);
|
||||||
|
assert(s.last_fail_dev != 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
const auto& s = mgr.getCommStats();
|
||||||
|
assert(s.writes_ok == 0);
|
||||||
|
assert(s.writes_fail == 0);
|
||||||
|
assert(s.reads_ok == 0);
|
||||||
|
assert(s.reads_fail == 0);
|
||||||
|
assert(s.adc_timeouts == 0);
|
||||||
|
assert(s.last_fail_dev == 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out-of-range device index counts as a write/read failure (not a silent skip).
|
||||||
|
// This is the kind of bug that would otherwise hide behind a "device 4 ignored"
|
||||||
|
// log line and never escalate to the caller.
|
||||||
|
static void test_out_of_range_device_index_counts_as_failure()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
bool wok = mgr.writeRegister(99, 0x010, 0x42);
|
||||||
|
assert(wok == false);
|
||||||
|
assert(mgr.getCommStats().writes_fail == 1);
|
||||||
|
assert(mgr.getCommStats().last_fail_dev == 99);
|
||||||
|
|
||||||
|
uint8_t v = 0;
|
||||||
|
bool rok = mgr.adarReadChecked(99, 0x010, &v);
|
||||||
|
assert(rok == false);
|
||||||
|
assert(mgr.getCommStats().reads_fail == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("=== ADAR1000 CommStats observability tests ===\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_default_stats_are_zero);
|
||||||
|
RUN_TEST(test_successful_write_increments_writes_ok);
|
||||||
|
RUN_TEST(test_failed_write_increments_writes_fail_and_records_dev);
|
||||||
|
RUN_TEST(test_successful_read_increments_reads_ok);
|
||||||
|
RUN_TEST(test_failed_read_increments_reads_fail_and_records_dev);
|
||||||
|
RUN_TEST(test_reset_clears_all_counters);
|
||||||
|
RUN_TEST(test_out_of_range_device_index_counts_as_failure);
|
||||||
|
|
||||||
|
printf("=== Results: %d/%d passed ===\n", tests_passed, tests_total);
|
||||||
|
return (tests_passed == tests_total) ? 0 : 1;
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
// test_adar_init_aborts_on_scratchpad_mismatch.cpp
|
||||||
|
//
|
||||||
|
// previously initializeSingleDevice() logged a
|
||||||
|
// warning when the scratchpad readback didn't match 0xA5 but still set
|
||||||
|
// devices_[i]->initialized = true and returned true. That meant
|
||||||
|
// initializeAllDevices() would happily report success with four dead chips.
|
||||||
|
//
|
||||||
|
// New: any scratchpad mismatch aborts init. The device stays
|
||||||
|
// uninitialized, the function returns false, and downstream calls (e.g.
|
||||||
|
// readTemperature) return the "not initialized" sentinel -273.15.
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "stm32_hal_mock.h"
|
||||||
|
#include "ADAR1000_Manager.h"
|
||||||
|
|
||||||
|
uint8_t GUI_start_flag_received = 0;
|
||||||
|
uint8_t USB_Buffer[64] = {0};
|
||||||
|
extern "C" void Error_Handler(void) {}
|
||||||
|
|
||||||
|
static int tests_passed = 0;
|
||||||
|
static int tests_total = 0;
|
||||||
|
|
||||||
|
#define RUN_TEST(fn) \
|
||||||
|
do { \
|
||||||
|
tests_total++; \
|
||||||
|
printf(" [%2d] %-65s ", tests_total, #fn); \
|
||||||
|
fn(); \
|
||||||
|
tests_passed++; \
|
||||||
|
printf("PASS\n"); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// With default mock state, HAL_SPI_TransmitReceive writes 0x00 into rx_buffer[2].
|
||||||
|
// The scratchpad write programs 0xA5; the readback gets 0x00; mismatch -> abort.
|
||||||
|
static void test_scratchpad_mismatch_aborts_init()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
|
||||||
|
assert(ok == false); // would have been true under the old bug
|
||||||
|
|
||||||
|
// Downstream proof: readTemperature must report "not initialized" sentinel,
|
||||||
|
// not a temperature value, because the device is not marked initialized.
|
||||||
|
float t = mgr.readTemperature(0);
|
||||||
|
assert(t == -273.15f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When scratchpad readback matches, init succeeds. mock_spi_set_rx_byte(0xA5)
|
||||||
|
// makes every read return 0xA5, satisfying the verify step.
|
||||||
|
static void test_scratchpad_match_lets_init_succeed()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
|
||||||
|
assert(ok == true);
|
||||||
|
|
||||||
|
// Now temperature read should produce a real number (not -273.15 sentinel).
|
||||||
|
// adarAdcRead loops on bit 0 of REG_ADC_CONTROL; with rx_byte=0xA5 (bit 0 = 1)
|
||||||
|
// the loop exits immediately, then REG_ADC_OUT also reads 0xA5.
|
||||||
|
float t = mgr.readTemperature(0);
|
||||||
|
assert(!std::isnan(t));
|
||||||
|
assert(t != -273.15f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init aborts on the first device that fails. Stats reflect partial progress
|
||||||
|
// (some writes_ok before the abort) which is the trend signal callers will
|
||||||
|
// query via getCommStats().
|
||||||
|
static void test_init_failure_recorded_in_stats()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
// Several writes happened before scratchpad verify failed: soft reset,
|
||||||
|
// configA, RAM bypass, ADC enable, scratchpad-write itself, and the
|
||||||
|
// SDO-active toggles inside the scratchpad-read.
|
||||||
|
assert(stats.writes_ok > 0);
|
||||||
|
// The scratchpad readback succeeded at the SPI layer (HAL_OK) but produced
|
||||||
|
// a wrong value -- that's not a read failure, it's a verify mismatch. So
|
||||||
|
// reads_fail can stay 0 in the pure-mismatch case.
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("=== ADAR1000 init scratchpad-mismatch propagation tests ===\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_scratchpad_mismatch_aborts_init);
|
||||||
|
RUN_TEST(test_scratchpad_match_lets_init_succeed);
|
||||||
|
RUN_TEST(test_init_failure_recorded_in_stats);
|
||||||
|
|
||||||
|
printf("=== Results: %d/%d passed ===\n", tests_passed, tests_total);
|
||||||
|
return (tests_passed == tests_total) ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// test_adar_mode_switch_does_not_lie.cpp
|
||||||
|
//
|
||||||
|
// setAllDevicesTXMode / setAllDevicesRXMode previously updated current_mode_
|
||||||
|
// (both global and per-device) before issuing the SPI writes that actually
|
||||||
|
// reconfigure the chip, then returned true unconditionally. A SPI failure
|
||||||
|
// during the mode switch left software believing it was in TX mode while the
|
||||||
|
// hardware was still in RX (or vice-versa) -- a real safety hazard given
|
||||||
|
// that PA biasing is mode-dependent.
|
||||||
|
//
|
||||||
|
// New: current_mode_ only updates if every underlying write
|
||||||
|
// succeeded; otherwise the function returns false and the mode flag is left
|
||||||
|
// at its last-known-good value.
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "stm32_hal_mock.h"
|
||||||
|
#include "ADAR1000_Manager.h"
|
||||||
|
|
||||||
|
uint8_t GUI_start_flag_received = 0;
|
||||||
|
uint8_t USB_Buffer[64] = {0};
|
||||||
|
extern "C" void Error_Handler(void) {}
|
||||||
|
|
||||||
|
static int tests_passed = 0;
|
||||||
|
static int tests_total = 0;
|
||||||
|
|
||||||
|
#define RUN_TEST(fn) \
|
||||||
|
do { \
|
||||||
|
tests_total++; \
|
||||||
|
printf(" [%2d] %-65s ", tests_total, #fn); \
|
||||||
|
fn(); \
|
||||||
|
tests_passed++; \
|
||||||
|
printf("PASS\n"); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static void init_devices_clean(ADAR1000Manager& mgr)
|
||||||
|
{
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
assert(ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
// After a clean init, the manager is in TX mode (initializeAllDevices ends
|
||||||
|
// with setAllDevicesTXMode). Force RX mode under sustained SPI failure: must
|
||||||
|
// return false AND must not flip current_mode_ to RX.
|
||||||
|
static void test_failed_rx_switch_does_not_update_mode()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::TX);
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
mock_spi_queue_failure(10000, HAL_ERROR);
|
||||||
|
|
||||||
|
bool ok = mgr.setAllDevicesRXMode();
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
// Mode flag must NOT have moved -- this is the dangerous lie we are fixing.
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::TX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symmetric case: cleanly transition to RX, then fail TX setup. Mode flag
|
||||||
|
// must stay RX.
|
||||||
|
static void test_failed_tx_switch_does_not_update_mode()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
bool rx_ok = mgr.setAllDevicesRXMode();
|
||||||
|
assert(rx_ok);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::RX);
|
||||||
|
|
||||||
|
mgr.resetCommStats();
|
||||||
|
mock_spi_queue_failure(10000, HAL_ERROR);
|
||||||
|
|
||||||
|
bool ok = mgr.setAllDevicesTXMode();
|
||||||
|
assert(ok == false);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::RX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healthy round-trip: mode flag tracks the call that was made. Guards against
|
||||||
|
// the new code path accidentally refusing to advance the mode on success.
|
||||||
|
static void test_clean_mode_switches_update_flag()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
init_devices_clean(mgr);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::TX);
|
||||||
|
|
||||||
|
mock_spi_set_rx_byte(0xA5);
|
||||||
|
bool to_rx = mgr.setAllDevicesRXMode();
|
||||||
|
assert(to_rx);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::RX);
|
||||||
|
|
||||||
|
bool to_tx = mgr.setAllDevicesTXMode();
|
||||||
|
assert(to_tx);
|
||||||
|
assert(mgr.getCurrentMode() == ADAR1000Manager::BeamDirection::TX);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("=== ADAR1000 mode-switch honesty tests ===\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_failed_rx_switch_does_not_update_mode);
|
||||||
|
RUN_TEST(test_failed_tx_switch_does_not_update_mode);
|
||||||
|
RUN_TEST(test_clean_mode_switches_update_flag);
|
||||||
|
|
||||||
|
printf("=== Results: %d/%d passed ===\n", tests_passed, tests_total);
|
||||||
|
return (tests_passed == tests_total) ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// test_adar_spi_write_failure_propagates.cpp
|
||||||
|
//
|
||||||
|
// When HAL_SPI_Transmit / HAL_SPI_TransmitReceive returns HAL_ERROR, every
|
||||||
|
// caller above must see the failure rather than silently continuing on.
|
||||||
|
// Previously adarWrite() returned void and dropped the SPI status on the
|
||||||
|
// floor, so a dead bus produced four "successful" inits.
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "stm32_hal_mock.h"
|
||||||
|
#include "ADAR1000_Manager.h"
|
||||||
|
|
||||||
|
uint8_t GUI_start_flag_received = 0;
|
||||||
|
uint8_t USB_Buffer[64] = {0};
|
||||||
|
extern "C" void Error_Handler(void) {}
|
||||||
|
|
||||||
|
static int tests_passed = 0;
|
||||||
|
static int tests_total = 0;
|
||||||
|
|
||||||
|
#define RUN_TEST(fn) \
|
||||||
|
do { \
|
||||||
|
tests_total++; \
|
||||||
|
printf(" [%2d] %-65s ", tests_total, #fn); \
|
||||||
|
fn(); \
|
||||||
|
tests_passed++; \
|
||||||
|
printf("PASS\n"); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// First SPI transmit fails (the soft-reset write inside initializeSingleDevice).
|
||||||
|
// Init must abort immediately and report false.
|
||||||
|
static void test_first_spi_failure_aborts_init()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
mock_spi_queue_failure(1, HAL_ERROR);
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.writes_fail >= 1);
|
||||||
|
assert(stats.last_fail_dev == 0); // failure was on dev[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sustained SPI failure (every call) must not produce a green init even if
|
||||||
|
// individual writes early in the sequence have already counted as failures
|
||||||
|
// without aborting -- the function must still return false at the end.
|
||||||
|
static void test_sustained_spi_failure_aborts_init()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
mock_spi_queue_failure(10000, HAL_ERROR);
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
|
||||||
|
bool ok = mgr.initializeAllDevices();
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.writes_ok == 0);
|
||||||
|
assert(stats.writes_fail >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// adarSetBit must NOT proceed with the write when the read-modify-write read
|
||||||
|
// fails -- otherwise it would clobber every other bit in the register by
|
||||||
|
// writing back (0 | mask) over a register whose actual contents are unknown.
|
||||||
|
// We use setTRSwitchPosition (which calls adarSetBit) and inject a failure
|
||||||
|
// on the read leg.
|
||||||
|
static void test_set_bit_skips_write_on_read_failure()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
mgr.resetCommStats();
|
||||||
|
|
||||||
|
// adarSetBit calls adarReadChecked first, which itself does:
|
||||||
|
// 1. adarWrite(SDO active) -- HAL_SPI_Transmit
|
||||||
|
// 2. HAL_SPI_TransmitReceive (the actual read)
|
||||||
|
// 3. adarWrite(SDO inactive) -- HAL_SPI_Transmit
|
||||||
|
// We want the actual read (call #2) to fail. Queue failure starting at
|
||||||
|
// call 2 by letting call 1 succeed first via a one-shot prime, then
|
||||||
|
// queueing the failure.
|
||||||
|
//
|
||||||
|
// Simpler approach: queue a failure of 100 calls, then call setTRSwitchPosition
|
||||||
|
// and assert reads_fail >= 1 and writes_fail >= 1 (the SDO-active write
|
||||||
|
// also fails). The key invariant: even though the read-modify-write was
|
||||||
|
// attempted, the function returned false.
|
||||||
|
mock_spi_queue_failure(100, HAL_ERROR);
|
||||||
|
|
||||||
|
bool ok = mgr.setTRSwitchPosition(0, true);
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.reads_fail >= 1 || stats.writes_fail >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode-switch must not fall through to another write block on the device that
|
||||||
|
// failed -- and the per-device current_mode must not be updated.
|
||||||
|
static void test_mode_switch_failure_propagates()
|
||||||
|
{
|
||||||
|
spy_reset();
|
||||||
|
mock_spi_set_rx_byte(0xA5); // make scratchpad verify succeed first
|
||||||
|
ADAR1000Manager mgr;
|
||||||
|
bool init_ok = mgr.initializeAllDevices();
|
||||||
|
assert(init_ok);
|
||||||
|
|
||||||
|
// Now inject sustained SPI failure for the mode switch.
|
||||||
|
mgr.resetCommStats();
|
||||||
|
mock_spi_queue_failure(10000, HAL_ERROR);
|
||||||
|
|
||||||
|
bool ok = mgr.setAllDevicesRXMode();
|
||||||
|
assert(ok == false);
|
||||||
|
|
||||||
|
const auto& stats = mgr.getCommStats();
|
||||||
|
assert(stats.writes_fail >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
printf("=== ADAR1000 SPI write-failure propagation tests ===\n");
|
||||||
|
|
||||||
|
RUN_TEST(test_first_spi_failure_aborts_init);
|
||||||
|
RUN_TEST(test_sustained_spi_failure_aborts_init);
|
||||||
|
RUN_TEST(test_set_bit_skips_write_on_read_failure);
|
||||||
|
RUN_TEST(test_mode_switch_failure_propagates);
|
||||||
|
|
||||||
|
printf("=== Results: %d/%d passed ===\n", tests_passed, tests_total);
|
||||||
|
return (tests_passed == tests_total) ? 0 : 1;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user