This is a library, initially targeted at the Arduino, to provide simple ‘thread’ support (very much in scare-quotes). This is version 1.2.1, 2022 December 27 (rev 3828920d0249).
The software is Copyright 2017–19, 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.
Links:
pqrst-1.2.1.tar.gz
or pqrst-1.2.1.zip
from the ‘distribution bundles’ on the
release page.The code is available at the project home page, which includes a link to the repository and to downloads. The code is now reasonably stable, and is used in production. See also the documentation and release notes.
This module implements a Priority Queue for Running Simple Tasks. It is intended to support a simple but very flexible threading framework for Arduino tasks.
When code uses this module, it should declare one or more
subclasses of the Task
class, and in each of those
declare and implement at least the run
method (overriding
Task::run
. These can then be queued using the
Task::start
method. There is a pre-existing subclass
called LoopTask
, which is a task which automatically
re-queues itself at a specified cadence.
The main loop of the program should then call
TaskQueue::run_ready
at a high cadence. This method runs
any task which has become due. The library's general expectation is
that the program main loop consists of little more than
while (1) { Queue.run_ready(millis()); }
but it is reasonable for this queue to instead service low-priority tasks, for example, or even to act as a general to-do queue.
Although the module is nominally targeted at the Arduino, it has only
a rather loose dependence on the Arduino framework (it uses the
Serial
object for some non-essential debugging and the PROGMEM
type for a version string in Flash). It could therefore be ported to
a different framework without much difficulty. In particular, the
library does not depend on the Arduino millis()
function,
and the run_ready()
method can be called with any
monotonically increasing 32-bit ‘time’ value.
The code behaves correctly when the ‘time’ value wraps around, as
the Arduino millis()
function does after 2^32 milliseconds, or 49.7
days, and as the micros()
function does after 71.5 minutes.
Notes:
run()
methods is run without interruption –
there is no preemption, and no yield
method. Thus the run()
method has the responsibility to finish reasonably promptly.
Typically, 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.For example, here is the Arduino ‘blink’ program, implemented with a task loop:
#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()); }
The code has compile-time options for
To use, drop the files pqrst.cpp
and
pqrst.h
into your source tree.
To run the unit tests,
% make check
The main documentation, currently, is the class and function documentation, generated by Doxygen.