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

Files at this revision

API Documentation at this revision

Comitter:
Jamie Smith
Date:
Tue Nov 24 15:06:05 2020 -0800
Parent:
8:199c7fad233d
Commit message:
Implement BNO080Async

Changed in this revision

BNO080.cpp Show annotated file Show diff for this revision Revisions of this file
BNO080.h Show annotated file Show diff for this revision Revisions of this file
BNO080Async.cpp Show annotated file Show diff for this revision Revisions of this file
BNO080Async.graphml Show annotated file Show diff for this revision Revisions of this file
BNO080Async.h Show annotated file Show diff for this revision Revisions of this file
--- a/BNO080.cpp	Wed Nov 18 18:07:27 2020 -0800
+++ b/BNO080.cpp	Tue Nov 24 15:06:05 2020 -0800
@@ -93,7 +93,7 @@
 
 bool BNO080Base::begin()
 {
-	//Configure the BNO080 for I2C communication
+	//Configure the BNO080
 
 	_rst = 0; // Reset BNO080
 	ThisThread::sleep_for(1ms); // Min length not specified in datasheet?
@@ -159,7 +159,7 @@
 	}
 
 	// Finally, we want to interrogate the device about its model and version.
-	zeroBuffer();
+	clearSendBuffer();
 	txShtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; //Request the product ID and reset info
 	txShtpData[1] = 0; //Reserved
 	sendPacket(CHANNEL_CONTROL, 2);
