A disco effect: Looping and wait() in mbed OS
This post was originally published on Mbed Developer Blog.
Let’s say I have a tri-color LED, and I want to make a small disco effect by toggling the three channels every few hundred milliseconds, and stopping after ten iterations.
Easy enough when doing traditional embedded development:
In mbed OS, the approach above is not allowed:
- It blocks main, which is bad, because no other operations can happen at this point.
- The microcontroller needs to be awake to honor the
wait
call, and therefore cannot go into deep sleep in between toggling LEDs.
Instead, mbed OS works with an event loop and a scheduler (MINAR), that manages the timing of function calls (similar to Node.js or Ruby’s EventMachine). The idea is that the operating system is a lot better at managing time than the developer, especially when applications get more complex. So rather than switching between modules or handling deep sleep yourself, you tell the scheduler ‘wake me up in half a second’, ‘activate me when this interrupt is triggered’ or ‘let me know when a Bluetooth connection comes in’. The scheduler then switches contexts when necessary, and lets the microcontroller sleep when possible.
We always need to be able to call MINAR, so we must not block the microcontroller with a long-running or infinite function. So instead of using wait(0.2)
we tell the OS: “hey! in 200 milliseconds I’d like to do something again, can you wake me up?”. That means that we need to rewrite this code a bit to use events rather than a blocking wait()
call:
In this approach:
- We create a function
disco
that takes two arguments.turns_left
is the number of times the function should run after the current iteration, anddelay_ms
is the delay until the next iteration. This function is invoked every 200 ms. - If
turns_left
is greater than zero, we create a function pointer:- Its return type is
void
and it takes anint8_t
and anint16_t
as arguments. - Surprise, this is the same function signature as the function we’re currently in (
disco
). - We initialize the function pointer with a reference to the
disco
function.
- Its return type is
- We tell MINAR (the event scheduler) that we want to execute the function pointer with the arguments
turns_left - 1
, and ourdelay_ms
. - We also tell MINAR that we want to delay the function call by
delay_ms
milliseconds. - When our application starts we kick off the sequence.
Relatively easy, and now our code is non-blocking!
Calling a member function
If the function disco
is a class member, we can use FunctionPointer
by passing in the object reference as the first argument:
Happy coding!