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-Examples BNO080-Examples
Revision 9:430f5302f9e1, committed 2020-11-24
- Comitter:
- Jamie Smith
- Date:
- Tue Nov 24 15:06:05 2020 -0800
- Parent:
- 8:199c7fad233d
- Commit message:
- Implement BNO080Async
Changed in this revision
--- 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