Skip to main content

Protothreads

qProtothreads is the qNimble variant of the Protothreads library, a library used to simplify emulating having multiple functions run at the same time. The library works by designing functions to pause their execution so other functions can run and then having the function resume its execution where it left off. Unlike most libraries, Protothreads is a set of macros which get expanded before the c/c++ code is compiled. Because of this, the syntax sometimes differs from proper c/c++. For examples of how to use Protothreads, please look at the Threading Example.

The macros used in Protothreads can be divided into three categories:

Defining Threaded Functions

These macros are used in defining the threaded function. They provide the glue structure for the threading and do not depend on the content of the function.

PT_THREAD

This is a wrapper to put around a function to set that it is a threaded function that will start, yield and stop based on the threading rather than a typical function that runs once from start to finish.

Example:

PT_THREAD(blinkLED(void)) {
PT_FUNC_START(pt);
while(true) {
UpdateSomething();
PT_YIELD(pt);
}
PT_FUNC_END(pt);
}

PT_FUNC_START

This should be the first line of the threaded function and it initializes internal variables used for threading. Those internal variables are stored in a pt object that is named based on the input to this function, typically pt.

Example:

PT_THREAD(blinkLED(void)) {
PT_FUNC_START(pt);
while(true) {
UpdateSomething();
PT_YIELD(pt);
}
PT_FUNC_END(pt);
}

PT_FUNC_START_EXT

Same as PT_FUNC_START except uses an externally created Protothread object to store internal information while PT_FUNC_START creates a local variable. When the threaded function's state needs to be altered (stopped, restarted, etc) outside of this function, create a global Protothread object and use this function at the stop of the function.

Example

pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;

PT_THREAD(processData(void)) {
PT_FUNC_START_EXT(ptProcess);

static double calc = 0.1234;
static uint i=0;

for(i=0; i< 2500000;i++) {
calc += i*(i+3);
PT_YIELD(ptProcess); // pause here to check if other threads need to be run
}
PT_FUNC_END(ptProcess);
}

PT_THREAD(reProcess(void)) {
PT_FUNC_START(pt);
while(true) {
PT_WAIT_THREAD(pt,processData()); //wait until processData thread completes
PT_SLEEP(pt,10000); //wait 10s after processData is done
PT_RESTART(ptProcess); //Restart the finished thread
}
PT_FUNC_END(pt);
}

PT_FUNC_END

This should be the last line of the threaded function and it sets that the function has completed it tasks. If executed, the function will stop running unless it is restarted.

PT_THREAD(blinkLED(void)) {
PT_FUNC_START(pt);
while(true) {
UpdateSomething();
PT_YIELD(pt);
}
PT_FUNC_END(pt);
}

Pausing & Sleeping Threaded Functions

These macros control the behavior of threaded function. They let the circumstances under which a function will stop running and the conditions under which is will resume executing.

PT_YIELD

When running a threaded function, when the processor gets to the PT_YIELD function, it exits the function, allowing the processor to run other functions. When the processor returns to this function, it resumes execution where it left off. This lets the program pause executing if other tasks have work to do, but continue executing as soon as those program(s) pause.

Example

PT_THREAD(processData(void)) {
PT_FUNC_START(pt);
double calc = 1.234;
for(uint i=0; i< 5000000;i++) {
double temp = cos(calc*(calc-1.23*i));
calc = temp*sin((calc-0.23*i)*3.456)+calc*calc/9.8765;
PT_YIELD(pt); // pause here to check if other threads need to be run
}

Serial.printf("Result of calculation is %f\n",calc);
PT_FUNC_END(pt);
}

PT_WAIT_UNTIL

This function takes a boolean argument and will conditionally yield based on this argument. This can be used to make a function pause its execution until some condition has been met.

Example

bool dataReady = false;

PT_THREAD(processData(void)) {
PT_FUNC_START(pt);
PT_WAIT_UNTIL(dataReady); // pause here until dataReady is set to true.
doCalculation();
PT_FUNC_END(pt);
}

Very similar to PT_YIELD_UNTIL, this function will not yield if the passed argument is true.

PT_YIELD_UNTIL

This function is very similar to PT_WAIT_UNTIL, exempt that it is guaranteed to pause execution at least once, even if the argument is always true. It is equivalent to