@@ -194,7 +194,7 @@
 
 void BNO080Base::tare(bool zOnly)
 {
-	zeroBuffer();
+	clearSendBuffer();
 
  	// from SH-2 section 6.4.4.1
 	txShtpData[3] = 0; // perform tare now
@@ -216,7 +216,7 @@
 bool BNO080Base::enableCalibration(bool calibrateAccel, bool calibrateGyro, bool calibrateMag)
 {
 	// send the Configure ME Calibration command
-	zeroBuffer();
+	clearSendBuffer();
 
 	txShtpData[3] = static_cast<uint8_t>(calibrateAccel ? 1 : 0);
 	txShtpData[4] = static_cast<uint8_t>(calibrateGyro ? 1 : 0);
@@ -259,7 +259,7 @@
 
 bool BNO080Base::saveCalibration()
 {
-	zeroBuffer();
+	clearSendBuffer();
 
 	// no arguments
 	sendCommand(COMMAND_SAVE_DCD);
@@ -295,7 +295,7 @@
 
 void BNO080Base::setSensorOrientation(Quaternion orientation)
 {
-	zeroBuffer();
+	clearSendBuffer();
 
 	// convert floats to Q
 	int16_t Q_x = floatToQ(orientation.x(), ORIENTATION_QUAT_Q_POINT);
@@ -915,7 +915,7 @@
 bool BNO080Base::readFRSRecord(uint16_t recordID, uint32_t* readBuffer, uint16_t readLength)
 {
 	// send initial read request
-	zeroBuffer();
+	clearSendBuffer();
 
 	txShtpData[0] = SHTP_REPORT_FRS_READ_REQUEST;
 	// read offset of 0 -> start at the start of the record
@@ -1024,7 +1024,7 @@
 bool BNO080Base::writeFRSRecord(uint16_t recordID, uint32_t* buffer, uint16_t length)
 {
 	// send initial write request, which tells the chip where we're writing
-	zeroBuffer();
+	clearSendBuffer();
 
 	txShtpData[0] = SHTP_REPORT_FRS_WRITE_REQUEST;
 	// length to write (must be <= record length)
@@ -1057,7 +1057,7 @@
 	for(uint16_t wordIndex = 0; wordIndex < length; wordIndex += 2)
 	{
 		// send packet containing 2 words
-		zeroBuffer();
+		clearSendBuffer();
 		txShtpData[0] = SHTP_REPORT_FRS_WRITE_DATA;
 
 		// offset to write at
@@ -1214,7 +1214,7 @@
 }
 
 
-void BNO080Base::zeroBuffer()
+void BNO080Base::clearSendBuffer()
 {
 	memset(txPacketBuffer, 0, SHTP_HEADER_SIZE + SHTP_MAX_TX_PACKET_SIZE);
 }
@@ -1548,7 +1548,7 @@
 
 	// send packet to IMU.
 	// This also might receive the first part of another packet, which there is no way to avoid.
-	_spiPort.write(reinterpret_cast<char*>(txPacketBuffer), totalLength, reinterpret_cast<char*>(rxPacketBuffer), totalLength);
+	spiTransferAndWait(txPacketBuffer, totalLength, rxPacketBuffer, totalLength);
 
 	if(rxShtpHeader[0] == 0 && rxShtpHeader[0] == 0)
 	{
@@ -1587,8 +1587,8 @@
 
 	}
 
-	// read the header bytes first
-	_spiPort.write(nullptr, 0, reinterpret_cast<char *>(rxPacketBuffer), SHTP_HEADER_SIZE);
+	// read the header bytes first.
+	spiTransferAndWait(nullptr, 0, rxPacketBuffer, SHTP_HEADER_SIZE);
 
 	// now read the data
 	return receiveCompletePacket(SHTP_HEADER_SIZE, timeout);
@@ -1655,7 +1655,7 @@
 	if(bytesRead == SHTP_HEADER_SIZE)
 	{
 		// just read the entire packet into the buffer
-		_spiPort.write(nullptr, 0, reinterpret_cast<char*>(rxPacketBuffer), totalLength);
+		spiTransferAndWait(nullptr, 0, rxPacketBuffer, totalLength);
 	}
 	else
 	{
@@ -1663,7 +1663,7 @@
 		size_t receiveLength = SHTP_HEADER_SIZE + (totalLength - bytesRead);
 
 		// read remaining bytes into the data buffer starting at the next byte
-		_spiPort.write(nullptr, 0, reinterpret_cast<char*>(rxPacketBuffer) + bytesRead, receiveLength);
+		spiTransferAndWait(nullptr, 0, rxPacketBuffer + bytesRead, receiveLength);
 
 		// erase the new header we just read, leaving only the data as a contiguous block
 		std::memmove(rxPacketBuffer + bytesRead, rxPacketBuffer + bytesRead + SHTP_HEADER_SIZE, receiveLength - SHTP_HEADER_SIZE);
@@ -1678,4 +1678,28 @@
 
 	// done!
 	return true;
-}
\ No newline at end of file
+}
+
+#if USE_ASYNC_SPI
+
+void BNO080SPI::spiTransferAndWait(const uint8_t *tx_buffer, int tx_length, uint8_t *rx_buffer, int rx_length)
+{
+	_spiPort.transfer(tx_buffer, tx_length, rx_buffer, rx_length,
+					  callback(this, &BNO080SPI::onSPITransferComplete),
+					  SPI_EVENT_COMPLETE | SPI_EVENT_ERROR);
+
+	uint32_t waitResult = spiCompleteFlag.wait_any(SPI_EVENT_ALL);
+	if(!(waitResult & SPI_EVENT_COMPLETE))
+	{
+		// at least let the user know the error happened...
+		_debugPort->printf("BNO Async SPI Error %" PRIu32 "\n", waitResult);
+	}
+
+}
+
+void BNO080SPI::onSPITransferComplete(int event)
+{
+	spiCompleteFlag.set(event);
+}
+
+#endif
\ No newline at end of file
--- a/BNO080.h	Wed Nov 18 18:07:27 2020 -0800
+++ b/BNO080.h	Tue Nov 24 15:06:05 2020 -0800
@@ -30,6 +30,17 @@
 // useful define when working with orientation quaternions
 #define SQRT_2 1.414213562f
 
+// Enable this to enable experimental support for Mbed's asynchronous SPI transfer API.
+// This will allow your processor to do other things while long SPI transfers are taking place
+// (and this IMU can end up transferring hundreds of bytes per packet, so this is useful).
+// To get this to work, you may need to use a slower clock rate (<1MHz on the STM32F429ZI I tested).
+// You also will need to edit Mbed OS code in order to use 0x00 as the SPI fill character for
+// asynchronous transfers (the API currently only allows changing this for synchronous transfers)
+// (I had to edit the SPI_FILL_CHAR constant in stm_spi_api.c).
+#define USE_ASYNC_SPI 0
+
+// Note: I filed a bug about the SPI fill char issue: https://github.com/ARMmbed/mbed-os/issues/13941
+
 /**
   Class to drive the BNO080 9-axis IMU.
   
@@ -44,7 +55,9 @@
 	Stream * _debugPort;
 
 	/// Interrupt pin -- signals to the host that the IMU has data to send
-	DigitalIn _int;
+	// Note: only ever used as a digital input by BNO080.
+	// Used for interrupts by BNO080Async.
+	InterruptIn _int;
 	
 	// Reset pin -- resets IMU when held low.
 	DigitalOut _rst;
@@ -393,9 +406,11 @@
 	 *
 	 * If this function is failing, it would be a good idea to turn on BNO_DEBUG in the cpp file to get detailed output.
 	 *
+	 * Note: this function takes several hundred ms to execute, mainly due to waiting for the BNO to boot.
+	 *
 	 * @return whether or not initialization was successful
 	 */
-	bool begin();
+	virtual bool begin();
 
 	/**
 	 * Tells the IMU to use its current rotation vector as the "zero" rotation vector and to reorient
@@ -466,6 +481,18 @@
 	 * @return true if the operation succeeded, false if it failed.
  	*/
 	bool setPermanentOrientation(Quaternion orientation);
+
+	/**
+	 * No-op on synchronous driver.  For compatibility with BNO080Async
+	 */
+	virtual void lockMutex()
+	{}
+
+	/**
+	 * No-op on synchronous driver.  For compatibility with BNO080Async
+	 */
+	virtual void unlockMutex()
+	{}
     
 	// Report functions
 	//-----------------------------------------------------------------------------------------------------------------
@@ -482,7 +509,7 @@
 	 * @return True iff new data packets of any kind were received.  If you need more fine-grained data change reporting,
 	 * check out hasNewData().
 	 */
-	bool updateData();
+	virtual bool updateData();
 
 	/**
 	 * Gets the status of a report as a 2 bit number.
@@ -623,12 +650,16 @@
 	/**
 	 * Call to wait for a packet with the given parameters to come in.
 	 *
+	 * Note: on BNO080Async, the received packet data will stay in the RX buffer
+	 * until either the public IMU function that was called returns, or you
+	 * call sendPacket() or waitForPacket() again.
+	 *
 	 * @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, std::chrono::milliseconds timeout = 125ms);
+	virtual bool waitForPacket(int channel, uint8_t reportID, std::chrono::milliseconds timeout = 125ms);
 
 	/**
 	 * Given a Q value, converts fixed point floating to regular floating point number.
@@ -737,9 +768,10 @@
 	void printPacket(uint8_t * buffer);
 
 	/**
-	 * Erases the current SHTP TX packet buffer
+	 * Erases the current SHTP TX packet buffer.
+	 * In BNO080Async, this blocks until the buffer is available.
 	 */
-	 void zeroBuffer();
+	 virtual void clearSendBuffer();
 
 	 /**
 	  * Loads the metadata for this report into the metadata buffer.
@@ -750,11 +782,6 @@
 
 };
 
-// TODO list:
-// - Better handling of continued packets (discard as an error)
-// - Unified TX/RX SPI comms function
-
-
 /**
  * Version of the BNO080 driver which uses the I2C interface
  */
@@ -819,6 +846,7 @@
  */
 class BNO080SPI : public BNO080Base
 {
+protected:
 	/**
 	 * I2C port object.  Provides physical layer communications with the chip.
 	 */
@@ -851,7 +879,7 @@
 	 */
 	BNO080SPI(Stream *debugPort, PinName rstPin, PinName intPin, PinName wakePin, PinName misoPin, PinName mosiPin, PinName sclkPin, PinName csPin, int spiSpeed=3000000);
 
-private:
+protected:
 
 	bool receivePacket(std::chrono::milliseconds timeout=200ms) override;
 
@@ -863,8 +891,38 @@
 	 * @return
 	 */
 	bool receiveCompletePacket(size_t bytesRead, std::chrono::milliseconds timeout=200ms);
+
+#if USE_ASYNC_SPI
+
+	/**
+	 * Start an SPI transfer and suspend the current thread until it is complete.
+	 * Used by functions in BNO080SPI.
+	 * Note: should only be called by one thread at a time.
+	 * @param tx_buffer
+	 * @param tx_length
+	 * @param rx_buffer
+	 * @param rx_length
+	 */
+	void spiTransferAndWait(const uint8_t *tx_buffer, int tx_length, uint8_t *rx_buffer, int rx_length);
+
+	// callback for finished SPI transfers
+	void onSPITransferComplete(int event);
+
+	// Signal whan an SPI transfer is complete.
+	EventFlags spiCompleteFlag;
+
+#else
+
+	/**
+	 * Start an SPI transfer and wait for it to complete.
+	 * BNO080Async swaps in a threaded implementation here.
+	 * API same as SPI::write().
+	 */
+	void spiTransferAndWait(const uint8_t *tx_buffer, int tx_length, uint8_t *rx_buffer, int rx_length)
+	{
+		_spiPort.write(reinterpret_cast<const char *>(tx_buffer), tx_length, reinterpret_cast<char *>(rx_buffer), rx_length);
+	}
+#endif
 };
 
-
-
 #endif //HAMSTER_BNO080_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BNO080Async.cpp	Tue Nov 24 15:06:05 2020 -0800
@@ -0,0 +1,305 @@
+//
+// Created by jamie on 11/18/2020.
+//
+
+#include "BNO080Async.h"
+
+#include <cinttypes>
+
+#define BNO_ASYNC_DEBUG 0
+
+// event flags constants for signalling the thread
+#define EF_INTERRUPT 0b1
+#define EF_SHUTDOWN 0b10
+#define EF_RESTART 0b100
+
+void BNO080Async::threadMain()
+{
+	while(commLoop())
+	{
+		// loop forever
+	}
+}
+
+bool BNO080Async::commLoop()
+{
+	_rst = 0; // Reset BNO080
+	ThisThread::sleep_for(1ms); // Min length not specified in datasheet?
+	_rst = 1; // Bring out of reset
+
+	// wait for a falling edge (NOT just a low) on the INT pin to denote startup
+	{
+		EventFlags edgeWaitFlags;
+		_int.fall(callback([&](){edgeWaitFlags.set(1);}));
+
+		// have the RTOS wait until an edge is detected or the timeout is hit
+		uint32_t edgeWaitEvent = edgeWaitFlags.wait_any(1, (BNO080_RESET_TIMEOUT).count());
+
+		if(!edgeWaitEvent)
+		{
+			_debugPort->printf("Error: BNO080 reset timed out, chip not detected.\n");
+		}
+	}
+
+#if BNO_ASYNC_DEBUG
+	_debugPort->printf("BNO080 detected!\r\n");
+#endif
+
+	wakeupFlags.set(EF_INTERRUPT);
+
+	// configure interrupt to send the event flag
+	_int.fall(callback([&]()
+	{
+		if(!inTXRX)
+		{
+			wakeupFlags.set(EF_INTERRUPT);
+		}
+	}));
+
+	while(true)
+	{
+		uint32_t wakeupEvent = wakeupFlags.wait_any(EF_INTERRUPT | EF_SHUTDOWN | EF_RESTART);
+
+		if(wakeupEvent & EF_SHUTDOWN)
+		{
+			// shutdown thread permanently
+			return false;
+		}
+		else if(wakeupEvent & EF_RESTART)
+		{
+			// restart thread
+			return true;
+		}
+
+		// lock the mutex to handle remaining cases
+		bnoDataMutex.lock();
+
+		if(wakeupEvent & EF_INTERRUPT)
+		{
+			while(_int == 0)
+			{
+				inTXRX = true;
+
+				// send data if there is data to send.  This may also receive a packet.
+				if (dataToSend)
+				{
+					BNO080SPI::sendPacket(txPacketChannelNumber, txPacketDataLength);
+					dataToSend = false;
+				}
+				else
+				{
+					BNO080SPI::receivePacket();
+				}
+
+				inTXRX = false;
+
+				// clear the wake flag if it was set
+				_wakePin = 1;
+
+				// If the IMU wants to send us an interrupt immediately after a transaction,
+				// it will be missed due to the inTXRX block.  So, we need to keep checking _int
+				// in a loop.
+
+				// update received flag
+				dataReceived = true;
+
+				// check if this packet is being waited on.
+				if (waitingForPacket)
+				{
+					if (waitingForReportID == rxShtpData[0] && waitingForChannel == rxShtpHeader[2])
+					{
+						// unblock main thread so it can process this packet
+						waitingPacketArrived = true;
+						waitingPacketArrivedCV.notify_all();
+
+						// unlock mutex and wait until the main thread says it's OK to receive another packet
+						clearToRxNextPacket = false;
+						waitingPacketProcessedCV.wait([&]() { return clearToRxNextPacket; });
+					}
+				}
+				else
+				{
+					processPacket();
+				}
+
+				// clear the interrupt flag if it has been set because we're about to check _int anyway.
+				// Prevents spurious wakeups.
+				wakeupFlags.clear(EF_INTERRUPT);
+
+			}
+
+		}
+
+		// unlock data mutex before waiting for flags
+		bnoDataMutex.unlock();
+	}
+}
+
+bool BNO080Async::sendPacket(uint8_t channelNumber, uint8_t dataLength)
+{
+	// first make sure mutex is locked
+	if(bnoDataMutex.get_owner() != ThisThread::get_id())
+	{
+		_debugPort->printf("IMU communication function called without bnoDataMutex locked!\n");
+		return false;
+	}
+
+	// now set class variables
+	txPacketChannelNumber = channelNumber;
+	txPacketDataLength = dataLength;
+	dataToSend = true;
+
+	// signal thread to wake up by sending _wake signal (which will cause the IMU to interrupt us once ready)
+	_wakePin = 0;
+
+	return true;
+}
+
+void BNO080Async::clearSendBuffer()
+{
+	// first make sure mutex is locked
+	if(bnoDataMutex.get_owner() != ThisThread::get_id())
+	{
+		_debugPort->printf("IMU communication function called without bnoDataMutex locked!\n");
+		return;
+	}
+
+	// Check if we are trying to send a packet while another packet is still queued.
+	// Since we don't have an actual queue, just wait until the other packet is sent.
+	while(dataToSend)
+	{
+		dataSentCV.wait();
+	}
+
+	// now actually erase the buffer
+	BNO080Base::clearSendBuffer();
+}
+
+bool BNO080Async::waitForPacket(int channel, uint8_t reportID, std::chrono::milliseconds timeout)
+{
+	// first make sure mutex is locked
+	if(bnoDataMutex.get_owner() != ThisThread::get_id())
+	{
+		_debugPort->printf("IMU communication function called without bnoDataMutex locked!\n");
+		return false;
+	}
+
+	// send information to thread
+	waitingForPacket = true;
+	waitingForChannel = channel;
+	waitingForReportID = reportID;
+	waitingPacketArrived = false;
+
+	// now unlock mutex and allow thread to run and receive packets
+	waitingPacketArrivedCV.wait_for(timeout, [&]() {return waitingPacketArrived;});
+
+	if(!waitingPacketArrived)
+	{
+		_debugPort->printf("Packet wait timeout.\n");
+		return false;
+	}
+
+	// packet we are waiting for is now in the buffer.
+	waitingForPacket = false;
+
+	// Now we can unblock the comms thread and allow it to run.
+	// BUT, it can't actually start until bnoDataMutex is released.
+	// This means that the packet data is guaranteed to stay in the buffer until that mutex is released
+	// which will happen either when the main thread is done with the IMU, or on the next sendPacket() or
+	// waitForPacket() call.
+	clearToRxNextPacket = true;
+	waitingPacketProcessedCV.notify_all();
+
+	return true;
+}
+
+BNO080Async::BNO080Async(Stream *debugPort, PinName rstPin, PinName intPin, PinName wakePin, PinName misoPin,
+						 PinName mosiPin, PinName sclkPin, PinName csPin, int spiSpeed, osPriority_t threadPriority):
+ BNO080SPI(debugPort, rstPin, intPin, wakePin, misoPin, mosiPin, sclkPin, csPin, spiSpeed),
+ commThread(threadPriority),
+ dataSentCV(bnoDataMutex),
+ waitingPacketArrivedCV(bnoDataMutex),
+ waitingPacketProcessedCV(bnoDataMutex)
+{
+
+}
+
+
+bool BNO080Async::begin()
+{
+	// shut down thread if it's running
+	if(commThread.get_state() == Thread::Deleted)
+	{
+		// start thread for the first time
+		commThread.start(callback(this, &BNO080Async::threadMain));
+	}
+	else
+	{
+		// restart thread
+		wakeupFlags.set(EF_RESTART);
+	}
+
+
+	{
+		ScopedLock<Mutex> lock(bnoDataMutex);
+
+		// once the thread starts it, the BNO will send an Unsolicited Initialize response (SH-2 section 6.4.5.2), and an Executable Reset command
+		if(!waitForPacket(CHANNEL_EXECUTABLE, EXECUTABLE_REPORTID_RESET, 1s))
+		{
+			_debugPort->printf("No initialization report from BNO080.\n");
+			return false;
+		}
+		else
+		{
+#if BNO_DEBUG
+			_debugPort->printf("BNO080 reports initialization successful!\n");
+#endif
+		}
+
+		// Finally, we want to interrogate the device about its model and version.
+		clearSendBuffer();
+		txShtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; //Request the product ID and reset info
+		txShtpData[1] = 0; //Reserved
+		sendPacket(CHANNEL_CONTROL, 2);
+
+		waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_PRODUCT_ID_RESPONSE);
+
+		if (rxShtpData[0] == SHTP_REPORT_PRODUCT_ID_RESPONSE)
+		{
+			majorSoftwareVersion = rxShtpData[2];
+			minorSoftwareVersion = rxShtpData[3];
+			patchSoftwareVersion = (rxShtpData[13] << 8) | rxShtpData[12];
+			partNumber = (rxShtpData[7] << 24) | (rxShtpData[6] << 16) | (rxShtpData[5] << 8) | rxShtpData[4];
+			buildNumber = (rxShtpData[11] << 24) | (rxShtpData[10] << 16) | (rxShtpData[9] << 8) | rxShtpData[8];
+
+#if BNO_DEBUG
+			_debugPort->printf("BNO080 reports as SW version %hhu.%hhu.%hu, build %lu, part no. %lu\n",
+						   majorSoftwareVersion, minorSoftwareVersion, patchSoftwareVersion,
+						   buildNumber, partNumber);
+#endif
+
+		}
+		else
+		{
+			_debugPort->printf("Bad response from product ID command.\n");
+			return false;
+		}
+	}
+
+
+	// successful init
+	return true;
+}
+
+bool BNO080Async::updateData()
+{
+	bool newData = dataReceived;
+	dataReceived = false;
+	return newData;
+}
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BNO080Async.graphml	Tue Nov 24 15:06:05 2020 -0800
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.19.1.1-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0"/>
+    <node id="n0">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="102.4799999999999" x="716.0" y="253.0"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="59.37109375" x="21.55445312500001" xml:space="preserve" y="15.498828124999989">Lock data 
+mutex<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="109.48800000000006" x="1135.976" y="253.0"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.376953125" x="13.055523437500142" xml:space="preserve" y="15.498828124999989">Start SPI Send
+Transaction<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="92.72000000000003" width="94.0" x="933.4279999999998" y="238.83999999999997"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.36328125" x="13.318359375" xml:space="preserve" y="29.658828125000014">Has packet 
+to send?<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="diamond"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="119.856" width="121.5106125970664" x="1329.1693874029331" y="225.272"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="90.712890625" x="15.398860986033242" xml:space="preserve" y="43.226828125">Received an RX
+packet header?<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="diamond"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="121.51061259706637" x="1072.4493874029338" y="384.088"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="109.38671875" x="6.061946923533242" xml:space="preserve" y="15.498828124999989">Start SPI Read 
+Header Transaction<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="109.48800000000006" x="1279.7120000000002" y="384.088"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="48.103515625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="106.720703125" x="1.3836484374999145" xml:space="preserve" y="8.148242187499989">Read remainder of 
+packet contained 
+in header<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="102.4799999999999" x="716.0000000000001" y="384.088"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="23.34765625" x="39.56617187500001" xml:space="preserve" y="22.84941406249999">Idle<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="121.51061259706637" x="1072.4493874029338" y="485.976"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="86.037109375" x="17.73675161103324" xml:space="preserve" y="22.849414062499932">Process packet<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d5"/>
+      <data key="d6">
+        <y:ShapeNode>
+          <y:Geometry height="64.39999999999998" width="109.48800000000006" x="712.496" y="485.97599999999994"/>
+          <y:Fill color="#FFCC00" transparent="false"/>
+          <y:BorderStyle color="#000000" raised="false" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="33.40234375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="67.369140625" x="21.059429687500028" xml:space="preserve" y="15.498828124999989">Unlock data
+mutex<y:LabelModel><y:SmartNodeLabelModel distance="4.0"/></y:LabelModel><y:ModelParameter><y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/></y:ModelParameter></y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n0" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="1.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.705078125" x="16.817358320994458" xml:space="preserve" y="-19.67059814453097">_int == 0<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="0.9694140624996893" distanceToCenter="false" position="left" ratio="0.2178809335704373" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n2" target="n1">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="24.677734375" x="4.997458007812384" xml:space="preserve" y="-23.894598144531244">Yes<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="14.543999999999967" distanceToCenter="true" position="left" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n1" target="n3">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n3" target="n0">
+      <data key="d9"/>
+      <data key="d10">
+        <y:ArcEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="1078.582275390625" y="196.39117431640625"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.33984375" x="-18.803273729879265" xml:space="preserve" y="-33.501677974926224">No<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="18.500571197434994" distanceToCenter="false" position="right" ratio="-27.95215815939211" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:Arc height="-88.80883026123047" ratio="-0.5704898834228516" type="fixedRatio"/>
+        </y:ArcEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n2" target="n4">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="980.4279999999998" y="416.288"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="19.33984375" x="1.2900996093746926" xml:space="preserve" y="2.4150375976562373">No<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="10.959999999999923" distanceToCenter="true" position="left" ratio="-2.571292968750015" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n3" target="n5">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.701171875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="24.677734375" x="-33.63749023543505" xml:space="preserve" y="-3.0204011714885155">Yes<y:LabelModel><y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/></y:LabelModel><y:ModelParameter><y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="17.148047352204344" distanceToCenter="true" position="right" ratio="-26.188525325283035" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n4" target="n5">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n6" target="n0">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n5" target="n7">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="1334.4560000000001" y="518.1759999999999"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n7" target="n8">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n8" target="n6">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BNO080Async.h	Tue Nov 24 15:06:05 2020 -0800
@@ -0,0 +1,143 @@
+#ifndef BNO080_BNO080ASYNC_H
+#define BNO080_BNO080ASYNC_H
+
+#if MBED_CONF_RTOS_PRESENT
+
+#include <BNO080.h>
+#include <Mutex.h>
+
+/**
+ * Asynchronous version of the SPI BNO080 driver.
+ * Since the timing requirements of this driver are so different,
+ * wouldn't it be great to have a dedicated thread to handle them?
+ * This class provides exactly that, using an internal thread triggered
+ * by interrupts to connect to the BNO.
+ *
+ * Note: the internal thread is started the first time begin() is called,
+ * and is shut down when the class is destroyed.
+ */
+class BNO080Async : public BNO080SPI
+{
+public:
+	// Mutex protecting all sensor data in the driver.
+	// You must lock this while reading data and unlock it when done.
+	// While this is locked, the background thread is prevented from running.
+	Mutex bnoDataMutex;
+
+private:
+	// thread in charge of communicating with the BNO
+	Thread commThread;
+
+	// EventFlags to allow signalling the thread to wake up
+	EventFlags wakeupFlags;
+
+	// main function for the thread.
+	// Handles starting the comm loop.
+	void threadMain();
+
+	// Code loop to communicate with the BNO.
+	// Runs forever in normal operation.
+	// Returns true to request a restart.
+	// Returns false to request the thread to shutdown.
+	bool commLoop();
+
+	// flag to indicate that we have valid data to send queued in the TX buffer.
+	// Protected by bnoDataMutex.
+	bool dataToSend = false;
+
+	// data for packet to send, if above flag is true
+	uint8_t txPacketChannelNumber;
+	uint8_t txPacketDataLength;
+
+	// Condition variable signaled whenever a packet is sent.
+	ConditionVariable dataSentCV;
+
+	// flag used for updateData() return value.
+	// True if any data packets have been received since the last update.
+	bool dataReceived = false;
+
+	// true iff the thread is currently in the middle of a TX/RX operation.
+	// Causes _int interrupts (which get generated by the comms process) to be ignored.
+	bool inTXRX = false;
+
+	bool sendPacket(uint8_t channelNumber, uint8_t dataLength) override;
+
+	void clearSendBuffer() override;
+
+	// waiting for packet state info
+	bool waitingForPacket = false;
+	uint8_t waitingForChannel;
+	uint8_t waitingForReportID;
+	bool waitingPacketArrived = false;
+	bool clearToRxNextPacket = false;
+
+	ConditionVariable waitingPacketArrivedCV;
+	ConditionVariable waitingPacketProcessedCV;
+
+	bool waitForPacket(int channel, uint8_t reportID, std::chrono::milliseconds timeout = 125ms) override;
+
+public:
+	/**
+	 * Construct a BNO080Async.
+	 * This doesn't actually initialize the chip, you will need to call begin() for that.
+	 *
+	 * NOTE: while some schematics tell you to connect the BOOTN pin to the processor, this driver does not use or require it.
+	 * Just tie it to VCC per the datasheet.
+	 *
+	 * @param debugPort Serial port to write output to.  Cannot be nullptr.
+	 * @param rstPin Hardware reset pin, resets the IMU
+	 * @param intPin Hardware interrupt pin, this is used for the IMU to signal the host that it has a message to send
+	 * @param wakePin Hardware wake pin, this is used by the processor to signal the BNO to wake up and receive a message
+	 * @param misoPin SPI MISO pin
+	 * @param mosiPin SPI MOSI pin
+	 * @param sclkPin SPI SCLK pin
+	 * @param csPin SPI CS pin
+	 * @param spiSpeed SPI frequency.  The BNO's max is 3MHz.
+	 * @param threadPriority Priority to give the internal thread.  Defaults to AboveNormal so it will run whenever it can.
+	 */
+	BNO080Async(Stream *debugPort, PinName rstPin, PinName intPin, PinName wakePin, PinName misoPin, PinName mosiPin, PinName sclkPin, PinName csPin, int spiSpeed=3000000, osPriority threadPriority=osPriorityAboveNormal);
+
+	/**
+	 * Resets and connects to the IMU.  Verifies that it's connected, and reads out its version
+	 * info into the class variables above.
+	 *
+	 * Async version also starts the internal communication thread, restarting it if it's
+	 * already started.
+	 *
+	 * 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() override;
+
+	/**
+	 * In BNO080Async, there is no need to call updateData() in order to get new
+	 * results, but this call is provided for compatibility.
+	 *
+	 * It maintains its behavior of returning true iff packets have been received since the last call.
+	 * You must lock bnoDataMutex to call this.
+	 * @return
+	 */
+	bool updateData() override;
+
+	/**
+	 * Locks the data mutex.
+	 */
+	void lockMutex() override
+	{
+		bnoDataMutex.lock();
+	}
+
+	/**
+	 * Unlocks the data mutex.
+	 */
+	void unlockMutex() override
+	{
+		bnoDataMutex.unlock();
+	}
+};
+
+#endif
+
+
+#endif //MBED_CMAKE_TEST_PROJECT_BNO080ASYNC_H