Coder Social home page Coder Social logo

bno055's Introduction

Bosch Sensortec BNO055 embedded-hal driver

docs.rs

What is this?

This is a embedded-hal driver for Bosch's Absolute Orientation Sensor BNO055.

It is device-agnostic and uses embedded-hal's Write/WriteRead (for I2C) and Delay traits for its operation.

Uses and re-exports mint's Quaternion for quaternion reading and EulerAngles for Euler angles and Vector3 for sensor readings.

Usage notes

Important note on I2C issues

As noted e.g. by Adafruit the sensor has issues with its I2C implementation, which causes it to not work correctly with certain microcontrollers.

This seems to be caused by clock stretching, thus running at lower I2C speeds and with increased I2C timeouts should resolve the issue.

Initial startup delay

The sensor has an initial startup time during which interaction with it will fail. As per the documentation this is in the 400ms - 650ms range (consult chapter 1.2 / page 14 for further details). If your microcontroller is faster in starting up you might have to delay before talking to the sensor (or retry on failure).

Feature flags

std

By default, this crate is no_std compatible. However, you can enable std features by enabling the std feature flag. At the moment this only adds std::error::Error trait implementation for the Error type.

serde

The serde flag adds implementation of Serialize / Deserialize to BNO055Calibration.

Note: serde itself is no_std compatible, however not all serializers are (e.g. serde-json is not but serde-json-core is), so be careful that you're not enabling serde's std feature by accident (see here for a complete explanation).

Usage

  1. Add a dependency to Cargo.toml:

    cargo add bno055
  2. Instantiate and init the device:

    // ... declare and configure your I2c and Delay implementations ...
    // let i2c = ...;
    // let delay = ...;
    
    // Init BNO055 IMU
    let mut imu = bno055::Bno055::new(i2c);
    
    imu.init(&mut delay)?;
    
    // Enable 9-degrees-of-freedom sensor fusion mode with fast magnetometer calibration
    imu.set_mode(bno055::BNO055OperationMode::NDOF, &mut delay)?;
    
    Ok(imu)
  3. Read orientation data, quaternion or euler angles (roll, pitch, yaw/heading):

    let quat: mint::Quaternion<f32> = imu.quaternion()?;
    // or:
    let euler: mint::EulerAngles<f32, ()> = imu.euler_angles()?;

    Due to the BNO055 firmware bugs, the Euler angles reading shouldn't be relied on. I recommend to stick with quaternion readings and convert them to the Euler angles later if needed.

Details and examples

Device calibration

To calibrate the device's sensors for first time:

use bno055::{BNO055Calibration, BNO055OperationMode, BNO055_CALIB_SIZE};

let bno055 = ...;

// Enter NDOF (absolute orientation) sensor fusion mode which is also performing
// a regular sensors calibration
bno055.set_mode(BNO055OperationMode::NDOF)?;

// Wait for device to auto-calibrate.
// Please perform steps necessary for auto-calibration to kick in.
// Required steps are described in Datasheet section 3.11
while !bno055.is_fully_calibrated() {}

let calib = bno055.calibration_profile()?;

// Save calibration profile in NVRAM
mcu.nvram_write(BNO055_CALIB_ADDR, calib.as_bytes(), BNO055_CALIB_SIZE)?;

To load a previously saved calibration profile:

use bno055::{BNO055Calibration, BNO055OperationMode, BNO055_CALIB_SIZE};

let bno055 = ...;

// Read saved calibration profile from MCUs NVRAM
let mut buf = [0u8; BNO055_CALIB_SIZE];
mcu.nvram_read(BNO055_CALIB_ADDR, &mut buf, BNO055_CALIB_SIZE)?;

// Apply calibration profile
let calib = BNO055Calibration::from_buf(buf);
bno055.set_calibration_profile(calib)?;

Remapping of axes to correspond your mounting

BNO055 allows to change default axes to meet the chip orientation with an actual physical device orientation, thus providing possibility to place BNO055 chip on PCB as suitable for the designer and to match the chip's axes to physical axes in software later.

use bno055::{AxisRemap, BNO055AxisConfig};
// ...

// Build remap configuration example with X and Y axes swapped:
let remap = AxisRemap::builder()
    .swap_x_with(BNO055AxisConfig::AXIS_AS_Y)
    .build()
    .expect("Failed to build axis remap config");

bno055.set_axis_remap(remap)?;

Please note that AxisRemap builder (and the chip itself) doesn't allow an invalid state to be constructed, that is, when one axis is swapped with multiple of others. For example, swapping axis X with both Y and Z at the same time is not allowed:

AxisRemap::builder()
    .swap_x_with(BNO055AxisConfig::AXIS_AS_Y)
    .swap_x_with(BNO055AxisConfig::AXIS_AS_Z)
    .build()
    .unwrap(); // <- panics, .build() returned Err

