Application flow control
We can use Blinky to explore flow control and task management in Arm Mbed OS applications. We'll look at automating actions with delays first, then move on to handling user actions.
Automating actions with delays
If we want to automatically blink an LED, we have four main techniques:
The techniques cater to different requirements for precision, efficiency and context.
Wait API
The Wait API creates delays by actively blocking the execution for a period of time. It uses the busy wait strategy - internally running a loop until the end of the period.
Here is an example that uses the API's wait_us()
function to wait a number of microseconds:
#include "mbed.h"
DigitalOut myled(LED1);
int main()
{
while (1) {
myled = 1;
// printf("LED On\r\n");
wait_us(200000);
myled = 0;
// printf("LED Off \r\n");
wait_us(200000);
}
}
The processor is active for the entire duration, so the Wait API is suitable when you need short delays (nanoseconds to a few microseconds) without triggering sleep modes, which have some wake-up latencies. It is also safe to use in interrupt contexts. However, we do not recommend busy waiting for longer delays: the system may not be able to switch to other tasks or save power by sleeping.
For longer delays - millisecond to seconds - use one of the other techniques.
Ticker and Timeout
Tickers and Timeouts are non-blocking, interrupt-based ways of creating a time interval - your code continues to execute or sleep when there is nothing to do. The difference is that Tickers are recurring whereas Timeouts are one-off.
Here is an example that uses a ticker object:
#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;
ThisThread::sleep_for(200);
}
}
Warning: A ticker/timeout's handlers are executed in interrupt contexts and thus, like any interrupt handlers, should return quickly and not use printf
or APIs that are not intended for interrupts.
If you don't need the precision of a high-frequency Ticker or Timeout, we recommend that you use LowPowerTicker or Low Power Timeout instead. The low power classes inform the operating system you want to allow deep sleep mode on your system. Note that entering deep sleep also depends on the specific environment and characteristics of your system, not just your API selection. For more information about sleep and deep sleep, please refer to our documentation about power management and our Mbed Office Hours video.
Thread
If your application is running in RTOS mode then Threads are another efficient way to blink an LED. During the waiting period, Mbed OS automatically conserves power and deals with other tasks. This makes Threads the most efficient way to run tasks in Mbed OS.
#include "mbed.h"
DigitalOut led1(LED1);
DigitalOut led2(LED2);
Thread thread;
void led2_thread()
{
while (true) {
led2 = !led2;
ThisThread::sleep_for(1000);
}
}
int main()
{
// Create a thread to execute the function led2_thread
thread.start(led2_thread);
// led2_thread is executing concurrently with main at this point
while (true) {
led1 = !led1;
ThisThread::sleep_for(500);
}
}
EventQueue
The EventQueue class uses the call_every()
function to schedule repeated actions:
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
void flip(DigitalOut &led)
{
led = !led;
}
int main()
{
DigitalOut led1(LED1);
DigitalOut led2(LED2);
// creates a queue with the default size
EventQueue queue;
// events are simple callbacks
queue.call_every(1000, flip, led1);
queue.call_every(500, flip, led2);
// events are executed by the dispatch method
queue.dispatch_forever();
}
For one-off delays, use call_in()
. Just as with Ticker and Timeout, if no threads are running during a wait, the system enters sleep or deep sleep mode. A major advantage of EventQueue over Ticker and Timeout is that the handler is called in the same context as the EventQueue is dispatched (thread, in the case of RTOS), so interrupt-related restrictions (such as no printf
and no Mutex
) do not apply.
Handling user actions
Let’s use 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, or set an interrupt to trigger when pressed. We’ll explore these methods below.
Active polling 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 one second.
#include "mbed.h"
DigitalIn button(BUTTON1); // Change to match your board
DigitalOut led(LED1);
#define BUTTON_PRESS 0
int main()
{
while (1) {
if (BUTTON_PRESS == button) {
led = !led;
ThisThread::sleep_for(1000);
}
}
}
We constantly poll the button to see whether it has a value that matches BUTTON_PRESS
. If it matches, we toggle the LED and wait one 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(BUTTON1);
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;
ThisThread::sleep_for(250);
}
}
In the code above a heartbeat function 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 the application can turn the LED on and off as needed, without needing to “waste” time waiting or actively polling an inactive button. The MCU is free to move on to other things .
Interrupt driven programming is one of the fundamental paradigms of microcontroller programming.