Qwiic VR IMU (BNO080) Hookup Guide

Pages
Contributors: Englandsaurus
Favorited Favorite 3

Introduction

Note: Unfortunately the BNO080 is EOL. For users looking for a replacement, try checking out the Qwiic VR IMU Breakout (BNO086) that is available as a drop-in replacement. There are also a few additional features available for the BNO086. For more information, check out the SparkFun VR IMU Breakout - BNO086 (Qwiic) Hookup Guide.

Bosch's BNO080 is a combination triple axis accelerometer/gyro/magnetometer packaged with an ARM Cortex M0+ running powerful algorithms. The BNO080 Inertial Measurement Unit (IMU) produces accurate rotation vector headings, excellently suited for VR and other heading applications with a static rotation error of 2 degrees or less. It’s what we’ve been waiting for: all the sensor data is combined and drift corrected into meaningful, accurate IMU information. It's perfect for any project that needs to sense orientation or motion. We've taken this IMU and stuck it on a Qwiic enabled breakout board, in order to make interfacing with the tiny, QFN package a bit easier to connect.

SparkFun VR IMU Breakout - BNO080 (Qwiic)

SEN-14686
5 Retired

In this hookup guide, we'll connect our sensor up to our microcontroller of choice and separately read the rotation vectors (which is what we will mainly want), acceleration vectors, gyro values, and magnetometer vectors. We'll check out how to implement the step counter on the BNO080 in order to use it as a pedometer. We'll also read Q values and various other metadata from the sensor. Knowing what activity you're performing is important so we'll learn how to classify what activity the IMU is performing (i.e. Sitting still, moving, biking, walking, running, etc...) and how confident the IMU is that each activity is being performed. The examples will also show how to calibrate our hardware to give us the most accurate readings possible. Printing out raw packets will also be examined for debugging purposes. Finally, we'll examine how to configure the sensor on different I2C ports and addresses. A bonus example is provided in Processing to show us how to use quaternion data to orient a cube.

Required Materials

To get started, you'll need a microcontroller to, well, control everything.

SparkFun RedBoard - Programmed with Arduino

SparkFun RedBoard - Programmed with Arduino

DEV-13975
$21.50
50
SparkFun ESP32 Thing

SparkFun ESP32 Thing

DEV-13907
$23.50
69

Particle Photon (Headers)

WRL-13774
32 Retired

Raspberry Pi 3

DEV-13825
92 Retired

Now to get into the Qwiic ecosystem, the key will be one of the following Qwiic shields to match your preference of microcontroller:

SparkFun Qwiic HAT for Raspberry Pi

SparkFun Qwiic HAT for Raspberry Pi

DEV-14459
$6.50
5
SparkFun Qwiic Shield for Arduino

SparkFun Qwiic Shield for Arduino

DEV-14352
$7.50
9

SparkFun Qwiic Shield for Photon

DEV-14477
Retired

You will also need a Qwiic cable to connect the shield to your accelerometer, choose a length that suits your needs.

Qwiic Cable - 100mm

Qwiic Cable - 100mm

PRT-14427
$1.50
Qwiic Cable - 50mm

Qwiic Cable - 50mm

PRT-14426
$0.95

Qwiic Cable - 200mm

PRT-14428
Retired

Qwiic Cable - 500mm

PRT-14429
1 Retired

Suggested Reading

If you aren't familiar with the Qwiic system, we recommend reading here for an overview.

Qwiic Connect System
Qwiic Connect System

We would also recommend taking a look at the following tutorials if you aren't familiar with them. We also delve into Processing in this tutorial, if you aren't familiar, check out the below tutorial on Processing.

Serial Communication

Asynchronous serial communication concepts: packets, signal levels, baud rates, UARTs and more!

Gyroscope

Gyroscopes measure the speed of rotation around an axis and are an essential part in determines ones orientation in space.

Accelerometer Basics

A quick introduction to accelerometers, how they work, and why they're used.

Connecting Arduino to Processing

Send serial data from Arduino to Processing and back - even at the same time!

I2C

An introduction to I2C, one of the main embedded communications protocols in use today.

Qwiic Shield for Arduino & Photon Hookup Guide

Get started with our Qwiic ecosystem with the Qwiic shield for Arduino or Photon.

Hardware Overview

Let's look over a few characteristics of the BNO080 sensor so we know a bit more about how it behaves.

