EventResponder doesn't behave in yield() as I expected

Status
Not open for further replies.

shawn

Well-known member
I expected this code to toggle the LED 10 times a second:
Code:
#include <Arduino.h>
#include <EventResponder.h>

EventResponder event;
bool ledState = false;

void eventFn(EventResponderRef r) {
  ledState = !ledState;
  digitalWriteFast(LED_BUILTIN, ledState ? HIGH : LOW);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  event.attach(&eventFn);
}

void loop() {
  delay(100);
}

My understanding was that the attached function is supposed to run during `yield()`, and that `yield()` runs after each call to `loop()` — per what's in `main.cpp` of the core. What am I missing? (I'm using v1.54 via PlatformIO.)
 
To begin to understand please say what the observed behavior is. Any blinks? No blinks? Wrong blink rate?

Was there a sample to start with?
 
The observed behaviour is that the LED doesn't flash. I would expect the same behaviour as this code (without `EventResponder`):
Code:
void loop() {
  delay(100);
  digitalWriteFast(LED_BUILTIN, HIGH);
  delay(100);
  digitalWriteFast(LED_BUILTIN, LOW);
}
 
I never understood how event responder is supposed to work (didn't find any documentation on this feature). But I definitely know that yield is also called by delay while it spins. So, even if you eventFN would be called (which it probably isn't) it wouldn't blink with 10Hz.

You can have a look here https://github.com/luni64/TeensyHelpers which, besides other stuff, contains code to attach a function to yield without disturbing the rest of the yield chain.


Here an example using this method.

Code:
#include <Arduino.h>
#include <EventResponder.h>

using yieldFunc_t = void(*)();

void eventFn()
{
    static elapsedMillis stopwatch = 0;

    if(stopwatch > 50)
    {
      digitalToggleFast(LED_BUILTIN);
      stopwatch -= 50;
    }
}

void attachYieldFunc(yieldFunc_t _yieldFunc)   // pass a pointer to the function you want to be called from yield
{
    static yieldFunc_t yieldFunc = _yieldFunc; // 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
        yieldFunc();                           // 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
}

bool ledState = false;

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    attachYieldFunc(eventFn);
}

void loop()
{
    delay(1000);    // event responder works in the background, so delaying won't do any harm here
}
(EDIT: I was interested in calling the attached function as often as possible. There might be some way to tell the responder to call it at intervals as well...)


Of course, if you don't care about the other stuff called by yield you can simply override the standard yield (defined as weak) and do:
Code:
void yield()
{
    static elapsedMillis stopwatch = 0;

    if(stopwatch > 50)
    {
      digitalToggleFast(LED_BUILTIN);
      stopwatch -= 50;
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);    
}

void loop()
{
    delay(1000);    // yield is called in the background, so delaying won't do any harm 
}
 
Last edited:
If you simply want to call something at regular intervals but not from an interrupt context you can also use the TeensyTimerTool. In addition to hardware timers it provides software timers (TCK) which utilize the yield method shown above. The following code shows how to call a function every 100ms 'from yield()'.

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

void someCallback()
{
    digitalToggleFast(LED_BUILTIN);
}

PeriodicTimer timer(TCK);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    timer.begin(someCallback, 100ms);
}

void loop(){
}

Or, if you prefer terse code:

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

void setup(){
    pinMode(LED_BUILTIN, OUTPUT);
    (new PeriodicTimer(TCK))->begin([] { digitalToggleFast(LED_BUILTIN); }, 100ms);
}

void loop(){
}
 
@luni - interesting I didn't find installed examples or notes on PJRC.com with search. Did find some sketches from who knows when - tested to not work either. Forum search suggests some samples - one was long - not sure about others.
 
@luni - interesting I didn't find installed examples or notes on PJRC.com with search.
Yes, it's a pity. This event stuff can come in handy from time to time but without any (findable?) documentation it is hard to use.
 
I actually am interested in calling something as often as possible; I was just using the 10Hz LED change as an experiment to test my understanding. I completely forgot that `delay` calls yield. Logically, that means the LED never changes state for very long, because in my code, `yield()` is called twice. I tried this fix:
Code:
#include <Arduino.h>
#include <EventResponder.h>

EventResponder event;
bool ledState = false;
bool active = false;  // Because delay calls yield() too

void eventFn(EventResponderRef r) {
  if (!active) return;
  ledState = !ledState;
  digitalWriteFast(LED_BUILTIN, ledState ? HIGH : LOW);
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  event.attach(&eventFn);
}

void loop() {
  active = false;
  delay(100);
  active = true;
}

Since `yield()` is now called once per loop with `active==true`, I expeced the LED to flash. It still did not. I then tried adding `event.triggerEvent()` after `event.attach`, and also at the exit of `eventFn`. This seemed to work. I didn't know about needing to call `triggerEvent()`. Here's the working code:
Code:
#include <Arduino.h>
#include <EventResponder.h>

EventResponder event;
bool ledState = false;
bool active = false;  // Because delay calls yield() too

void eventFn(EventResponderRef r) {
  if (active) {
    ledState = !ledState;
    digitalWriteFast(LED_BUILTIN, ledState ? HIGH : LOW);
  }
  event.triggerEvent();
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  event.attach(&eventFn);
  event.triggerEvent();
}

void loop() {
  active = false;
  delay(100);
  active = true;
}

Thanks for the help!

What I'm doing: I've implemented an lwIP version of an Ethernet stack because NativeEthernet/FNET isn't quite working for me. I'm at the stage where I'm deciding how best to poll the stack, and I'm toying with calling it using `EventResponder` in `yield()`. (Yes, I plan on sharing. :))
 
Great you got it showing it is working!

Is this on a T_4.1? If not loaded it will run loop some Million times per second. Obviously the delay(100) will throttle that.

Seems this code doesn't want to run from an interrupt _isr() - but a timer interrupt could be set up to set a 'volatile flag' to pool - and when set call it from loop(), or just watch millis() or micros() directly or with an elapsed[Millis, Micros] variable.

Not having documentation makes it hard to use, I did check the TeensyUser wiki before my first post to see if there was anything ...
 
Status
Not open for further replies.
Back
Top