Application flow control and task management

We can use Blinky to explore flow control and task management in mbed OS applications. We’ll look at automated actions first, then move on to handling user actions.

Flow control for automated actions

If we want to automatically blink an LED, we have three main techniques:

  1. Busy wait
  2. Ticker
  3. Threads

Busy wait

Busy wait is a method that blocks the processor for a period of time. This is an effective way to create time delays, but it’s rather inefficient because it wastes processor time and keeps the processor running at full power for the duration of the wait:

 


#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    while(1) {
        myled = 1;
        // printf("LED On\r\n");
        wait(0.2);
        myled = 0;
        // printf("LED Off \r\n");
        wait(0.2);
    }
}

Notice printf(); you can enable this by uncommenting the line (remove the ‘//’). printf() prints to the terminal, so you can use them to get debug information. We recommend using CoolTerm, as it works the same on Windows, Linux and OS X. Here is a handy video on how to use CoolTerm to connect to your board and view the printf() statements.

Ticker

Tickers and timers are another way of creating a time interval. These methods are somewhat better than busy wait, because they allow other code to run while you are waiting. It is even possible, though non-trivial, to sleep during the wait period.

Here is an example that doesn’t include sleeping:

 


#include "mbed.h"
 
Ticker flipper;
DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void flip() {
    led2 = !led2;
}
 
int main() {
    led2 = 1;
    flipper.attach(&flip, 2.0); // call flip function every 2 seconds 


    // spin in a main loop. flipper will interrupt it to call flip
    while(1) {
        led1 = !led1;
        wait(0.2);
    }
}

Thread

Threads are the most efficient way to blink an LED. During the waiting period it is possible to take advantage of mbed OS optimizations to automatically conserve power and deal with other tasks. While this is not the most visually appealing method, nor the simplest, it is the preferred way for large scale deployments:

 


#include "mbed.h"
#include "rtos.h"
 
DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) {
    while (true) {
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
int main() {
    //Create a thread to execute the function led2_thread
    Thread thread(led2_thread);
    //led2_thread is executing concurrently with main at this point
    
    while (true) {
        led1 = !led1;
        Thread::wait(500);
    }
}

Flow control for manual actions

Let’s try using a DigitalIn pin from the button to control the application. There are two ways to read input data: we can either constantly poll the button in a busy wait, or set an interrupt to trigger when pressed. We’ll explore these methods below.

Busy wait button

We can wait for digital input the same way we waited for time to pass - using a while() loop. In the example below the digital input is a button press, which causes the application to flash the LED and then wait for 1 second.

Tip: You may need to change the SW1 pin, as the button on your board may be called something else. Please refer to the pinmap on the Boards page.

 


#include "mbed.h"
#include "rtos.h"
 
DigitalIn button(SW1); // Change to match your board
DigitalOut led(LED1);

#define button_press 0

int main() {
    while(1) {
        if(button_press == button){
           led = !led;
           wait(1);
       }
    }
}

We constantly poll the button to see if it has a value that matches ‘button_press’. If it matches, we toggle the LED and wait 1 second.

button_press is used to denote what value the switch uses to represent the state pushed. Most switches are by default open (unpressed), so they will read as 0 while pressed. If you see your LED blinking without the button being pressed - try changing button_press to 1.

Interrupt button

An alternative way to poll the button is to use an interrupt. Interrupts let you say ‘when that pin changes value, call this function’. In other words, we can tell the MCU to call a function when the button is pressed. In our case, that function toggles the LED:

 


#include "mbed.h"
 
InterruptIn button(SW1);
DigitalOut led(LED1);
DigitalOut heartbeat(LED2);
 
void toggle() {
    led = !led;
}
 
int main() {
    button.rise(&toggle);  // call toggle function on the rising edge
    while(1) {           // wait around, interrupts will interrupt this!
        heartbeat= !heartbeat;
        wait(0.25);
    }
}

In the code above we have a heartbeat function that runs on LED2, which lets you see that your code is running. Then we connect an InterruptIn object to the button and set it so that when the button rises from 0 to 1 the toggle function is called; the function toggles LED1. This way we can turn the LED on and off as needed, without needing to “waste” our time waiting or actively polling an inactive button. We (or rather - the MCU) are free to move on to other things .

Interrupt driven programming is one of the fundamental paradigms of microcontroller programming.