CharacteristicRange
Operating Voltage1.65V - 3.6V
Linear Acceleration Accuracy±.35m/s2
Gyroscope Accuracy±.35m/s2
I2C Address0x4B (S0 Pulled High) or 0x4A (S0 grounded)

Pins

There are multiple rows of pins on the BNO080, the first row, used for the default I2C interface (configurable up to 400 kHz) is explained in the table below.

Pin LabelPin FunctionInput/OutputNotes
PS0Protocol SelectionInputConfiguration of the communication interface (Default: 0, I2C)
PS1Protocol SelectionInputConfiguration of the communication interface (Default: 0, I2C)
GNDGroundInput0V/common voltage.
3V3Power SupplyInputShould be between 1.65 - 3.6V
SDAI2C Data SignalBi-directionalBi-directional data line. Voltage should not exceed power supply (e.g. 3.3V).
SCLI2C Clock SignalInputClock signal. Voltage should not exceed power supply (e.g. 3.3V).
RSTReset SignalInputReset signal, active low, pull low to reset IC
INTInterruptOutputInterrupt, active low, pulls low when the BNO080 is ready for communication.


Also broken out on the board is a Serial Peripheral Interface (SPI) which can run data up to 3MHz. The pins for this interface are outlined below. On any pin, the voltage should not exceed that supplied on the 3V3 pin.

Note: You may not recognize the COPI/CIPO labels for SPI pins. SparkFun has joined with other members of OSHWA in a resolution to move away from using "Master" and "Slave" to describe signals between the controller and the peripheral. Check out this page for more on our reasoning behind this change. You can also see OSHWA's resolution here.
Pin LabelPin FunctionInput/OutputNotes
GNDGroundInput0V/Common Voltage
3V3PowerInputShould be between 1.65 - 3.6V
SCKClockInputClock signal to synchronize controller and peripheral.
SOCIPOOutputController in, peripheral out. Device sends data to the controller on this line.
SICOPI/ADDRInputController out, peripheral in. Device receives data from the microcontroller on this line. Tie to 3.3V to change I2C address from 0x4A to 0x4B
CSChip SelectInputChip select, active low, used as chip select on SPI
WAKWakeInputActive low, Used to wake the processor from a sleep mode.
RSTReset SignalInputReset signal, active low, pull low to reset IC


You can also use the UART interface at up to 3 Mbps or a simplified UART called UART-RVC (Used for robotic vacuum cleaners) which can run at a data rate of 115200 kbps. The UART interface is in the middle of the board, with the black and green pins labeled on the back of the board as shown below. These serial pins have been arranged to work with our Serial Basic board to make interfacing to a computer simple and fast. The GRN and BLK labels help align the serial connection properly.

Also note the BOOT pin next to the Qwiic connector, which is necessary for configuration of the communication mode. If the BOOT pin is low upon reset or power up, the chip will go into bootloader mode to allow for programming of new firmware.

Boot Pin

Optional Features

Pull-Up Resistor Jumper

The Qwiic VR IMU has onboard I2C pull up resistors; if multiple sensors are connected to the bus with the pull-up resistors enabled, the parallel equivalent resistance will create too strong of a pull-up for the bus to operate correctly. As a general rule of thumb, disable all but one pair of pull-up resistors if multiple devices are connected to the bus. If you need to disconnect the pull up resistors they can be removed by removing the solder on the corresponding jumpers highlighted below.

I2C Pullup

Protocol Selection Jumpers

You can use the PS0 and PS1 jumpers to change the communication protocol that the BNO080 is using. The jumpers are left open (0) by default, and the following configurations will allow for their corresponding communications protocols.

PS0PS1Interface
00I2C
10UART-RVC
01UART
11SPI


The jumpers themselves are located on the back of the board, shown below

Protocol Selection

I2C Jumper

You can also change the address of the BNO080 from 0x4B (default) to 0x4A by connecting the I2C ADR jumper. The jumper itself is shown in the below image.

Address Jumper

Axis Reference

Also, be sure to check out the labeling on the front of the board that indicates the orientation of the positive X, Y, and Z axes so you know which way your data is pointing.

Axis Reference Photo

Hardware Assembly

