BNO080 Observations

Don Kelly

Well-known member
I've been doing some testing on the BNO080 IMU and have found some things that I thought might be interesting to others. I created two perma-boards, one with T4 connected to a BNO080 via I2C, and the other a T4 connected to a BNO080 via SPI (see attached photo).

First, the BNO080 IMU is fascinating in that it has "self-calibration," meaning can keep a pretty darn good heading without going through a messy magnetometer calibration process. (Note: I do run the Sparkfun calibration sketch when I start up testing just to make sure it's starting out ok, but it's a quick process).

Would love to hear what others are finding w.r.t. the BNO080, or to get feedback on my thoughts below.

As far as a few observations:

1) The calibration process (see the Sparkfun lib for sample sketch) works pretty well. Oddly enough, though, it seems that while it's easy to get the "high" system level calibration, there's always one or two sensors that read medium. And no matter what I do, I don't seem to be able to get ALL the sensors AND the system to go high together. Nonetheless, the IMU seems to be performing really well.

2) The BNO080 internal quaternion processor seems amazingly smooth. My two sketches (below) compare the internal BNO080-calculated quaternion with one that I do with a Madgwick filter. To really visualize it, open up a Serial Plotter and compare what I'm sending out serially. I'm guessing their internal quaternion processor is running much faster than the Madgwick I'm running.

3) After running this via I2C for awhile, I thought I'd try the SPI interface. I expected to see a huge speed increase, but don't think I'm seeing that. Maybe someone has a suggestion here. Is there a simple way for me to see how fast the I2C version is running vs. the SPI version? The Sparkfun lib sets the sensor speeds using "myIMU.enableGyro(5);" for example, to set the gyro to 5ms (200Hz) updates. How do I tell what I'm really getting? The sensors should be able to be run at 400Hz, but that'd be an input of "myIMU.enableGyro(2.5);" yet only integers are allowed by the lib it seems.

Anyway, it's a fascinating sensor to work with...

Attached are some photos of my perma-boards, along with the ZIP files.
 

Attachments

  • BNO080 Boards.JPG
    BNO080 Boards.JPG
    143.9 KB · Views: 161
  • BNO080_CompareQuat_I2C_Ver2.zip
    8.4 KB · Views: 117
  • BNO080_CompareQuat_SPI_Ver2.zip
    8.6 KB · Views: 98
Last edited:
Hi Don
Long time. Yeah the BNO080 is kind of a neat little chip. But it was a pain to get working.

Haven't really played with it much so can't really help you much but as long as the time between reports is fixed not sure you are going to see much difference. As a quick test you can test the time between reports and see what you get or if you have logic analyzer or scope you might be able to measure the timings.
 
Hey, great to hear from you. I'm using an "if (myIMU.dataAvailable() == true)" line to grab my data, so I'm not really using a fixed time between reports. I guess I need to think about it some more. Maybe I need to try a fixed rate (like 250Hz) and see what happens.

Interesting stuff. I'll post some things I'm seeing with the ICM-20948 soon too. I also have built a couple of nice self-balancing robots (using the ICM-20948), so will post that as well when I get a chance.
 
Cool. I picked up a ICM-20948 as well but haven't really had much time to play with it or the BNO080 except to make sure they worked with the T4. One of these will get back to it.
 
That's really cool, are the quat updates also at 1Kz, or is it just gyro 1k updates? Are you connecting the INT pin on the BNO080? I was unsure if that does anything or not.
 
That's really cool, are the quat updates also at 1Kz, or is it just gyro 1k updates? Are you connecting the INT pin on the BNO080? I was unsure if that does anything or not.

Yes the quats update at 1khz (and you also get the gyro data). The IMU integrates it from the gyro and corrects it with the underlying rotation vector every 100hz. This period is adjustable and you can also choose which vector corrects it. Either 6 axis game rotation vector (default) or the 9 axis vector rotation vector. There is also an option to enable predictions. I added a method whits lets you write the config to FRS. But that is not yet really tested yet (the prediction part which is disabled by default).

With SPI you have to connect the INT pin and also the WAKE/P0 pin. I'ts how the ICs let each other know when to communicate.
 
Great, thanks. I have the INT and WAK connected, just wasn't sure if that was right, so thanks. Will try your 1K updates out...

By the way, has anyone been able to consistently get the self-calibration to go to HIGH for all sensors? I invariably get one or two that remain at MED no matter what I seem to try.
 
