# LSM6DS External Component Bug Investigation ## Purpose This document records the investigation and correction of an LSM6DS external-component bug found while diagnosing recurring ESP32-C6 interrupt-watchdog resets in the RV level sensor. It separates confirmed driver bugs, supporting diagnostic evidence, fixes made in the `rv-lsm6ds` branch, validation results, and the still-unproven relationship between these bugs and the interrupt-watchdog resets. A concise upstream bug report is included at the end. ------------------------------------------------------------------------ ## 1. Test environment ### Hardware - Seeed Studio XIAO ESP32-C6 - Modulino Movement / LSM6DSOXTR IMU - I2C address: `0x6A` - I2C SDA: GPIO22 - I2C SCL: GPIO23 ### Software - ESPHome 2026.8.2 - ESP-IDF framework - External component fork: `https://git.mr2web.net/mr2web/esphome-lsm6ds.git` - Working branch: `rv-lsm6ds` Relevant configuration: ``` yaml lsm6ds_i2c: id: imu i2c_id: i2c_bus address: 0x6A accel_range: 2G gyro_range: 250DPS accel_odr: 104HZ gyro_odr: "OFF" update_interval: 10ms ``` The accelerometer intentionally remains at 104 Hz. High-rate sampling is required by the RV leveling application because lower sampling previously produced vibration/aliasing-related drift. ------------------------------------------------------------------------ ## 2. Original investigation context The RV level sensor experienced recurring hangs followed by ESP32-C6 interrupt-watchdog resets: ``` text Reason: Interrupt wdt Crashed core: 0 ``` During a live failure, ESPHome API/entity updates stopped while the device continued responding to ping. It later rebooted and reported the watchdog reset. The leveling calculation uses accelerometer data only. Gyroscope and temperature data are not required for leveling. Inspection of the external component showed that its polling `update()` function reads `STATUS_REG` and conditionally reads/publishes gyroscope, accelerometer and temperature data. At a 10 ms polling interval this is a high-frequency I2C path. ------------------------------------------------------------------------ ## 3. Adding support for ODR OFF The C++ component already defined: ``` cpp LSM6DS_ODR_OFF = 0x00 ``` but the ESPHome configuration schema did not expose `OFF`. This was corrected so YAML could use: ``` yaml gyro_odr: "OFF" ``` `OFF` is quoted because an unquoted YAML value may be interpreted as boolean false. Commit: ``` text 41cb715d6 lsm6ds: expose OFF output data rate ``` ------------------------------------------------------------------------ ## 4. Disabled-sensor read guards After configuring `gyro_odr: "OFF"`, the driver continued publishing repeated frozen gyroscope values. A guard was added so a disabled sensor would not be read even if the status test appeared true: ``` cpp if (this->gyro_odr_ != LSM6DS_ODR_OFF && ...) { // gyro read } if (this->accel_odr_ != LSM6DS_ODR_OFF && ...) { // accelerometer read } ``` Commit: ``` text 3b86faa44 lsm6ds: skip reads for disabled sensors ``` After this change: - gyroscope publications stopped; - temperature continued to update; - accelerometer publications unexpectedly disappeared; - the application reported `IMU DATA INVALID`. Because temperature was still read successfully, the IMU and I2C communication were still functioning. This pointed to the interpretation of `STATUS_REG`. ------------------------------------------------------------------------ ## 5. STATUS_REG diagnostic Temporary instrumentation was added after reading register `0x1E`: ``` cpp uint8_t status; if (!this->read_register(0x1E, &status, 1)) return; ESP_LOGW(TAG, "STATUS=0x%02X accel_odr=0x%02X gyro_odr=0x%02X", status, static_cast(this->accel_odr_), static_cast(this->gyro_odr_)); ``` Diagnostic commits: ``` text e2d4b947e lsm6ds: add temporary status diagnostics 53218bd4d lsm6ds: raise temporary status diagnostic to warning ``` With: ``` text accel_odr = 0x04 (104 Hz) gyro_odr = 0x00 (OFF) ``` the device repeatedly reported: ``` text STATUS=0x01 accel_odr=0x04 gyro_odr=0x00 STATUS=0x05 accel_odr=0x04 gyro_odr=0x00 ``` `0x02` was not observed. `0x05` is `0x01 | 0x04` and appeared when temperature data was also available. This demonstrated that `0x01` was the accelerometer-ready flag, not the gyroscope-ready flag. ------------------------------------------------------------------------ ## 6. Confirmed STATUS_REG bug Correct mapping: Bit Mask Meaning ----- -------- --------------------------------------- 0 `0x01` XLDA --- accelerometer data available 1 `0x02` GDA --- gyroscope data available 2 `0x04` TDA --- temperature data available The external component had XLDA and GDA reversed. Previous logic was effectively: ``` cpp if (status & 0x01) { // GDA - incorrect // read gyroscope } if (status & 0x02) { // XLDA - incorrect // read accelerometer } ``` Corrected implementation: ``` cpp if (this->gyro_odr_ != LSM6DS_ODR_OFF && (status & 0x02)) { // GDA // read gyroscope } if (this->accel_odr_ != LSM6DS_ODR_OFF && (status & 0x01)) { // XLDA // read accelerometer } ``` Temperature remains: ``` cpp if (status & 0x04) { // TDA // read temperature } ``` The temporary diagnostic logging was removed in the final correction. Commit: ``` text 7adb22de2 lsm6ds: fix swapped accel and gyro status bits ``` ------------------------------------------------------------------------ ## 7. Validation The firmware containing commit `7adb22de2` was deployed with: ``` text Accelerometer ODR: 104 Hz Gyroscope ODR: OFF Polling interval: 10 ms ``` After correction: - accelerometer X/Y/Z publishes normally; - gyroscope values no longer publish; - temperature continues to update; - raw and calibrated pitch/roll publish normally; - orientation calibration reports READY; - RV Level diagnostic reports `OK`; - activation from INACTIVE to ACTIVE works normally. This confirms the XLDA/GDA mask error independently of the watchdog investigation. ------------------------------------------------------------------------ ## 8. Relevant commit history ``` text 7adb22de2 lsm6ds: fix swapped accel and gyro status bits 53218bd4d lsm6ds: raise temporary status diagnostic to warning e2d4b947e lsm6ds: add temporary status diagnostics 3b86faa44 lsm6ds: skip reads for disabled sensors 41cb715d6 lsm6ds: expose OFF output data rate ``` Branch: `rv-lsm6ds` Repository: `https://git.mr2web.net/mr2web/esphome-lsm6ds.git` ------------------------------------------------------------------------ ## 9. Interrupt-watchdog relationship The STATUS_REG bug was discovered while investigating recurring ESP32-C6 resets reporting: ``` text Reason: Interrupt wdt Crashed core: 0 ``` Earlier firmware polled the LSM6DS every 10 ms with both accelerometer and gyroscope configured at high rate. The leveling application requires accelerometer data but not gyroscope data. The corrected firmware removes unnecessary gyroscope activity and fixes incorrect status-bit handling. ### Confirmed 1. XLDA and GDA status masks were reversed. 2. `0x01` is XLDA and `0x02` is GDA. 3. The configuration schema did not expose the already-supported C++ `LSM6DS_ODR_OFF` value. 4. A sensor configured with ODR OFF should not be read by the polling path. 5. With corrected masks, accelerometer at 104 Hz and gyroscope OFF operate as expected. ### Not yet proven It has **not** been established that the reversed status bits, unnecessary gyroscope path, or their combined workload caused the ESP32-C6 interrupt-watchdog resets. The corrected firmware is being left unchanged for an endurance test. ------------------------------------------------------------------------ ## 10. Exact endurance-test firmware artifacts Exact ELF preserved on the RockPi: ``` text /srv/docker/esphome/firmware-archive/rv-level/2026-09-20_2148/firmware.elf ``` SHA-256: ``` text dca98d924fb09c63646d59a1ed7a10e48790692ae67c51bf260bd0275b2d5171 ``` Corresponding OTA image: ``` text /srv/docker/esphome/firmware-archive/rv-level/2026-09-20_2148/firmware.ota.bin ``` SHA-256: ``` text 1e7dc78e4b40fe7b1f7f31294c70968b5cf6736840e4fd37926a64afd1477d67 ``` These came from the ESPHome remote build generated at approximately 21:48 on 2026-09-20. If another watchdog reset occurs, its `PC` and `BT` addresses should be decoded against this exact `firmware.elf`, not a rebuilt ELF. ------------------------------------------------------------------------ # Upstream Bug Report ## LSM6DS STATUS_REG XLDA/GDA masks are reversed The LSM6DS base component has the XLDA and GDA bits of `STATUS_REG` (`0x1E`) reversed. Affected logic treated: ``` cpp status & 0x01 // GDA status & 0x02 // XLDA ``` Correct mapping: ``` text bit 0 / 0x01 = XLDA — accelerometer data available bit 1 / 0x02 = GDA — gyroscope data available bit 2 / 0x04 = TDA — temperature data available ``` ## Reproduction Test environment: - LSM6DSOXTR - ESP32-C6 - ESPHome 2026.8.2 - I2C - accelerometer ODR: 104 Hz - gyroscope ODR: OFF - polling interval: 10 ms Temporary logging with: ``` text accel_odr=0x04 gyro_odr=0x00 ``` repeatedly produced: ``` text STATUS=0x01 accel_odr=0x04 gyro_odr=0x00 STATUS=0x05 accel_odr=0x04 gyro_odr=0x00 ``` `0x02` was not observed. This is consistent with bit 0 being XLDA and bit 2 being TDA. ## Fix ``` cpp if (this->gyro_odr_ != LSM6DS_ODR_OFF && (status & 0x02)) { // GDA ... } if (this->accel_odr_ != LSM6DS_ODR_OFF && (status & 0x01)) { // XLDA ... } ``` After correction with accelerometer at 104 Hz and gyroscope OFF: - accelerometer values update normally; - no gyroscope values are read/published; - temperature continues to update normally. Fix commit: ``` text 7adb22de2 lsm6ds: fix swapped accel and gyro status bits ``` Related work also exposes `OFF` as an ODR configuration option and prevents reads for an output whose ODR is OFF. Those changes are separable from the XLDA/GDA correction. No claim is currently made that the status-bit bug caused the separately observed ESP32-C6 interrupt-watchdog resets.