USC RPL's Driver Compendium

USC RPL
At USC Rocket Propulsion Lab, we use Mbed OS a lot! And since we have gotten so much out of this community, we are trying to contribute our own efforts back whenever possible.
Me and the other engineers on my team have written quite a few drivers and other utilities over the last few years, and I wanted to create a single thread to collect and demonstrate them all. We’d also hope that this can be a place where people can ask questions and get support with any of these projects, so feel free to comment if you run into any trouble.
Without further ado, here’s the full list:

BNO080 Driver

Author: Jamie Smith

FSM300

Use This To:

Control CEVA (formerly Hilcrest)‘s BNO080 and FSM300 IMU modules.
These sophisticated IMU modules provide a 9-axis orientation reading combining magnetometer, accelerometer, and gyroscope data. RPL currently uses them in concert with other sensors for measuring our rockets’ flight dynamics. However, they’re also very useful for a variety of mobile device and human interface applications.

Note: This driver supports I2C and SPI mode using synchronous communications (where you must manually invoke the driver every so often to receive data from the IMU). It also supports an asynchronous mode for SPI communications, where a dedicated thread handles communicating with the IMU when it has new data.

Code Example:

int main()
{
    BNO080 imu(&pc, p28, p27, p16, p30, 0x4a, 100000); 
 
 
    // 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
                printf("IMU Rotation Euler: ");
                TVector3 eulerRadians = imu.rotationVector.euler();
                TVector3 eulerDegrees = eulerRadians * (180.0 / M_PI);
                eulerDegrees.print(pc, true);
                pc.printf("\n");
            }
        }
    }
}

Code Repository

Examples Repository

ADIS16467 Driver

Author: Rita Yang

ADIS16467

Use This To:

Read data from Analog Devices’ ADIS16467 line of precision IMU modules. This “wish-it-was-military-grade” IMU is much, much more accurate than consumer grade units like the BNO080, boasting three-sig-fig accelerometer precision and gyroscope drift of less than a degree per second. However, it is also priced to match this accuracy, and it’s missing the sensor fusion and integration features found on complete IMU modules. So, it’s ideally suited to robotics and aerospace applications, as a building block of a complete motion recording and/or control system.

This driver supports nearly all features of the IMU, including burst and individual reads, decimation factors, and delta angles/positions.

Code Example

int main() {
 
    //Create IMU, passing in all parameters
    //All pins here are arbitrary, you will need to configure them accordingly
    ADIS16467 imu(&pc, PIN_SPI_MOSI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_ADIS_CS, PIN_ADIS_RST);
 
    //Initialize the IMU
    imu.initADIS();
 
    struct ADIS16467::BurstReadResult result;
    imu.burstRead(result);
 
    printf("Gyro X : %.2f degree/sec\r\n", ((float)result.gyroX * imu.GYRO_CONV));
    printf("Gyro Y : %.2f degree/sec\r\n", ((float)result.gyroY * imu.GYRO_CONV));
    printf("Gyro Z : %.2f degree/sec\r\n", ((float)result.gyroZ * imu.GYRO_CONV));
    printf("Accel X : %.2f mg\r\n", ((float)result.accelX * imu.ACCEL_CONV));
    printf("Accel Y : %.2f mg\r\n", ((float)result.accelY * imu.ACCEL_CONV));
    printf("Accel Z : %.2f mg\r\n", ((float)result.accelZ * imu.ACCEL_CONV));
    printf("Internal Temperature : %.2f C\r\n", ((float)result.internalTemp * imu.TEMP_CONV));
    printf("Data Counter : %5d\r\n", result.dataCounter);
    printf("Checksum Value : %5d\r\n", result.checkSum);
 
    return 0;
}

Code Repository

Examples Repository

U-Blox MAX-8 GPS Driver

Authors: Adhyyan Sekhsaria, Jamie Smith

MAX-8C

Use This To:

Read a variety of common data items from U-Blox’s MAX-8 series of GPS modules. Our driver automatically configures a number of reports, and provides all the important GPS data including position, time, velocity, and fix quality. U-Blox’s GPS modules have solid performance and include a number of handy features, such as I2C and SPI interfaces, an active antenna power supply, and a much larger variety of available reports than standard NMEA GPSs.

Note: This driver currently only supports I2C (who needs UART anyway?!?). We are working on an SPI version as well as updating to add support for U-Blox’s 9th gen GPS modules.

Note 2: U-Blox publishes an official Mbed driver for their GPS units (a fact that we didn’t learn about until after we wrote this…). However, as far as I can tell, this driver only provides low-level communications and doesn’t handle encoding or decoding the binary message data. In contrast, our driver handles the entire process of encoding and decoding the data.

Code Example

