True Arduino Multithreading

#004 True Arduino Multithreading

last updated: 11/30/21

If you haven't read the page Arduino Timed Events, this project was a development from that.

Arduino Timed Events still had the issue that a blocking task (Ex. waiting for Serial data) had to be broken down into non blocking tasks. (Ex. checking if Serial data is available). The program wasn't truly better utilizing CPU time. Nor was the programmer freed to write bloocking code and have it act as non-blocking code. The old code just allowed you to plan out when things should be run. A poor scheduler, that's all.

This project, written in C and assembler, performs true multithreading. I wrote it completely from scratch, learning assembly and the inner workings of the ATMEGA328p along the way. It uses a timer to interrupt threads, which runs some assembly to store program context (registers, etc...), switch threads, and restore context. The available ram (between the end of the bss section and RAMEND) is divided evenly for each thread's stack. There is no concept of thread priority, however threads can voluntarily call sleep functions that do not give them CPU time until the sleep has concluded. The delay() function can be used, but CPU time will be given to the delay function.

Calling a sleep function does not block the CPU at all. Sleeping threads will be skipped. For the developers ease of coding, several functions are given.
  • sleep(time) - waits until the system clock time has reached millis() + time --> relative delay
  • sleepUntil(time) - waits until the system clock time has reached time --> absolute delay
  • sleepAnother(time) - waits until the system clock time has reached the previous delay time plus time. See the code for an example--> absolute delay interval

Here is some sample code. Note the three infinite functions, loop, worker1 and worker2. The amount of threads currently must be fixed, as the RAM is split evenly between them. Runtime threads make things complicated. Also note the use of "sleepAnother", which sleeps for x amount of milliseconds past the end of the last delay.

"Config.h"

        #define NUM_WORKERS 2 //Additional threads plus the main thread.
        #define WORKER_1_NAME worker1
        #define WORKER_2_NAME worker2
        #define SWITCHING_FREQ 1000

Main Arduino Sketch

        #include "config.h"

        void setup() {
           Serial.begin(115200);
           Serial.println(F("Starting Program!"));
           delay(20);
           startThreads();
           pinMode(LED_BUILTIN, OUTPUT);
        }

        // Worker 0
        void loop() {
           digitalWrite(LED_BUILTIN, HIGH);
           sleepAnother(500);
           digitalWrite(LED_BUILTIN, LOW);
           sleepAnother(500);
        }
        extern "C" void worker1() {
           Serial.println(F("Starting worker 1"));
           sleepAnother(500);
           while (true) {
               for(unsigned int i = 1; i <= 10; i++) {
                   for(unsigned int j = 1; j <= 10; j++) {
                       noInterrupts();
                       Serial.print(F("W 1: "));
                       Serial.print(i);
                       Serial.print(F(" * "));
                       Serial.print(j);
                       Serial.print(F(" = "));
                       Serial.println(i * j);
                       interrupts();
                       sleepAnother(1000);
                   }
               }
           }
        }

        extern "C" void worker2() {
           Serial.println(F("Starting worker thread 2"));
           while (true) {
               for(unsigned int i = 10; i > 0; i--) {
                   for(unsigned int j = 10; j > 0; j--) {
                       noInterrupts();
                       Serial.print(F("W 2: "));
                       Serial.print(i);
                       Serial.print(F(" * "));
                       Serial.print(j);
                       Serial.print(F(" = "));
                       Serial.println(i * j);
                       interrupts();
                       sleepAnother(1000);
                   }
               }
           }
        }

You'll note that noInterrupts is placed during the prints. This forbids the switching of tasks while the code is printing something that must be in order. This is only required if two threads attempt to print at the same time, which this does not. But it is there for safety. The code when ran flashes the onboard LED and prints:

    Starting Program!
    Starting worker 1
    Starting worker thread 2
    W 2: 10 * 10 = 100
    W 1: 1 * 1 = 1
    W 2: 10 * 9 = 90
    W 1: 1 * 2 = 2
    W 2: 10 * 8 = 80
    W 1: 1 * 3 = 3
    W 2: 10 * 7 = 70
    W 1: 1 * 4 = 4
    W 2: 10 * 6 = 60
    W 1: 1 * 5 = 5
    W 2: 10 * 5 = 50
    W 1: 1 * 6 = 6
    W 2: 10 * 4 = 40

To see the full code, click here.

Go To Top Guess what? I made this website! Check it and many of my other projects on my github.