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:
- Macros for Defining Threaded Functions
- Macros for Pausing and Sleeping Threaded Functions
- Macros for 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.
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);
}