LSM6DSO 6-Axis IMU

The Lsm6dso component provides a driver for the LSM6DSO 6-Axis Inertial Measurement Unit (IMU) from STMicroelectronics.

API Reference

Header File

Classes

template<lsm6dso::Interface Interface = lsm6dso::Interface::I2C>
class Lsm6dso : public espp::BasePeripheral<uint8_t, lsm6dso::Interface::I2C == lsm6dso::Interface::I2C>

Class for the LSM6DSO 6-axis IMU (Accel + Gyro)

The LSM6DSO is a 6-axis IMU with a 3-axis accelerometer and 3-axis gyroscope. It supports both I2C and SPI, and on-chip filtering.

For more information, see the LSM6DSO datasheet: https://www.st.com/resource/en/datasheet/lsm6dso.pdf

Example

  using Imu = espp::Lsm6dso<espp::lsm6dso::Interface::I2C>;

  // I2C config (customize as needed)
  static constexpr auto i2c_port = I2C_NUM_0;
  static constexpr auto i2c_clock_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ;     // Set in sdkconfig
  static constexpr gpio_num_t i2c_sda = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO; // Set in sdkconfig
  static constexpr gpio_num_t i2c_scl = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO; // Set in sdkconfig
  espp::I2c i2c({.port = i2c_port,
                 .sda_io_num = i2c_sda,
                 .scl_io_num = i2c_scl,
                 .sda_pullup_en = GPIO_PULLUP_ENABLE,
                 .scl_pullup_en = GPIO_PULLUP_ENABLE,
                 .clk_speed = i2c_clock_speed});

  // make the orientation filter to compute orientation from accel + gyro
  static constexpr float angle_noise = 0.001f;
  static constexpr float rate_noise = 0.1f;
  static espp::KalmanFilter<2> kf;
  kf.set_process_noise(rate_noise);
  kf.set_measurement_noise(angle_noise);

  auto kalman_filter_fn = [](float dt, const Imu::Value &accel,
                             const Imu::Value &gyro) -> Imu::Value {
    // Apply Kalman filter
    float accelRoll = atan2(accel.y, accel.z);
    float accelPitch = atan2(-accel.x, sqrt(accel.y * accel.y + accel.z * accel.z));
    kf.predict({espp::deg_to_rad(gyro.x), espp::deg_to_rad(gyro.y)}, dt);
    kf.update({accelRoll, accelPitch});
    float roll, pitch;
    std::tie(roll, pitch) = kf.get_state();
    // return the computed orientation
    Imu::Value orientation{};
    orientation.roll = roll;
    orientation.pitch = pitch;
    orientation.yaw = 0.0f;
    return orientation;
  };

  // Madgwick filter for orientation
  static constexpr float beta = 0.1f;
  static espp::MadgwickFilter madgwick(beta);
  auto madgwick_filter_fn = [](float dt, const Imu::Value &accel,
                               const Imu::Value &gyro) -> Imu::Value {
    madgwick.update(dt, accel.x, accel.y, accel.z, espp::deg_to_rad(gyro.x),
                    espp::deg_to_rad(gyro.y), espp::deg_to_rad(gyro.z));
    float roll, pitch, yaw;
    madgwick.get_euler(roll, pitch, yaw);
    Imu::Value orientation{};
    orientation.roll = espp::deg_to_rad(roll);
    orientation.pitch = espp::deg_to_rad(pitch);
    orientation.yaw = espp::deg_to_rad(yaw);
    return orientation;
  };

  // IMU config
  Imu::Config config{
      .device_address = Imu::DEFAULT_I2C_ADDRESS,
      .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2,
                         std::placeholders::_3),
      .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2,
                        std::placeholders::_3),
      .imu_config =
          {
              .accel_range = Imu::AccelRange::RANGE_2G,
              .accel_odr = Imu::AccelODR::ODR_416_HZ,
              .gyro_range = Imu::GyroRange::DPS_2000,
              .gyro_odr = Imu::GyroODR::ODR_416_HZ,
          },
      .orientation_filter = kalman_filter_fn,
      .auto_init = true,
      .log_level = espp::Logger::Verbosity::INFO,
  };

  logger.info("Creating LSM6DSO IMU");
  Imu imu(config);

  std::error_code ec;

  // set the accel / gyro on-chip filters
  static constexpr uint8_t accel_filter_bandwidth = 0b001; // ODR / 10
  static constexpr uint8_t gyro_lpf_bandwidth = 0b001;     // ODR / 3
  static constexpr bool gyro_hpf_enabled = false;          // disable high-pass filter on gyro
  static constexpr auto gyro_hpf_bandwidth = Imu::GyroHPF::HPF_0_26_HZ; // 0.26Hz
  if (!imu.set_accelerometer_filter(accel_filter_bandwidth, Imu::AccelFilter::LOWPASS, ec)) {
    logger.error("Failed to set accelerometer filter: {}", ec.message());
  }
  // set the gyroscope filter to have lowpass
  if (!imu.set_gyroscope_filter(gyro_lpf_bandwidth, gyro_hpf_enabled, gyro_hpf_bandwidth, ec)) {
    logger.error("Failed to set gyroscope filter: {}", ec.message());
  }

  // make a task to read out the IMU data and print it to console
  espp::Task imu_task({.callback = [&](std::mutex &m, std::condition_variable &cv) -> bool {
                         static auto start = std::chrono::steady_clock::now();

                         auto now = esp_timer_get_time(); // time in microseconds
                         static auto t0 = now;
                         auto t1 = now;
                         float dt = (t1 - t0) / 1'000'000.0f; // convert us to s
                         t0 = t1;

                         std::error_code ec;
                         // update the imu data
                         if (!imu.update(dt, ec)) {
                           return false;
                         }

                         // get accel
                         auto accel = imu.get_accelerometer();
                         auto gyro = imu.get_gyroscope();
                         auto temp = imu.get_temperature();
                         auto orientation = imu.get_orientation();
                         auto gravity_vector = imu.get_gravity_vector();

                         [[maybe_unused]] auto t2 = esp_timer_get_time(); // time in microseconds

                         auto madgwick_orientation = madgwick_filter_fn(dt, accel, gyro);
                         float roll = madgwick_orientation.roll;
                         float pitch = madgwick_orientation.pitch;
                         float yaw = madgwick_orientation.yaw;
                         float vx = sin(pitch);
                         float vy = -cos(pitch) * sin(roll);
                         float vz = -cos(pitch) * cos(roll);

                         // print time and raw IMU data
                         std::string text = "";
                         text += fmt::format("{:.3f},", now / 1'000'000.0f);
                         text += fmt::format("{:02.3f},{:02.3f},{:02.3f},", (float)accel.x,
                                             (float)accel.y, (float)accel.z);
                         text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gyro.x,
                                             (float)gyro.y, (float)gyro.z);
                         text += fmt::format("{:02.1f},", temp);
                         // print kalman filter outputs
                         text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)orientation.x,
                                             (float)orientation.y, (float)orientation.z);
                         text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", (float)gravity_vector.x,
                                             (float)gravity_vector.y, (float)gravity_vector.z);
                         // print madgwick filter outputs
                         text += fmt::format("{:03.3f},{:03.3f},{:03.3f},", roll, pitch, yaw);
                         text += fmt::format("{:03.3f},{:03.3f},{:03.3f}", vx, vy, vz);

                         fmt::print("{}\n", text);

                         // fmt::print("IMU update took {:.3f} ms\n", (t2 - t0) / 1000.0f);

                         // sleep first in case we don't get IMU data and need to exit early
                         {
                           std::unique_lock<std::mutex> lock(m);
                           cv.wait_until(lock, start + 10ms);
                         }

                         return false;
                       },
                       .task_config = {
                           .name = "IMU",
                           .stack_size_bytes = 6 * 1024,
                           .priority = 10,
                           .core_id = 0,
                       }});

  // print the header for the IMU data (for plotting)
  fmt::print("% Time (s), "
             // raw IMU data (accel, gyro, temp)
             "Accel X (m/s^2), Accel Y (m/s^2), Accel Z (m/s^2), "
             "Gyro X (rad/s), Gyro Y (rad/s), Gyro Z (rad/s), "
             "Temp (C), "
             // kalman filter outputs
             "Kalman Roll (rad), Kalman Pitch (rad), Kalman Yaw (rad), "
             "Kalman Gravity X, Kalman Gravity Y, Kalman Gravity Z, "
             // madgwick filter outputs
             "Madgwick Roll (rad), Madgwick Pitch (rad), Madgwick Yaw (rad), "
             "Madgwick Gravity X, Madgwick Gravity Y, Madgwick Gravity Z\n");

  logger.info("Starting IMU task");
  imu_task.start();

  // loop forever
  while (true) {
    std::this_thread::sleep_for(1s);
  }