PT_YIELD(pt);
PT_WAIT_UNTIL(pt,condition);
//These two lines are same as PT_YIELD_UNTIL(pt,condition)

Generally, use PT_WAIT_UNTIL when you want the threaded application to wait until something happens or is ready. Use PT_WAIT_UNTIL when in a loop and you want other threads to run even if the condition has been met.

PT_WAIT_WHILE

Similar to PT_WAIT_UNTIL, but inverted logic on the condition to continue execution.

Example

bool waitforData = true;

PT_THREAD(processData(void)) {
PT_FUNC_START(pt);
PT_WAIT_WHILE(waitforData); // pause here until waitforData is set to false.
doCalculation();
PT_FUNC_END(pt);
}

PT_WAIT_THREAD

Similar to PT_WAIT_UNTIL, but instead wait until a thread has completed execution.

Example

pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;

PT_THREAD(processData(void)) {
PT_FUNC_START_EXT(ptProcess);

static double calc = 0.1234;
static uint i=0;

for(i=0; i< 2500000;i++) {
calc += i*(i+3);
PT_YIELD(ptProcess); // pause here to check if other threads need to be run
}
PT_FUNC_END(ptProcess);
}

PT_THREAD(reProcess(void)) {
PT_FUNC_START(pt);
while(true) {
PT_WAIT_THREAD(pt,processData()); //wait until processData thread completes
PT_SLEEP(pt,10000); //wait 10s after processData is done
PT_RESTART(ptProcess); //Restart the finished thread
}
PT_FUNC_END(pt);
}

PT_SLEEP

Yield program execution for a specified number of milliseconds and then resume execution.

Example

PT_THREAD(blinkRed(void)) {
PT_FUNC_START(pt);
// Loop forever
while(true) {
toggleLEDRed();
PT_SLEEP(pt, 500); // Wait 500ms before resuming execution.
}

PT_FUNC_END(pt);
}

Starting & Stopping Threaded Functions

PT_SCHEDULE

Calls a threaded function. Typically this would be from the loop function

Example

PT_THREAD(blinkRed(void)) {
PT_FUNC_START(pt);
// Loop forever
while(true) {
toggleLEDRed();
PT_SLEEP(pt, 500);
}
PT_FUNC_END(pt);
}

void loop() {
PT_SCHEDULE(blinkRed());
}

PT_RESTART

Causes a threaded function to restart from the top. Typically called after a threaded function completes.

Example

pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;

PT_THREAD(processData(void)) {
PT_FUNC_START_EXT(ptProcess);

static double calc = 0.1234;
static uint i=0;

for(i=0; i< 2500000;i++) {
calc += i*(i+3);
PT_YIELD(ptProcess); // pause here to check if other threads need to be run
}
PT_FUNC_END(ptProcess);
}

PT_THREAD(reProcess(void)) {
PT_FUNC_START(pt);
while(true) {
PT_WAIT_THREAD(pt,processData()); //wait until processData thread completes
PT_SLEEP(pt,10000); //wait 10s after processData is done
PT_RESTART(ptProcess); //Restart the finished thread
}
PT_FUNC_END(pt);
}

PT_EXIT

Causes the threaded function to stop running. Subsequent calls to the function will do nothing (until PT_RESTART called)

Example

PT_THREAD(blinkRedWhenProcessing(void)) {
PT_FUNC_START(pt);
// Loop forever
while(true) {
toggleLEDRed();
PT_SLEEP(pt, 500);
if (processingDone) {
setLEDRed(false);
PT_EXIT(pt); //Stop function
}
}
PT_FUNC_END(pt);
}

PT_SPAWN

Equilivant to running PT_RESTART and then PT_WAIT_THREAD. Causes another threaded function to reset to the top of its function and then waits for that function to complete.

Example

pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;

PT_THREAD(processData(void)) {
PT_FUNC_START_EXT(ptProcess);

static double calc = 0.1234;
static uint i=0;

for(i=0; i< 2500000;i++) {
calc += i*(i+3);
PT_YIELD(ptProcess); // pause here to check if other threads need to be run
}
PT_FUNC_END(ptProcess);
}

PT_THREAD(reProcess(void)) {
PT_FUNC_START(pt);
while(true) {
PT_SPAWN(pt,ptProcess,processData(); // Start processData from the top and wait until it completes
PT_SLEEP(pt,10000); //wait 10s after processData is done
}
PT_FUNC_END(pt);
}