Silicon Labs


Silicon Labs a leading provider of microcontroller, sensing and wireless connectivity solutions for the Internet of Things (IoT).

You are viewing an older revision! See the latest version

Using the mbed asynchronous APIs

Introduction

A lot of mbed libraries rely on I/O transactions of some kind. Whether it's a toggling pin, an I2C, SPI or Serial transfer, or even something else, these transactions have one thing in common: they tend to take time. Since the bus speed is commonly much lower then the frequency at which the core operates, these libraries tend to introduce a lot of while(transmitting)-like code. Since this just burns CPU cycles without doing anything useful, it's not a recommended design practice, but up until recently mbed had no support for doing something else.

Enter the asynchronous APIs. They allow you to set up a transfer, send it to the peripheral for execution, and get a callback when the transfer is finished. This way, you can do something else, such as more number crunching, or even go to sleep using the improved sleep API [Link to sleep API wiki page]. Going to sleep while waiting for an I/O transaction to finish can save you a lot of power, as demonstrated in this article: Reducing Power Consumption with the ARM mbed Low Power APIs and EFM32 MCUs

Event callbacks

The first thing you realize is different about asynchronous calls is that they can optionally tell your program that the transfer has completed (or something bad has happened). This is called a callback, and happens through executing a function you define with some status flags. Beware though, this can happen anytime, so you should make sure your code is robust enough to handle that!

In order to deal with this callback system in C++, mbed introduced a new object type called 'event_callback_t'. It declares a FunctionPointer object, and initializes it so it can point to a function taking an int as argument, and returning void. The benefit of doing it this way, instead of passing around regular C-like function pointers, is that you can also have member functions as a callback. This is particularly handy when you have multiple instances of an object doing I/O!

Setting up an event_callback_t object

event_callback_t functionpointer; //event_callback_t can only point to a void (int) style function

void handler(int events) {
    // Handle events here
    // The events argument holds the event flags
}

void init_fp() {
    // Set up the functionpointer object to point to the 'handler' method on this object
    functionpointer.attach(handler);
}

From a back-end perspective, calling the call(arg) method on the event_callback_t object, will call the attached function with the passed argument.

Asynchronous APIs

Now that we know how to set up a callback mechanism, let's look at how the different communication peripherals now do asynchronous operations.

SPI

Until now, SPI really only consisted of one function, write, which would write the configured amount of bits to the bus, and return the response. With the asynchronous API, we are introducing the ‘transfer’ function, much like the I2C equivalent. The difference here is that if the amount of bytes to receive is greater then the amount of writes, the transaction will automatically write fill words to receive the given number of bytes. The other way around also works, the transaction will only fill the receive buffer with the specified amount of data, even if the write buffer is larger.

The transfer function also takes an event argument, which is the logical OR of all events you want to get a callback for (defined in spi_api.h), and a pointer to an event_callback_t for your callback handler.

You can see the new APIs being used in Silicon Labs’ mbed library for the Sharp LS013B7DH03 memory LCD.

Serial

The serial bus is the odd one out of the three. Because it is asynchronous by nature, the functions had to be split up in two: one for sending, and one for receiving data.

The transmit function is called ‘write’, and takes a buffer, its length, a logical OR of events to listen for, and a pointer to the relevant event_callback_t object. The event flags are defined in serial_api.h. The transmit function will return immediately, and the callback will get called on completion of the transmission, or on an error condition. When there is already a transmission in progress, the transmit function will return an error code, and the callback will not get called.

Likewise, the receive function, called ‘read’, takes a buffer, its length, a collection of event flags, and a pointer to the relevant event_callback_t object. However, it can also take a fifth argument, which is a character to listen for. If the SERIAL_EVENT_RX_CHARACTER_MATCH flag is enabled, reception will cease upon seeing this character, and the callback will get called with this flag.

You can see the new API in action in Silicon Labs’ low power serial demo program.

I2C

For I2C, the traditional start, stop, read and write functions have been superseded by a single ‘transfer’ function. It takes both a write (tx) and read (rx) length in bytes, and char pointers to the respective write and read buffers. It will automatically add start and stop conditions before and after the transaction, and if both write and read lengths are greater then zero, a restart condition will be generated between the write and the read. Additionally, it takes an event argument, which is the logical OR of all events you want to get a callback for (defined in [[https://developer.mbed.org/users/mbed_official/code/mbed-src/file/aad7ca793a89/hal/i2c_api.h|i2c_api.h), and a pointer to an event_callback_t for your callback handler.

When the transfer has started, the function will return 0. If not, there is an error or there is another I2C transfer already in progress. The abort_transfer function will abort the current transfer.

You can see the new APIs being used in a real-world scenario in Silicon Labs’ mbed library for the si70xx series of relative temperature and humidity sensors.

Ticker/Timer/Timeout

The API to Ticker, Timer and Timeout functionality did not change. However, I would like to point out that they don't take event_callback_t objects, since they have no event flags to communicate. Instead, you directly attach a static or member function through using attach().

Using a timeout with a member function as callback

//In SimpleObject.h:
public:
    SimpleObject(mbed::DigitalOut *led);

private:
    mbed::Timeout blinker;
    mbed::DigitalOut *ledReference;

    void TimeOutHandler(void);

//in SimpleObject.cpp:
SimpleObject::SimpleObject(DigitalOut* led) {
    ledReference = led;
    blinker.attach(this, &SimpleObject::TimeOutHandler, 0.5f);
}

void SimpleObject::TimeOutHandler(void) {
    led = !led;
    blinker.attach(this, &SimpleObject::TimeOutHandler, 0.5f);
}

All wikipages