Changing axes sign

It is also possible to flip the sign of either axis of the chip.

Example of flipping X and Y axes:

bno055
    .set_axis_sign(BNO055AxisSign::X_NEGATIVE | bno055::BNO055AxisSign::Y_NEGATIVE)
    .expect("Unable to communicate");

Using external 32k crystal

For better performance, it is recommended to connect and use external 32k quartz crystal.

You enable or disable its use by calling set_external_crystal:

bno055
    .set_external_crystal(true)
    .expect("Failed to set to external crystal");

Using alternative I2C address

BNO055 allows to change its I2C address from default 0x29 to alternative 0x28 by setting COM3 pin LOW.

To connect to device with an alternative address, enable its use by calling with_alternative_address():

// use default 0x29 address
let mut bno = bno055::Bno055::new(i2c, delay);

// or:

// use 0x28 address
let mut bno = bno055::Bno055::new(i2c, delay).with_alternative_address();

Change BNO055 power mode

use bno055::{Bno055, BNO055PowerMode};
// Normal mode
bno055.set_power_mode(BNO055PowerMode::NORMAL)?;

// Low-power mode (only accelerometer being awake)
bno055.set_power_mode(BNO055PowerMode::LOW_POWER)?;

// Suspend mode (all sensors and controller are sleeping)
bno055.set_power_mode(BNO055PowerMode::SUSPEND)?;

Read chip temperature

Temperature is specified in degrees Celsius by default.

let temp: i8 = bno055.temperature()?;

Status

What is done and tested and what is not yet:

  • Sensor initialization
  • Device mode setup
  • Device status readout
  • Calibration status readout
  • External crystal selection
  • Axis remap
  • Axis sign setup
  • Calibration data readout
  • Calibration data setup
  • Alternative I2C address
  • Take register pages into account
  • Orientation data readout
    • Quaternions
    • Euler angles
  • Raw sensor data readout
    • Raw accelerometer data readout
    • Raw gyroscope data readout
    • Raw magnetometer data readout
  • Linear acceleration data readout
  • Gravity vector data readout
  • Temperature readout
  • Per-sensor configuration (when not in fusion mode)
  • Unit selection
  • Interrupts

License: MIT.

Contributions welcomed!

bno055's People

Contributors

alexbohm avatar elaye avatar elpiel avatar eupn avatar hargonix avatar piroro-hs avatar quietlychris avatar rursprung avatar silvergasp avatar tdholmes avatar thezoq2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

bno055's Issues

Function signature for usage code

First off, thanks for making this library.

