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
- Pausing and Sleeping Threaded Functions
- Starting and Stopping Threaded Function
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.
- ptFunc
- PT_FUNC_START
- PT_FUNC_START_EXT
- PT_FUNC_END
- PT_THREAD (Deprecated)
ptFunc
This is the return type for a threaded function.
Example:
ptFunc blinkLED(void) {
PT_FUNC_START(pt);
while(true) {
toggleLEDGreen();
PT_SLEEP(pt, 500);
}
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:
ptFunc blinkLED(void) {
PT_FUNC_START(pt);
while(true) {
toggleLEDGreen();
PT_SLEEP(pt, 500);
}
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;
ptFunc 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);
}
ptFunc 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.
ptFunc blinkLED(void) {
PT_FUNC_START(pt);
while(true) {
toggleGreen();
PT_SLEEP(pt, 500);
}
PT_FUNC_END(pt);
}
PT_THREAD (Deprecated)
Before the introduction of ptFunc, PT_THREAD was used as a MACRO to create a threaded function. But because of it being a macro, the syntax was not proper for C++ so it is preferred to use ptFunc instead. This older syntax is still supported however.
Example:
PT_THREAD(blinkLED(void)) {
PT_FUNC_START(pt);
while(true) {
toggleLEDGreen();
PT_SLEEP(pt, 500);
}
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_SLEEP
Yield program execution for a specified number of milliseconds and then resume execution.
Example
ptFunc blinkRed(void) {
PT_FUNC_START(pt);
// Loop forever
while(true) {
toggleLEDRed();
PT_SLEEP(pt, 500); // Wait 500ms before resuming execution.
}
PT_FUNC_END(pt);
}
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
ptFunc processData(void) {
PT_FUNC_START(pt);
static double calc = 1.234;
for(static 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;
ptFunc 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_YIELD_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;
ptFunc 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 of waiting for a variable, it waits until a thread has completed its execution.
Example
pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;
ptFunc 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);
}
ptFunc 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);
}
Starting & Stopping Threaded Functions
- PT_IS_RUNNING
- PT_FUNC_RESTART
- PT_RESTART
- PT_EXIT
- PT_SPAWN
- PT_SCHEDULE (Deprecated)
PT_IS_RUNNING
Takes a threaded function as an argument and returns true if the thread is running and false if it has finished.
ptFunc blinkLED(void) {
PT_FUNC_START(pt);
while(true) {
PT_SLEEP(pt, 500);
if (PT_IS_RUNNING(processData)) {
setLED(1,0,0); //turn on RED LED
} else {
setLED(0,1,0); //turn on Green LED
}
PT_SLEEP(pt, 500);
setLED(0,0,0); //turn off LEDs
}
PT_FUNC_END(pt);
}
PT_FUNC_RESTART
Causes the current threaded function to restart its execution from the top of the function. Often placed at the bottom of a threaded function to get it to run indefinitely. Compare this example to previous examples that use a while(true) structure.
Example
ptFunc blinkRed(void) {
PT_FUNC_START(pt);
toggleLEDRed();
PT_SLEEP(pt, 500); // Wait 500ms before resuming execution.
PT_FUNC_RESTART(pt);
PT_FUNC_END(pt);
}
PT_RESTART
Causes a different threaded function to restart execution from the top of the function. Typically called after a threaded function completes and requires pt variable used by that function to be accessible by the function calling PT_RESTART.
Example
pt ptProcessData = {0};
pt* ptProcess = &ptProcessData;
ptFunc 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);
}
ptFunc 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 and unless 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
Equivalent 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;
ptFunc 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);
}
ptFunc 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);
}
PT_SCHEDULE (Deprecated)
This function wrapper is no longer necessary. Previously, in the main loop, we would have:
void loop() {
PT_SCHEDULE(blinkRed());
}
but this can just be
void loop() {
blinkRed();
}