Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: BNO080_stm32_compatible
Revision 0:f677e13975d0, committed 2018-12-23
- Comitter:
- MultipleMonomials
- Date:
- Sun Dec 23 05:23:21 2018 +0000
- Child:
- 1:aac28ffd63ed
- Commit message:
- Initial commit
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 |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/BNO080.cpp Sun Dec 23 05:23:21 2018 +0000
@@ -0,0 +1,743 @@
+//
+// USC RPL BNO080 driver.
+//
+
+/*
+ * Overview of BNO080 Communications
+ * ===============================================
+ *
+ * Hilcrest has developed a protocol called SHTP (Sensor Hub Transport Protocol) for binary communications with
+ * the BNO080 and the other IMUs it sells. Over this protocol, SH-2 (Sensor Hub 2) messages are sent to configure
+ * the chip and read data back.
+ *
+ * SHTP messages are divided at two hierarchical levels: first the channel, then the report ID. Each category
+ * of messages (system commands, sensor data reports, etc.) has its own channel, and the individual messages
+ * in each channel are identified by their report id, which is the first byte of the message payload (note that the
+ * datasheets don't *always* call the first byte the report ID, but that byte does identify the report, so I'm going
+ * with it).
+ *
+ * ===============================================
+ *
+ * Information about the BNO080 is split into three datasheets. Here's the download links and what they cover:
+ *
+ * - the BNO080 datasheet: http://www.hillcrestlabs.com/download/5a05f340566d07c196001ec1
+ * -- Chip pinouts
+ * -- Example circuits
+ * -- Physical specifications
+ * -- Supported reports and configuration settings (at a high level)
+ * -- List of packets on the SHTP executable channel
+ *
+ * - the SHTP protocol: http://www.hillcrestlabs.com/download/59de8f99cd829e94dc0029d7
+ * -- SHTP transmit and receive protcols (for SPI, I2C, and UART)
+ * -- SHTP binary format
+ * -- packet types on the SHTP command channel
+ *
+ * - the SH-2 reference: http://www.hillcrestlabs.com/download/59de8f398934bf6faa00293f
+ * -- list of packets and their formats for all channels other than command and executable
+ * -- list of FRS (Flash Record System) entries and their formats
+ *
+ * ===============================================
+ *
+ * Overview of SHTP channels:
+ *
+ * 0 -> Command
+ * -- Used for protocol-global packets, currently only the advertisement packet (which lists all the channels) and error reports
+ *
+ * 1 -> Executable
+ * -- Used for things that control the software on the chip: commands to reset and sleep
+ * -- Also used by the chip to report when it's done booting up
+ *
+ * 2 -> Control
+ * -- Used to send configuration commands to the IMU and for it to send back responses.
+ * -- Common report IDs: Command Request (0xF2), Set Feature (0xFD)
+ *
+ * 3 -> Sensor Reports
+ * -- Used for sensors to send back data reports.
+ * -- AFAIK the only report ID on this channel will be 0xFB (Report Base Timestamp); sensor data is send in a series of structures
+ * following an 0xFB
+ *
+ * 4 -> Wake Sensor Reports
+ * -- same as above, but for sensors configured to wake the device
+ *
+ * 5 -> Gyro Rotation Vector
+ * -- a dedicated channel for the Gyro Rotation Vector sensor report
+ * -- Why does this get its own channel? I don't know!!!
+ */
+
+#include "BNO080.h"
+#include "BNO080Constants.h"
+
+/// Set to 1 to enable debug printouts. Should be very useful if the chip is giving you trouble.
+/// When debugging, it is recommended to use the highest possible serial baudrate so as not to interrupt the timing of operations.
+#define BNO_DEBUG 1
+
+BNO080::BNO080(Serial *debugPort, PinName user_SDApin, PinName user_SCLpin, PinName user_INTPin, PinName user_RSTPin,
+ uint8_t i2cAddress, int i2cPortSpeed) :
+ _debugPort(debugPort),
+ _i2cPort(user_SDApin, user_SCLpin),
+ _i2cAddress(i2cAddress),
+ _int(user_INTPin),
+ _rst(user_RSTPin, 1),
+ _scope(p21, 1)
+{
+ //Get user settings
+ _i2cPortSpeed = i2cPortSpeed;
+ if(_i2cPortSpeed > 4000000)
+ {
+ _i2cPortSpeed = 4000000; //BNO080 max is 400Khz
+ }
+ _i2cPort.frequency(_i2cPortSpeed);
+
+}
+
+bool BNO080::begin()
+{
+ //Configure the BNO080 for SPI communication
+
+ _rst = 0; // Reset BNO080
+ wait(.002f); // 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
+ Timer timeoutTimer;
+
+ bool highDetected = false;
+ bool lowDetected = false;
+
+ while(true)
+ {
+ if(timeoutTimer.read() > BNO080_RESET_TIMEOUT)
+ {
+ _debugPort->printf("Error: BNO080 reset timed out, chip not detected.\n");
+ return false;
+ }
+
+ // simple edge detector
+ if(!highDetected)
+ {
+ if(_int == 1)
+ {
+ highDetected = true;
+ }
+ }
+ else if(!lowDetected)
+ {
+ if(_int == 0)
+ {
+ lowDetected = true;
+ }
+ }
+ else
+ {
+ // high and low detected
+ break;
+ }
+ }
+
+ _debugPort->printf("BNO080 detected!\n");
+
+ // At system startup, the hub must send its full advertisement message (see SHTP 5.2 and 5.3) to the
+ // host. It must not send any other data until this step is complete.
+ // We don't actually care what's in it, we're just using it as a signal to indicate that the reset is complete.
+ receivePacket();
+
+ // now, after startup, the BNO will send an Unsolicited Initialize response (SH-2 section 6.4.5.2), and an Executable Reset command
+ waitForPacket(CHANNEL_EXECUTABLE, EXECUTABLE_REPORTID_RESET);
+
+ // Next, officially tell it to initialize, and wait for a successful Initialize Response
+ zeroBuffer();
+ shtpData[3] = 0;
+ _scope = 0;
+ sendCommand(COMMAND_INITIALIZE);
+
+
+ if(!waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_COMMAND_RESPONSE) || shtpData[2] != COMMAND_INITIALIZE || shtpData[5] != 0)
+ {
+ _debugPort->printf("BNO080 reports initialization failed.\n");
+ __enable_irq();
+ 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.
+ zeroBuffer();
+ shtpData[0] = SHTP_REPORT_PRODUCT_ID_REQUEST; //Request the product ID and reset info
+ shtpData[1] = 0; //Reserved
+ sendPacket(CHANNEL_CONTROL, 2);
+
+ waitForPacket(CHANNEL_CONTROL, SHTP_REPORT_PRODUCT_ID_RESPONSE, 5);
+
+ if (shtpData[0] == SHTP_REPORT_PRODUCT_ID_RESPONSE)
+ {
+ majorSoftwareVersion = shtpData[2];
+ minorSoftwareVersion = shtpData[3];
+ patchSoftwareVersion = (shtpData[13] << 8) | shtpData[12];
+ partNumber = (shtpData[7] << 24) | (shtpData[6] << 16) | (shtpData[5] << 8) | shtpData[4];
+ buildNumber = (shtpData[11] << 24) | (shtpData[10] << 16) | (shtpData[9] << 8) | shtpData[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;
+
+}
+
+void BNO080::tare(bool zOnly)
+{
+ zeroBuffer();
+
+ // from SH-2 section 6.4.4.1
+ shtpData[3] = 0; // perform tare now
+
+ if(zOnly)
+ {
+ shtpData[4] = 0b100; // tare Z axis
+ }
+ else
+ {
+ shtpData[4] = 0b111; // tare X, Y, and Z axes
+ }
+
+ shtpData[5] = 0; // reorient all motion outputs
+
+ sendCommand(COMMAND_TARE);
+}
+
+bool BNO080::updateData()
+{
+ if(_int.read() != 0)
+ {
+ // no waiting packets
+ return false;
+ }
+
+ while(_int.read() == 0)
+ {
+ if(!receivePacket())
+ {
+ // comms error
+ return false;
+ }
+
+ processPacket();
+ }
+
+ // packets were received, so data may have changed
+ return true;
+}
+
+uint8_t BNO080::getReportStatus(Report report)
+{
+ uint8_t reportNum = static_cast<uint8_t>(report);
+ if(reportNum > STATUS_ARRAY_LEN)
+ {
+ return 0;
+ }
+
+ return reportStatus[reportNum];
+}
+
+//Sends the packet to enable the rotation vector
+void BNO080::enableReport(Report report, uint16_t timeBetweenReports)
+{
+ setFeatureCommand(static_cast<uint8_t>(report), timeBetweenReports);
+
+ // note: we don't wait for ACKs on these packets because they can take quite a while, like half a second, to come in
+}
+
+
+void BNO080::processPacket()
+{
+ if(shtpHeader[2] == CHANNEL_CONTROL)
+ {
+ // currently no command reports are read
+ }
+ else if(shtpHeader[2] == CHANNEL_EXECUTABLE)
+ {
+ // currently no executable reports are read
+ }
+ else if(shtpHeader[2] == CHANNEL_COMMAND)
+ {
+
+ }
+ else if(shtpHeader[2] == CHANNEL_REPORTS || shtpHeader[2] == CHANNEL_WAKE_REPORTS)
+ {
+ if(shtpData[0] == SHTP_REPORT_BASE_TIMESTAMP)
+ {
+ // sensor data packet
+ parseSensorDataPacket();
+ }
+ }
+}
+
+// sizes of various sensor data packet elements
+#define SIZEOF_BASE_TIMESTAMP 5
+#define SIZEOF_TIMESTAMP_REBASE 5
+#define SIZEOF_ACCELEROMETER 10
+#define SIZEOF_LINEAR_ACCELERATION 10
+#define SIZEOF_GYROSCOPE_CALIBRATED 10
+#define SIZEOF_MAGNETIC_FIELD_CALIBRATED 10
+#define SIZEOF_ROTATION_VECTOR 14
+#define SIZEOF_GAME_ROTATION_VECTOR 12
+#define SIZEOF_GEOMAGNETIC_ROTATION_VECTOR 14
+#define SIZEOF_TAP_DETECTOR 5
+
+
+void BNO080::parseSensorDataPacket()
+{
+ size_t currReportOffset = 0;
+
+ // every sensor data report first contains a timestamp offset to show how long it has been between when
+ // the host interrupt was sent and when the packet was transmitted.
+ // We don't use interrupts and don't care about times, so we can throw this out.
+ currReportOffset += SIZEOF_BASE_TIMESTAMP;
+
+ while(currReportOffset < packetLength)
+ {
+ // lots of sensor reports use 3 16-bit numbers stored in bytes 4 through 9
+ // we can save some time by parsing those out here.
+ uint16_t data1 = (uint16_t)shtpData[currReportOffset + 5] << 8 | shtpData[currReportOffset + 4];
+ uint16_t data2 = (uint16_t)shtpData[currReportOffset + 7] << 8 | shtpData[currReportOffset + 6];
+ uint16_t data3 = (uint16_t)shtpData[currReportOffset + 9] << 8 | shtpData[currReportOffset + 8];
+
+ uint8_t reportNum = shtpData[currReportOffset];
+
+ if(reportNum != SENSOR_REPORTID_TIMESTAMP_REBASE)
+ {
+ // set status from byte 2
+ reportStatus[reportNum] = static_cast<uint8_t>(shtpData[currReportOffset + 2] & 0b11);
+ }
+
+ switch(shtpData[currReportOffset])
+ {
+ case SENSOR_REPORTID_TIMESTAMP_REBASE:
+ currReportOffset += SIZEOF_TIMESTAMP_REBASE;
+ break;
+
+ case SENSOR_REPORTID_ACCELEROMETER:
+
+ totalAcceleration = TVector3(
+ qToFloat(data1, ACCELEROMETER_Q_POINT),
+ qToFloat(data2, ACCELEROMETER_Q_POINT),
+ qToFloat(data3, ACCELEROMETER_Q_POINT));
+
+ currReportOffset += SIZEOF_ACCELEROMETER;
+ break;
+
+ case SENSOR_REPORTID_LINEAR_ACCELERATION:
+
+ linearAcceleration = TVector3(
+ qToFloat(data1, ACCELEROMETER_Q_POINT),
+ qToFloat(data2, ACCELEROMETER_Q_POINT),
+ qToFloat(data3, ACCELEROMETER_Q_POINT));
+
+ currReportOffset += SIZEOF_LINEAR_ACCELERATION;
+ break;
+
+ case SENSOR_REPORTID_GRAVITY:
+
+ gravityAcceleration = TVector3(
+ qToFloat(data1, ACCELEROMETER_Q_POINT),
+ qToFloat(data2, ACCELEROMETER_Q_POINT),
+ qToFloat(data3, ACCELEROMETER_Q_POINT));
+
+ currReportOffset += SIZEOF_LINEAR_ACCELERATION;
+ break;
+
+ case SENSOR_REPORTID_GYROSCOPE_CALIBRATED:
+
+ gyroRotation = TVector3(
+ qToFloat(data1, GYRO_Q_POINT),
+ qToFloat(data2, GYRO_Q_POINT),
+ qToFloat(data3, GYRO_Q_POINT));
+
+ currReportOffset += SIZEOF_GYROSCOPE_CALIBRATED;
+ break;
+
+ case SENSOR_REPORTID_MAGNETIC_FIELD_CALIBRATED:
+
+ magField = TVector3(
+ qToFloat(data1, MAGNETOMETER_Q_POINT),
+ qToFloat(data2, MAGNETOMETER_Q_POINT),
+ qToFloat(data3, MAGNETOMETER_Q_POINT));
+
+ currReportOffset += SIZEOF_MAGNETIC_FIELD_CALIBRATED;
+ break;
+
+ case SENSOR_REPORTID_ROTATION_VECTOR:
+ {
+ uint16_t realPartQ = (uint16_t) shtpData[currReportOffset + 11] << 8 | shtpData[currReportOffset + 10];
+ uint16_t accuracyQ = (uint16_t) shtpData[currReportOffset + 13] << 8 | shtpData[currReportOffset + 12];
+
+ rotationVector = TVector4(
+ qToFloat(data1, ROTATION_Q_POINT),
+ qToFloat(data2, ROTATION_Q_POINT),
+ qToFloat(data3, ROTATION_Q_POINT),
+ qToFloat(realPartQ, ROTATION_Q_POINT));
+
+ rotationAccuracy = qToFloat(accuracyQ, ROTATION_ACCURACY_Q_POINT);
+
+ currReportOffset += SIZEOF_ROTATION_VECTOR;
+ }
+ break;
+
+ case SENSOR_REPORTID_GAME_ROTATION_VECTOR:
+ {
+ uint16_t realPartQ = (uint16_t) shtpData[currReportOffset + 11] << 8 | shtpData[currReportOffset + 10];
+
+ gameRotationVector = TVector4(
+ qToFloat(data1, ROTATION_Q_POINT),
+ qToFloat(data2, ROTATION_Q_POINT),
+ qToFloat(data3, ROTATION_Q_POINT),
+ qToFloat(realPartQ, ROTATION_Q_POINT));
+
+ currReportOffset += SIZEOF_GAME_ROTATION_VECTOR;
+ }
+ break;
+
+ case SENSOR_REPORTID_GEOMAGNETIC_ROTATION_VECTOR:
+ {
+ uint16_t realPartQ = (uint16_t) shtpData[currReportOffset + 11] << 8 | shtpData[currReportOffset + 10];
+ uint16_t accuracyQ = (uint16_t) shtpData[currReportOffset + 13] << 8 | shtpData[currReportOffset + 12];
+
+ geomagneticRotationVector = TVector4(
+ qToFloat(data1, ROTATION_Q_POINT),
+ qToFloat(data2, ROTATION_Q_POINT),
+ qToFloat(data3, ROTATION_Q_POINT),
+ qToFloat(realPartQ, ROTATION_Q_POINT));
+
+ geomagneticRotationAccuracy = qToFloat(accuracyQ, ROTATION_ACCURACY_Q_POINT);
+
+ currReportOffset += SIZEOF_GEOMAGNETIC_ROTATION_VECTOR;
+ }
+ break;
+
+ case SENSOR_REPORTID_TAP_DETECTOR:
+
+ // since we got the report, a tap was detected
+ tapDetected = true;
+
+ doubleTap = (shtpData[currReportOffset + 4] & (1 << 6)) != 0;
+
+ currReportOffset += SIZEOF_TAP_DETECTOR;
+ break;
+
+ default:
+ _debugPort->printf("Error: unrecognized report ID in sensor report: %hhx. Byte %u, length %hu\n", shtpData[currReportOffset], currReportOffset, packetLength);
+ return;
+ }
+ }
+
+}
+
+bool BNO080::waitForPacket(int channel, uint8_t reportID, float timeout)
+{
+ Timer timeoutTimer;
+ timeoutTimer.start();
+
+ while(timeoutTimer.read() <= timeout)
+ {
+ if(_int.read() == 0)
+ {
+ if(!receivePacket(timeout))
+ {
+ return false;
+ }
+
+ if(channel == shtpHeader[2] && reportID == shtpData[0])
+ {
+ // found correct packet!
+ return true;
+ }
+ else
+ {
+ // other data packet, send to proper channels
+ processPacket();
+ }
+ }
+ }
+
+ _debugPort->printf("Packet wait timeout.\n");
+ return false;
+}
+
+//Given a register value and a Q point, convert to float
+//See https://en.wikipedia.org/wiki/Q_(number_format)
+float BNO080::qToFloat(int16_t fixedPointValue, uint8_t qPoint)
+{
+ float qFloat = fixedPointValue;
+ qFloat *= pow(2, qPoint * -1);
+ return (qFloat);
+}
+
+//Given a floating point value and a Q point, convert to Q
+//See https://en.wikipedia.org/wiki/Q_(number_format)
+int16_t BNO080::floatToQ(float qFloat, uint8_t qPoint)
+{
+ int16_t qVal = static_cast<int16_t>(qFloat * pow(2, qPoint));
+ return qVal;
+}
+
+//Tell the sensor to do a command
+//See 6.3.8 page 41, Command request
+//The caller is expected to set P0 through P8 prior to calling
+void BNO080::sendCommand(uint8_t command)
+{
+ shtpData[0] = SHTP_REPORT_COMMAND_REQUEST; //Command Request
+ shtpData[1] = commandSequenceNumber++; //Increments automatically each function call
+ shtpData[2] = command; //Command
+
+ //Caller must set these
+ /*shtpData[3] = 0; //P0
+ shtpData[4] = 0; //P1
+ shtpData[5] = 0; //P2
+ shtpData[6] = 0;
+ shtpData[7] = 0;
+ shtpData[8] = 0;
+ shtpData[9] = 0;
+ shtpData[10] = 0;
+ shtpData[11] = 0;*/
+
+ //Transmit packet on channel 2, 12 bytes
+ sendPacket(CHANNEL_CONTROL, 12);
+}
+
+//Given a sensor's report ID, this tells the BNO080 to begin reporting the values
+//Also sets the specific config word. Useful for personal activity classifier
+void BNO080::setFeatureCommand(uint8_t reportID, uint16_t timeBetweenReports, uint32_t specificConfig)
+{
+ uint32_t microsBetweenReports = static_cast<uint32_t>(timeBetweenReports * 1000);
+
+ const uint32_t batchMicros = 0;
+
+ shtpData[0] = SHTP_REPORT_SET_FEATURE_COMMAND; //Set feature command. Reference page 55
+ shtpData[1] = reportID; //Feature Report ID. 0x01 = Accelerometer, 0x05 = Rotation vector
+ shtpData[2] = 0; //Feature flags
+ shtpData[3] = 0; //Change sensitivity (LSB)
+ shtpData[4] = 0; //Change sensitivity (MSB)
+ shtpData[5] = (microsBetweenReports >> 0) & 0xFF; //Report interval (LSB) in microseconds. 0x7A120 = 500ms
+ shtpData[6] = (microsBetweenReports >> 8) & 0xFF; //Report interval
+ shtpData[7] = (microsBetweenReports >> 16) & 0xFF; //Report interval
+ shtpData[8] = (microsBetweenReports >> 24) & 0xFF; //Report interval (MSB)
+ shtpData[9] = (batchMicros >> 0) & 0xFF; //Batch Interval (LSB)
+ shtpData[10] = (batchMicros >> 8) & 0xFF; //Batch Interval
+ shtpData[11] = (batchMicros >> 16) & 0xFF;//Batch Interval
+ shtpData[12] = (batchMicros >> 24) & 0xFF;//Batch Interval (MSB)
+ shtpData[13] = (specificConfig >> 0) & 0xFF; //Sensor-specific config (LSB)
+ shtpData[14] = (specificConfig >> 8) & 0xFF; //Sensor-specific config
+ shtpData[15] = (specificConfig >> 16) & 0xFF; //Sensor-specific config
+ shtpData[16] = (specificConfig >> 24) & 0xFF; //Sensor-specific config (MSB)
+
+ //Transmit packet on channel 2, 17 bytes
+ sendPacket(CHANNEL_CONTROL, 17);
+}
+
+
+//Given the data packet, send the header then the data
+//Returns false if sensor does not ACK
+bool BNO080::sendPacket(uint8_t channelNumber, uint8_t dataLength)
+{
+ // start the transaction and contact the IMU
+ _i2cPort.start();
+
+ // to indicate an i2c read, shift the 7 bit address up 1 bit and keep bit 0 as a 0
+ int writeResult = _i2cPort.write(_i2cAddress << 1);
+
+ if(writeResult != 1)
+ {
+ _debugPort->printf("BNO I2C write failed!\n");
+ _scope = 0;
+ _i2cPort.stop();
+ return false;
+ }
+
+
+ uint16_t totalLength = dataLength + 4; //Add four bytes for the header
+ packetLength = dataLength;
+
+#if BNO_DEBUG
+ shtpHeader[0] = totalLength & 0xFF;
+ shtpHeader[1] = totalLength >> 8;
+ shtpHeader[2] = channelNumber;
+ shtpHeader[3] = sequenceNumber[channelNumber];
+
+ _debugPort->printf("Transmitting packet: ----------------\n");
+ printPacket();
+#endif
+
+ //Send the 4 byte packet header
+ _i2cPort.write(totalLength & 0xFF); //Packet length LSB
+ _i2cPort.write(totalLength >> 8); //Packet length MSB
+ _i2cPort.write(channelNumber); //Channel number
+ _i2cPort.write(sequenceNumber[channelNumber]++); //Send the sequence number, increments with each packet sent, different counter for each channel
+
+ //Send the user's data packet
+ for (uint8_t i = 0 ; i < dataLength ; i++)
+ {
+ _i2cPort.write(shtpData[i]);
+ }
+ _i2cPort.stop();
+
+ return (true);
+}
+
+//Check to see if there is any new data available
+//Read the contents of the incoming packet into the shtpData array
+bool BNO080::receivePacket(float timeout)
+{
+ Timer waitStartTime;
+ waitStartTime.start();
+
+ while(_int.read() != 0)
+ {
+ if(waitStartTime.read() > timeout)
+ {
+ _debugPort->printf("BNO I2C wait timeout\n");
+ return false;
+ }
+
+ }
+
+ // start the transaction and contact the IMU
+ _i2cPort.start();
+
+ // to indicate an i2c read, shift the 7 bit address up 1 bit and set bit 0 to a 1
+ int writeResult = _i2cPort.write((_i2cAddress << 1) | 0x1);
+
+ if(writeResult != 1)
+ {
+ _debugPort->printf("BNO I2C read failed!\n");
+ return false;
+ }
+
+ //Get the first four bytes, aka the packet header
+ uint8_t packetLSB = static_cast<uint8_t>(_i2cPort.read(true));
+ uint8_t packetMSB = static_cast<uint8_t>(_i2cPort.read(true));
+ uint8_t channelNumber = static_cast<uint8_t>(_i2cPort.read(true));
+ uint8_t sequenceNum = static_cast<uint8_t>(_i2cPort.read(true)); //Not sure if we need to store this or not
+
+ //Store the header info
+ shtpHeader[0] = packetLSB;
+ shtpHeader[1] = packetMSB;
+ shtpHeader[2] = channelNumber;
+ shtpHeader[3] = sequenceNum;
+
+ if(shtpHeader[0] == 0xFF && shtpHeader[1] == 0xFF)
+ {
+ // invalid according to BNO080 datasheet section 1.4.1
+
+#if BNO_DEBUG
+ _debugPort->printf("Recieved 0xFFFF packet length, protocol error!\n");
+#endif
+ return false;
+ }
+
+ //Calculate the number of data bytes in this packet
+ packetLength = (static_cast<uint16_t>(packetMSB) << 8 | packetLSB);
+
+ // Clear the MSbit.
+ // This bit indicates if this package is a continuation of the last. TBH, I don't really know what this means (it's not really explained in the datasheet)
+ // but we don't actually care about any of the advertisement packets
+ // that use this, so we can just cut off the rest of the packet by releasing chip select.
+ packetLength &= ~(1 << 15);
+
+ if (packetLength == 0)
+ {
+ // Packet is empty
+ return (false); //All done
+ }
+
+ packetLength -= 4; //Remove the header bytes from the data count
+
+ //Read incoming data into the shtpData array
+ for (uint16_t dataSpot = 0 ; dataSpot < packetLength ; dataSpot++)
+ {
+ bool sendACK = dataSpot < packetLength - 1;
+
+ // per the datasheet, 0xFF is used as filler for the receiver to transmit back
+ uint8_t incoming = static_cast<uint8_t>(_i2cPort.read(sendACK));
+ if (dataSpot < STORED_PACKET_SIZE) //BNO080 can respond with upto 270 bytes, avoid overflow
+ shtpData[dataSpot] = incoming; //Store data into the shtpData array
+ }
+
+ _i2cPort.stop();
+
+#if BNO_DEBUG
+ _debugPort->printf("Recieved packet: ----------------\n");
+ printPacket(); // note: add 4 for the header length
+#endif
+
+ return (true); //We're done!
+}
+
+//Pretty prints the contents of the current shtp header and data packets
+void BNO080::printPacket()
+{
+#if BNO_DEBUG
+ //Print the four byte header
+ _debugPort->printf("Header:");
+ for (uint8_t x = 0 ; x < 4 ; x++)
+ {
+ _debugPort->printf(" ");
+ if (shtpHeader[x] < 0x10) _debugPort->printf("0");
+ _debugPort->printf("%hhx", shtpHeader[x]);
+ }
+
+ uint16_t printLength = packetLength;
+ if (printLength > 40) printLength = 40; //Artificial limit. We don't want the phone book.
+
+ _debugPort->printf(" Body:");
+ for (uint16_t x = 0 ; x < printLength ; x++)
+ {
+ _debugPort->printf(" ");
+ if (shtpData[x] < 0x10) _debugPort->printf("0");
+ _debugPort->printf("%hhx", shtpData[x]);
+ }
+
+ _debugPort->printf(", Length:");
+ _debugPort->printf("%hhu", packetLength + SHTP_HEADER_SIZE);
+
+ if(shtpHeader[1] >> 7)
+ {
+ _debugPort->printf("[C]");
+ }
+
+ _debugPort->printf(", SeqNum: %hhu", shtpHeader[3]);
+
+ _debugPort->printf(", Channel:");
+ if (shtpHeader[2] == 0) _debugPort->printf("Command");
+ else if (shtpHeader[2] == 1) _debugPort->printf("Executable");
+ else if (shtpHeader[2] == 2) _debugPort->printf("Control");
+ else if (shtpHeader[2] == 3) _debugPort->printf("Sensor-report");
+ else if (shtpHeader[2] == 4) _debugPort->printf("Wake-report");
+ else if (shtpHeader[2] == 5) _debugPort->printf("Gyro-vector");
+ else _debugPort->printf("%hhu", shtpHeader[2]);
+
+ _debugPort->printf("\n");
+#endif
+}
+
+
+void BNO080::zeroBuffer()
+{
+ memset(shtpHeader, 0, SHTP_HEADER_SIZE);
+ memset(shtpData, 0, STORED_PACKET_SIZE);
+ packetLength = 0;
+}
--- /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