If you haven't yet assembled your Qwiic Shield, now would be the time to head on over to that tutorial. With the shield assembled, SparkFun's new Qwiic environment means that connecting the sensor could not be easier. Just plug one end of the Qwiic cable into the BNO080 breakout, the other into the Qwiic Shield of your choice and you'll be ready to upload a sketch and figure out how you're moving that board. It seems like it's too easy too use, but that's why we made it that way!

Connected IMU

Library Overview

Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE. If you have not previously installed an Arduino library, please check out our installation guide.

Before we get into programming our IMU, let's download and check out the available functions in our library. SparkFun has written a library to control the Qwiic VR IMU. You can obtain these libraries through the Arduino Library Manager. Search for SparkFun BNO080 Cortex Based IMU and you should be able to install the latest version. If you prefer manually downloading the libraries from the GitHub repository, you can grab them here:

Let's get started by looking at the functions that set up the IMU.

Setup and Settings

  • boolean begin(uint8_t deviceAddress = BNO080_DEFAULT_ADDRESS, TwoWire &wirePort = Wire); --- By default use the default I2C address, and use Wire port, otherwise, pass in a custom I2C address and wire port.

  • void enableDebugging(Stream &debugPort = Serial); --- Turn on debug printing. If user doesn't specify method of printing then Serial will be used.

  • void enableRotationVector(uint16_t timeBetweenReports); --- Enables the rotation vector to give a report every timeBetweenReports ms.
  • void enableGameRotationVector(uint16_t timeBetweenReports); --- Enables the game rotation vector to give a report every timeBetweenReports ms.
  • void enableAccelerometer(uint16_t timeBetweenReports); --- Enables the accelerometer with timeBetweenReports in ms.
  • void enableGyro(uint16_t timeBetweenReports); --- Enables the gyroscope with timeBetweenReports in ms.
  • **void enableMagnetometer(uint16_t timeBetweenReports); --- Enables the magnetometer with timeBetweenReports in ms.
  • void enableStepCounter(uint16_t timeBetweenReports); --- Enables the step counter with timeBetweenReports in ms.
  • void enableStabilityClassifier(uint16_t timeBetweenReports); --- Enables the stability classifier with timeBetweenReports in ms.
  • void enableActivityClassifier(uint16_t timeBetweenReports, uint32_t activitiesToEnable,
         uint8_t (&activityConfidences)[9]); --- Enables the activity classifier with a timeBetweenReports (in ms), the activitiesToEnable (0x1F to enable all activities) and the activityConfidences[9] array to store the IMU's confidence that the activity is occurring.

  • void softReset(); --- Try to reset the IMU via software

  • uint8_t resetReason(); --- Query the IMU for the reason it last reset

  • float qToFloat(int16_t fixedPointValue, uint8_t qPoint); --- Given a Q value, converts fixed point floating to regular floating point number

  • void setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports(); --- Used to enable different sensors and functions (features) of the IMU with to report with timeBetweenReports ms between reports.

  • void setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig); --- Used to enable different sensors and functions (features) of the IMU with to report with timeBetweenReports ms between reports.
  • void sendCommand(uint8_t command); --- Sends a command to the sensor.
  • void sendCalibrateCommand(uint8_t thingToCalibrate); --- Sends calibrations commands to the sensor of choice. Possible arguments are listed below.
    • CALIBRATE_ACCEL
    • CALIBRATE_GYRO
    • CALIBRATE_MAG
    • CALIBRATE_PLANAR_ACCEL
    • CALIBRATE_ACCEL_GYRO_MAG --- Calibrates all sensors.
    • CALIBRATE_STOP --- Stops all calibration.

Communication and Data Handling

  • boolean waitForI2C(); --- Delay based polling for I2C traffic
  • boolean receivePacket(void); --- Receives an I2C packet from the IMU.
  • boolean getData(uint16_t bytesRemaining); --- Given a number of bytes, send the requests in I2C_BUFFER_LENGTH chunks
  • boolean sendPacket(uint8_t channelNumber, uint8_t dataLength); --- Sends a packet of length dataLength to channelNumber.
  • void printPacket(void); --- Prints the current shtp header and data packets

  • bool dataAvailable(void); --- Checks if new data is available from the IMU.

  • void parseInputReport(void); --- Takes the data from the IMu and places it into the proper variable so they can be properly read.

