From ea6d5499b3b52ee81ee44e0ece768de3fdc2c6fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 14 Oct 2026 19:24:00 -1000 Subject: [PATCH] [lsm6ds] Add the LSM6DSO and match all chips to their datasheets (#20733) --- esphome/components/lsm6ds/lsm6ds.cpp | 19 ++++++++---- esphome/components/lsm6ds/lsm6ds.h | 28 +++++++++++------ tests/components/lsm6ds/__init__.py | 6 ++++ tests/components/lsm6ds/test_chip_ids.cpp | 38 +++++++++++++++++++++++ 4 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 tests/components/lsm6ds/__init__.py create mode 100644 tests/components/lsm6ds/test_chip_ids.cpp diff --git a/esphome/components/lsm6ds/lsm6ds.cpp b/esphome/components/lsm6ds/lsm6ds.cpp index 0bd14bd0875..e34735a4160 100644 --- a/esphome/components/lsm6ds/lsm6ds.cpp +++ b/esphome/components/lsm6ds/lsm6ds.cpp @@ -27,17 +27,23 @@ static const LogString *gyro_range_to_str(LSM6DSGyroRange range) { } } -static const LogString *chip_name_from_who_am_i(uint8_t who_am_i) { +const LogString *chip_name_from_who_am_i(uint8_t who_am_i) { switch (who_am_i) { - case 0x69: - return LOG_STR("LSMDSO"); - case 0x6A: + case LSM6DS_WHO_AM_I_LSM6DS3: return LOG_STR("LSM6DS3"); + case LSM6DS_WHO_AM_I_LSM6DS3TR_C: + return LOG_STR("LSM6DS3TR-C"); + case LSM6DS_WHO_AM_I_LSM6DSO: + return LOG_STR("LSM6DSO"); default: return nullptr; } } +uint16_t temperature_sensitivity_from_who_am_i(uint8_t who_am_i) { + return who_am_i == LSM6DS_WHO_AM_I_LSM6DS3 ? 16 : 256; +} + void LSM6DSComponent::setup() { MotionComponent::setup(); uint8_t who_am_i = 0; @@ -54,6 +60,7 @@ void LSM6DSComponent::setup() { } ESP_LOGD(TAG, "Found %s (WHO_AM_I = 0x%02X)", LOG_STR_ARG(chip_name), who_am_i); this->chip_name_ = chip_name; + this->temperature_sensitivity_ = temperature_sensitivity_from_who_am_i(who_am_i); // 2. Software reset — clears all registers to defaults if (this->write_register(LSM6DS_REG_CTRL3_C, &CTRL3_C_SW_RESET, 1) != i2c::ERROR_OK) { @@ -185,12 +192,12 @@ bool LSM6DSComponent::update_data(motion::MotionData &data) { // Temperature (lazy — only read if a listener is registered) // Kept as a separate 2-byte read to avoid extending the burst to 14 bytes when // temperature is not needed. - // Formula: T(°C) = (raw / 256.0) + 25.0 (datasheet Table 90, OUT_TEMP register) + // Formula: T(°C) = raw / sensitivity + 25.0, sensitivity in LSB/°C per chip (0 LSB at 25 °C) if (!this->temperature_callback_.empty()) { uint8_t raw_t[2]; if (this->read_bytes(LSM6DS_REG_OUT_TEMP_L, raw_t, 2)) { int16_t temp_raw = (int16_t) ((raw_t[1] << 8) | raw_t[0]); - float temperature = (temp_raw / 256.0f) + 25.0f; + float temperature = temp_raw / static_cast(this->temperature_sensitivity_) + 25.0f; this->temperature_callback_.call(temperature); } } diff --git a/esphome/components/lsm6ds/lsm6ds.h b/esphome/components/lsm6ds/lsm6ds.h index 2cc282ba124..d754b08a252 100644 --- a/esphome/components/lsm6ds/lsm6ds.h +++ b/esphome/components/lsm6ds/lsm6ds.h @@ -9,15 +9,19 @@ namespace esphome::lsm6ds { // ── Register map (datasheet DocID030071 Rev 3, Table 19) ──────────────────── static const uint8_t LSM6DS_REG_WHO_AM_I = 0x0F; -static const uint8_t LSM6DS_REG_CTRL1_XL = 0x10; // Accel ODR + FS -static const uint8_t LSM6DS_REG_CTRL2_G = 0x11; // Gyro ODR + FS -static const uint8_t LSM6DS_REG_CTRL3_C = 0x12; // SW_RESET, BDU, IF_INC -static const uint8_t LSM6DS_REG_CTRL6_C = 0x15; // Accel HP disable, Gyro LPF1 -static const uint8_t LSM6DS_REG_CTRL7_G = 0x16; // Gyro HP disable -static const uint8_t LSM6DS_REG_STATUS = 0x1E; // XLDA, GDA, TDA -static const uint8_t LSM6DS_REG_OUT_TEMP_L = 0x20; // Temperature LSB -static const uint8_t LSM6DS_REG_OUTX_L_G = 0x22; // Gyro X LSB (burst start) -static const uint8_t LSM6DS_REG_OUTX_L_XL = 0x28; // Accel X LSB +// WHO_AM_I values; the LSM6DS3 (DocID026899) and LSM6DSO (DS12140) use the same registers as the TR-C here +static constexpr uint8_t LSM6DS_WHO_AM_I_LSM6DS3 = 0x69; +static constexpr uint8_t LSM6DS_WHO_AM_I_LSM6DS3TR_C = 0x6A; +static constexpr uint8_t LSM6DS_WHO_AM_I_LSM6DSO = 0x6C; // also the LSM6DSO32, whose accel ranges differ (unsupported) +static const uint8_t LSM6DS_REG_CTRL1_XL = 0x10; // Accel ODR + FS +static const uint8_t LSM6DS_REG_CTRL2_G = 0x11; // Gyro ODR + FS +static const uint8_t LSM6DS_REG_CTRL3_C = 0x12; // SW_RESET, BDU, IF_INC +static const uint8_t LSM6DS_REG_CTRL6_C = 0x15; // Accel HP disable, Gyro LPF1 +static const uint8_t LSM6DS_REG_CTRL7_G = 0x16; // Gyro HP disable +static const uint8_t LSM6DS_REG_STATUS = 0x1E; // XLDA, GDA, TDA +static const uint8_t LSM6DS_REG_OUT_TEMP_L = 0x20; // Temperature LSB +static const uint8_t LSM6DS_REG_OUTX_L_G = 0x22; // Gyro X LSB (burst start) +static const uint8_t LSM6DS_REG_OUTX_L_XL = 0x28; // Accel X LSB // Burst read from 0x22 to 0x2D inclusive: gyro XYZ (6 bytes) + accel XYZ (6 bytes) static const uint8_t LSM6DS_BURST_LEN = 12; @@ -83,6 +87,11 @@ enum LSM6DSGyroODR : uint8_t { }; // ── Main component class ───────────────────────────────────────────────────── +/// Chip name for a WHO_AM_I value, or nullptr when the chip is not supported. +const LogString *chip_name_from_who_am_i(uint8_t who_am_i); +/// Temperature sensitivity in LSB/°C for a supported WHO_AM_I value, from the chip's datasheet. +uint16_t temperature_sensitivity_from_who_am_i(uint8_t who_am_i); + class LSM6DSComponent final : public motion::MotionComponent, public i2c::I2CDevice { public: void setup() override; @@ -105,6 +114,7 @@ class LSM6DSComponent final : public motion::MotionComponent, public i2c::I2CDev LSM6DSAccelODR accel_odr_{LSM6DS_ACCEL_ODR_104}; LSM6DSGyroRange gyro_range_{LSM6DS_GYRO_RANGE_2000}; LSM6DSGyroODR gyro_odr_{LSM6DS_GYRO_ODR_208}; + uint16_t temperature_sensitivity_{256}; // LSB/°C: 16 on the LSM6DS3, 256 on the others LazyCallbackManager temperature_callback_{}; }; diff --git a/tests/components/lsm6ds/__init__.py b/tests/components/lsm6ds/__init__.py new file mode 100644 index 00000000000..3b260b767b0 --- /dev/null +++ b/tests/components/lsm6ds/__init__.py @@ -0,0 +1,6 @@ +from tests.testing_helpers import ComponentManifestOverride + + +def override_manifest(manifest: ComponentManifestOverride) -> None: + # lsm6ds declares i2c on its motion platform, which the harness does not walk + manifest.dependencies = manifest.dependencies + ["i2c", "motion"] diff --git a/tests/components/lsm6ds/test_chip_ids.cpp b/tests/components/lsm6ds/test_chip_ids.cpp new file mode 100644 index 00000000000..4b990d69b90 --- /dev/null +++ b/tests/components/lsm6ds/test_chip_ids.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include "esphome/components/lsm6ds/lsm6ds.h" + +namespace esphome::lsm6ds::testing { + +// WHO_AM_I values and temperature sensitivities from ST's datasheets: +// LSM6DS3 DocID026899, LSM6DS3TR-C DocID030071, LSM6DSO DS12140 + +static const char *name_of(uint8_t who_am_i) { + const LogString *name = chip_name_from_who_am_i(who_am_i); + return name == nullptr ? nullptr : LOG_STR_ARG(name); +} + +TEST(LSM6DSChipIds, Lsm6ds3) { + EXPECT_STREQ(name_of(0x69), "LSM6DS3"); + EXPECT_EQ(temperature_sensitivity_from_who_am_i(0x69), 16); +} + +TEST(LSM6DSChipIds, Lsm6ds3trC) { + EXPECT_STREQ(name_of(0x6A), "LSM6DS3TR-C"); + EXPECT_EQ(temperature_sensitivity_from_who_am_i(0x6A), 256); +} + +TEST(LSM6DSChipIds, Lsm6dso) { + EXPECT_STREQ(name_of(0x6C), "LSM6DSO"); + EXPECT_EQ(temperature_sensitivity_from_who_am_i(0x6C), 256); +} + +TEST(LSM6DSChipIds, UnknownIsRejected) { + EXPECT_EQ(name_of(0x00), nullptr); + EXPECT_EQ(name_of(0x6B), nullptr); + EXPECT_EQ(name_of(0xFF), nullptr); +} + +} // namespace esphome::lsm6ds::testing