How to use the BMI160 IMU

The Bosch BMI160 IMU is a cheap and easy way to add motion sensing to your Arduino, so today I’m going to show you how to use the BMI160 IMU in your next project!

The nerdy stuff.

The Bosch BMI160 is a 16-bit, tri-axial gyroscope, and accelerometer, basically meaning that it has a 3-axis gyro and a 3-axis accelerometer for a total of 6 axes of motion sensing.

It can also interface with an external magnetometer for full 9-axis sensing or an OIS (Optical Image Stabilization) module.

Communication with the BMI160 can be accomplished with either SPI (Serial Peripheral Interface) or I2C (Inter-Integrated Circuit), with my un-scientific test showing that SPI is up to 25 times faster for a read of all 6 axes than the I2C bus.

The BMI160 in “suspend” power saving mode (gyro and acc off) uses a frankly ludicrous 0.0099‬ mW(!) of power and just 0.305 mW in normal mode for the ultimate power saving.

It also has two interrupt outputs with step-counting, free-fall detection, shock detection, and many other modes.

Lunar Orbit Rendezvous.
(The connection.)

Follow the picture for the correct connections to your board.

wiring

This is mostly self-explanatory apart from the address select pin with I2C, which should be connected to ground for most setups.

BMI160 breakout

The other pins on these breakout boards are the secondary interface (I2C for magnetometer or SPI for OIS), For the magnetometer SCx/ASCx for SCL and SDx/ASDx for SDA. For an OIS (only compatible with the breakout on the right in the picture), ASCx for SCK, ASDx for MOSI, OSCB for CSB and OSDO for MISO.

Obligatory L.O Rendezvous picture.

lunar orbit rendevous
Lunar lander lining up for rendezvous with the command module.

Bits and Bytes.
(The code.)

Arduino IDE

My favourite library for this IMU is the BMI160-Arduino library by hanyazou which you can download from GitHub here.

Once you have downloaded it, unzip it and place it in the libraries folder wherever you keep your Arduino sketches (on windows usually Documents > Arduino).

Next, download my example sketch here, or copy-and-paste the following code.


// This sketch shows the use of the BMI160-Arduino library to read both the gyro and the accelerometer.

#include <BMI160Gen.h> // Include the BMI160-Arduino library.


const int selectPin = 2; // For I2C only, Change this to match whatever pin you have connected it to.


void setup()
{
  Serial.begin(115200); // Initialize Serial communication.
  while (!Serial);    // Wait for the serial port to open.

  // Initialize device.
  Serial.println("Initializing IMU device...");
  BMI160.begin(BMI160GenClass::SPI_MODE, /* SS pin# = */ selectPin);
  //BMI160.begin(BMI160GenClass::I2C_MODE); // For I2C mode uncomment this line and comment the line above.
  byte dev_id = BMI160.getDeviceID();
  Serial.print("DEVICE ID: ");
  Serial.println(dev_id, HEX);

  // Set accelerometer and gyro refresh rate to 1600Hz.
  BMI160.setGyroRate(1600);
  BMI160.setAccelerometerRate(1600);
  // Set accelerometer range to 16g and gyro range to 2000 degrees/second.
  BMI160.setGyroRange(2000);
  BMI160.setAccelerometerRange(16);
  Serial.println("Initializing IMU device...done.");
}


void loop()
{
  int gxRaw, gyRaw, gzRaw, axRaw, ayRaw, azRaw; // Raw values.
  float gx, gy, gz, ax, ay, az; // Converted values.
  unsigned long beforeTime, afterTime;

  beforeTime = micros();
  // Read raw data from device.
  BMI160.readMotionSensor(axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw);
  afterTime = micros();
  Serial.print("time to read = ");
  Serial.println(afterTime - beforeTime);

  // Convert the raw gyro data to degrees/second.
  convertRawGyro(gxRaw, gyRaw, gzRaw, gx, gy, gz);

  // Convert the raw accelerometer data to g.
  convertRawAcc(axRaw, ayRaw, azRaw, ax, ay, az);

  // Display tab-separated gyro x/y/z values.
  Serial.print("g:\t");
  Serial.print(gx);
  Serial.print("\t");
  Serial.print(gy);
  Serial.print("\t");
  Serial.print(gz);
  Serial.println();

  // Display tab-separated acc x/y/z values.
  Serial.print("a:\t");
  Serial.print(ax);
  Serial.print("\t");
  Serial.print(ay);
  Serial.print("\t");
  Serial.print(az);
  Serial.println();

  delay(500);
}


// This function uses pointers to directly change the values passed to it.
void convertRawGyro(int& gxRawC, int& gyRawC, int& gzRawC, float& gxC, float& gyC, float& gzC)
{
  gxC = ((gxRawC * 2000) / 32768.0); // Multiply the raw number by gyro range (2000 deg/sec) and divide by 16-bit number + 1 (32768) 
  gyC = ((gyRawC * 2000) / 32768.0);
  gzC = ((gzRawC * 2000) / 32768.0);
}


// This function uses pointers to directly change the values passed to it.
void convertRawAcc(int& axRawC, int& ayRawC, int& azRawC, float& axC, float& ayC, float& azC)
{
  axC = (((axRawC) * 16) / 32768.0); // Multiply the raw number by acc range (16g) and divide by 16-bit number + 1 (32768)
  ayC = (((ayRawC) * 16) / 32768.0);
  azC = (((azRawC) * 16) / 32768.0);
}