Usage:

Note

This class is intended for use with the LSM6DSO and compatible variants.

Template Parameters:

Interface – The interface type (I2C or SPI)

Public Types

using AccelRange = lsm6dso::AccelRange

Alias for accelerometer range.

using AccelODR = lsm6dso::AccelODR

Alias for accelerometer output data rate (ODR)

using AccelFilter = lsm6dso::AccelFilter

Alias for accelerometer filter type.

using GyroRange = lsm6dso::GyroRange

Alias for gyroscope range.

using GyroODR = lsm6dso::GyroODR

Alias for gyroscope output data rate (ODR)

using GyroHPF = lsm6dso::GyroHPF

Alias for gyroscope high-pass filter (HPF)

using RawValue = lsm6dso::RawValue

Alias for raw sensor values.

using Value = lsm6dso::Value

Alias for processed sensor values.

using ImuConfig = lsm6dso::ImuConfig

Alias for IMU configuration.

typedef std::function<Value(float, const Value&, const Value&)> filter_fn

Filter function for orientation filtering

Param dt:

The time step in seconds

Param accel:

The accelerometer data

Param gyro:

The gyroscope data

Return:

The filtered orientation data in radians

typedef std::function<bool(uint8_t)> probe_fn

Function to probe the peripheral

Param address:

