Uses the protothreads approach to enable imperative synchronous programming (as promoted by Blech) in C or C++.
With this header-only library you can simplify your embedded programming projects by keeping a delay
based approach but still enable multiple things to happen at once in a structured, modular and deterministic way.
/* This blinks an LED on every other tick. */
pa_activity (FastBlinker, pa_ctx(pa_defer_res), int pin) {
pa_defer {
setLED(pin, BLACK);
};
while (true) {
setLED(pin, RED);
pa_pause;
setLED(pin, BLACK);
pa_pause;
}
} pa_end
/* This blinks an LED on a custom schedule. */
pa_activity (SlowBlinker, pa_ctx_tm(pa_defer_res), int pin, unsigned on_ticks, unsigned off_ticks) {
pa_defer {
setLED(pin, BLACK);
};
while (true) {
setLED(pin, RED);
pa_delay (on_ticks);
setLED(pin, BLACK);
pa_delay (off_ticks);
}
} pa_end
/* An activity which delays for a given number of ticks. */
pa_activity (Delay, pa_ctx_tm(), unsigned ticks) {
pa_delay (ticks);
} pa_end
/* This drives blinking LEDs and preempts them after 3 and 10 ticks. */
pa_activity (Main, pa_ctx_tm(pa_co_res(3); pa_use(Delay); pa_use(FastBlinker); pa_use(SlowBlinker))) {
printf("Begin\n");
/* Blink Fast LED for 3 ticks */
pa_after_abort (3, FastBlinker, 0);
/* Blink both LED for 10 ticks */
pa_co(3) {
pa_with (Delay, 10);
pa_with_weak (FastBlinker, 0);
pa_with_weak (SlowBlinker, 1, 3, 2);
} pa_co_end
printf("Done\n");
} pa_end
In this example, a fast led is blinked for 3 ticks and then both the fast and a slow led are blinked concurrently for 10 ticks.
As can be seen in the example above, an activity is defined by the pa_activity
macro which takes the
name of the activity as first parameter.
This is followed by what is called a context (pa_ctx(...)
) and which stores the
state which should outlive a single tick. Also sub-activities used in the activity are declared here with the pa_use(<SomeActivity>)
macro. Separate context elements need to be separated by a semicolon (;
).
To use delays within an activity, use pa_ctx_tm
instead of pa_ctx
, which holds an implicit time variable.
After the context, place the input and output parameters of the activity.
At the end of an activity, use pa_activity_end
or just pa_end
to close it off.
Within an activity you can place normal C control structures and the following synchronous statements.
For a detailed description of the statements, please currently refer to the Blech documentation or look at the proto_activities
test and example programs.
pa_pause
: will pause processing of an activity and resume it at the next tickpa_halt
: will pause the activity foreverpa_await (cond)
: will pause the activity and resume it oncecond
becomes truepa_await_immediate (cond)
: likepa_await
but will not pause ifcond
is true in the current tickpa_delay (ticks)
: will pause the activity for the given number of tickspa_delay_ms (ms)
: will pause the activity for the given number of millisecondspa_run (activity, ...)
: runs the given sub-activity until it returnspa_return
: end an activity from within its body - otherwise returns implicitly at the endpa_co(n)
: starts a concurrent section withn
trails - reserve the number of trails withpa_co_res(num_trails)
in the activities context - end section withpa_co_end
pa_with (activity, ...)
: runs the given activity concurrently with the others of this section - only applicable withinpa_co
pa_with_weak (activity, ...)
: runs the given activity concurrently with the others of this section and can be preempted - only applicable withinpa_co
pa_when_abort (cond, activity, ...)
: runs the given activity untilcond
becomes true in a subsequent tick - unless it ends beforepa_when_reset (cond, activity, ...)
: runs the given activity and restarts it whencond
becomes true in a subsequent tickpa_when_suspend (cond, activity, ...)
: will suspend the given activity whilecond
is true and lets it continue whencond
is false againpa_after_abort (ticks, activity, ...)
: will abort the given activity after the specified number of tickspa_after_ms_abort (ms, activity, ...)
: will abort the given activity after the specified time in millisecondspa_did_abort (activity)
: reports whether an activity was aborted in a call beforepa_always
: will run code on every tick - end block withpa_always_end
pa_every (cond)
: will run code everytimecond
is true - end block withpa_every_end
pa_every_ms (ms)
: will run code now and everyms
milliseconds thereafter - end block withpa_every_end
. Note: Do not use any other construct which uses timing (likepa_delay_ms
) in the enclosed blockpa_whenever (cond, activity, ...)
: will run the given activity whenevercond
is true and abort it ifcond
turns false
When compiling wit C++ you could also define the following lifecycle callbacks:
pa_defer
: defines an instantaneous block of code to run when the activity ends by itself or gets aborted. Addpa_defer_res
annotation to the context to enable this feature.pa_suspend
: defines an instantaneous block of code to run when an activity gets suspended by the surroundingpa_when_suspend
. Addpa_susres_res
annotation to the context to enable this feature.pa_resume
: defines an instantaneous block of code to run when an activity gets resumed by the surroundingpa_when_suspend
. Addpa_susres_res
annotation to the context to enable this feature.
- A medium article about proto_activities can be found here.
- Here is a little robot with
proto_activities
running on three ESP32 nodes. - See running proto_activities code in this online Wokwi simulator.
- Blech is a new programming language for the embedded domain which inspired
proto_activities
. - Pappe is a sibling project which uses an embedded DSL to allow Blech-style imperative synchronous programming in Swift.