The first piece of code includes the BMI160-Arduino library and defines the chip-select pin for the SPI interface.


#include <BMI160Gen.h> // Include the BMI160-Arduino library.


const int selectPin = 2; // For I2C only, Change this to match whatever pin you have connected it to.

The setup function starts by beginning the Serial output, then initializing the BMI160 and printing its device ID.

Next, it sets the gyro and accelerometer’s refresh rate and ranges.


void setup()
{
  Serial.begin(115200); // Initialize Serial communication.
  while (!Serial);    // Wait for the serial port to open.

  // Initialize device.
  Serial.println("Initializing IMU device...");
  BMI160.begin(BMI160GenClass::SPI_MODE, /* SS pin# = */ selectPin);
  //BMI160.begin(BMI160GenClass::I2C_MODE); // For I2C mode uncomment this line and comment the line above.
  byte dev_id = BMI160.getDeviceID();
  Serial.print("DEVICE ID: ");
  Serial.println(dev_id, HEX);

  // Set accelerometer and gyro refresh rate to 1600Hz.
  BMI160.setGyroRate(1600);
  BMI160.setAccelerometerRate(1600);
  // Set accelerometer range to 16g and gyro range to 2000 degrees/second.
  BMI160.setGyroRange(2000);
  BMI160.setAccelerometerRange(16);
  Serial.println("Initializing IMU device...done.");
}

The loop function starts by reading the current time since reset, then reads the raw values from both the gyro and accelerometer and then reads the current time again.


void loop()
{
  int gxRaw, gyRaw, gzRaw, axRaw, ayRaw, azRaw; // Raw values.
  float gx, gy, gz, ax, ay, az; // Converted values.
  unsigned long beforeTime, afterTime;

  beforeTime = micros();
  // Read raw data from device.
  BMI160.readMotionSensor(axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw);
  afterTime = micros();

It then prints the total time it took to read the IMU in microseconds out to the serial monitor.


  Serial.print("time to read = ");
  Serial.println(afterTime - beforeTime);

Next, it sends the raw values off to be converted into degrees/second and g values by calling the convertRawGyro() and convertRawAcc() functions.


  convertRawGyro(gxRaw, gyRaw, gzRaw, gx, gy, gz);

  // Convert the raw accelerometer data to g.
  convertRawAcc(axRaw, ayRaw, azRaw, ax, ay, az);

These functions then convert the values and return back to the loop.


// This function uses pointers to directly change the values passed to it.
void convertRawGyro(int& gxRawC, int& gyRawC, int& gzRawC, float& gxC, float& gyC, float& gzC)
{
  gxC = ((gxRawC * 2000) / 32768.0); // Multiply the raw number by gyro range (2000 deg/sec) and divide by 16-bit number + 1 (32768) 
  gyC = ((gyRawC * 2000) / 32768.0);
  gzC = ((gzRawC * 2000) / 32768.0);
}


// This function uses pointers to directly change the values passed to it.
void convertRawAcc(int& axRawC, int& ayRawC, int& azRawC, float& axC, float& ayC, float& azC)
{
  axC = (((axRawC) * 16) / 32768.0); // Multiply the raw number by acc range (16g) and divide by 16-bit number + 1 (32768)
  ayC = (((ayRawC) * 16) / 32768.0);
  azC = (((azRawC) * 16) / 32768.0);
}

Lastly, the loop function prints the converted values to the serial monitor and then finally waits for half a second, and does it all again!


  // Display tab-separated gyro x/y/z values.
  Serial.print("g:\t");
  Serial.print(gx);
  Serial.print("\t");
  Serial.print(gy);
  Serial.print("\t");
  Serial.print(gz);
  Serial.println();

  // Display tab-separated acc x/y/z values.
  Serial.print("a:\t");
  Serial.print(ax);
  Serial.print("\t");
  Serial.print(ay);
  Serial.print("\t");
  Serial.print(az);
  Serial.println();

  delay(500);
}

BMI160 vs LSM6DS3 vs MPU-6050

I know lots of you are wondering, why not just use the tried and tested MPU-6050 from InvenSense?

Well, right at the top of the datasheet I’m looking at it says,

Revision: 3.4
release date: 08/19/2013

This is kind of a long time ago, and while almost all drone flight controllers still use it if you go on Digikey or Mouser it’s either out of stock or “Not recommended for new designs” in bright red.

While it’s still perfectly fine for Arduino projects and the like where you’re just using a breakout board, it’s no good if you’re designing a board for production.

That led me to ST’s LSM6DS3, which most people use today (Arduino’s nano 33 and MKR boards, and various Adafruit and SparkFun boards), but while searching for similar IMU’s I stumbled across the BMI160, which apart from being older and slightly less supported, is basically identical and even pin-to-pin compatible, which means once BMI160 production ends it can be swapped for the LSM6DS3 with the only change being the code.

I am currently not sure whether I will use the BMI160 or the LSM6DS3 on my secret project yet, but being basically identical it will probably come down to cost.

Tease.

Project

Heres some photos of my upcoming secret project involving ST’s Nucleo32-L432KC dev board, aforementioned BMI160 breakout, a DRV8837 H-bridge breakout, Logic level translator, Turnigy (actually a FlySky…) receiver and a micro USB breakout.

And it all fits on a 1×1.8in (25.4×43.2mm) board…

Leave a Reply

Your email address will not be published. Required fields are marked *