Getting Values

  • float getQuatI(); --- Retrieves the i-axis quaternion from the IMU.
  • float getQuatJ(); --- Retrieves the j-axis quaternion from the IMU.
  • float getQuatK(); --- Retrieves the k-axis quaternion from the IMU.
  • float getQuatReal(); --- Retrieves the real component of the quaternion from the IMU.
  • float getQuatRadianAccuracy(); --- Retrieves the accuracy of the quaternion from the IMU in radians.
  • uint8_t getQuatAccuracy(); --- Retrieves the accuracy of the quaternion from the IMU.

  • float getAccelX(); --- Retrieves the x-axis acceleration.

  • float getAccelY(); --- Retrieves the y-axis acceleration.
  • float getAccelZ(); --- Retrieves the z-axis acceleration.
  • uint8_t getAccelAccuracy(); --- Retrieves the accuracy of the accelerometer readings.

  • float getGyroX(); --- Retrieves the x-axis gyroscope reading.

  • float getGyroY(); --- Retrieves the y-axis gyroscope reading.
  • float getGyroZ(); --- Retrieves the z-axis gyroscope reading.
  • uint8_t getGyroAccuracy(); --- Retrieves the accuracy of the gyroscope reading.

  • float getMagX(); --- Retrieves the x-axis magnetometer reading.

  • float getMagY(); --- Retrieves the y-axis magnetometer reading.
  • float getMagZ(); --- Retrieves the z-axis magnetometer reading.
  • uint8_t getMagAccuracy(); --- Retrieves the accuracy of the magnetometer reading.

Sensor Calibration

Check the calibration procedure in order to calibrate the IMU with the functions listed below.

  • void calibrateAccelerometer(); --- Begins the calibration function for the IMU's accelerometer.
  • void calibrateGyro(); --- Begins the calibration function for the IMU's gyroscope.
  • void calibrateMagnetometer(); --- Begins the calibration function for the IMU's magnetometer.
  • void calibratePlanarAccelerometer(); --- Begins the planar calibration function for the IMU's accelerometer.
  • void calibrateAll(); --- Begins all calibration functions.
  • void endCalibration(); --- Ends the active calibration functions.
  • void saveCalibration(); --- Saves data from current calibration.

Special Functions

  • uint16_t getStepCount(); --- Gets the number of steps from the BNO080's onboard pedometer.
  • uint8_t getStabilityClassifier(); --- Retrieves the stability classification, a number between 0 and 6.
    • 0 --- Unknown classification
    • 1 --- On table
    • 2 --- Stationary
    • 3 --- Stable
    • 4 --- Motion
    • 5 --- Reserved
  • uint8_t getActivityClassifier(); --- Retrieves the Activity classification, a number between 0 and 8, based on a comparison of the confidence levels in each activity.
    • 0 --- Unknown
    • 1 --- In Vehicle
    • 2 --- On Bicycle
    • 3 --- On Foot
    • 4 --- Still
    • 5 --- Tilting
    • 6 --- Walking
    • 7 --- Running
    • 8 --- On stairs

Metadata Handling Functions

  • int16_t getQ1(uint16_t recordID); --- Given a record ID, read the Q1 value from the metaData record in the Flash Record System (FRS). Q1 is used for all sensor data calculations.
  • int16_t getQ2(uint16_t recordID); --- Given a record ID, read the Q2 value from the metaData record in the FRS. Q2 is used in sensor bias.
  • int16_t getQ3(uint16_t recordID); --- Given a record ID, read the Q3 value from the metaData record in the FRS. Q3 is used in sensor change sensitivity.
  • float getResolution(uint16_t recordID); --- Given a record ID, reads the resolution value from the sensors metadata
  • float getRange(uint16_t recordID); --- Given a record ID, read the range value from the metaData record in the FRS for a given sensor
  • ****uint32_t readFRSword(uint16_t recordID, uint8_t wordNumber);** --- Given a record ID and a word number, find the word data. Used in getQ1(), getResolution(), etc..
  • void frsReadRequest(uint16_t recordID, uint16_t readOffset, uint16_t blockSize); --- Ask the sensor for data from the Flash Record System.
  • bool readFRSdata(uint16_t recordID, uint8_t startLocation, uint8_t wordsToRead); --- Reads data from the Flash Record System.

