ULWOS2: Multithreading on Arduino!

ULWOS2 is now part of Arduino libraries! In this article I present some of the examples I wrote to demonstrate ULWOS2 on Arduino. We also discuss some new features added to ULWOS2 1.0.0 and how to use ULWOS2 to ease designing applications on Arduino and enable simple multithreading on any Arduino board.

ULWOS2 1.0.0

Version 1.0.0 of ULWOS2 completely redesigned thread control blocks (TCBs). The reasoning behind the redesign is the fact that ULWOS2 previously relied on a precompiler symbol to set the maximum number of threads. The kernel then used that number to create a static array to store all TCBs. While this approach works, it ends up consuming memory that might not get used. That also posed a limit on Arduino since you can’t have global defines when using Arduino IDE.

The new approach creates the TCB inside each thread, meaning that now there is no global array storing TCBs! This also means that ULWOS2 only uses memory that is actually needed! This approach requires the use of a linked link in order to keep track of all TCBs. Instead of using a separate linked list to keep track of all TCBs, I decided to turn each TCB entry into a linked list node. This means that each TCB entry points to the next one. At first they follow the creation order, but after sorting, they follow their priority order.

ULWOS2 on Arduino

As expected, porting ULWOS2 to Arduino was pretty easy. All it needs is a proper handler for ULWOS2 milliseconds timebase. In terms of Arduino, the easiest way is to use millis() function. In order to perform automatic detection of Arduino platform (ARDUINO symbol is present). If ARDUINO is present we define our own internal Arduino symbol (ULWOS2_TARGET_ARDUINO) which we use to configure ULWOS2’s timebase. This way, there is no configuration needed from user’s perspective. ULWOS2 automatically detects and configures itself for Arduino!

On AVR-based Arduinos (such as UNO, NANO, MINI, etc), each TCB uses 14 bytes of RAM (with signals disabled). That means that each new thread needs at least 14 bytes of RAM, plus any static variables defined within its scope. Therefore, it is possible to create several tens of threads even on the simplest Arduino boards!

On 32-bit platforms (such as Arduino ZERO, DUE, etc), each TCB uses 20 bytes (with signals disabled). If you are wondering why, the answer is that pointers on 32-bit processors are 32-bit wide, while on AVR they are 16-bit wide.

Using ULWOS2 in your sketch is quite simple: because it is available as an Arduino library, all you have to do is to go to Sketch > Include Library and select ULWOS2.

If ULWOS2 is not listed, you can easily install the library! Go to Tools > Manage Libraries, type ULWOS2 in the search box and install the latest version:
Once you add ULWOS2 to your sketch you can start creating multiple threads and playing with multitasking on Arduino, easy as pie!

Examples

I have written some very simple examples showing how to use ULWOS2 on Arduino. The first one is the traditional LED blinker.

LED Blinker

The complete source code for Blink.ino is shown below:

#include <ULWOS2.h>

/*
  ULWOS2 - single thread running LED blinker on Arduino
  Author: Fábio Pereira
  Please visit www.embeddedsystems.io for more information on ULWOS2
*/

// This is our thread, it blinks the builtin LED without blocking code
void thread1(void)
{
  ULWOS2_THREAD_START();	// this is necessary for each thread in ULWOS2!
  while(1)
  {
    digitalWrite(LED_BUILTIN, HIGH);	// turn LED on
    ULWOS2_THREAD_SLEEP_MS(50);	        // sleep for 50ms
    digitalWrite(LED_BUILTIN, LOW);	// turn LED off
    ULWOS2_THREAD_SLEEP_MS(500);	// sleep for 500ms
  }
}  

void setup() {
  ULWOS2_INIT();	// initialize ULWOS2
  ULWOS2_THREAD_CREATE(thread1, 10);	// create thread 1 with priority 10
  pinMode(LED_BUILTIN, OUTPUT);	// initialize LED I/O pin as output
}

void loop() {
  // run ULWOS2 scheduler (it will run all threads that are ready to run)
  ULWOS2_START_SCHEDULER();
}

Note that the setup() function handles ULWOS2 initialization and thread creation, in the example we are creating a thread (thread1) with a priority of 10. Priority levels go from 0 (highest) to 255 (lowest). In this example, priority is not important as there is only one thread.

When you have multiple threads, ULWOS2 will always run the highest priority thread (or threads) first. It will only run lower priority threads when higher priority threads are not ready to run. When a thread is not ready to run? One typical example is when a thread is sleeping. By calling ULWOS2_THREAD_SLEEP_MS(x) you tell ULWOS2 that the thread is gonna sleep for x milliseconds. During that time, other threads (even lower priority ones) are allowed to run!

When using ULWOS2, each thread must have a call to ULWOS2_THREAD_START() at the beginning of its code. Any code before ULWOS2_THREAD_START() will run every time the thread runs! Code placed after ULWOS2_THREAD_START() runs according to the thread flow (it can be suspended/resumed by using ULWOS2 calls).

Warning: you can’t have  any other ULWOS2 call before ULWOS2_THREAD_START()!

Usually you are going to have a while (1) following ULWOS2_THREAD_START() and inside it you are going to add your code. A typical thread will look like this:

void myThread(void)
{
  ULWOS2_THREAD_START();	// this is necessary for each thread in ULWOS2!
  while(1)
  {
    // my code goes here
  }
}

There are other examples of a breathing LED using software PWM and push-button debouncing, but I want to focus on a more complex example.

Seconds Up/Down Counter on 7-segment Display

This example demonstrates how ULWOS2 can be used to perform several tasks at the “same time”. We have 6 threads performing different tasks:

  • A thread running a software PWM on pin D13 (built-in LED on most Arduino boards);
  • A second thread controlling the LED brightness (breathing effect);
  • A third thread multiplexing the 4-digit LED display;
  • A fourth thread running a one-second timer;
  • A fifth thread handling the reset key (with debouncing);
  • A sixth thread handling the up/down key (also with debouncing).

These threads are created inside setup() in the following order and priorities:

ULWOS2_THREAD_CREATE(softPWMthread, 0);
ULWOS2_THREAD_CREATE(breathThread, 2);
ULWOS2_THREAD_CREATE(displayThread, 1);
ULWOS2_THREAD_CREATE(secondsThread, 3);
ULWOS2_THREAD_CREATE(processResetButtonThread, 3);
ULWOS2_THREAD_CREATE(processDirectionButtonThread, 3);

The the highest priority thread is the software PWM one (priority 0), followed by the display multiplexing thread (priority 1), LED brightness thread (priority 2). All other threads use priority 3, meaning that they only run when no other thread with higher priority is ready. These priorities where chosen for demonstration only. Choosing the right priority usually takes into account the importance of the thread. I would say that maybe the seconds thread should get a higher priority if we wanted the timing to be more precise, but since software timing here will always be imprecise, this would be pointless. This is why I have decided that the threads related to presentation (LED and Display) should have the highest priority.

The final result can be seen in the figure below. Note that LED breathing, display multiplexing, seconds timing and key debouncing is happening in “parallel”. Welcome to multithreading world!

If you are interested in using ULWOS2 you can install the library on Arduino IDE or you can check its github repository, which shows how to use ULWOS2 on other platforms as well.

You are welcome to comment this post, ask for features or contribute to ULWOS2 if you want.

See you next time!

Leave a Reply