-
Notifications
You must be signed in to change notification settings - Fork 5
Hooking into yield
Sometimes your have code that needs to be called by the
user as often as possible. Prominent examples are AccelStepper
where you are required to call run()
at high speed. Using the debounce library, you need to call update()
. The PID library requires a frequent call to Compute()
and so on.
Usually, the user of these libraries just calls these functions in loop which works fine for simple code. As soon as there is longer running code or some delays in loop the call rate of these functions can get unacceptably low and the libraries don't work as expected.
Teensyduino provides a yield()
function which is called before each call to loop()
, during delay()
and probably a lot of other long running core functions while they spin.
Yield
is defined as weak function and can be overridden by user code. Here an example how to do this.
void yield() // override the built in yield function
{
digitalToggleFast(0);
}
void setup()
{
pinMode(0,OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
digitalToggleFast(LED_BUILTIN);
delay(250);
}
On a T4 this code generates the following pattern on pin 0 which shows that the overridden yield is called about 3 million times per second. The delay in loop does not influence this call frequency at all.
While this works nicely it overrides all the standard stuff which usually is done in the stock yield. In particular it handles the EnventResponders and the SerialEvent logic. As long as you don't need SerialEvents or the EventResponder you are fine with the simple approach shown above.
Instead of overriding yield you can as well add your own functions to be called from yield without disturbing the standard stuff. Just add this function somewhere in your code or in a separate file and call it from setup.
void attachYieldFunc(void (*_callback)(void)) // pass a pointer to the function you want to be called from yield
{
static void (*callback)() = _callback; // store the passed in function pointer
static EventResponder er; // define a new EventResponder which will handle the calls.
er.attach([](EventResponderRef r) { // we can not directly attach our function to the responder since we need to retrigger the repsonder
callback(); // instead we attach an inline defined relay function as lambda expression
r.triggerEvent(); // the relay calls our function and afterwards triggers the responder to schedule the next call.
});
er.triggerEvent(); // to start the call chain we need to trigger the responder once
}
Here the example from above with attached callback instead of overriden yield
#include "EventResponder.h"
void myCallback() //will be called from yield
{
digitalToggleFast(0);
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
pinMode(0,OUTPUT);
attachYieldFunc(myCallback); // attach our callback to the yield stack
}
void loop()
{
digitalToggleFast(LED_BUILTIN);
delay(250);
}
// helpers
void attachYieldFunc(void (*_callback)(void))
{
static EventResponder er;
static void (*callback)() = _callback;
er.attach([](EventResponderRef r) {
callback();
r.triggerEvent();
});
er.triggerEvent();
}
Especially if you do libraries this technique is much more friendly than overriding yield which might be used by the user of your library.
The disadvantage of Yield is that you have no control over it. Yield is called at many places in the core, even within interrupts. Therefore one should be very careful with additional code. It can slow down the rest of your code more than you want.
Teensy is a PJRC trademark. Notes here are for reference and will typically refer to the ARM variants unless noted.