Global Variables

  • uint8_t shtpHeader[4]; --- In Hillcrest's Sensor Hubtransfer Protocol, each packet has a header of 4 bytes
  • uint8_t shtpData[MAX_PACKET_SIZE]; --- Creates an array for SHTP data to be stored.
  • uint8_t sequenceNumber[6] = {0, 0, 0, 0, 0, 0}; --- There are 6 com channels. Each channel has its own sequence number.
  • uint8_t commandSequenceNumber = 0; --- Commands have a sequence number as well. These are inside command packet, the header uses its own sequence number for each channel.
  • uint32_t metaData[MAX_METADATA_SIZE]; --- There is more than 10 words in a metadata record but we'll stop at Q point 3

Arduino Example Code

Now that we have our library installed, we can get started playing around with our examples to learn more about how the IMU behaves. From there we'll be able to build our own custom code to integrate the sensor into a project.

Example 1 - Rotation Vector

This first example gets us started taking a reading of our complex valued rotation vector, or quaternion, which tells us how we are oriented. To get started, open up Example1-RotationVector under File > Examples > SparkFun BNO080 Cortex Based IMU > Example1-RotationVector. To access all of the functions in the BNO080 library we'll have to include the library and create an IMU object, this is accomplished in the following lines of code.

language:c
#include "SparkFun_BNO080_Arduino_Library.h"
BNO080 myIMU;

In our setup(), we'll have to initialize the sensor and enable the parts of the sensor (gyro, accelerometer, magnetometer) that we want to obtain readings from. We'll also tell the IMU how often we want a reading from our sensor of choice by passing this value into our enable function in ms. The code outlining this sensor setup is shown below. Pay attention to this as we'll be performing a similar setup in many of the remaining examples.

language:c
Serial.begin(9600); //Don't forget to enable Serial to talk to your microcontroller
Serial.println();
Serial.println("BNO080 Read Example");

Wire.begin(); //Begin the I2C bus
Wire.setClock(400000); //Increase I2C data rate to 400kHz

myIMU.begin();
myIMU.enableRotationVector(50); //Send data update every 50ms

Now that our sensor is setup, we can look at our void loop() to see how we obtain and print data. When the loop executes, it begins by checking to see if the sensor has new data with myIMU.dataAvailable(), which returns true when new data is available. We then proceed to get the i, j, k, and real quaternion values along with the accuracy in radians of our measurement using the following lines of code.

language:c
float quatI = myIMU.getQuatI();
float quatJ = myIMU.getQuatJ();
float quatK = myIMU.getQuatK();
float quatReal = myIMU.getQuatReal();
float quatRadianAccuracy = myIMU.getQuatRadianAccuracy();

Printing the rotation vector is then as easy as using a few Serial.print(quatI, 2) statements. Uploading the code and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Quaternion Serial Output

Example 2 - Accelerometer

Examples 2 deals with pulling the accelerometer values from our sensor to figure out how it is moving. To get started, open up Example2-Accelerometer under File > Examples > SparkFun BNO080 Cortex Based IMU > Example2-Accelerometer. At first glance, you'll notice that the way we set up our IMU is nearly identical to the first example. The one difference being in our setup(), where we call myIMU.enableAccelerometer(50); instead of myIMU.enableRotationVector(50); which has the accelerometer report a value every 50 ms. We once again obtain and output our data in our void loop() by waiting for data using myIMU.dataAvailable(). Once data is available we use the following lines of code to get our x, y, and z acceleration values.

language:c
float x = myIMU.getAccelX();
float y = myIMU.getAccelY();
float z = myIMU.getAccelZ();

We can then print these values to get our acceleration vector, uploading the code, and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Accelerometer Serial Output

Example 3 - Gyro

In example 3, we'll pull values from the IMU's gyroscope to get a vector for our angular velocity. To get started, open up Example3-Gyro under File > Examples > SparkFun BNO080 Cortex Based IMU > Example3-Gyro. The differences between this example and example 1 are very similar to the differences between examples 1 & 2. We initialize the sensor the exact same way as example 1 only calling myIMU.enableGyro(50); instead of myIMU.enableRotationVector(50); to have the gyro report it's value every 50 ms. We once again obtain and output our data in our void loop() by waiting for data using myIMU.dataAvailable(). Once data is available, we use the following lines of code to get our x, y, and z gyroscope values.

language:c
float x = myIMU.getGyroX();
float y = myIMU.getGyroY();
float z = myIMU.getGyroZ();

