Multiple BNO055 sensors attached to one Arduino

Dear Forums,

I'm setting up a wearable project with 2 AdaFruit BNO055 absolute orientation sensors attached to one Arduino Nano. It would appear that using the supplied libraries only allows for one sensor to be attached as the pin address is defined in the Adafruit_BNO055 class.

Any assistance on how to attach two therefore would be much appreciated, I don't have much experience editing the addresses in the header files!

Many thanks, O

You don't need to use the Adafruit libraries, just use the I2C (Wire) routines.
The COM3 pin allows you to select from the 7 bit I2C addresses 0x28 or 0x29, so connect COM3 to 3.3V on one sensor, and to ground on the other.

Add a pull-up resistor (to 3.3V) on the two sensors' COM3 pins. Connect these pins to separate I/O pins on the Arduino. Use these pins kind of like SPI chip select pins.

Make sure you never set the I/O pins high on the Arduino side. You'll only pull the lines low with the Arduino by setting the I/O pin as an output (but leaving it low). By trading off which COM3 line is low you can take turns communicating with the sensors.

You'll need to run the initialization code ("bno.begin") twice. I didn't see anything in the library which would prevent this from working but I'm not positive it will.

It shouldn't be too hard to add a public function to the .cpp file but I don't know how to do this myself. I like to think I could figure this out but using the address pin as a chip select pin seems like an easy solution.

Dear Duane, thank you for your reply; I'm a little confused as to which is the COM3 pin you are referring to. I definitely understand the concept you're getting to though!

I've attached an image of the chipset and will continue to read up - I'll let you know if I progress any further.

ohceejay:
I'm a little confused as to which is the COM3 pin you are referring to.

I was calling it COM3 since that's what jremington called it. I suppose that the pin's proper name.

One the Adafruit board, it's labelled "ADR". Here's the quote from the Adafruit tutorial.

ADR: Set this pin low to change the default I2C address for the BNO055 if you need to connect two ICs on the same I2C bus. The default address is 0x28. If this pin is connected to 3V, the address will be 0x29

I just noticed the pin has a 10k pull-down resistor on it.

You could either use a level shifter to set the pin high to 3.3V or remove the pull-down and use a pull-up in its place. With the pin pulled high to 3.3V, the Arduino could set the pin low to select it.

I'm trying to save the calibration data from the sensor to EEPROM. I'm hoping to only need to calibrate the sensor a single time.

Gotcha, properly acquainting myself with the I2C bus now. Thank you ever so much, and jremington also

In my application, I ran through the BNO055 calibration procedure several times, then calculated the average of the 11 16 bit values of the calibration table. The calibration must be done in place, after the BNO055 has been installed in the robot (or whatever you are building).

Upon startup, I just write those values and it works pretty well. I use very simple I2C routines, and so far this has worked flawlessly.

main code:

//predetermined BNO055 calibration constants

 int precal[]={-6,3,17,-2,395,112,0,1,0,1000,832}; //averages of 9 in situ calibration runs

 I2C_Init();

// check for BNO055 presence

 if (0xa0 != (result=I2C_ReadRegister(BNO055_A0,BNO055_WHO_AM_I))) { //check internal device ID
 clear();
 print(" No BNO");
 while(1); //hang here
 }

// reset BNO055, in case of soft reboot
 I2C_WriteRegister(BNO055_A0, BNO055_SYS_TRIGGER, 0x20); //BNO055 system reset
 delay(1000); //required reset delay

// store earlier derived calibration parameters
 I2C_WriteCal(BNO055_A0, BNO055_CAL_DATA, (int *)precal);
 I2C_WriteRegister(BNO055_A0, BNO055_SYS_TRIGGER, 0x80); //use external 32 kHz crystal.

// set to measurement mode

 I2C_WriteRegister(BNO055_A0,BNO055_OPER_MODE,BNO055_OPER_MODE_NDOF);
 delay(10); //minimum 7 ms delay

//  end of setup. Now,
// get/set default initial heading using compass

Support routines:

// Read a two-byte word, low order first

signed int I2C_ReadWord(unsigned char busAddr, unsigned char deviceRegister) {

    unsigned int data = 0;
 unsigned char l;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister); // set register pointer
    I2C_Start(busAddr+READBIT); // restart as a read operation
    l = I2C_ReadACK(); // read the register data
 data = I2C_ReadNACK(); //read next unsigned char
    I2C_Stop(); // stop
    return (signed int) ((data<<8)|l);
}

// read 3 int16 values from successive BNO055 registers (using autoincrement)

void I2C_Read3Vectors(unsigned char busAddr, unsigned char deviceRegister, int* vector) {

    unsigned int data = 0;
 unsigned char l;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister); // set register pointer
    I2C_Start(busAddr+READBIT); // restart as a read operation
    l = I2C_ReadACK(); // read the register data
 data = I2C_ReadACK(); //read high byte
 *vector++ = ((data<<8)|l); //store X
    l = I2C_ReadACK();
 data = I2C_ReadACK();
 *vector++ = ((data<<8)|l); //store Y
    l = I2C_ReadACK();
 data = I2C_ReadNACK();
 *vector = ((data<<8)|l);  //store Z
    I2C_Stop();
}

