59

Is there a way I can have multiple parts of the program running together without doing multiple things in the same code block?

One thread waiting for an external device while also blinking a LED in another thread.

  • 3
    You should probably first ask yourself if you really need threads. Timers may be OK for your needs already and they are natively supported on Arduino. – jfpoilpret Feb 18 '14 at 19:34
  • 1
    You may want to check out the Uzebox too. It's a two chip homebrew video game console. So while it isn't exactly an Arduino, the entire system is built on interrupts. So audio, video, controls, etc. are all interrupt driven while the main program doesn't have to worry about any of it. May be good reference. – cbmeeks Jul 14 '15 at 16:59
50

There is no multi-process, nor multi-threading, support on the Arduino. You can do something close to multiple threads with some software though.

You want to look at Protothreads:

Protothreads are extremely lightweight stackless threads designed for severely memory constrained systems, such as small embedded systems or wireless sensor network nodes. Protothreads provide linear code execution for event-driven systems implemented in C. Protothreads can be used with or without an underlying operating system to provide blocking event-handlers. Protothreads provide sequential flow of control without complex state machines or full multi-threading.

Of course, there is an Arduino example here with example code. This SO question might be useful, too.

ArduinoThread is a good one too.

18

AVR based Arduino's do not support (hardware) threading, I am unfamiliar with the ARM based Arduino's. One way around this limitation is the use of interrupts, especially timed interrupts. You can program a timer to interrupt the main routine every so many microseconds, to run a specific other routine.

http://arduino.cc/en/Reference/Interrupts

15

It is possible to do software side multi-threading on the Uno. Hardware level threading is not supported.

To achieve multithreading, it will require the implementation of a basic scheduler and maintaining a process or task list to track the different tasks that need to be run.

The structure of a very simple non-preemptive scheduler would be like:

//Pseudocode
void loop()
{

for(i=o; i<n; i++) 
run(tasklist[i] for timelimit):

}

Here, tasklist can be an array of function pointers.

tasklist [] = {function1, function2, function3, ...}

With each function of the form:

int function1(long time_available)
{
   top:
   //Do short task
   if (run_time<time_available)
   goto top;
}

Each function can perform a separate task such as function1 performing LED manipulations, and function2 doing float calculations. It will be the responsibility of each task(function) to adhere to the time allocated to it.

Hopefully, this should be enough to get you started.

  • 2
    I am not sure I would talk about "threads" when using a non preemptive scheduler. By the way, such a scheduler already exists as an arduino library: arduino.cc/en/Reference/Scheduler – jfpoilpret Feb 18 '14 at 20:40
  • 5
    @jfpoilpret - Cooperative multithreading is a real thing. – Connor Wolf Feb 20 '14 at 1:09
  • Yes you're right! My mistake; it had been so long ago I had not faced cooperative multithreading that in my mind, multithreading had to be preemptive. – jfpoilpret Feb 20 '14 at 4:47
9

As per the description of your requirements:

  • one thread waiting for an external device
  • one thread blinking a LED

It seems you could use one Arduino interrupt for the first "thread" (I would rather call it "task" in fact).

Arduino interrupts can call one function (your code) based on an external event (voltage level or level change on a digital input pin), that will trigger your function immediately.

However, one important point to keep in mind with interrupts is that the called function should be as fast as possible (typically, there should be no delay() call or any other API that would depend on delay()).

If you have a long task to activate upon external event trigger, then you could potentially use a cooperative scheduler and add a new task to it from your interrupt function.

A second important point about interrupts is that their number is limited (e.g. only 2 on UNO). So if you start tohave more external events, you would need to implement some kind of multiplexing all inputs into one, and have your interrupt function determine what multiplexed inut was the actual trigger.

6

A simple solution is to use a Scheduler. There are several implementations. This describes shortly one that is available for AVR and SAM based boards. Basically a single call will start a task; "sketch within a sketch".

#include <Scheduler.h>
....
void setup()
{
  ...
  Scheduler.start(taskSetup, taskLoop);
}

Scheduler.start() will add a new task that will run the taskSetup once and then repeatedly call taskLoop just as the Arduino sketch works. The task has its own stack. The size of the stack is an optional parameter. Default stack size is 128 bytes.

To allow context switching the tasks need to call yield() or delay(). There is also a support macro for waiting for a condition.

await(Serial.available());

The macro is syntactic sugar for the following:

while (!(Serial.available())) yield();

Await can also be used to synchronize tasks. Below is an example snippet:

volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
  await(taskEvent);
  switch (taskEvent) {
  case 1: 
  ...
  }
  taskEvent = 0;
}
...
void loop()
{
  ...
  signal(1);
}

For further details see the examples. There are examples from multiple LED blink to debounce button and a simple shell with non-blocking command line read. Templates and namespaces can be used to help structure and reduce the source code. Below sketch shows how to use template functions for multi-blink. It is sufficient with 64 bytes for the stack.

#include <Scheduler.h>

template<int pin> void setupBlink()
{
  pinMode(pin, OUTPUT);
}

template<int pin, unsigned int ms> void loopBlink()
{
  digitalWrite(pin, HIGH);
  delay(ms);
  digitalWrite(pin, LOW);
  delay(ms);
}

void setup()
{
  Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
  Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
  Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}

void loop()
{
  yield();
}

There is also a benchmark to give some idea of the performance, i.e. time to start task, context switch, etc.

Last, there are a few support classes for task level synchronization and communication; Queue and Semaphore.

3

From a previous incantation of this forum, the following question/answer was moved to Electrical Engineering. It has sample arduino code to blink an LED using a timer interrupt while using the main loop to do serial IO.

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

Repost:

Interrupts are a common way to get things done while something else is going on. In the example below, the LED is blinking without using delay(). Whenever Timer1 fires, the interrupt service routine (ISR) isrBlinker() is called. It switches the LED on/off.

To show that other things can simultaneously happen, loop() repeatedly writes foo/bar to the serial port independent of the LED blinking.

#include "TimerOne.h"

int led = 13;

void isrBlinker()
{
  static bool on = false;
  digitalWrite( led, on ? HIGH : LOW );
  on = !on;
}

void setup() {                
  Serial.begin(9600);
  Serial.flush();
  Serial.println("Serial initialized");

  pinMode(led, OUTPUT);

  // initialize the ISR blinker
  Timer1.initialize(1000000);
  Timer1.attachInterrupt( isrBlinker );
}

void loop() {
  Serial.println("foo");
  delay(1000);
  Serial.println("bar");
  delay(1000);
}

This is a very simple demo. ISRs can be much more complex and can be triggered by timers and external events (pins). Many of the common libraries are implemented using ISRs.

3

I also came to this topic while implementing a matrix LED display.

In one word, you may build a polling scheduler by using millis() function and timer interrupt in Arduino.

I suggest the following articles from Bill Earl:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview

2

You could also give my ThreadHandler library a try

https://bitbucket.org/adamb3_14/threadhandler/src/master/

It uses an interrupting scheduler to allow context switching without relaying on yield() or delay().

I created the library because I needed three threads and I needed two of them to run at a precise time no matter what the others were doing. The first thread handled serial communication. The second was running a Kalman filter using float matrix multiplication with the Eigen library. And the third was a fast current control loop thread which had to be able to interrupt the matrix calculations.

How it works

Each cyclic thread has a priority and a period. If a thread, with higher priority than the current executing thread, reaches its next execution time the scheduler will pause the current thread and switch to the higher priority one. Once the high priority thread completes its execution the scheduler switches back to the previous thread.

Scheduling rules

The scheduling scheme of the ThreadHandler library is as follows:

  1. Highest priority first.
  2. If the priority is the same then the thread with the earliest deadline is executed first.
  3. If two threads have the same deadline then the first created thread will execute first.
  4. A thread can only be interrupted by threads with higher priority.
  5. Once a thread is executing it will block execution for all threads with lower priority until the run function returns.
  6. The loop function has priority -128 compared to ThreadHandler threads.

How to use

Threads can be created via c++ inheritance

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

Or via createThread and a lambda function

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //code to run
    });

Thread objects automatically connect to the ThreadHandler when they are created.

To start execution of created thread objects call:

ThreadHandler::getInstance()->enableThreadExecution();
1

And here is yet another microprocessor cooperative multitasking library – PQRST: a Priority Queue for Running Simple Tasks.

In this model, a thread is implemented as a subclass of a Task, which is scheduled for some future time (and possibly rescheduled at regular intervals, if, as is common, it subclasses LoopTask instead). The run() method of the object is called when the task becomes due. The run() method does some due work, and then returns (this is the cooperative bit); it'll typically maintain some sort of state machine to manage its actions on successive invocations (a trivial example is the light_on_p_ variable in the example below). It requires a slight rethinking of how you organise your code, but has proven very flexible and robust in fairly intensive use.

It's agnostic about the time units, so it is as happy running in units of millis() as micros(), or any other tick that is convenient.

Here is the ‘blink’ program implemented using this library. This shows only a single task running: other tasks would typically be created, and started within setup().

#include "pqrst.h"

class BlinkTask : public LoopTask {
private:
    int my_pin_;
    bool light_on_p_;
public:
    BlinkTask(int pin, ms_t cadence);
    void run(ms_t) override;
};

BlinkTask::BlinkTask(int pin, ms_t cadence)
    : LoopTask(cadence),
      my_pin_(pin),
      light_on_p_(false)
{
    // empty
}
void BlinkTask::run(ms_t t)
{
    // toggle the LED state every time we are called
    light_on_p_ = !light_on_p_;
    digitalWrite(my_pin_, light_on_p_);
}

// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    flasher.start(2000);  // start after 2000ms (=2s)
}

void loop()
{
    Queue.run_ready(millis());
}
  • These are “run-to-completion” tasks, right? – Edgar Bonet Jul 8 at 15:48
  • @EdgarBonet I'm not sure quite what you mean. After the run() method is called, it is not interrupted, so it has the responsibility to finish reasonably promptly. Typically, however, it'll do its work then reschedule itself (possibly automatically, in the case of a subclass of LoopTask) for some future time. A common pattern is for the task to maintain some internal state machine (a trivial example is the light_on_p_ state above) so that it behaves suitably when it is next due. – Norman Gray Jul 8 at 15:58
  • So yes, those are run-to-completion (RtC) tasks: no task can run before the current one completes its execution by returning from run(). This is in contrast with cooperative threads, that can yield the CPU by, e.g., calling yield() or delay(). Or preemptive threads, that can be scheduled-out at any time. I feel the distinction is important, as I have seen that many people who come around here searching for threads do so because they prefer writing blocking code rather than state machines. Blocking real threads that yield the CPU is fine. Blocking RtC tasks is not. – Edgar Bonet Jul 8 at 18:09
  • @EdgarBonet It's a useful distinction, yes. I would regard both this style, and yield-style threads, as simply different styles of cooperative thread, as opposed to preemptive threads, but it's true that they require a different approach to coding them. It would be interesting to see a thoughtful and in-depth comparison of the various approaches mentioned here; one nice library not mentioned above is protothreads. I find things to criticise in both, but also to praise. I (of course) prefer my approach, because it seems most explicit and needs no extra stacks. – Norman Gray Jul 9 at 10:34
  • (correction: protothreads was mentioned, in @sachleen's answer) – Norman Gray Jul 9 at 12:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.