We can then print these values to get our gyroscope angular velocity vector, uploading the code, and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Gyroscope Serial Monitor

Also, check out the values in a graph by opening the Serial Plotter to the same baud rate to see the readings from each gyroscope channel plotted against each other. Rotate the IMU and see how the values respond, I got the following output just letting the IMU swing on its cable.

Gyroscope Serial Plotter

Example 4 - Magnetometer

The following example will get us reading a vector for the magnetic field. To get started, open up Example4-Magnetometer under File > Examples > SparkFun BNO080 Cortex Based IMU > Example4-Magnetometer. The differences between this example and example 1 are very similar to the differences between examples 1 & 2, are you starting to see a pattern here? We initialize the sensor the exact same way as example 1 only calling myIMU.enableMagnetometer(50); instead of myIMU.enableRotationVector(50); to have the magnetometer report it's value every 50 ms. We once again obtain and output our data in our void loop() by waiting for data using myIMU.dataAvailable(). Once data is available we use the following lines of code to get our x, y, and z magnetometer values. We also obtain the uncertainty in the magnetometer.

language:c
float x = myIMU.getMagX();
float y = myIMU.getMagY();
float z = myIMU.getMagZ();
byte accuracy = myIMU.getMagAccuracy();

We can then print these values to get our magnetometer vector, uploading the code, and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Magnetometer Serial Output

Example 5 - Step Counter

The BNO080 has some really neat built in features due to its built in Cortex. One of these is a built in step counter. To get started with this pedometer, open up Example5-StepCounter under File > Examples > SparkFun BNO080 Cortex Based IMU > Example5-StepCounter. The differences between this example and example 1 are very similar to the differences in the previous examples. We initialize our step counter function with a lower sample rate than our previous functions as we don't expect steps to happen at as high of a rate. Due to this, we call myIMU.enableStepCounter(500) to allow for a half a second in between reports. We once again obtain and output our data in our void loop() by waiting for data using myIMU.dataAvailable(). Once data is available, we pull the amount of steps from the IMU using unsigned int steps = myIMU.getStepCount() to initialize and populate an unsigned int with the step count. We then print this value to our serial monitor. Uploading the code and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Step Counter

Example 6 - Metadata

This example shows us how to retrieve the static metadata from the different sensors in the IMU. To get started, open up Example6-Metadata under File > Examples > SparkFun BNO080 Cortex Based IMU > Example6-Metadata. Notice how this example has an empty void loop()? Everything just happens once in our setup() loop. To get the metadata for a sensor, we simply pass its corresponding FRS_RECORD_ID into getRange(), getResolution(), getQ1(), getQ2(), and getQ3() to retrieve the metadata for a sensor. The different FRS_RECORD_ID variables are shown below.

language:c
#define FRS_RECORDID_ACCELEROMETER 0xE302
#define FRS_RECORDID_GYROSCOPE_CALIBRATED 0xE306
#define FRS_RECORDID_MAGNETIC_FIELD_CALIBRATED 0xE309
#define FRS_RECORDID_ROTATION_VECTOR 0xE30B

These are then used like so to print the different parts of each sensors metadata. For a little bit more on metadata, check out page 29 of the Reference Manual. Uploading the code and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Metadata

Example 7 - Stability Classifier

This example sketch allows us to use the built in stability classifier to figure out how stable the IMU is. To get started, open up Example7-StabilityClassifier under File > Examples > SparkFun BNO080 Cortex Based IMU > Example7-StabilityClassifier. This example is very similar to our first few examples in that we must call myIMU.enableStabilityClassifier(50); in our setup() function to have the stability classifier report its data every 50 ms. However, we also need to call myIMU.calibrateGyro() in our setup() function to enable all of our stability classification outputs. We then check if data is available using myIMU.dataAvailable(). If it is, we pull the stability classification (a number from 0-5 using myIMU.getStabilityClassifier() ) and output the text corresponding to the classification. The code that does this in the void loop() is shown below. Also, take note of which number corresponds to which activity.

language:c
if (myIMU.dataAvailable() == true)
  {
    byte classification = myIMU.getStabilityClassifier();

    if(classification == 0) Serial.print(F("Unknown classification"));
    else if(classification == 1) Serial.print(F("On table"));
    else if(classification == 2) Serial.print(F("Stationary"));
    else if(classification == 3) Serial.print(F("Stable"));
    else if(classification == 4) Serial.print(F("Motion"));
    else if(classification == 5) Serial.print(F("[Reserved]"));

    Serial.println();
  }