//  read out 11 word calibration table

void I2C_ReadCal(unsigned char busAddr, unsigned char deviceRegister, int* vector) {

    unsigned int data = 0;
 unsigned char i,l;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister); // set register pointer
    I2C_Start(busAddr+READBIT); // restart as a read operation
 for (i=0; i<10; i++) {
     l = I2C_ReadACK(); // read low byte register data
 data = I2C_ReadACK(); //read high byte
 *vector++ = ((data<<8)|l); //store int
 }
    l = I2C_ReadACK(); // read the last 2 bytes
 data = I2C_ReadNACK();
 *vector = ((data<<8)|l);
    I2C_Stop();
}


//  write 11 word calibration table to BNO055

void I2C_WriteCal(unsigned char busAddr, unsigned char deviceRegister, int* vector) {

 unsigned char i,l;
    I2C_Start(busAddr); // send device address
    I2C_Write(deviceRegister); // set register pointer
 for (i=0; i<11; i++) {
     l = (*vector)&0x00ff; // low byte register data
 I2C_Write(l); //write it
 l = (*vector)>>8;
 I2C_Write(l);
 vector++;
 }
    I2C_Stop();
}

// I2C register definitions for BNO055

//  I2C Slave Addresses

#define BNO055_A0             (0x28<<1)
#define BNO055_A1             (0x29<<1)
#define BNO055_CHIP_ID              0xa0

//  Register map

#define BNO055_WHO_AM_I             0x00
#define BNO055_SW_ID_L 0x04
#define BNO055_SW_ID_H 0x05
#define BNO055_PAGE_ID              0x07
#define BNO055_ACCEL_DATA           0x08
#define BNO055_MAG_DATA             0x0e
#define BNO055_GYRO_DATA            0x14
#define BNO055_FUSED_EULER          0x1a
#define BNO055_FUSED_QUAT           0x20
#define BNO055_TEMP 0x34
#define BNO055_CALIB_STAT 0x35
#define BNO055_ST_RESULT 0x36
#define BNO055_SYS_STATUS 0x39
#define BNO055_SYS_CLK_STATUS 0x38
#define BNO055_SYS_ERR 0x3a
#define BNO055_UNIT_SEL             0x3b
#define BNO055_OPER_MODE            0x3d
#define BNO055_PWR_MODE             0x3e
#define BNO055_SYS_TRIGGER          0x3f
#define BNO055_AXIS_MAP_CONFIG      0x41
#define BNO055_AXIS_MAP_SIGN        0x42
#define BNO055_CAL_DATA 0x55  //11 16 bit integers, offsets and scale


//  Operation modes

#define BNO055_OPER_MODE_CONFIG     0x00
#define BNO055_OPER_MODE_FMC_OFF 0x0b
#define BNO055_OPER_MODE_NDOF       0x0c

//  Power modes

#define BNO055_PWR_MODE_NORMAL      0x00

Thanks for sharing your code jremington, I know that I'm somewhat chasing a shortcut here but I had a look at Adafruit_BNO055.h and found that whilst they define the two addresses:

#define BNO055_ADDRESS_A (0x28)
#define BNO055_ADDRESS_B (0x29)
#define BNO055_ID        (0xA0)

In the Adafruit_BNO055 class the constructor seems to automatically call ADDRESS_A.

Adafruit_BNO055 ( int32_t sensorID = -1, uint8_t address = BNO055_ADDRESS_A );

Might it be possible to alter the constructor so as to allow for two BNO055 with separate addresses to be instantiated, and run on the two I2C addresses as you indicated above? I'm a tad short on time here, so it would be great to make use of the pre-existing libraries for the purposes of a demonstration on Thursday. I intend to more broadly understand the I2C protocol when I next find the time!

jremington:
In my application, I ran through the BNO055 calibration procedure several times, then calculated the average of the 11 16 bit values of the calibration table. The calibration must be done in place, after the BNO055 has been installed in the robot (or whatever you are building).

Interesting approach. I wouldn't have thought to average multiple calibration tables.

I have been using the function below to check if the calibration table is updated.

byte compareNewCalWithOld(boolean writeFlag, boolean localDebugFlag)
{
  byte difference = 0;
  byte newData;
  if (localDebugFlag)
  {
    Serial.print("compareNewCalWithOld ");

    if (writeFlag == 0)
    {
      Serial.print("without ");
    }
    Serial.println("writing to EEPROM");
  }
  for (int i = 0; i < CALIBRATION_REGISTERS; i++)
  {
    if ((i % ID_COLUMN_SIZE == 0) && localDebugFlag)
    {
      Serial.println();
    }
    newData = localRead8(FIRST_CALIBRATION_REG + i);
    if (newData != calibrationData[i])
    {
      difference++;
      if (writeFlag)
      {
        calibrationData[i] = newData;
        safeWrite(FIRST_CAL_LOCATION + i, calibrationData[i], localDebugFlag);
      }
      else if (localDebugFlag)
      {
        Serial.print(", ");
        padHex2(newData);
        Serial.print(" != ");
        padHex2(calibrationData[i]);
      }
    }
    else if (localDebugFlag)
    {
      Serial.print(", ");
      padHex2(newData);
      Serial.print("(no change)");
    }
  }

  if (localDebugFlag)
  {
    Serial.println();
  }

  return difference;
}