The address to probe

Return:

True if the peripheral is found at the given address

Public Functions

explicit Lsm6dso(const Config &config)

Constructor

Parameters:

config – The configuration

bool init(std::error_code &ec)

Initialize the LSM6DSO

Parameters:

ec – The error code to set if an error occurs

Returns:

True if initialized successfully, false otherwise

uint8_t get_device_id(std::error_code &ec)

Get the device ID

Parameters:

ec – The error code to set if an error occurs

Returns:

The device ID

bool set_config(const ImuConfig &config, std::error_code &ec)

Set the IMU configuration

Parameters:
  • config – The configuration

  • ec – The error code to set if an error occurs

Returns:

True if the configuration was set successfully, false otherwise

ImuConfig get_config() const

Get the IMU configuration

Returns:

The configuration

bool set_accelerometer_range(AccelRange range, std::error_code &ec)

Set the accelerometer output data rate (ODR)

Parameters:
  • odr – The output data rate

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_gyroscope_range(GyroRange range, std::error_code &ec)

Set the gyroscope output data rate (ODR)

Parameters:
  • odr – The output data rate

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_accelerometer_odr(AccelODR odr, std::error_code &ec)

Set the accelerometer output data rate (ODR)

Parameters:
  • odr – The output data rate

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_gyroscope_odr(GyroODR odr, std::error_code &ec)

Set the gyroscope output data rate (ODR)

Parameters:
  • odr – The output data rate

  • ec – The error code to set if an error occurs

Returns:

True if successful

inline float get_accelerometer_sensitivity() const

Get the accelerometer sensitivity in g/LSB

Returns:

The accelerometer sensitivity

inline float get_gyroscope_sensitivity() const

Get the gyroscope sensitivity in dps/LSB

Returns:

The gyroscope sensitivity

float read_accelerometer_sensitivity(std::error_code &ec)

Read the accelerometer sensitivity in g/LSB

Parameters:

ec – The error code to set if an error occurs

Returns:

The accelerometer sensitivity

float read_gyroscope_sensitivity(std::error_code &ec)

Read the gyroscope sensitivity in dps/LSB

Parameters:

ec – The error code to set if an error occurs

Returns:

The gyroscope sensitivity

bool set_accelerometer_power_mode(bool enable, std::error_code &ec)

Set the accelerometer power mode

Parameters:
  • enable – True to enable, false to power down

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_gyroscope_power_mode(bool enable, std::error_code &ec)

Set the gyroscope power mode

Parameters:
  • enable – True to enable, false to power down

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_accelerometer_filter(uint8_t bw, AccelFilter accel_filter, std::error_code &ec)

Set the accelerometer filter bandwidth

Parameters:
  • bw – The filter bandwidth

  • accel_filter – The accelerometer filter type

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool set_gyroscope_filter(uint8_t lpf1_bw, bool hpf_enabled, GyroHPF hpf_bw, std::error_code &ec)

Set the gyroscope filter bandwidth

Parameters:
  • lpf1_bw – The low-pass filter 1 bandwidth. Bandwidth differs depending on ODR setting.

  • hpf_enabled – True to enable high-pass filter, false to disable

  • hpf_bw – The high-pass filter bandwidth

  • ec – The error code to set if an error occurs

Returns:

True if successful

bool update(float dt, std::error_code &ec)

Update the sensor values (read accel, gyro, temp, run filter)