Uploading the code and opening the Serial Monitor to a baud rate of 9600 should yield an output similar to the below image.

Stability Classifier

Example 8 - Activity Classifier

The activity classifier is somewhat similar to the stability classifier in that it uses the on-board cortex to determine what activity the IMU is doing. To get started, open up Example8-ActivityClassifier under File > Examples > SparkFun BNO080 Cortex Based IMU > Example8-ActivityClassifier. To set up the activity classifier, we need to tell the IMU which activities to look for. We do this using a 32-bit word. There are only 8 possible activities at the moment, so we set our word enableActivities = 0x1F to enable everything. The activity classifier also gives a confidence level in each activity. To store these confidences, we create a variable byte activityConfidences[9] above our setup() function. Then, we can set up the activity classifier in our setup() function by calling myIMU.enableActivityClassifier(50, enableActivities, activityConfidences);. Using this function enables the activity classifier with 50 ms between reports, activities specified by enableActivities, and their confidences are stored in activityConfidences. We then check if data is available using myIMU.dataAvailable(). If it is, we pull the activity classification (a number from 0-9 using myIMU.getActivityClassifier()) and output the text corresponding to the classification. The code that does this is shown below. Take note of which number corresponds to which activity.

language:c
void loop()
{
  //Look for reports from the IMU
  if (myIMU.dataAvailable() == true)
  {
    //getActivityClassifier will modify our activityConfidences array
    //It will return the most likely activity as well.
    byte mostLikelyActivity = myIMU.getActivityClassifier(); 

    Serial.print("Most likely activity: ");
    printActivityName(mostLikelyActivity);
    Serial.println();

    Serial.println("Confidence levels:");
    for(int x = 0 ; x < 9 ; x++)
    {
      printActivityName(x);
      Serial.print(F(") "));
      Serial.print(activityConfidences[x]);
      Serial.print(F("%"));
      Serial.println();
    }

    Serial.println();
  }
}

//Given a number between 0 and 8, print the name of the activity
//See page 73 of reference manual for activity list
void printActivityName(byte activityNumber)
{
  if(activityNumber == 0) Serial.print("Unknown");
  else if(activityNumber == 1) Serial.print("In vehicle");
  else if(activityNumber == 2) Serial.print("On bicycle");
  else if(activityNumber == 3) Serial.print("On foot");
  else if(activityNumber == 4) Serial.print("Still");
  else if(activityNumber == 5) Serial.print("Tilting");
  else if(activityNumber == 6) Serial.print("Walking");
  else if(activityNumber == 7) Serial.print("Running");
  else if(activityNumber == 8) Serial.print("On stairs");
}

The output of this code should look something like the below image.

Activity Classifier

Example 9 - Calibrate

When moving between different magnetic environments (different rooms, indoors, outdoors, etc...), it might be necessary to recalibrate your IMU to obtain the best readings. In order to do this, we'll run a calibration function,. To get started, open up Example9-Calibrate under File > Examples > SparkFun BNO080 Cortex Based IMU > Example9-Calibrate. In our setup function, we call the function myIMU.calibrateAll() to begin calibration of our sensor. We also need to make sure the we enable our game rotation vector and magnetometer as these are necessary for calculating the calibration of the magnetometer. Go ahead and upload the code to the IMU and open the serial monitor to 9600 baud. Take a look at your output and look for the calibration status. This should probably say Unreliable. Make sure you're in a clean magnetic environment and go through the calibration steps listed in the calibration procedure. Once your sensor is calibrated, your accuracy should change from Unreliable to Medium or High. After calibration, send an s to your microcontroller over the serial monitor to run the following code and save the calibration.

language:c
if(incoming == 's')
{
  myIMU.saveCalibration(); //Saves the current dynamic calibration data (DCD) to memory
  myIMU.endCalibration(); //Turns off all calibration
  Serial.println("Calibration ended");
  }
}

Your Serial Monitor should look something like the following image when the sensor is being calibrated. Remember, when your confidence levels are satisfactory, send an s to save the calibration.

Calibration

Example 10 - Print Packet