The "safeWrite" function only writes the data to EEPROM if the data to be written is different than the current EEPROM contents.

I had thought maybe the sensor continuously refined its calibration values but so far, I haven't observed the calibration data changing.

You've given me a bit to think about concerning how I want to use previous calibration data.

As usual, I greatly appreciate your input on these matters.

ohceejay:
Might it be possible to alter the constructor so as to allow for two BNO055 with separate addresses to be instantiated, and run on the two I2C addresses as you indicated above?

This has got to be a relatively easy thing to do. Still, I don't presently know how to do this.

I took a look at the Adafruit BNO055 library and abandoned it immediately. It is full of stuff I didn't need (and I was short of program memory anyway), and it didn't do some things I wanted to do. But, can't you take advantage of this call?

/**************************************************************************/
/*!
    @brief  Instantiates a new Adafruit_BNO055 class
*/
/**************************************************************************/
Adafruit_BNO055::Adafruit_BNO055(int32_t sensorID, uint8_t address)
{
  _sensorID = sensorID;
  _address = address;
}

My decision to average the results of several calibration runs resulted from the fact that the results were a bit different every time I ran it.

I monitor and output the BNO055 calibration status constantly, and see that it varies from time to time (usually, it decides that the magnetometer is no longer calibrated). However, the results from the sensor seem to be pretty consistent, so I don't know what that means.

Siemens doesn't describe the details of the calibration process but obviously, it could be significantly improved. For example, I found that the gains for the magnetometer differ by several percent for each axis, and this is a problem because the Siemens procedure assumes the gains are all equal. This automatically degrades the accuracy of the compass!

I didn't even spot that, thanks again! Your calibration technique is most intriguing, especially given

the magnetometer differ by several percent for each axis, and this is a problem because the Siemens procedure assumes the gains are all equal. This automatically degrades the accuracy of the compass!

I'll be sure to check that out in due course.

Without asking too much of you, my current Arduino setup instantiates the IMU using:

Adafruit_BNO055 bno = Adafruit_BNO055(55);

And I'm a little lost as to what the 55 does in this, or how I could therefore get two IMUs using it; as before I'm sure it's my own reading and I've definitely taken on your points about the limitations of the library! Thanks again chaps - when I've got a spot more time I'd love to have a further look at what you have used these sensors for and better comprehend their inner workings, and potential

"55" is the BNO055's chip ID. The program just makes sure the correct sensor is connected to the Arduino.

Up and running, chuffed to bits. I'm using the sensors as part of a wearable tracking rig, am I right in believing that one of the above posters is or has worked on a similar project?

I'd be interesting to know your results, how effective is the articulation of individual joints using these sensors in modelling a limb?

It would be great if you posted your code. Others would probably find it helpful.
What does "chuffed to bits." mean?

I'll be sure to post it later today, the presentation was well received so thank you for your assistance.

Chuffed to bits is a rather British term expressing satisfaction!

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>

// The two BNO055 modules, bnoB has the ADR pin wired to 3.3v to change its i2c address
// Both are wired: SCL to analog 5, SDA to analog 4, VIN to 5v, GRN to ground
Adafruit_BNO055 bnoA = Adafruit_BNO055(-1, BNO055_ADDRESS_A);
Adafruit_BNO055 bnoB = Adafruit_BNO055(-1, BNO055_ADDRESS_B);

void setup() {
Serial.begin(115200);
if(!bnoA.begin()) {
Serial.print("Ooops, BNO055(A) not detected");
while(1);
}
bnoA.setExtCrystalUse(true);
if(!bnoB.begin()) {
Serial.print("Ooops, BNO055(B) not detected");
while(1);
}
bnoB.setExtCrystalUse(true);
}

void loop() {
imu::Vector<3> euler = bnoA.getVector(Adafruit_BNO055::VECTOR_EULER);
Serial.print(euler.z()); Serial.print(", ");
euler = bnoB.getVector(Adafruit_BNO055::VECTOR_EULER);
Serial.println(euler.z());
}

thank you so much

gclub

I've successfully connected two BNO055 to my board following Critters' post, however I need to work with three BNO055; if I use the ADR outpin for the second sensor to change its address by connecting it to the 3.3v how do I set the third sensor to a different address?

Thank you in advance!

There are only two allowable addresses. Connect all the ADR pins to output ports on the Arduino, and write your code so that only one BNO055 of the three is addressed at any one time.