3) After running this via I2C for awhile, I thought I'd try the SPI interface. I expected to see a huge speed increase, but don't think I'm seeing that. Maybe someone has a suggestion here. Is there a simple way for me to see how fast the I2C version is running vs. the SPI version? The Sparkfun lib sets the sensor speeds using "myIMU.enableGyro(5);" for example, to set the gyro to 5ms (200Hz) updates. How do I tell what I'm really getting? The sensors should be able to be run at 400Hz, but that'd be an input of "myIMU.enableGyro(2.5);" yet only integers are allowed by the lib it seems.

Hi Don,
I am also working with the BNO080 and also switched from I2C to SPI due to speed. The main difference you might see is the time it takes for pulling data from the sensor. Therefore you need to measure the time, which it takes to evaluate "myIMU.dataAvailable()".

The update rates should be similar for I2C and SPI, you can use the following code to check:
Code:
#include <SPI.h>
#include <SparkFun_BNO080_Arduino_Library.h>

BNO080 myIMU;

//These pins can be any GPIO
byte imuCSPin = 10;
byte imuWAKPin = 14;
byte imuINTPin = 16;
byte imuRSTPin = 15;

unsigned long lastUpdate;  //Used for calc'ing Hz

void setup()
{
  Serial.begin(115200);
  while (!Serial) {delay(100);}
  Serial.println("BNO080 SPI Read Example");
  
  if(myIMU.beginSPI(imuCSPin, imuWAKPin, imuINTPin, imuRSTPin, 3000000) == false)
  {
    Serial.println("BNO080 over SPI not detected. Are you sure you have all 6 connections? Freezing...");
    while(1);
  }
  //The IMU is now connected over SPI
  //Please see the other examples for library functions that you can call
  myIMU.enableRotationVector(10); //Send data update every 10ms

  lastUpdate = micros();
}

void loop()
{
  // Look for reports from the IMU
  if (myIMU.dataAvailable() == true)
  {
    Serial.print(1000000.0/(float)(micros()-lastUpdate), 2);
//    Serial.print(F("Hz"));
    Serial.println();
    lastUpdate = micros();
  }
}