Parameters:
  • dt – The time step in seconds

  • ec – The error code to set if an error occurs

Returns:

True if successful

Value get_accelerometer() const

Get the accelerometer values (g)

Returns:

The accelerometer values

Value read_accelerometer(std::error_code &ec)

Read the accelerometer values (g)

Parameters:

ec – The error code to set if an error occurs

Returns:

The accelerometer values

Value get_gyroscope() const

Get the gyroscope values (dps)

Returns:

The gyroscope values

Value read_gyroscope(std::error_code &ec)

Read the gyroscope values (dps)

Parameters:

ec – The error code to set if an error occurs

Returns:

The gyroscope values

float get_temperature() const

Get the temperature (deg C)

Returns:

The temperature

float read_temperature(std::error_code &ec)

Read the temperature (deg C)

Parameters:

ec – The error code to set if an error occurs

Returns:

The temperature

Value get_orientation() const

Get the orientation values (radians)

Returns:

The orientation values

Value get_gravity_vector() const

Get the gravity vector (in Gs)

Returns:

The gravity vector

inline bool probe(std::error_code &ec)

Probe the peripheral

Note

This function is thread safe

Note

If the probe function is not set, this function will return false and set the error code to operation_not_supported

Note

This function is only available if UseAddress is true

Parameters:

ec – The error code to set if there is an error

Returns:

True if the peripheral is found

inline void set_address(uint8_t address)

Set the address of the peripheral

Note

This function is thread safe

Note

This function is only available if UseAddress is true

Parameters:

address – The address of the peripheral

inline void set_probe(const probe_fn &probe)

Set the probe function

Note

This function is thread safe

Note

This should rarely be used, as the probe function is usually set in the constructor. If you need to change the probe function, consider using the set_config function instead.

Note

This function is only available if UseAddress is true

Parameters:

probe – The probe function

inline void set_write(const write_fn &write)

Set the write function

Note

This function is thread safe

Note

This should rarely be used, as the write function is usually set in the constructor. If you need to change the write function, consider using the set_config function instead.

Parameters:

write – The write function

inline void set_read(const read_fn &read)

Set the read function

Note

This function is thread safe

Note

This should rarely be used, as the read function is usually set in the constructor. If you need to change the read function, consider using the set_config function instead.

Parameters:

read – The read function

inline void set_read_register(const read_register_fn &read_register)

Set the read register function

Note

This function is thread safe

Note

This should rarely be used, as the read register function is usually set in the constructor. If you need to change the read register function, consider using the set_config function instead.

Parameters:

read_register – The read register function

inline void set_write_then_read(const write_then_read_fn &write_then_read)

Set the write then read function

Note

This function is thread safe

Note

This should rarely be used, as the write then read function is usually set in the constructor. If you need to change the write then

Parameters:

write_then_read – The write then read function

inline void set_separate_write_then_read_delay(const std::chrono::milliseconds &delay)

Set the delay between the write and read operations in write_then_read

Note

This function is thread safe

Note

This should rarely be used, as the delay is usually set in the constructor. If you need to change the delay, consider using the set_config function instead.

Note

This delay is only used if the write_then_read function is not set to a custom function and the write and read functions are separate functions.

Parameters:

delay – The delay between the write and read operations in write_then_read

inline void set_config(const Config &config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline void set_config(Config &&config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Attributes

static constexpr uint8_t DEFAULT_I2C_ADDRESS = 0x6A

Default I2C address (can be 0x6B)

struct Config

Configuration struct for the LSM6DSO.

Public Members

uint8_t device_address = DEFAULT_I2C_ADDRESS

I2C address (if I2C)

BasePeripheral<uint8_t, Interface == lsm6dso::Interface::I2C>::write_fn write = nullptr

Write function.

BasePeripheral<uint8_t, Interface == lsm6dso::Interface::I2C>::read_fn read = nullptr

Read function.

ImuConfig imu_config

IMU configuration.

filter_fn orientation_filter = nullptr

Algorithmic orientation filter.

bool auto_init = {true}

Automatically initialize on construction.

Logger::Verbosity log_level = {Logger::Verbosity::WARN}

Log level.

Header File

Unions

espp::lsm6dso::RawValue.__unnamed150__

Public Members

struct espp::lsm6dso::RawValue
uint8_t raw[6]

Raw byte array for direct access.

uint16_t values[3]

Array for direct access to raw values.

espp::lsm6dso::Value.__unnamed154__

Public Members

struct espp::lsm6dso::Value
struct espp::lsm6dso::Value
float values[3]