int main(){
    MAX8U gps(&pc, PIN_I2C_SDA, PIN_I2C_SCL, p25);
    bool booted = gps.begin();
 
    if(!booted){
        //handle error
    }
    booted = gps.configure();
    if(!booted){
        //handle error
    }
 
    while(true){
        bool newMessageRcvd = gps.update();
        printf(">Position: %.06f %c, %.06f %c, Height %.02f m\r\n",
                  std::abs(gps.latitude), gps.latitude > 0 ? 'N' : 'S',
                  std::abs(gps.longitude), gps.longitude > 0 ? 'E' : 'W',
                  gps.height);
 
        // accuracy
        printf(">Fix: Quality: %" PRIu8 ", Num Satellites: %" PRIu8 ", Position Accuracy: %.02f m\r\n",
                static_cast<uint8_t>(gps.fixQuality), gps.numSatellites, gps.posAccuracy);
    }
}

Code Repository

CC1200 Driver

Author: Jamie Smith

CC1200 Dev Kit

Use This To:

Send and receive data using Texas Instruments’ CC1200 (and CC1201) radio transceiver ICs. These radio chips are incredibly flexible, and feature a variety of different configurable frequency bands (from 136MHz-960MHz), radio modulations (ASK, FSK, and OOK), and symbol rates (1Hz-500kHz), as well as a litany of other packet formatting options. The CC1200 automatically handles forming data into packets and checksumming it, and also provides 128 byte FIFOs for data being transmitted and received. So, your processor can let the CC1200 handle the real-time aspect of radio communications and pick up the data later at its leisure. All in all, the CC1200 is a solid option if you want to add RF communications to an existing MCU or board.

Since this radio has so many different configuration settings to set, historically users have been limited to the premade configuration values that TI provides in their SmartRF Studio. However, since RPL needed to experiment with lots of different settings on the CC1200, this driver implements the calculation of all register values from the raw RF settings (such as symbol rate and frequency) that you select. This wasn’t easy, and required us to fight through many vague datasheet register descriptions and calculations (as well as some that were flat-out wrong), but it’s working well now! And thanks to our labor, you can select absolutely any RF configuration supported by the chip.

Note: The CC1200 supports operation as an 802.15.4 transceiver, but RPL hasn’t implemented this functionality because we don’t need it. If you did want to implement this, however, this driver would be a good place to start, and we’re open to contributions!

Code Repository

Examples Repository

Morse Code Transmitter Example

SerialStream

Author: Jamie Smith

Use This To:

Convert a BufferedSerial or UnbufferedSerial object into an Mbed Stream class. This lets you call printf() and scanf() on it, and pass it to existing (Mbed 5 legacy) code that expects a Stream.

Note: The use of Stream does introduce some additional overhead into your code, and this was part of the reason BufferedSerial did not implement Stream. However, this is unlikely to make a difference in many applications.

Note 2: There is also a newer way to use printf() with BufferedSerial using fdopen(); this (along with some other interesting info) can be found in the PR that deprecated Serial. In my opinion, this is something that ARM has done an atrocious job of documenting and it’s criminal that the only place I found this information was by digging through closed PRs! (though it’s also buried in the FileHandle docs if you look hard enough)

Code Example:

#include <mbed.h>
#include "SerialStream.h"
 
BufferedSerial serial(USBTX, USBRX, 115200);
SerialStream<BufferedSerial> pc(serial);
 
int main() {
 
    while(true)
    {
        pc.printf("Hello world!\n");
        ThisThread::sleep_for(1000);
    }
}

Code Repository

Mbed-Benchtest

Authors: Jamie Smith, Luka Govedič, Jasper Swallen

Use This To:

Run code that uses Mbed RTOS on your desktop PC. This code nearly perfectly emulates the behavior of ARM’s RTX RTOS using our own RTXOff(-Board) RTOS, so you can test your threaded application code from the comfort of your own desktop.

Currently only the RTOS as well as a few stubbed Mbed OS headers are implemented, so you can only run application code, not code that communicates with hardware. However, in the future as we become more invested in testing infrastructure, we hope to add emulation for common Mbed classes like Serial and Timer.

Code Repository

KX134-1211 Driver

Author: Jasper Swallen
KX134-1211

Use This To:

Communicate with Kionix’s KX134-1211 accelerometer IC. Especially at its size and price point, this IC is amazingly accurate, beating the ADXL375 that we used to use by almost a factor of 10 in accuracy and resolution. It also sports extremely high measurement range (up to ±64g) and update rate (up to 25.6kHz). We’re pretty excited about using it on our future projects!

