diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 846fb57..da285ed 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,7 @@ - Prevented echoed or oversized RS485 frames from starving the XIAO sensor loop. - Silently discard malformed partial RS485 echoes instead of emitting recursive `invalid_json` responses. - Replaced sensor auto-detection with the verified installation settings: BNO086 address `0x4B` and MAX-M10S UART at 38400 baud. +- Added fixed-address BNO086 power-up retries, runtime recovery, and report restoration after sensor resets. ## Unreleased diff --git a/firmware/xiao-esp32c6-sensor-node/README.md b/firmware/xiao-esp32c6-sensor-node/README.md index 3c62a0a..0631ba7 100644 --- a/firmware/xiao-esp32c6-sensor-node/README.md +++ b/firmware/xiao-esp32c6-sensor-node/README.md @@ -12,6 +12,7 @@ This Arduino sketch reads a BNO086 IMU and MAX-M10S GPS and publishes versioned, Power both sensors from 3.3V with shared ground. Required Arduino libraries are Adafruit BNO08x, TinyGPSPlus, and ArduinoJson 6.x. Enable **USB CDC On Boot** for diagnostics. GPS uses hardware UART0 at the verified 38400 baud; RS485 uses hardware UART1. The BNO086 uses the verified `0x4B` I2C address on D7/D8. If initialization fails, verify 3.3V/GND, SDA/SCL wiring, and the breakout's PS0/PS1 configuration. +Firmware retries the fixed address during power-up and periodically retries afterward, so a slow BNO086 power-up does not require cycling the XIAO. If the BNO086 resets at runtime, rotation-vector reporting is enabled again automatically. The node sends `sensor_status` at 115200 baud every 200 ms. See `docs/SENSOR_NODE_PROTOCOL.md`. The initial firmware is transmit-only; add a dashboard receiver after bench-testing the node, RS485 polarity, and sensor axes. diff --git a/firmware/xiao-esp32c6-sensor-node/imu.cpp b/firmware/xiao-esp32c6-sensor-node/imu.cpp index 5ef6197..f3c7a4b 100644 --- a/firmware/xiao-esp32c6-sensor-node/imu.cpp +++ b/firmware/xiao-esp32c6-sensor-node/imu.cpp @@ -8,7 +8,14 @@ namespace { Adafruit_BNO08x bno086(-1); sh2_SensorValue_t value; bool initialized = false; +unsigned long lastInitAttemptMs = 0; float radiansToDegrees(float r) { return r * 180.0f / PI; } +bool tryInitImu() { + lastInitAttemptMs = millis(); + initialized = bno086.begin_I2C(BNO086_I2C_ADDRESS, &Wire); + if (initialized) initialized = bno086.enableReport(SH2_ROTATION_VECTOR, IMU_REPORT_INTERVAL_US); + return initialized; +} void updateEuler(ImuState& s) { float sinr = 2 * (s.real * s.i + s.j * s.k); float cosr = 1 - 2 * (s.i * s.i + s.j * s.j); @@ -27,13 +34,29 @@ bool initImu() { delay(100); Serial.print("Starting BNO086 at 0x"); Serial.println(BNO086_I2C_ADDRESS, HEX); - initialized = bno086.begin_I2C(BNO086_I2C_ADDRESS, &Wire); - if (initialized) initialized = bno086.enableReport(SH2_ROTATION_VECTOR, IMU_REPORT_INTERVAL_US); - return initialized; + for (int attempt = 1; attempt <= BNO086_INIT_ATTEMPTS; attempt++) { + if (tryInitImu()) return true; + Serial.print("BNO086 init attempt failed: "); + Serial.println(attempt); + delay(BNO086_INIT_RETRY_DELAY_MS); + } + return false; +} + +void maintainImu(ImuState& s) { + if (initialized || millis() - lastInitAttemptMs < BNO086_RUNTIME_RETRY_MS) return; + Serial.println("Retrying BNO086 initialization"); + s.online = tryInitImu(); + if (s.online) Serial.println("BNO086 recovered"); } void updateImu(ImuState& s) { s.online = initialized; + if (initialized && bno086.wasReset()) { + initialized = bno086.enableReport(SH2_ROTATION_VECTOR, IMU_REPORT_INTERVAL_US); + s.online = initialized; + if (!initialized) return; + } if (!initialized || !bno086.getSensorEvent(&value) || value.sensorId != SH2_ROTATION_VECTOR) return; s.i = value.un.rotationVector.i; s.j = value.un.rotationVector.j; s.k = value.un.rotationVector.k; s.real = value.un.rotationVector.real; diff --git a/firmware/xiao-esp32c6-sensor-node/imu.h b/firmware/xiao-esp32c6-sensor-node/imu.h index 22e97fd..5364cb8 100644 --- a/firmware/xiao-esp32c6-sensor-node/imu.h +++ b/firmware/xiao-esp32c6-sensor-node/imu.h @@ -1,5 +1,5 @@ #pragma once #include "sensor_state.h" bool initImu(); +void maintainImu(ImuState& state); void updateImu(ImuState& state); - diff --git a/firmware/xiao-esp32c6-sensor-node/sensor_node_config.h b/firmware/xiao-esp32c6-sensor-node/sensor_node_config.h index 91a568b..29c4a07 100644 --- a/firmware/xiao-esp32c6-sensor-node/sensor_node_config.h +++ b/firmware/xiao-esp32c6-sensor-node/sensor_node_config.h @@ -13,6 +13,9 @@ #define GPS_TX_PIN D10 #define BNO086_I2C_ADDRESS 0x4B #define BNO086_I2C_CLOCK_HZ 100000 +#define BNO086_INIT_ATTEMPTS 8 +#define BNO086_INIT_RETRY_DELAY_MS 250 +#define BNO086_RUNTIME_RETRY_MS 5000 #define GPS_BAUD 38400 #define RS485_BAUD 115200 #define IMU_REPORT_INTERVAL_US 20000 diff --git a/firmware/xiao-esp32c6-sensor-node/xiao-esp32c6-sensor-node.ino b/firmware/xiao-esp32c6-sensor-node/xiao-esp32c6-sensor-node.ino index ae67ab1..22a7bb1 100644 --- a/firmware/xiao-esp32c6-sensor-node/xiao-esp32c6-sensor-node.ino +++ b/firmware/xiao-esp32c6-sensor-node/xiao-esp32c6-sensor-node.ino @@ -20,7 +20,7 @@ void setup() { } void loop() { - updateGps(gpsSerial, gpsState); updateImu(imuState); + updateGps(gpsSerial, gpsState); maintainImu(imuState); updateImu(imuState); pollRs485Commands(rs485Serial, imuState); String calibrationResult; if (updateLevelCalibration(imuState, calibrationResult)) { diff --git a/tests/test_xiao_sensor_node_contract.py b/tests/test_xiao_sensor_node_contract.py index 76556ca..0ba1e7b 100644 --- a/tests/test_xiao_sensor_node_contract.py +++ b/tests/test_xiao_sensor_node_contract.py @@ -35,6 +35,9 @@ def test_imu_uses_verified_i2c_address(): imu = source("imu.cpp") assert "BNO086_I2C_ADDRESS 0x4B" in config assert "findBnoAddress" not in imu + assert "BNO086_INIT_ATTEMPTS" in imu + assert "BNO086_RUNTIME_RETRY_MS" in imu + assert "bno086.wasReset()" in imu def test_gps_uses_verified_uart_baud(): config = source("sensor_node_config.h")