To enable the update rate of 400Hz, you need to do some minor changes in the sparkfun library. The sample time is converted to microseconds in the library and only this value is used, hence you can change the enableFunctions input to take microseconds instead of milliseconds. But only sample rates supported by the sensor will work. You need to replace these lines in the library
Code:
void BNO080::setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig)
{
	 long microsBetweenReports = (long)timeBetweenReports * 1000L;

	shtpData[0] = SHTP_REPORT_SET_FEATURE_COMMAND;	 //Set feature command. Reference page 55
	shtpData[1] = reportID;							   //Feature Report ID. 0x01 = Accelerometer, 0x05 = Rotation vector
	shtpData[2] = 0;								   //Feature flags
	shtpData[3] = 0;								   //Change sensitivity (LSB)
	shtpData[4] = 0;								   //Change sensitivity (MSB)
	shtpData[5] = (microsBetweenReports >> 0) & 0xFF;  //Report interval (LSB) in microseconds. 0x7A120 = 500ms
	shtpData[6] = (microsBetweenReports >> 8) & 0xFF;  //Report interval
	shtpData[7] = (microsBetweenReports >> 16) & 0xFF; //Report interval
	shtpData[8] = (microsBetweenReports >> 24) & 0xFF; //Report interval (MSB)
	shtpData[9] = 0;								   //Batch Interval (LSB)
	shtpData[10] = 0;								   //Batch Interval
	shtpData[11] = 0;								   //Batch Interval
	shtpData[12] = 0;								   //Batch Interval (MSB)
	shtpData[13] = (specificConfig >> 0) & 0xFF;	   //Sensor-specific config (LSB)
	shtpData[14] = (specificConfig >> 8) & 0xFF;	   //Sensor-specific config
	shtpData[15] = (specificConfig >> 16) & 0xFF;	  //Sensor-specific config
	shtpData[16] = (specificConfig >> 24) & 0xFF;	  //Sensor-specific config (MSB)
with
Code:
void BNO080::setFeatureCommand(uint8_t reportID, uint32_t timeBetweenReports, uint32_t specificConfig)
{

	shtpData[0] = SHTP_REPORT_SET_FEATURE_COMMAND;	 //Set feature command. Reference page 55
	shtpData[1] = reportID;							   //Feature Report ID. 0x01 = Accelerometer, 0x05 = Rotation vector
	shtpData[2] = 0;								   //Feature flags
	shtpData[3] = 0;								   //Change sensitivity (LSB)
	shtpData[4] = 0;								   //Change sensitivity (MSB)
	shtpData[5] = (timeBetweenReports >> 0) & 0xFF;  //Report interval (LSB) in microseconds. 0x7A120 = 500ms
	shtpData[6] = (timeBetweenReports >> 8) & 0xFF;  //Report interval
	shtpData[7] = (timeBetweenReports >> 16) & 0xFF; //Report interval
	shtpData[8] = (timeBetweenReports >> 24) & 0xFF; //Report interval (MSB)
	shtpData[9] = 0;								   //Batch Interval (LSB)
	shtpData[10] = 0;								   //Batch Interval
	shtpData[11] = 0;								   //Batch Interval
	shtpData[12] = 0;								   //Batch Interval (MSB)
	shtpData[13] = (specificConfig >> 0) & 0xFF;	   //Sensor-specific config (LSB)
	shtpData[14] = (specificConfig >> 8) & 0xFF;	   //Sensor-specific config
	shtpData[15] = (specificConfig >> 16) & 0xFF;	  //Sensor-specific config
	shtpData[16] = (specificConfig >> 24) & 0xFF;	  //Sensor-specific config (MSB)
and you also need to change the type of "timeBetweenReports" in all function declarations and definitions from "uint16_t" to "uint32_t"
 
I made two other observations when using the BNO080:
  1. The update rates are not constant, espacially for the Gyro the fluctuations are quite large
  2. There is a problem with receiving data over SPI when the data is not pulled fast enough. I do not know whether this is a library issue or a sensor issue (see https://forum.sparkfun.com/viewtopic.php?f=83&t=52705 for more details)
    @fsk: I have the same problem with the original Sparkfun library and with your modified version.
 
DavidG, looks awesome, thx! Will take me a couple days to get time to try this, but looking forward to trying your mods!
 
DavidG,
Very cool! Works great. Here's what I get with the T4.0:

405.84
369.28
484.73
350.39
427.35
446.23
392.16
384.91
365.36
399.84
484.97
353.73
418.94
478.24
363.11
394.94
364.83
391.39
401.28
411.18

Awesome!
Don
 
See above post for what I see with SPI. It's pretty fast. Here's what I see running same code on I2C:

227.58 Hz
228.05 Hz
375.38 Hz
376.08 Hz
376.51 Hz
376.93 Hz
193.72 Hz
373.55 Hz
197.01 Hz
371.89 Hz
376.65 Hz
219.20 Hz
225.78 Hz
375.80 Hz
226.40 Hz
377.36 Hz
377.50 Hz
376.22 Hz

Still pretty fast, jumps around more.
 
Hi Don, would you mind to check something for me? I only have one BNO080 and would like to check, whether the problem I have is a problem with my BNO or if it is a general problem...

The problem I have is, that if I have my BNO connected over SPI (maybe also with I2C and the interrupt pin connected), I will not be able to receive any sensor updates if I have a delay at the end of the setup part of the code or if I have a process in my main loop, that takes longer than the sample time of the sensor.

Could you check whether you can reproduce this problem? You just need to add "delay(2000);" at the end of the setup routine or "delay(20);" somewhere in the main loop in code that I have posted above:
Code:
#include <SPI.h>
#include <SparkFun_BNO080_Arduino_Library.h>

BNO080 myIMU;

//These pins can be any GPIO
byte imuCSPin = 10;
byte imuWAKPin = 14;
byte imuINTPin = 16;
byte imuRSTPin = 15;

unsigned long lastUpdate;  //Used for calc'ing Hz

void setup()
{
  Serial.begin(115200);
  while (!Serial) {delay(100);}
  Serial.println("BNO080 SPI Read Example");
  
  if(myIMU.beginSPI(imuCSPin, imuWAKPin, imuINTPin, imuRSTPin, 3000000) == false)
  {
    Serial.println("BNO080 over SPI not detected. Are you sure you have all 6 connections? Freezing...");
    while(1);
  }
  //The IMU is now connected over SPI
  //Please see the other examples for library functions that you can call
  myIMU.enableRotationVector(10); //Send data update every 10ms

  lastUpdate = micros();
  [COLOR="#FF0000"]delay(2000);       // add this delay[/COLOR]
}

void loop()
{
  [COLOR="#FF0000"]delay(20);       // or this delay[/COLOR]
  // Look for reports from the IMU
  if (myIMU.dataAvailable() == true)
  {
    Serial.print(1000000.0/(float)(micros()-lastUpdate), 2);
//    Serial.print(F("Hz"));
    Serial.println();
    lastUpdate = micros();
  }
}

With the delay in the setup function I do not get any sensor readings at all. With the delay in the main loop, I get some updates, but after a few updates it stops.
 
For SPI, I get the same thing with delay(2000) in the void setup. If I set it to delay(2), delay(20), delay(200), it runs fine, but locks up if set to delay(2000). weird.

For I2C, it runs fine with the delay(2000).

Are you getting your board to calibrate all sensors to high? I get that on the I2C, but not on the SPI setup. I'm thinking it's perhaps an issue with the particular board for the SPI setup.
 
Thanks for the confirmation, maybe I need to switch back to I2C... Do you use I2C with or without interrupt pin?

I tried to calibrate the sensors and it really difficult, but I managed to calibrate all of them to "high". But after a (very) short time the magnetometer calibration jumped back to "medium" or "low".

This is the code, that I used for calibration:
Code:
#include <SPI.h>
#include <SparkFun_BNO080_Arduino_Library.h>

BNO080 myIMU;

//These pins can be any GPIO
byte imuCSPin = 10;
byte imuWAKPin = 14;
byte imuINTPin = 16;
byte imuRSTPin = 15;

void setup()
{
  Serial.begin(115200);
  while (!Serial) {delay(100);}
  Serial.println("BNO080 SPI Read Example");
  
  if(myIMU.beginSPI(imuCSPin, imuWAKPin, imuINTPin, imuRSTPin, 3000000) == false)
  {
    Serial.println("BNO080 over SPI not detected. Are you sure you have all 6 connections? Freezing...");
    while(1);
  }

  //Enable dynamic calibration for accel, gyro, and mag
  myIMU.calibrateAll();                 //Turn on cal for Accel, Gyro, and Mag

  myIMU.enableAccelerometer(100);       //Send data update every 100ms
  myIMU.enableGyro(100);                //Send data update every 100ms
  myIMU.enableMagnetometer(100);        //Send data update every 100ms
  myIMU.enableRotationVector(100);      //Send data update every 100ms

  //Once magnetic field is 2 or 3, run the Save DCD Now command
  Serial.println(F("Calibrating. Press 's' to save to flash"));
}

void loop()
{
  if(Serial.available())
  {
    byte incoming = Serial.read();

    if(incoming == 's')
    {
      myIMU.saveCalibration(); //Saves the current dynamic calibration data (DCD) to memory
      myIMU.requestCalibrationStatus(); //Sends command to get the latest calibration status

      //Wait for calibration response, timeout if no response
      int counter = 100;
      while(1)
      {
        if(--counter == 0) break;
        if(myIMU.dataAvailable() == true)
        {
          //The IMU can report many different things. We must wait
          //for the ME Calibration Response Status byte to go to zero
          if(myIMU.calibrationComplete() == true)
          {
            Serial.println("Calibration data successfully stored");
            delay(1000);
            break;
          }
        }

        delay(1);
      }
      if(counter == 0)
      {
        Serial.println("Calibration data failed to store. Please try again.");
      }
      
      //myIMU.endCalibration(); //Turns off all calibration
      //In general, calibration should be left on at all times. The BNO080
      //auto-calibrates and auto-records cal data roughly every 5 minutes
    }
  }

  //Look for reports from the IMU
  if (myIMU.dataAvailable() == true)
  {
    byte accAccuracy = myIMU.getAccelAccuracy();
    byte gyrAccuracy = myIMU.getGyroAccuracy();
    byte magAccuracy = myIMU.getMagAccuracy();
    byte sysAccuracy = myIMU.getQuatAccuracy();

    Serial.print(F("Accelerometer: \t"));   printAccuracyLevel(accAccuracy);
    Serial.print(F(",\tGyroscope: \t"));    printAccuracyLevel(gyrAccuracy);
    Serial.print(F(",\tMagnetometer: \t")); printAccuracyLevel(magAccuracy);
    Serial.print(F(",\tSystem: \t"));       printAccuracyLevel(sysAccuracy);
    Serial.println();
  }
}

//Given a accuracy number, print what it means
void printAccuracyLevel(byte accuracyNumber)
{
  if (accuracyNumber == 0) Serial.print(F("Unreliable"));
  else if (accuracyNumber == 1) Serial.print(F("Low"));
  else if (accuracyNumber == 2) Serial.print(F("Medium"));
  else if (accuracyNumber == 3) Serial.print(F("High"));
}
 
I use I2C without INT, I just use a qwiic cableIMG_3718.JPG.

I'm getting ready to mount a BNO080 on a larger (SuperDroid Robots model) self balancing robot, and I'll use I2C for that since I'm already using two SPI links to the encoders. I may enable the BNO at a very fast rate, but only grab the sensor data at 100Hz or so.

I'm using the Sparkfun cal routine, but will give yours a try to see if the BNO behaves any differently. Thx!
 
My calibration routine is basically a modification of the Sparkfun routine. I doubt that it will give much better results.
 
Back
Top