I'm new to Rust, and don't have much experience with the Result<> return type. I used code under the "Usage" portion of the README (ending with Ok(imu).
However, my compiler says:

the `?` operator can only be used in a function that returns `Result` or `Option`

I tried adding -> Result<(), ()> to the function signature, but I got different errors. What should the return type be?

My current code:

fn new() {
    let i2c_dev = I2cdev::new("/dev/i2c-1").unwrap();
    // Use the 0x28 address instead of 0x29
    let mut imu_dev = bno055::Bno055::new(i2c_dev, Delay).with_alternative_address();
    imu_dev.init()?;
    imu_dev.set_mode(bno055::BNO055OperationMode::NDOF)?;
}

std support

Hello,

First of all, thank you for this crate. It's very useful for quickly getting started with the BNO055!

I use it on an embedded linux and there's a couple of features that I plan to develop for my needs by forking the repo.
For example:

  • std::error::Error implementation for Error
  • serde serialization for the calibration data

However these features need the crate to support std, so I'll add it behind a feature flag.
My question is: since I'll develop this, will you be willing to consider merging this to your repo?
If so I'll cut the work in one branch per feature and submit merge requests.

SemVer broken from 0.3.3 to 0.3.4 due to embedded-hal dependency update

Hello,

It appears that this library accidentally broke semantic versioning recently, which was the cause of a confused set of errors around trait bounds. In particular, bno055 v0.3.3 used embedded-hal v0.2.7, but the update in cc365a9 led to the bno055 v0.3.4 release from a few week ago used embedded-hal v1.0.0. Those two libraries do not have compatible APIs around certain traits, which means that folks specifying bno055 = "0.3" (or basically anything besides bno055 = "=0.3.3" in their Cargo.toml file will likely see breaks.

I would suggest cutting a new 0.4 release with the updated embedded-hal v1 dependency, and yanking the bno055 v0.3.4 release from crates.io. Additionally, it may be useful to look at https://github.com/rust-lang/rust-semverver for CI tooling or just as a precaution ahead of future releases.

Thanks for your consideration, and if you have any questions or there's anything I can do to support this, please let me know.

Thank you!

Thanks for making this library. I’ve been reading it to learn embedded Rust, and the clarity and comprehensiveness makes doing so a great experience. I’m also considering using it for a project I’m starting soon.

Just wanted to let you know I appreciate the work you’ve done!

Mint is not no_std

Hi, thanks for putting in the effort to making this lib!

It looks like you added mint as a dependency in 0.3.0, but unfortunately, it doesn't seem to compile without the standard library and as far as I can tell, there is no feature to enable/disable to fix that.

Am I missing something obvious here?

Example code building full example using delay

Hello,

I'm working on getting an example of this up and running, and am a little confused about actually setting up the device using the embedded_hal traits, especially because it doesn't seem to be explicitly included in the Cargo.toml dependency list.

Right now my example code looks like

use bno055::Bno055;
use linux_embedded_hal::{Delay, I2cdev};
use embedded_hal::blocking::delay::DelayMs;
use mint::Quaternion;

use std::error::Error;
use std::time::Duration;

fn main() {
    let mut dev = I2cdev::new("/dev/i2c-0").unwrap();
    let mut delay: dyn DelayMs = DelayMs {100u16};
    //    let mut delay = Delay {};
    let mut imu = bno055::Bno055::new(dev).with_alternative_address();
    imu.init(&mut delay).expect("Error initializing the IMU");
    imu.set_mode(bno055::BNO055OperationMode::NDOF, &mut delay)
        .expect("Error setting the IMU mode");

    loop {
        let quat: mint::Quaternion<f32> = imu.quaternion().expect("Error getting the IMU quaternion");
        println!("Quaternion: {:?}", quat);
    }

}

but am getting error relating to using the Delay. If I just use let mut delay = Delay {}; it compiles, but then immediately panics with

thread 'main' panicked at 'Error initializing the IMU: I2c(Io(Os { code: 6, kind: Other, message: "No such device or address" }))', src/main.rs:14:26

I'm pretty sure that my BNO055 is properly connected to the single-board computer (I'm using $ sudo i2cdump 0 0x28 which seems to show that I do have bytes being read in from a device at that address), so my next debugging step is checking that the delay struct I'm passing to init() is properly constructed, but am a little confused about what that should look like. I see in the docs that it looks like it should be a dyn DelayMs<u16> but am not totally sure how to go about building that. Any advice would be really appreciated!

README example code seems to be outdated

This is the first example code in README.md:

// ... declare and configure your I2c and Delay implementations ...

// Init BNO055 IMU
let imu = bno055::Bno055::new(i2c, delay);

imu.init()?;

// Enable 9-degrees-of-freedom sensor fusion mode with fast magnetometer calibration
imu.set_mode(bno055::BNO055OperationMode::NDOF)?;

Ok(imu)

But the function signature in rustdoc is:

pub fn new(i2c: I) -> Self;
pub fn init(&mut self, delay: &mut dyn DelayMs<u8>) -> Result<(), Error<E>>;

So, imu's new function call and init function call will will fail since the wrong arguments were given.

Combination BNO055 and BME280

Hello,
Sorry, I have only very basic knowledge on sensor communications. I could run a bme280 and also BNO055, separately without problem. The problem is when I combine both and override the wiring. Then the BME280 does not work anymore.
As you see here, the BME280 uses different SDA and SCL than BNO055 and I don't know how to define the I2C communication for both sensors.
Many thanks for your help!

//+++++++++++++++++++++++++++++++++++++++++++++++ Start and test BME280 Sensor
Serial.println(F("BME280 test"));
I2CBME.begin(21, 22, 100000); // BME 280 I2C_SDA, BME280 I2C_SCL
bool status;
status = bme.begin(0x76, &I2CBME);
Serial.println("-- Test --");

//+++++++++++++++++++++++++++++++++++++++++++++++ Start BNO055 Sensor
Wire.begin(14, 15, 400000); // (SDA, SCL) (14, 15) are default on ESP32, 400 kHz I2C bus speed
pinMode(intPin, INPUT);
pinMode(myLed, OUTPUT);
digitalWrite(myLed, HIGH);
I2Cscan(); // check for I2C devices on the bus8
// Read the WHO_AM_I register, this is a good test of communication
Serial.println("BNO055 9-axis motion sensor...");

I2c Device Ownership

Firstly, thank you for writing this! I just started playing with Rust on embedded and it's a real joy.

The Bno055 struct holds onto ownership of the I2c object, which precludes me from using multiple I2c devices (on a Pi, using rppal, which has non-Send I2c impls). I see a destroy method that returns back the I2c object, but recreating the Bno055 object each sync doesn't seem ideal because mode wouldn't persist.

Really hoping I'm just missing something obvious?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.