## How to Measure Inclination with the BNO055

Along with a gyroscope and a magnetometer, the BNO055 integrates a triaxial accelerometer into a single package. Here you will learn how accelerometers are used as inclinometers and how to measure inclination with the BNO055. In other words, you will learn how to use the BNO055 as an inclinometer.

We will start with the practical part by making some inclination measurements with the BNO055. Then I’ll show you the Arduino example sketch and explain the mathematical formulas behind it. Please note that this tutorial does not cover the integration of gyroscope and accelerometer data. Nevertheless, you don’t necessarily need a gyroscope for static inclination measurements. With that in mind, let’s get started!

## Basic Inclination Measurements

Accelerometers use gravity to measure their inclination, and they will constantly measure the Earth’s gravity as an acceleration pointing to the ground. If no other forces are being applied to an accelerometer, its output will only be affected by Earth’s gravity.

A triaxial accelerometer, like the BNO055, outputs one acceleration measurement for each axis. Therefore, the BNO055 at rest with its z-axis pointing down will measure no acceleration along the x- and y-axes, and the output from the z-axis will be identical to the acceleration of gravity.

Now start tilting the BNO055 around the x-axis. The acceleration of gravity is now being measured by the z-axis and the y-axis. How strongly each axis measures gravity depends on the inclination angle. Because of this, if we know the accelerations for the z- and y-axes, we can calculate the inclination angle.

We can make a simple experiment to put this into practice. In this crude arrangement, the BNO055 board is fixed to a metal ruler. By placing one side of the ruler on an object, we can easily visualize a triangle with known sides. For example, if we place a 50 mm thick book under the ruler at the 300 mm mark (the other corner of the ruler starts exactly at 0 mm), we calculate the angle of inclination as about 9.60°, given by the equation arcsin (50/300). What would the BNO055 measure under these conditions?

Using the Adafruit Library for the BNO055 sensor, I took the example sketch read_all_data and made a few changes. You can download the Adafruit Library from their GitHub repository and copy the code at the bottom of the page for your own measurements.

This example sketch is using 5 computation methods to measure inclination with the BNO055:

1. Non-Fusion Mode ACCONLY with formula arcsin. The calculation uses the output from the triaxial accelerometer. The output range lies between +90° and -90°, and the formula is less accurate for inclinations close to ±90°.
2. Non-Fusion Mode ACCONLY with formula arccos. The calculation uses the output from the triaxial accelerometer. The output range lies between 0° and +180° (that means, the output is always positive). The formula is less accurate for inclinations close to 0° and to 180°.
3. Non-Fusion Mode ACCONLY with formula arctan. The calculation uses the output from the triaxial accelerometer. The output range lies between -90° and +90°. The formula has a discontinuity at ±90°, meaning that for an inclination of +90° the output will suddenly jump to -90°, and vice-versa. This formula should be avoided.
4. Non-Fusion Mode ACCONLY with formula arctan2. The calculation uses the output from the triaxial accelerometer. The output range lies between -180° and +180° and the formula has only a discontinuity at ±180°. This formula is more accurate than the other three.
5.  Fusion Mode NDOF. The BNO055 uses data from the accelerometer, gyroscope, and magnetometer to calculate its orientation. The output lies between 0° and 360° and corresponds to one of the Euler angles (pitch, yaw, and roll). In Fusion Mode the BNO055 also distinguishes between accelerations due to movement and gravity. This method can be used if the accelerometer is not at rest.
The image below shows the measured values for the inclination theta for each computation method. The measurements were made after the accelerometer from the BNO055 was calibrated. The results are very close to the calculated inclination of 9.60°.

When the accelerometer is still uncalibrated (that means, just after powering up the sensor), the measured values are about 1 degree higher than calculated.

Yes, I know, a metal ruler on a book is not a good setup to determine the accuracy of inclination measurements. However, after repeating this experiment for different inclinations, I can make the following observations:

• All 5 methods delivered nearly identical results for inclinations between 0° and 90°.
• The difference between measurements when the sensor was calibrated and not calibrated was small (about 1° with my setup).
• The values from the Fusion Mode NDOF barely changed if the sensor was at rest.
• The values from the Fusion Mode NDOF did not change due to vibrations.
• The values from the 4 Non-Fusion Modes showed some variation due to noise from the raw acceleration measurements.
• The values from the 4 Non-Fusion Modes were very sensitive to vibrations  (like light bumps on the table).

In summary, I have found the Fusion Mode NDOF as the best method to measure inclination when the sensor is at rest. This is because the BNO055 integrates the measurements from the accelerometer, gyroscope, and magnetometer to remove any disturbances from sensor movements. However, it is unclear how the data is being processed within the BNO055, and depending on your application, you might need to develop your signal processing directly from the raw data. In this case, the method with the arctan2 formula is probably the best starting point.

## How Accelerometers calculate Inclination

Even if you prefer using the Fusion Mode to measure inclination, you might want to know how these calculations are being made in the background.

Let’s call the measured accelerations for the z- and y-axes az and ay respectively. The acceleration of gravity is g and the inclination angle is θ (theta). Using basic trigonometry we can describe the angle θ with these three equations:

Simple enough. Now using the inverse trigonometric functions we calculate the angle θ as follows:

As you can see, we have four equations at our disposal. But what is the difference between them and which equation should you choose?

## Comparing the Inverse Trigonometric Functions

The following analysis is based on the document from STMicroelectronics DT040 Tilt computation using accelerometer data for inclinometer applications

Now let’s compare these inverse trigonometric functions. Suppose you place the BNO055 sensor in such a way, that the inclination is at -180°. Now slowly change the inclination and only stop when you reach +180°. What values would you get by calculating the inclination with these four functions?

