Silicon Labs
Silicon Labs a leading provider of microcontroller, sensing and wireless connectivity solutions for the Internet of Things (IoT).
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. 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!
Import library
Public Member Functions |
|
FunctionPointerArg1 (R(*function)(A1)=0) | |
Create a FunctionPointer, attaching a static function.
|
|
template<typename T > | |
FunctionPointerArg1 (T *object, R(T::*member)(A1)) | |
Create a FunctionPointer, attaching a member function.
|
|
void | attach (R(*function)(A1)) |
Attach a static function.
|
|
template<typename T > | |
void | attach (T *object, R(T::*member)(A1)) |
Attach a member function.
|
|
R | call (A1 a) |
Call the attached static or member function.
|
|
Data Fields |
|
R(*)() | get_function (A1) |
Get registered static function.
|
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.
Import library
Public Member Functions |
|
SPI (PinName mosi, PinName miso, PinName sclk, PinName ssel=NC) | |
Create a
SPI
master connected to the specified pins.
|
|
void | format (int bits, int mode=0) |
Configure the data transmission format.
|
|
void | frequency (int hz=1000000) |
Set the spi bus clock frequency.
|
|
virtual int | write (int value) |
Write to the
SPI
Slave and return the response.
|
|
template<typename Type > | |
int | transfer (const Type *tx_buffer, int tx_length, Type *rx_buffer, int rx_length, const event_callback_t &callback, int event=SPI_EVENT_COMPLETE) |
Start non-blocking
SPI
transfer using 8bit buffers.
|
|
void | abort_transfer () |
Abort the on-going
SPI
transfer, and continue with transfer's in the queue if any.
|
|
void | clear_transfer_buffer () |
Clear the transaction buffer.
|
|
void | abort_all_transfers () |
Clear the transaction buffer and abort on-going transfer.
|
|
int | set_dma_usage (DMAUsage usage) |
Configure DMA usage suggestion for non-blocking transfers.
|
|
Protected Member Functions |
|
void | irq_handler_asynch (void) |
SPI
IRQ handler.
|
|
int | transfer (const void *tx_buffer, int tx_length, void *rx_buffer, int rx_length, unsigned char bit_width, const event_callback_t &callback, int event) |
Common transfer method.
|
|
int | queue_transfer (const void *tx_buffer, int tx_length, void *rx_buffer, int rx_length, unsigned char bit_width, const event_callback_t &callback, int event) |
void | start_transfer (const void *tx_buffer, int tx_length, void *rx_buffer, int rx_length, unsigned char bit_width, const event_callback_t &callback, int event) |
Configures a callback, spi peripheral and initiate a new transfer.
|
|
void | start_transaction ( transaction_t *data) |
Start a new transaction.
|
|
void | dequeue_transaction () |
Dequeue a transaction.
|
You can see the new APIs being used in Silicon Labs’ mbed library for the Sharp LS013B7DH03 memory LCD.
Import libraryMemoryLCD
Display driver for Sharp's range of SPI-driven memory LCD's, leveraging the power of asynchronous transfers. Currently supports LS013B7DH03, but easily extendable to other models as well.
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.
Import library
Public Member Functions |
|
Serial (PinName tx, PinName rx, const char *name=NULL) | |
Create a
Serial
port, connected to the specified transmit and receive pins.
|
|
void | baud (int baudrate) |
Set the baud rate of the serial port.
|
|
void | format (int bits=8, Parity parity=SerialBase::None, int stop_bits=1) |
Set the transmission format used by the serial port.
|
|
int | readable () |
Determine if there is a character available to read.
|
|
int | writeable () |
Determine if there is space available to write a character.
|
|
void | attach (void(*fptr)(void), IrqType type=RxIrq) |
Attach a function to call whenever a serial interrupt is generated.
|
|
template<typename T > | |
void | attach (T *tptr, void(T::*mptr)(void), IrqType type=RxIrq) |
Attach a member function to call whenever a serial interrupt is generated.
|
|
void | send_break () |
Generate a break condition on the serial line.
|
|
void | set_flow_control (Flow type, PinName flow1=NC, PinName flow2=NC) |
Set the flow control type on the serial port.
|
|
int | write (const uint8_t *buffer, int length, const event_callback_t &callback, int event=SERIAL_EVENT_TX_COMPLETE) |
Begin asynchronous write using 8bit buffer.
|
|
int | write (const uint16_t *buffer, int length, const event_callback_t &callback, int event=SERIAL_EVENT_TX_COMPLETE) |
Begin asynchronous write using 16bit buffer.
|
|
void | abort_write () |
Abort the on-going write transfer.
|
|
int | read (uint8_t *buffer, int length, const event_callback_t &callback, int event=SERIAL_EVENT_RX_COMPLETE, unsigned char char_match=SERIAL_RESERVED_CHAR_MATCH) |
Begin asynchronous reading using 8bit buffer.
|
|
int | read (uint16_t *buffer, int length, const event_callback_t &callback, int event=SERIAL_EVENT_RX_COMPLETE, unsigned char char_match=SERIAL_RESERVED_CHAR_MATCH) |
Begin asynchronous reading using 16bit buffer.
|
|
void | abort_read () |
Abort the on-going read transfer.
|
|
int | set_dma_usage_tx (DMAUsage usage) |
Configure DMA usage suggestion for non-blocking TX transfers.
|
|
int | set_dma_usage_rx (DMAUsage usage) |
Configure DMA usage suggestion for non-blocking RX transfers.
|
You can see the new API in action in Silicon Labs’ low power serial demo program.
Import programSerial-LowPower-Demo
A code sample showing the use of the asynchronous Serial APIs.
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 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.
Import library
Public Member Functions |
|
I2C (PinName sda, PinName scl) | |
Create an
I2C
Master interface, connected to the specified pins.
|
|
void | frequency (int hz) |
Set the frequency of the
I2C
interface.
|
|
int | read (int address, char *data, int length, bool repeated=false) |
Read from an
I2C
slave.
|
|
int | read (int ack) |
Read a single byte from the
I2C
bus.
|
|
int | write (int address, const char *data, int length, bool repeated=false) |
Write to an
I2C
slave.
|
|
int | write (int data) |
Write single byte out on the
I2C
bus.
|
|
void | start (void) |
Creates a start condition on the
I2C
bus.
|
|
void | stop (void) |
Creates a stop condition on the
I2C
bus.
|
|
int | transfer (int address, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, const event_callback_t &callback, int event=I2C_EVENT_TRANSFER_COMPLETE, bool repeated=false) |
Start non-blocking
I2C
transfer.
|
|
void | abort_transfer () |
Abort the on-going
I2C
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.
Import librarySILABS_RHT
This is a library to read out sensor values from Silicon Labs' si70xx-range of relative humidity and temperature sensors.
(Low Power) Ticker/Timer/Timeout¶
The API to Ticker, Timer and Timeout functionality did not change. However, since they are also asynchronous in nature, they are included here for reference. Please note that starting with mbed release 99, on supported platforms, there are also Low Power versions of these APIs. The time resolution for these is quite granular (on Silicon Labs about a quarter millisecond), but they are much more power friendly.
The one thing that sets their API apart from the transactional model of Serial/SPI/I2C, is that the callbacks you get from a time-based event have no event flags. Therefore, they do not use the event_callback_t model. Instead, the API has an attach() method that allows you to attach either a static or member function by passing the relevant pointer(s). The callback function signature has to be of the type
void functionName(void);
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); }
Import library
Public Member Functions |
|
void | attach (void(*fptr)(void), float t) |
Attach a function to be called by the
Ticker
, specifiying the interval in seconds.
|
|
template<typename T > | |
void | attach (T *tptr, void(T::*mptr)(void), float t) |
Attach a member function to be called by the
Ticker
, specifiying the interval in seconds.
|
|
void | attach_us (void(*fptr)(void), timestamp_t t) |
Attach a function to be called by the
Ticker
, specifiying the interval in micro-seconds.
|
|
template<typename T > | |
void | attach_us (T *tptr, void(T::*mptr)(void), timestamp_t t) |
Attach a member function to be called by the
Ticker
, specifiying the interval in micro-seconds.
|
|
void | detach () |
Detach the function.
|
|
Static Public Member Functions |
|
static void | irq (uint32_t id) |
The handler registered with the underlying timer interrupt.
|
|
Protected Attributes |
|
timestamp_t | _delay |
Time delay (in microseconds) for re-setting the multi-shot callback.
|
|
FunctionPointer | _function |
Callback.
|