Code Example:

    KX134 kx134Obj(PIN_SPI_MOSI, PIN_SPI_MISO, PIN_SPI_SCK, PIN_SPI_CS,
                   PIN_INT1, PIN_INT2, PIN_RST);

    if(!kx134Obj.init())
    {
        printf("Failed to initialize KX134\r\n");
        return 1;
    }

    printf("Successfully initialized KX134\r\n");

    int hz = 25600;

    // initialize asynch reading
    kx134Obj.enableRegisterWriting();
    kx134Obj.setOutputDataRateHz(hz);
    kx134Obj.setAccelRange(KX134::Range::RANGE_64G);
    kx134Obj.disableRegisterWriting();
    
    int16_t output[3];
    kx134Obj.getAccelerations(output);
    float ax = kx134Obj.convertRawToGravs(output[0]);
    float ay = kx134Obj.convertRawToGravs(output[1]);
    float az = kx134Obj.convertRawToGravs(output[2]);

Code Repository (w/ example code)

AccurateWaiter

Author: Jamie Smith

AccurateWaiter Test

Use This To:

Wait for an exact number of microseconds while allowing other threads to run. In standard Mbed OS, threaded applications are limited by the precision of ThisThread::sleep_for(), which only allows sleeping for a whole number of milliseconds. This limitation comes from the underlying RTX RTOS, which uses a 1 ms scheduling interrupt. However, AccurateWaiter uses some EventFlags trickery to get around this, allowing a thread to sleep for a time accurate to the millisecond and then wake up immediately with little overhead. Use it to create finer-grain threaded apps then ever before!

Code Repository

Example Code


This is all we’ve got for now, but we are always making more drivers as we build new hardware. Expect to see drivers for the INA226, ADS124S0x, and BQ34Z100 soonish!

7 Likes

One other notable release:

Mbed Target Viewer

Authors: Jamie Smith, Keshav Sriram

Use This To:

View the list of all supported target processors/boards and their features in Mbed OS.

When starting an Mbed project, it’s important to keep in mind that not all Mbed targets are able to support all features of the operating system. Things like asynchronous SPI, I2C slave support, and Ethernet must be both present in the chip’s silicon and have software implemented for them by the chip manufacturer before you can use them in Mbed OS, so it’s far from guaranteed that these things will be available. You might think that Mbed OS would make an easy, convenient viewer that will tell you what features are available with what boards, right? Unfortunately, this has not happened yet, and all that’s available is the often incomplete Mbed Boards list pages. The “true” data file that controls what features the OS enables is targets.json inside the Mbed OS repository, but this file’s format is hierarchical and difficult for humans to read.

So, we at RPL stepped in to fix this problem! We created a web app that automatically downloads the list of all boards from Mbed’s GitHub repository. It then processes the data, converts the feature names to human readable terms, and displays a sortable and filterable list. If you’re searching for a new processor or board to use on your next project, and you need it to support specific features, this tool should be a big help!

Note: Currently Target Viewer is hardcoded to pull data from the Mbed master branch on GitHub, which corresponds to the next major OS version that is going to be released. So, all data displayed will be indicative of this version, and targets that have been dropped in previous versions won’t be shown.

View Target Viewer now

3 Likes

Hi Jamie,

what a great idea. Many thanks for this and all the contributions your team have made.
I’ve pinned this topic so that people can find it easily.

Regards,
Anna

New release:

BQ34Z100-G1 Driver

Authors: Arpad Kovesdy, Kyle Marino, Jamie Smith, Ralim (original author)

Flags Display

Use This To:

Configure, calibrate, and read TI’s BQ34Z100-G1 battery fuel gauge IC.

Batteries, especially lithium ones, can be real enigmas. They’re extremely nonlinear, so the relationship between pack voltage and charge left is extremely convoluted. Even worse, when no current is being drawn, the voltage will slowly creep up by a volt or more, then suddenly crash back down again when you try to use the battery. Finally, batteries they go through hundreds of charge and discharge cycles, their capacity slowly decreases. These attributes make getting an accurate read on their charge level very difficult.

TI’s BQ34Z100 IC attempts to get around this complexity with both smarts and additional sensing. Instead of just monitoring the battery’s voltage, it also connects to a current sense resistor and monitors current flow, and it continuously integrates this current value dTime to calculate charge remaining. On top of this, it implements a sophisticated analysis algorithm (Impedance Track) that monitors the battery’s behavior and updates the calibration data over time (it even has its own mini CPU core). The result is that it’s able to produce a solidly accurate percentage reading of how much energy is left in the battery.

In my experience, the biggest downside of the BQ34Z100 is limited information about it. Its internal workings are mostly a black box, and the datasheet does a pretty abysmal job documenting what many of its hundred plus configuration values are actually for or why they’re important. Even worse, official info about how to calibrate the chip and debug calibration problems is very sparse, and that’s only if you count posts on TI’s forum as “official” info. It took a lot of trial and error for us to discover the right combo of settings that makes the chip work.

With this driver, RPL is trying to address the biggest weakness of what is otherwise a really cool IC. We ported this driver from Arduino, fixed a bunch of bugs, cleaned up the code, and added several new features. However, even more importantly, we also created a test suite and a calibration guide that will help you break through the confusion and get set up and using the chip right away.

Code Repository

Utils/Test Suite

Calibration Guide