BNO055 9-Axis Absolute Orientation IMU

The Bno055 component provides a driver for the Bosch BNO055 9-axis absolute orientation sensor. The BNO055 runs sensor fusion on its own microcontroller and reports orientation, linear acceleration, and gravity alongside the raw accelerometer, gyroscope, and magnetometer data.

API Reference

Header File

Classes

class Bno055 : public espp::BasePeripheral<uint8_t>

Driver for the Bosch BNO055 9-axis absolute orientation sensor over I2C.

The BNO055 combines a 3-axis accelerometer, 3-axis gyroscope, and 3-axis magnetometer with an on-chip Cortex-M0 that runs Bosch’s sensor fusion and outputs orientation (Euler angles and a quaternion), linear acceleration, and gravity.

This driver configures the sensor into one of its operating modes and reads all sensor and fusion outputs in a single 46-byte burst. Units are set to SI: m/s^2 for acceleration, rad/s for angular rate, rad for Euler angles, and uT for the magnetic field.

The default mode is IMU (accelerometer + gyroscope fusion, heading relative to power-on). NDOF adds the magnetometer for an absolute heading, but is sensitive to nearby motors and ferrous metal.

Datasheet:

Example

  // make the i2c we'll use to communicate
  static constexpr auto i2c_port = I2C_NUM_0;
  static constexpr auto i2c_clock_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ;
  static constexpr gpio_num_t i2c_sda = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO;
  static constexpr gpio_num_t i2c_scl = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO;
  logger.info("Creating I2C on port {} with SDA {} and SCL {}", i2c_port, i2c_sda, i2c_scl);
  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});

  static constexpr uint8_t address = CONFIG_EXAMPLE_I2C_ADDRESS;
  std::error_code ec;
  auto imu_device =
      i2c.add_device<uint8_t>({.device_address = address,
                               .timeout_ms = static_cast<int>(i2c.config().timeout_ms),
                               .scl_speed_hz = i2c.config().clk_speed,
                               .log_level = espp::Logger::Verbosity::WARN},
                              ec);
  if (!imu_device) {
    logger.error("Failed to create BNO055 I2C device: {}", ec.message());
    return;
  }

  // create the IMU; auto_init resets it and enters IMU (accel + gyro) fusion
  // mode, which takes about 700 ms
  espp::Bno055 imu({.device_address = address,
                    .probe = espp::make_i2c_addressed_probe(imu_device),
                    .write = espp::make_i2c_addressed_write(imu_device),
                    .write_then_read = espp::make_i2c_addressed_write_then_read(imu_device),
                    .mode = espp::Bno055::OperatingMode::IMU,
                    .use_external_crystal = true,
                    .auto_init = true,
                    .log_level = espp::Logger::Verbosity::INFO});
  if (!imu.initialized()) {
    logger.error("BNO055 not found at address {:#04x}", address);
    return;
  }

  // print the header for the IMU data (for plotting)
  fmt::print("% Time (s), "
             "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), "
             "Heading (rad), Roll (rad), Pitch (rad), "
             "Quat W, Quat X, Quat Y, Quat Z, "
             "Temp (C), Calib Sys, Calib Gyro, Calib Accel, Calib Mag\n");

  // read the IMU at 100 Hz and print one CSV row per sample
  auto task_fn = [&](std::mutex &m, std::condition_variable &cv) {
    auto start = std::chrono::high_resolution_clock::now();

    std::error_code ec;
    if (!imu.update(ec)) {
      logger.error("Failed to update BNO055: {}", ec.message());
    } else {
      auto data = imu.data();
      std::string text = fmt::format("{:.3f},", esp_timer_get_time() / 1'000'000.0f);
      text += fmt::format("{:.3f},{:.3f},{:.3f},", data.accel_mps2.x, data.accel_mps2.y,
                          data.accel_mps2.z);
      text += fmt::format("{:.4f},{:.4f},{:.4f},", data.gyro_radps.x, data.gyro_radps.y,
                          data.gyro_radps.z);
      text += fmt::format("{:.4f},{:.4f},{:.4f},", data.euler.heading, data.euler.roll,
                          data.euler.pitch);
      text += fmt::format("{:.4f},{:.4f},{:.4f},{:.4f},", data.orientation.w, data.orientation.x,
                          data.orientation.y, data.orientation.z);
      text += fmt::format("{},{},{},{},{}", data.temperature_c, data.calibration.system,
                          data.calibration.gyro, data.calibration.accel, data.calibration.mag);
      fmt::print("{}\n", text);
    }

    std::unique_lock<std::mutex> lk(m);
    cv.wait_until(lk, start + 10ms);
    return false;
  };

  espp::Task task({.callback = task_fn,
                   .task_config = {.name = "BNO055 Task", .stack_size_bytes = 6 * 1024},
                   .log_level = espp::Logger::Verbosity::WARN});
  task.start();

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

