The PQRST library: Priority Queue for Running Simple Tasks

Add tasks with a ‘due time’ to a queue, and retrieve the earliest. It is intended to provide a simple Arduino ‘thread’ library (very much in scare-quotes).

Add tasks with a ‘due time’ to a queue, and retrieve the earliest. It is intended to provide a simple Arduino ‘thread’ library (very much in scare-quotes).

The software is Copyright 2016--2019, Norman Gray, and is available under the terms of the 2-clause BSD licence. It is therefore perfectly happy to disappear into your source tree.

The code is available at code.nxg.name. It is stable, and used in production.

Installation and use

Tasks are represented as subclasses of a Task class, which implements a run() method. The main loop of the program can end up as simple as

while (1) {
    Queue.run_ready(millis());
}

or even, within the Arduino framework,

void loop(void)
{
    Queue.run_ready(millis());
}

To run the unit tests, do simply:

% make check

To use the module, copy the files pqrst.cpp and pqrst.h into your source tree. See the documentation and examples in the distributed docs/ tree, or the detailed class and function documentation at the project web page.

How it works – an example

This implements a cooperative multitasking model.

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());
}

Points to note:

Norman Gray
https://nxg.me.uk