Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 9 of 9

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

  1. #1
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    313

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

    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.)

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,081
    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?

  3. #3
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    313
    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);
    }

  4. #4
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,623
    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 by luni; 08-26-2021 at 06:24 AM.

  5. #5
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,623
    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(){
    }

  6. #6
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,081
    @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.

  7. #7
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,623
    Quote Originally Posted by defragster View Post
    @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.

  8. #8
    Senior Member
    Join Date
    Mar 2017
    Location
    Oakland, CA, USA
    Posts
    313
    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. )

  9. #9
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,081
    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 ...

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •