Fully featured I2C and SPI driver for CEVA (Hilcrest)'s BNO080 and FSM300 Inertial Measurement Units.

Dependents:   BNO080-Examples BNO080-Examples

BNO080 Driver

by Jamie Smith / USC Rocket Propulsion Lab

After lots of development, we are proud to present our driver for the Hilcrest BNO080 IMU! This driver is inspired by SparkFun and Nathan Seidle's Arduino driver for this chip, but has been substantially rewritten and adapted.

It supports the main features of the chip, such as reading rotation and acceleration data, as well as some of its more esoteric functionality, such as counting steps and detecting whether the device is being hand-held.

Features

  • Support for 15 different data reports from the IMU, from acceleration to rotation to tap detection
  • Support for reading of sensor data, and automatic checking of update rate against allowed values in metadata
  • BNO_DEBUG switch enabling verbose, detailed output about communications with the chip for ease of debugging
  • Ability to tare sensor rotation and set mounting orientation
  • Can operate in several execution modes: polling I2C, polling SPI, and threaded SPI (which handles timing-critical functions in a dedicated thread, and automatically activates when the IMU has data available)
    • Also has experimental support for using asynchronous SPI transactions, allowing other threads to execute while communication with the BNO is occurring. Note that this functionality requires a patch to Mbed OS source code due to Mbed bug #13941
  • Calibration function
  • Reasonable code size for what you get: the library uses about 4K of flash and one instance of the object uses about 1700 bytes of RAM.

Documentation

Full Doxygen documentation is available online here

Example Code

Here's a simple example:

BNO080 Rotation Vector and Acceleration

#include <mbed.h>
#include <BNO080.h>

int main()
{
	Serial pc(USBTX, USBRX);

	// Create IMU, passing in output stream, pins, I2C address, and I2C frequency
	// These pin assignments are specific to my dev setup -- you'll need to change them
	BNO080I2C imu(&pc, p28, p27, p16, p30, 0x4a, 100000); 

	pc.baud(115200);
	pc.printf("============================================================\n");

	// Tell the IMU to report rotation every 100ms and acceleration every 200ms
	imu.enableReport(BNO080::ROTATION, 100);
	imu.enableReport(BNO080::TOTAL_ACCELERATION, 200);

	while (true)
	{
		wait(.001f);
		
		// poll the IMU for new data -- this returns true if any packets were received
		if(imu.updateData())
		{
			// now check for the specific type of data that was received (can be multiple at once)
			if (imu.hasNewData(BNO080::ROTATION))
			{
				// convert quaternion to Euler degrees and print
				pc.printf("IMU Rotation Euler: ");
				TVector3 eulerRadians = imu.rotationVector.euler();
				TVector3 eulerDegrees = eulerRadians * (180.0 / M_PI);
				eulerDegrees.print(pc, true);
				pc.printf("\n");
			}
			if (imu.hasNewData(BNO080::TOTAL_ACCELERATION))
			{
				// print the acceleration vector using its builtin print() method
				pc.printf("IMU Total Acceleration: ");
				imu.totalAcceleration.print(pc, true);
				pc.printf("\n");
			}
		}
	}

}


If you want more, a comprehensive, ready-to-run set of examples is available on my BNO080-Examples repository.

Credits

This driver makes use of a lightweight, public-domain library for vectors and quaternions available here.

Changelog

Version 2.1 (Nov 24 2020)

  • Added BNO080Async, which provides a threaded implementation of the SPI driver. This should help get the best performance and remove annoying timing requirements on the code calling the driver
  • Added experimental USE_ASYNC_SPI option
  • Fixed bug in v2.0 causing calibrations to fail

Version 2.0 (Nov 18 2020)

  • Added SPI support
  • Refactored buffer system so that SPI could be implemented as a subclass. Unfortunately this does substantially increase the memory usage of the driver, but I believe that the benefits are worth it.

Version 1.3 (Jul 21 2020)

  • Fix deprecation warnings and compile errors in Mbed 6
  • Fix compile errors in Arm Compiler (why doesn't it have M_PI????)

Version 1.2 (Jan 30 2020)

  • Removed accidental IRQ change
  • Fixed hard iron offset reading incorrectly due to missing cast

Version 1.1 (Jun 14 2019)

  • Added support for changing permanent orientation
  • Add FRS writing functions
  • Removed some errant printfs

Version 1.0 (Dec 29 2018)

  • Initial Mbed OS release
Revision:
0:f677e13975d0
Child:
1:aac28ffd63ed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BNO080.h	Sun Dec 23 05:23:21 2018 +0000
@@ -0,0 +1,453 @@
+/*
+ * This is USC RPL's ARM MBed BNO080 IMU driver, by Jamie Smith.
+ *
+ * It is based on SparkFun and Nathan Seidle's Arduino driver for this chip, but is substantially rewritten and adapted.
+ * It also supports some extra features, such as setting the mounting orientation and
+ * enabling some additional data reports.
+ *
+ * This driver uses no dynamic allocation, but does allocate a couple hundred bytes of class variables as buffers.
+ * This should allow you to monitor its memory usage using MBed's size printout.
+ *
+ * The BNO080 is a very complex chip; it's capable of monitoring and controlling other sensors and making
+ * intelligent decisions and calculations using its data.  Accordingly, the protocol for communicating with it
+ * is quite complex, and it took me quite a while to wrap my head around it.  If you need to modify or debug
+ * this driver, look at the CPP file for an overview of the chip's communication protocol.
+ *
+ * Note: this driver only supports I2C.  I attempted to create an SPI version, but as far as I can tell,
+ * the BNO's SPI interface has a bug that causes you to be unable to wake the chip from sleep in some conditions.
+ * Until this is fixed, SPI on it is virtually unusable.
+ */
+
+#ifndef HAMSTER_BNO080_H
+#define HAMSTER_BNO080_H
+
+#include <mbed.h>
+#include <quaternion.h>
+
+#include "BNO080Constants.h"
+
+class BNO080
+{
+    /**
+     * Serial stream to print debug info to.  Used for errors, and debugging output if debugging is enabled.
+     */
+    Serial * _debugPort;
+
+    /**
+     * I2C port object
+     */
+    I2C _i2cPort;
+
+    /// user defined port speed
+    int  _i2cPortSpeed;
+
+    /// i2c address of IMU (7 bits)
+    uint8_t _i2cAddress;
+
+    DigitalIn _int;
+    DigitalOut _rst;
+
+    DigitalOut _scope;
+
+    // packet storage
+    //-----------------------------------------------------------------------------------------------------------------
+
+#define SHTP_HEADER_SIZE 4
+#define STORED_PACKET_SIZE 128
+
+    /// Each packet has a header of 4 uint8_ts
+    uint8_t shtpHeader[SHTP_HEADER_SIZE];
+
+    /// Stores data contained in each packet.  Packets can contain an arbitrary amount of data, but we shouldn't need to read more than a few hundred bytes of them.
+    /// The only long packets we actually care about are batched sensor data packets, and with how this driver handles batching, we *should* only have to deal
+    /// with at most 9 reports at a time = ~90 bytes + a few bytes of padding
+    uint8_t shtpData[STORED_PACKET_SIZE];
+
+    /// Length of packet that was received into buffer.  Does NOT include header bytes.
+    uint16_t packetLength;
+
+    /// There are 6 com channels. Each channel has its own seqnum
+    uint8_t sequenceNumber[6] = {0, 0, 0, 0, 0, 0};
+
+    /// Commands have a seqNum as well. These are inside command packet, the header uses its own seqNum per channel
+    uint8_t commandSequenceNumber = 0;
+
+    // data storage
+    //-----------------------------------------------------------------------------------------------------------------
+
+    // 1 larger than the largest sensor report ID
+#define STATUS_ARRAY_LEN 0x1A
+
+    /// stores status of each sensor, indexed by report ID
+    uint8_t reportStatus[STATUS_ARRAY_LEN] = {};
+
+public:
+
+    /// List of all sensor reports that the IMU supports.
+    enum class Report : uint8_t
+    {
+        /**
+         * Total acceleration of the IMU in world space.
+         * See BNO datasheet section 2.1.1
+         */
+        TOTAL_ACCELERATION = SENSOR_REPORTID_ACCELEROMETER,
+
+        /**
+         * Acceleration of the IMU not including the acceleration of gravity.
+         * See BNO datasheet section 2.1.1
+         */
+        LINEAR_ACCELERATION = SENSOR_REPORTID_LINEAR_ACCELERATION,
+
+        /**
+         * Acceleration of gravity felt by the IMU.
+         * See BNO datasheet section 2.1.1
+         */
+        GRAVITY_ACCELERATION = SENSOR_REPORTID_GRAVITY,
+
+        /**
+         * (calibrated) gyroscope reading of the rotational speed of the IMU.
+         * See BNO datasheet section 2.1.2
+         */
+        GYROSCOPE = SENSOR_REPORTID_GYROSCOPE_CALIBRATED,
+
+        /**
+         * (calibrated) reading of magnetic field levels.
+         * See BNO datasheet section 2.1.3
+         */
+        MAG_FIELD = SENSOR_REPORTID_MAGNETIC_FIELD_CALIBRATED,
+
+        /**
+         * Fused reading of the IMU's rotation in space using all three sensors.  This is the most accurate reading
+         * of absolute orientation that the IMU can provide.
+         * See BNO datasheet section 2.2.4
+         */
+        ROTATION = SENSOR_REPORTID_ROTATION_VECTOR,
+
+        /**
+         * Fused reading of rotation from accelerometer and magnetometer readings.  This report is designed to decrease
+         * power consumption (by turning off the gyroscope) in exchange for reduced responsiveness.
+         */
+        GEOMAGNETIC_ROTATION = SENSOR_REPORTID_GEOMAGNETIC_ROTATION_VECTOR,
+
+        /**
+         * Fused reading of the IMU's rotation in space.  Unlike the regular rotation vector, the Game Rotation Vector
+         * is not referenced against the magnetic field and the "zero yaw" point is arbitrary.
+         * See BNO datasheet section 2.2.2
+         */
+        GAME_ROTATION = SENSOR_REPORTID_GAME_ROTATION_VECTOR,
+
+        /**
+         * Detects a user tapping on the device containing the IMU.
+         * See BNO datasheet section 2.4.2
+         */
+        TAP_DETECTOR = SENSOR_REPORTID_TAP_DETECTOR,
+
+        /**
+         * Detects whether the device is on a table, being held stably, or being moved.
+         * See BNO datasheet section 2.4.1
+         */
+        STABILITY_CLASSIFIER = SENSOR_REPORTID_STABILITY_CLASSIFIER,
+
+        /**
+         * Detects a user taking a step with the IMU worn on their person.
+         * See BNO datasheet section 2.4.3
+         */
+        STEP_DETECTOR = SENSOR_REPORTID_STEP_DETECTOR,
+
+        /**
+         * Detects how many steps a user has taken.
+         * See BNO datasheet section 2.4.4
+         */
+        STEP_COUNTER = SENSOR_REPORTID_STEP_COUNTER,
+
+        /**
+         * Detects when the IMU has made a "significant" motion, defined as moving a few steps and/or accelerating significantly.
+         * See BNO datasheet section 2.4.6
+         */
+        SIGNIFICANT_MOTION = SENSOR_REPORTID_SIGNIFICANT_MOTION,
+
+        /**
+         * Detects when the IMU is being shaken.
+         * See BNO datasheet section 2.4.7
+         */
+        SHAKE_DETECTOR = SENSOR_REPORTID_SHAKE_DETECTOR
+    };
+
+    // data variables to read reports from
+    //-----------------------------------------------------------------------------------------------------------------
+
+    // @{
+    /// Version info read from the IMU when it starts up
+    uint8_t majorSoftwareVersion;
+    uint8_t minorSoftwareVersion;
+    uint16_t patchSoftwareVersion;
+    uint32_t partNumber;
+    uint32_t buildNumber;
+    // @}
+
+
+    /**
+     * Readout from Accleration report.
+     * Represents total acceleration in m/s^2 felt by the BNO's accelerometer.
+     */
+    TVector3 totalAcceleration;
+
+    /**
+     * Readout from Linear Acceleration report.
+     * Represents acceleration felt in m/s^2 by the BNO's accelerometer not including the force of gravity.
+     */
+    TVector3 linearAcceleration;
+
+    /**
+     * Readout from Gravity report.
+     * Represents the force of gravity in m/s^2 felt by the BNO's accelerometer.
+     */
+    TVector3 gravityAcceleration;
+
+    /**
+     * Readout from Calibrated Gyroscope report
+     * Represents the angular velocities of the chip in rad/s in the X, Y, and Z axes
+     */
+    TVector3 gyroRotation;
+
+    /**
+     * Readout from the Magnetic Field Calibrated report.
+     * Represents the magnetic field read by the chip in uT in the X, Y, and Z axes
+     */
+    TVector3 magField;
+
+    /**
+     * Readout from the Rotation Vector report.
+     * Represents the rotation of the IMU (relative to magnetic north) in radians.
+     */
+    Quaternion rotationVector;
+
+    /**
+     * Auxillary accuracy readout from the Rotation Vector report.
+     * Represents the estimated accuracy of the rotation vector in radians.
+     */
+    float rotationAccuracy;
+
+    /**
+     * Readout from the Game Rotation Vector report.
+     * Represents the rotation of the IMU in radians.  Unlike the regular rotation vector, the Game Rotation Vector
+     * is not referenced against the magnetic field and the "zero yaw" point is arbitrary.
+     */
+    Quaternion gameRotationVector;
+
+    /**
+     * Readout from the Geomagnetic Rotation Vector report.
+     * Represents the geomagnetic rotation of the IMU (relative to magnetic north) in radians.
+     */
+    Quaternion geomagneticRotationVector;
+
+    /**
+     * Auxillary accuracy readout from the Geomagnetic Rotation Vector report.
+     * Represents the estimated accuracy of the rotation vector in radians.
+     */
+    float geomagneticRotationAccuracy;
+
+    /**
+     * Tap readout from the Tap Detector report.  This flag is set to true whenever a tap is detected, and you should
+     * manually clear it when you have processed the tap.
+     */
+    bool tapDetected;
+
+    /**
+     * Whether the last tap detected was a single or double tap.
+     */
+    bool doubleTap;
+
+
+    // Management functions
+    //-----------------------------------------------------------------------------------------------------------------
+
+    /**
+     * Construct a BNO080, providing pins and parameters.
+     *
+     * NOTE: while some schematics tell you to connect the BOOTN and WAKEN pins to the processor, this driver does not use or require them.
+     * Just tie them both to VCC per the datasheet.
+     *
+     * @param debugPort Serial port to write output to.  Cannot be nullptr.
+     * @param user_SDApin Hardware SPI MOSI pin
+     * @param user_SCLpin Hardware SPI MISO pin
+     * @param user_SCLKPin Hardware SPI SCLK pin
+     * @param user_CSPin SPI CS pin.  Can be any IO pin, no restrictions.
+     * @param user_INTPin Input pin connected to HINTN
+     * @param user_RSTPin Output pin connected to NRST
+     * @param i2cPortSpeed SPI frequency.  The BNO's max is 3Mhz, we default to 300Khz for safety.
+     */
+    BNO080(Serial *debugPort, PinName user_SDApin, PinName user_SCLpin, PinName user_INTPin, PinName user_RSTPin,
+           uint8_t i2cAddress=0x4a, int i2cPortSpeed=400000);
+
+    /**
+     * Resets and connects to the IMU.
+     *
+     * If this function is failing, it would be a good idea to turn on BNO_DEBUG in the cpp file to get detailed output
+     *
+     * @return whether or not initialization was successful
+     */
+    bool begin();
+
+    /**
+     * Tells the IMU to use its current rotation vector as the "zero" rotation vector and to reorient
+     * all outputs accordingly.
+     *
+     * @param zOnly If true, only the rotation about the Z axis (the heading) will be tared.
+     */
+    void tare(bool zOnly = false);
+
+    /**
+     * Tells the IMU to begin a dynamic sensor calibration.  To calibrate the IMU, call this function and move
+     * the IMU according to the instructions in the "BNO080 Sensor Calibration Procedure" app note
+     * (http://www.hillcrestlabs.com/download/59de9014566d0727bd002ae7).
+     *
+     * To tell when the calibration is complete, look at the status bits for Game Rotation Vector (for accel and gyro)
+     * and Magnetic Field (for the magnetometer).
+     *
+     * The gyro and accelerometer should only need to be calibrated once, but the magnetometer will need to be recalibrated
+     * every time the orientation of ferrous metals around the IMU changes (e.g. when it is put into a new enclosure).
+     *
+     * The new calibration will not be saved in flash until you call saveCalibration().
+     *
+     * @param calibrateAccel Whether to calibrate the accelerometer.
+     * @param calibrateGyro Whether to calibrate the gyro.
+     * @param calibrateMag Whether to calibrate the magnetometer.
+     */
+    void startCalibration(bool calibrateAccel, bool calibrateGyro, bool calibrateMag);
+
+    /**
+     * Saves the calibration started with startCalibration() and ends the calibration.
+     * You will want to call this once the status bits read as "accuracy high".
+     *
+     * WARNING: if you paid for a factory calibrated IMU, then this WILL OVERWRITE THE FACTORY CALIBRATION in whatever sensors
+     * are being calibrated.  Use with caution!
+     */
+    void saveCalibration();
+
+    // Report functions
+    //-----------------------------------------------------------------------------------------------------------------
+
+    /**
+     * Checks for new data packets queued on the IMU.
+     * If there are packets queued, receives all of them and updates
+     * the class variables with the results.
+     *
+     * @return true iff new data was received
+     */
+    bool updateData();
+
+
+    /**
+     * Gets the status of a report as a 2 bit number.
+     * per SH-2 section 6.5.1, this is interpreted as: <br>
+     * 0 - unreliable <br>
+     * 1 - accuracy low <br>
+     * 2 - accuracy medium <br>
+     * 3 - accuracy high <br>
+     * of course, these are only updated if a given report is enabled.
+     * @param report
+     * @return
+     */
+    uint8_t getReportStatus(Report report);
+
+
+    /**
+     * Enable a data report from the IMU.  Look at the comments above to see what the reports do.
+     *
+     * @param timeBetweenReports time in milliseconds between data updates.
+     */
+    void enableReport(Report report, uint16_t timeBetweenReports);
+
+private:
+
+    // internal utility functions
+    //-----------------------------------------------------------------------------------------------------------------
+
+    /**
+     * Processes the packet currently stored in the buffer, and updates class variables to reflect the data it contains
+     */
+    void processPacket();
+
+    /**
+     * Processes the sensor data packet currently stored in the buffer.
+     * Only called from processPacket()
+     */
+    void parseSensorDataPacket();
+
+    /**
+     * Call to wait for a packet with the given parameters to come in.
+     *
+     * @param channel Channel of the packet
+     * @param reportID Report ID (first data byte) of the packet
+     * @param timeout how long to wait for the packet
+     * @return true if the packet has been received, false if it timed out
+     */
+    bool waitForPacket(int channel, uint8_t reportID, float timeout = .125f);
+
+    /**
+     * Given a Q value, converts fixed point floating to regular floating point number.
+     * @param fixedPointValue
+     * @param qPoint
+     * @return
+     */
+    float qToFloat(int16_t fixedPointValue, uint8_t qPoint);
+
+    /**
+     * Given a floating point value and a Q point, convert to Q
+     * See https://en.wikipedia.org/wiki/Q_(number_format)
+     * @param qFloat
+     * @param qPoint
+     * @return
+     */
+    int16_t floatToQ(float qFloat, uint8_t qPoint);
+
+    /**
+     *  Tell the sensor to do a command.
+     *  See SH-2 Reference Manual section 6.3.8 page 42, Command request
+     *  The caller is expected to set shtpData 3 though 11 prior to calling
+     */
+    void sendCommand(uint8_t command);
+
+    /**
+     * Given a sensor's report ID, this tells the BNO080 to begin reporting the values.
+     *
+     * @param reportID
+     * @param timeBetweenReports
+     * @param specificConfig the specific config word. Useful for personal activity classifier.
+     */
+    void setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig = 0);
+
+    /**
+     * Reads a packet from the IMU and stores it in the class variables.
+     *
+     * @param timeout how long to wait for there to be a packet
+     *
+     * @return whether a packet was recieved.
+     */
+    bool receivePacket(float timeout=.2f);
+
+    /**
+     * Sends the current shtpData contents to the BNO.  It's a good idea to disable interrupts before you call this.
+     *
+     * @param channelNumber the channel to send on
+     * @param dataLength How many bits of shtpData to send
+     * @return
+     */
+    bool sendPacket(uint8_t channelNumber, uint8_t dataLength);
+
+    /**
+     * Prints the current shtp packet stored in the buffer.
+     * @param length
+     */
+    void printPacket();
+
+    /**
+     * Erases the current SHTP packet buffer so new data can be written
+     */
+     void zeroBuffer();
+
+};
+
+
+#endif //HAMSTER_BNO080_H