Let’s first look at the behavior of the arcsin function. The plot below shows the relationship between the real inclination from the sensor (in degrees) and the inclination calculated with the arcsin function.

If you tilt the sensor between -90° and +90°, the output from the arcsin function will also stay in this range. But as soon as the sensor inclination surpasses +90°, for example, the function arcsin will start counting backward. Therefore, the output from the arcsin function is restricted to the range between -90° and +90°.

The calculated inclination from the arccos function, on the other hand, is always positive. The output range lies between 0° and 180°. This means, that if you tilt the sensor at an inclination of +30° and then at -30°, the arccos function will output the same value of +30°.

The arctan function not only restricts the output between ±90°, but it also has a discontinuity at these angles. In other words, if you start increasing the inclination of the sensor, as soon as you reach +90°, the output from the arctan function will jump from +90° to -90°. According to the document DT040 from STMicroelectronics (see the link above), this function should be avoided.

The arctan2 function, on the other hand, has an output range of ±180° and has no discontinuity. The document DT040 from STMicroelectronics also compared the effects of noise for each of these formulas and concluded that the arctan2 function is the most accurate. Because of these reasons, argues the document, the arctan2 function should be selected.

If you want to take a look at my calculations for plotting these curves, you can download the Excel file Comparing Inverse Trigonometric Functions.xlsx.  Do you also want to learn more about the arctan2 function? I’ve found the websites below very helpful:

## The Code

Finally, here is the code I adapted from the example sketch read_all_data from the Adafruit Library BNO055.

```				```
#include <Wire.h>
#include <utility/imumaths.h>

which provides a common 'type' for sensor data and some helper functions.

library and include it in your libraries folder.

You should also assign a unique ID to this sensor for use with
the Adafruit Sensor API so that you can identify this particular
sensor in any data logs, etc.  To assign a unique ID, simply
provide an appropriate value in the constructor below (12345
is used by default in this example).

Connections
===========
Connect SCL to analog 5
Connect SDA to analog 4
Connect VDD to 3.3-5V DC
Connect GROUND to common ground

History
=======
2015/MAR/03  - First release (KTOWN)
*/

/* Set the delay between fresh samples */
uint16_t BNO055_SAMPLERATE_DELAY_MS = 100;

// Check I2C device address and correct line below (by default address is 0x29 or 0x28)

void setup(void)
{
Serial.begin(115200);

while (!Serial) delay(10);  // wait for serial port to open!

Serial.println("Orientation Sensor Test"); Serial.println("");

/* Initialise the sensor */
if (!bno.begin())
{
/* There was a problem detecting the BNO055 ... check your connections */
while (1);
}

delay(1000);
}

void loop(void)
{
sensors_event_t orientationData , angVelocityData , linearAccelData, magnetometerData, accelerometerData, gravityData;

uint8_t system, gyro, accel, mag = 0;
bno.getCalibration(&system, &gyro, &accel, &mag);

Serial.print("Calibration: Sys=");
Serial.print(system);
Serial.print(" Gyro=");
Serial.print(gyro);
Serial.print(" Accel=");
Serial.print(accel);
Serial.print(" Mag=");
Serial.print(mag);
Serial.print("  ");

printEvent(&accelerometerData);
printEvent(&orientationData);

delay(BNO055_SAMPLERATE_DELAY_MS);
Serial.println();
}

void printEvent(sensors_event_t* event) {
double x = -1000000, y = -1000000 , z = -1000000; //dumb values, easy to spot problem
if (event->type == SENSOR_TYPE_ACCELEROMETER) {
x = event->acceleration.x;
y = event->acceleration.y;
z = event->acceleration.z;

Serial.print("   \tAccx= ");
Serial.print(x, 2);
Serial.print("   \tAccy= ");
Serial.print(y, 2);
Serial.print("   \tAccz= ");
Serial.print(z, 2);

//######### Computation for Inclinometer Applications #########

//Now let's calculate the magnitude g of the gravity vector
//This is given by the equation: gravity = sqrt(x²+y²+z²)
//The unit of gravity is the same as the units of x, y and z.

float gravity;
gravity = sqrt(sq(x) + sq(y) + sq(z));
Serial.print("   \tgravity= ");
Serial.print(gravity, 2);

//Let's assume that the z-axis is pointing down. The values of z and gravity are the same.
//If we incline the sensor on the y-axis, the value of y will increase, z will decrease and x will remain the same.
//Let's call the angle of inclination theta.
//We can calculate theta using four different formulas:
//theta_asin = asin(y / gravity)
//theta_acos = acos(z / gravity)
//theta_atan = atan(y / z)
//theta_atan2 = atan2(y, z)
//The unit of theta is radians. We can convert theta to degrees by multiplying by 180° and dividing by pi.

double theta_asin;
theta_asin = asin(y / gravity);
theta_asin = theta_asin * 180/PI;

double theta_acos;
theta_acos = acos(z / gravity);
theta_acos = theta_acos * 180/PI;

double theta_atan;
theta_atan = atan(y / z);
theta_atan = theta_atan * 180/PI;

double theta_atan2;
theta_atan2 = atan2(y, z);
theta_atan2 = theta_atan2 * 180/PI;

Serial.print("=    \ttheta_asin= ");
Serial.print(theta_asin, 2);
Serial.print("=    \ttheta_acos= ");
Serial.print(theta_acos, 2);
Serial.print("=    \ttheta_atan= ");
Serial.print(theta_atan, 2);
Serial.print("=   \ttheta_atan2= ");
Serial.print(theta_atan2, 2);

}
else if (event->type == SENSOR_TYPE_ORIENTATION) {
x = event->orientation.x;
y = event->orientation.y;
z = event->orientation.z;

Serial.print("=    \tEuler_z= ");
Serial.print(z, 2);
}

else {
Serial.print("Unk:");
}

}

```
```