Silicon Labs


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

Using the improved mbed sleep API

Introduction

Starting with the introduction of asynchronous I/O APIs to mbed, sleeping has become a lot more powerful. Initially supported by Silicon Labs, the new behavior of sleep() will allow you to take full advantage of the target's low-power capabilities by combining it with an asynchronous programming model.

Behavior

Starting with mbed release 99 on Silicon Labs platforms, sleep() will no longer only halt the core, something known as the WFI (wait for interrupt) instruction in the ARM world.

Calling sleep() will now, based on a specific platform's implementation, dynamically determine what sleep mode to go to. This is done by checking which peripherals are actually in use, and what events the user program is listening for. Unused peripherals and/or clock trees can be switched off by the sleep implementation, resulting in a very easy way to save power in your application.

Pitfalls

Currently, there are some known pitfalls:

  • If you are mixing asynchronous API calls with synchronous (old-style) calls, there is a possibility the synchronous call actually returns before the I/O transaction is fully complete. This is especially true for the transmit functions of Serial and I2C, where the data to send can be living in a FIFO buffer somewhere in the system waiting to be sent by the corresponding peripheral, but from a code standpoint, the transmission is complete. Calling sleep() shortly after (synchronously) transmitting data through I2C or Serial will thus result in undefined behavior. If the data is still in the FIFO at the time of going to sleep, the data transfer will get corrupted.

    Calling sleep() is perfectly safe when used in conjunction with the asynchronous APIs, since the system will rely on the completion callback to tell it that it is safe to switch that peripheral off.
  • sleep() will return after any interrupt / event, regardless of whether or not a callback has actually been registered to it. This means that for most use cases, you will have to wrap it in a while statement. See the demo code at the bottom of the page for a real-world example of this.

Wrapping sleep() in a while() loop

LowPowerTimeout halfSecondTimeout;
bool expired = false;

void callback(void) {
    expired = true;
}

void main(void) {
    /* 
    * For some reason, we want to stop the program flow for half a second
    * Instead of using wait(), which is ridiculous from a power consumption standpoint,
    * we register a timeout and sleep until the delay has passed
    */
    halfSecondTimeout.attach(callback, 0.5f);
    
    //Ensure that we only continue the program flow here after the timeout expired.
    while(!expired) sleep();

    ....
}

Mixing sleep() with synchronous code

When utilizing the asynchronous api, there is one usage of the interrupt callback handler that worth notice when developing an application: timing sensitive commands are not supposedly to be executed within an interrupt

Let's take a look at this short example here:

/media/uploads/lichang/asyncdemo.png

The MCU is set to go into sleep mode when executing an asynchronous procedure.

When function sleep() is called, the current clock state is stored before the clock that is currently running is disabled. Then, a wait for interrupt function WFI() is executed to listen for any incoming interrupt requests while the execution of MCU is halted. These interrupts could be receiving data through serial, or a pin is set to high, etc. See the flow char here: /media/uploads/lichang/sleepfnflowchart.png

When a processor executs an interrupt handler or event callback function, we say that the system is in interrupt context.

For example, the processor is sleeping until it receives some serial data from UART (serial) to light up an LED. Lighting up an LED is done in the serial callback, and as such is in an interrupt context.

/media/uploads/lichang/serialcb.png

An interrupt is asynchronous with respect to the current main process. Interrupt context is time critical because the interrupt handler interrupts other code. So, it should be quick and simple, unless you really know what you are doing and all of the consequences.

Because of its asynchronous nature and the way the sleep API is built, there are certain functions that are not supposed to be called within an interrupt context. The major caveat is that when the interrupt routine calls some timing sensitive functions, the system core clock needs to be re-enabled in order for these functions to work properly. However, as shown in the flowchart above, the processor is still in interrupt context and has not yet gotten to execute the clock re-enable function. As a result, any timing sensitive execution should be avoided here, or you must manually re-enable the clocks. Such timing sensitive commands include but are not limited to: printf("content"), wait(number), serial.getc(),serial.putc(), spi.read(), spi.write(), i2c.read(), i2c.write(), ...


All wikipages