Note

The BNO055 stretches the I2C clock; keep the I2C timeout at or above the espp default (10 ms).

Public Types

enum class OperatingMode : uint8_t

Sensor operating mode (OPR_MODE register).

Values:

enumerator CONFIG

Configuration mode; no outputs are updated.

enumerator ACC_ONLY

Accelerometer only.

enumerator ACC_GYRO

Accelerometer and gyroscope, no fusion.

enumerator AMG

All three sensors, no fusion.

enumerator IMU

Accel + gyro fusion; heading is relative to power-on.

enumerator COMPASS

Accel + mag fusion.

enumerator M4G

Accel + mag fusion, mag used in place of gyro.

enumerator NDOF_FMC_OFF

Full 9-DoF fusion without fast mag calibration.

enumerator NDOF

Full 9-DoF fusion; heading is absolute (magnetic north).

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 Bno055(const Config &config)

Construct the driver.

Parameters:

config – Bus access functions and sensor settings.

bool init(std::error_code &ec)

Reset the sensor, verify its chip ID, set SI units, and enter the configured mode.

Note

Blocks for roughly 700 ms while the sensor resets.

Parameters:

ec – Set on any bus error or if the chip ID does not match.

Returns:

True if the sensor is running in the configured mode.

bool update(std::error_code &ec)

Read all sensor and fusion outputs in one burst and cache them.

Parameters:

ec – Set on a bus error.

Returns:

True if the read succeeded.

Data data() const

Get the most recent outputs read by update().

Returns:

A copy of the cached data.

inline bool initialized() const

Check whether init() completed successfully.

Returns:

True if the sensor is initialized.

void read_system_status(uint8_t &status, uint8_t &error, std::error_code &ec)

Read the SYS_STATUS and SYS_ERR registers.

Parameters:
  • status – Set to SYS_STATUS (5 = fusion running, 1 = system error).

  • error – Set to SYS_ERR (0 = no error).

  • ec – Set on a bus error.

inline bool probe(std::error_code &ec) const

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 Config &config() const

Get the configuration for the peripheral

Returns:

The configuration for the peripheral

inline uint8_t address() const

Get the address of the peripheral

Returns:

The address of 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_ADDRESS = 0x28

Address with ADR (COM3) pulled low.

static constexpr uint8_t ALTERNATE_ADDRESS = 0x29

Address with ADR (COM3) pulled high.

static constexpr uint8_t CHIP_ID = 0xA0

Expected value of the CHIP_ID register.

struct CalibrationStatus

Calibration status of each sensor, 0 (uncalibrated) to 3 (fully calibrated).

Public Members

uint8_t system = {0}

Overall fusion calibration.

uint8_t gyro = {0}

Gyroscope calibration.

uint8_t accel = {0}

Accelerometer calibration.

uint8_t mag = {0}

Magnetometer calibration.

struct Config

Driver configuration.

Public Members

uint8_t device_address = {DEFAULT_ADDRESS}

I2C address of the sensor.

BasePeripheral::probe_fn probe = {nullptr}

Probe the I2C address.

BasePeripheral::write_fn write = {nullptr}

Write bytes to the sensor.

BasePeripheral::write_then_read_fn write_then_read = {nullptr}

Write then read.

OperatingMode mode = {OperatingMode::IMU}

Fusion mode to run in.

bool use_external_crystal = {true}

Use the board’s 32 kHz crystal.

bool auto_init = {true}

Call init() on construction.

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

Log verbosity.

struct Data

One complete set of sensor and fusion outputs.

Public Members

Vector3 accel_mps2

Raw acceleration including gravity.

Vector3 mag_ut

Magnetic field, in microtesla.

Vector3 gyro_radps

Angular rate.

Euler euler

Fused orientation as Euler angles.

Quaternion orientation

Fused orientation as a quaternion.

Vector3 linear_accel_mps2

Acceleration with gravity removed.

Vector3 gravity_mps2

Gravity vector.

int8_t temperature_c = {0}

Die temperature, in degrees Celsius.

CalibrationStatus calibration

Per-sensor calibration status.

struct Euler

Euler angles, in radians.

Public Members

float heading = {0.0f}

Yaw, 0 to 2*pi.

float roll = {0.0f}

Roll, -pi/2 to pi/2.

float pitch = {0.0f}

Pitch, -pi to pi.

struct Quaternion

Unit quaternion.

Public Members

float w = {1.0f}

Scalar component.

float x = {0.0f}

X component.

float y = {0.0f}

Y component.

float z = {0.0f}

Z component.

struct Vector3

Three-axis vector.

Public Members

float x = {0.0f}

X component.

float y = {0.0f}

Y component.

float z = {0.0f}

Z component.