Sometimes it's easier to look at the raw data coming from the sensor for debugging purposes. This example shows you how to do just that. To get started, open up Example10-PrintPacket under File > Examples > SparkFun BNO080 Cortex Based IMU > Example10-PrintPacket. In our setup, we set up the sensors that we would like to use (in this case, we'll set up the magnetometer and accelerometer with 1000 ms sample rates). Since we're most likely debugging in this mode, we'll also call myIMU.enableDebugging(Serial). Our void loop() then simply listens for and prints packets using the below code.

language:c
void loop()
{
  //Look for reports from the IMU
  if (myIMU.receivePacket() == true)
  {
    myIMU.printPacket();
  }
}

Your Serial Monitor should look something like the following image with this example code uploaded. Make sure to change the baud rate to 115200 as opposed to 9600 like the previous examples.

Print Packet Example

Example 11 - Advanced Configuration

The final example simply shows us how to configure the sensor on different addresses and I2C buses. To get started, open up Example11-AdvancedConfig under File > Examples > SparkFun BNO080 Cortex Based IMU > Example11-AdvancedConfig. This is simply a matter of setting up the sensor differently. Instead of calling myIMU.begin() with no arguments, we call it as myIMU.begin(0x4A, Wire1). If we've pulled the SI pin high, the address will be 0x4B. We can also set up the sensor on a different I2C bus if by passing in Wire2 in place of Wire1.

Bonus Example - Serial Cube Visualizer

Note: Processing is a software that enables visual representation of data, among other things. If you've never dealt with Processing before, we recommend you also check out the Arduino to Processing tutorial. Follow the below button to go ahead and download and install Processing.

Download Processing IDE

This extra example isn't included in the library as it requires Processing. To grab it, go ahead and download or clone the BNO080 Github Repo.

Processing listens for serial data, so we'll need to get our Arduino producing serial data that makes sense to Processing. The required Arduino sketch is located in Qwiic_IMU_BNO080 > Software > Serial_Cube_Rotate > Serial_Cube_Rotate.ino. This sketch simply prints a list of our quaternions separated by a comma over serial for Processing to listen to.

Once this sketch is uploaded, we need to tell Processing how to turn this data into a visualization. The Processing sketch to do this is located one folder above the Arduino sketch, in Qwiic_IMU_BNO080 > Software > Serial_Cube_Rotate.pde. Open the Serial_Cube_Rotate file in Processing. Before running the sketch, we'll need to download ToxicLibs, a library used for computational design. To do this, go to Sketch > Import Library... > Add Library.... Then search for and download ToxicLibs. Attempting to run the Processing sketch will show us available serial ports in the debug window from this line of code.

language:c
myPort = new Serial(this, Serial.list()[0], 115200);

Identify which serial port your Arduino is on. For instance, my RedBoard is on COM6, which corresponds to [1] in the image below, so I will need to change 0 to 1 in the following line to ensure Processing is listening to the correct serial port.

Serial Ports

Once we've done this, we should be able to run the Processing sketch and it will give us a nice visualization of how our IMU is oriented in 3D space as a cube. Try rotating the IMU to see how it responds. You should get a neat little output like the one in the below GIF.

Processing Cube Example

Resources and Going Further

Thanks for reading! We're excited to see what you build with the Qwiic VR IMU. If you're left needing more BNO080-related documentation, check out some of these resources:

Need some inspiration for your next project? Check out some of these related tutorials:

Dungeons and Dragons Dice Gauntlet

A playful, geeky tutorial for a leather bracer that uses a LilyPad Arduino, LilyPad accelerometer, and seven segment display to roll virtual 4, 6, 8, 10, 12, 20, and 100 side dice for gaming.

Das Blinken Top Hat

A top hat decked out with LED strips makes for a heck of a wedding gift.

Blynk Board Washer/Dryer Alarm

How to configure the Blynk Board and app to notify you when your washer or dryer is done shaking.

9DoF Razor IMU M0 Hookup Guide

How to use and re-program the 9DoF Razor IMU M0, a combination of ATSAMD21 ARM Cortex-M0 microprocessor and MPU-9250 9DoF-in-a-chip.

Raspberry Pi Zero Helmet Impact Force Monitor

How much impact can the human body handle? This tutorial will teach you how to build your very own impact force monitor using a helmet, Raspberry Pi Zero, and accelerometer!

Or check out this blog post for ideas: