Need examples of how to use eventResponder properly

The "dumb" version is just this:
Code:
void yield(void)
{      
 [COLOR=#00ff00]        if (!_eventResponder_enabled) return;[/COLOR]

    static uint8_t running=0;

    if (running) return; // TODO: does this need to be atomic?
    running = 1;


    // USB Serail - Add hack to minimize impact...
    if (usb_enable_serial_event_processing && Serial.available() ) serialEvent();

    // Current workaround until integrate with EventResponder.
    if (HardwareSerial::serial_event_handlers_active) HardwareSerial::processSerialEvents();

    running = 0;
    EventResponder::runFromYield();
    
};
A simpler version of "smart" could look like this:
Code:
void yield(void)
{  
     static uint8_t running=0;    
     uint32_t e = [COLOR=#00ff00]_eventResponder_flags[/COLOR]
 [COLOR=#00ff00]    if ( ([/COLOR][COLOR=#00ff00][COLOR=#00ff00]e & 1[/COLOR] == 0) || running) return;[/COLOR]    
    
    if ([COLOR=#00ff00][COLOR=#00ff00]e > 1) {[/COLOR][/COLOR]
        running = 1;   

        // USB Serail - Add hack to minimize impact...
        if ([COLOR=#00ff00][COLOR=#00ff00]([/COLOR][/COLOR][COLOR=#00ff00][COLOR=#00ff00]e & 0x02[/COLOR][/COLOR] ) ) serialEvent();

        // Current workaround until integrate with EventResponder.
        if ([COLOR=#00ff00][COLOR=#00ff00]([/COLOR][/COLOR][COLOR=#00ff00][COLOR=#00ff00]e & 0x04[/COLOR][/COLOR]) ) HardwareSerial::processSerialEvents();

    [COLOR=#00ff00]}[/COLOR]
    running = 0;
    EventResponder::runFromYield();
};

I personally would be very happy with the first version, because I don't use "SerialEVents".
The additions in the 2nd Version are just for the few who use it. It needs some more tweaks in the core to toggle the bits.
You see - it is faster in any case.

Both would switch to a faster Systick version, if it is disabled, too.
 
Last edited:
Frank, I guess I am missing something with your "dumb" version, that is how is: _eventResponder_enabled
This set?

If you say that either a program sets it or an API sets it... Then now sure if any easier than simply telling those sketches to do:
Code:
void yield() {}

If it is sort of computed in the way that we are doing but is a composite value such that you only check maybe one variable instead of 3... Then yes easily doable. Still with all of the caveat, that it will stay active for USB and every Serial port you do a begin on until something causes their event function (and it is our default weak one) to be called, which removes themself.

Again I am perfectly happy to help out. Now if we really wanted to remove the yield overhead, one could also go to bypass most/all of the yield calls.

That is suppose we rename your: _eventResponder_enabled to _yield_enabled which maybe API to set value.

Then change many/all places the call yield like:
delay.c
Code:
void delay(uint32_t msec)
{
	uint32_t start;

	if (msec == 0) return;
	start = micros();
	while (1) {
		while ((micros() - start) >= 1000) {
			if (--msec == 0) return;
			start += 1000;
		}
		[COLOR="#FF0000"]if(_yield_enabled)[/COLOR] yield();
	}
	// TODO...
}
main.cpp
Code:
{
extern "C" int main(void)
#ifdef USING_MAKEFILE

	// To use Teensy 4.0 without Arduino, simply put your code here.
	// For example:

	pinMode(13, OUTPUT);
	while (1) {
		digitalWriteFast(13, HIGH);
		delay(500);
		digitalWriteFast(13, LOW);
		delay(500);
	}


#else
	// Arduino's main() function just calls setup() and loop()....
	setup();
	while (1) {
		loop();
		[COLOR="#FF0000"]if (_yield_enabled)[/COLOR] yield();
	}
#endif
}

From Stream.cpp
Code:
int Stream::timedRead()
{
  int c;
  unsigned long startMillis = millis();
  do {
    c = read();
    if (c >= 0) return c;
    [COLOR="#FF0000"]if(_yield_enabled)[/COLOR]yield();
  } while(millis() - startMillis < _timeout);
  return -1;     // -1 indicates timeout
}

// private method to peek stream with timeout
int Stream::timedPeek()
{
  int c;
  unsigned long startMillis = millis();
  do {
    c = peek();
    if (c >= 0) return c;
    [COLOR="#FF0000"]if(_yield_enabled)[/COLOR]yield();
  } while(millis() - startMillis < _timeout);
  return -1;     // -1 indicates timeout
}
I believe there are a few others scattered around, but I think this would hit the majority of them...
 
EDIT of previous post, we could also maybe make where (or if) yield is called.
Example we could have an api like: enableYieldOn(uint8_t yield_flags);
And have flags like: YIELD_ON_LOOP, YIELD_ON_DELAY, YIELD_ON_STREAM, ...
probably defaults to all. But one could make their sketch more compatible by setting on only ON_LOOP...


Also I think I see that me may have some code missing from yield ;)

Code:
#if defined(CDC2_STATUS_INTERFACE) && defined(CDC2_DATA_INTERFACE)
if (SerialUSB1.available()) serialEventUSB1();
#endif
#if defined(CDC3_STATUS_INTERFACE) && defined(CDC3_DATA_INTERFACE)
if (SerialUSB2.available()) serialEventUSB2();
#endif
And default implementations for these... Suppose we should add them(They are actually defined in usb_serial.h) :D :p
 
Disable: #Post 14

User calls a disable function

(or enabled if he want to switch it on again)

No it's NOT easier to add a emtpy yield to the skecth.
You have to replace the Systick, too.

Of course this can this do the user too .. but then we are at the point where we are now.

Great, if you want to replace all the calls - perfect. Even better. I hope Paul will merge it. Plaese add a efficient way for the systick, too.

My version would look like this:
Code:
extern "C" void systick_isr(void)  // ORIGINAL
{
    systick_cycle_count = ARM_DWT_CYCCNT;
    systick_millis_count++;
    MillisTimer::runFromTimer();
}

extern "C" void systick_isr_noEventResponder(void)
{
    systick_cycle_count = ARM_DWT_CYCCNT;
    systick_millis_count++;
}

uint8_t _eventResponder_enabled = true;
void eventResponder_enable(void)
{    
    _VectorsRam[15] = systick_isr;
    _eventResponder_enabled = true;
}
void eventResponder_disable(void) {
    _VectorsRam[15] = systick_isr_noEventResponder;
    _eventResponder_enabled = false;
}
(+ the new yield code - yours or mine)
 
@Frank - I am starting off with simplistic changes to see how far they get toward desired goal without requiring code to be changed to make use of it.
I believe that makes it easier to get PR pulled in.

That is instead of adding a new member which disables he eventResponder, I instead try to localize down the effects of it.
So I did take your two version of the ISR and renamed them. The default on does not call the runFromTimer.

Instead I only run the runFromTimer version if something in the sketch calls some thing like: myEventResponder.attachInterrupt(&myFunction);
Which my guess will be about 99.98% of the sketches will not enable that code.

As for Yield - I just made the yield function to be a friend of the eventResponder class and so it can check to see if there is anything waiting on the queue before calling off.
So it is still testing maybe 4 things and then return...

Will next look to see about reducing these tests slightly, but again not sure how big a win that will be. Will hack it slightly and see what you think.

Pass one is up on https://github.com/KurtE/cores/tree/eventResponder_reduce_overhead

Update: I know have the different parts all set one flag variable, such that it can try just one test at the start and if 0 returns.

Currently that variable is defined in core_pins.h ...

Let me know what you think.
 
Last edited:
Looks good to me!
Have you tested this?

I have done some rudimentary tests, but have not done anything yet with eventResponder.

I will hack up one of the SPI test programs, to try out eventResponder using 3 different ways to be called. (normally I just use immediate)
 
Here is a quick and dirty test to see if/when things change:
Code:
#include <SPI.h>
#include <EventResponder.h>
#define CS_PIN 10
volatile bool event_happened = false;

EventResponder event;
static const uint8_t buffer[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

void asyncEventResponder(EventResponderRef event_responder)
{
  digitalWriteFast(CS_PIN, HIGH);
  event_happened = true;
  //Serial.println("Event happened");
}

void setup() {
  pinMode(CS_PIN, OUTPUT);
  digitalWriteFast(CS_PIN, HIGH);
  while (!Serial && millis() < 4000) ;  // wait for Serial port
  Serial.begin(115200);
  SPI.begin();
  Serial.println("SPI Test program");
}

void TimeYieldCalls(const char *sz) {
  yield();
  Serial.print(sz); Serial.flush();
  elapsedMicros em = 0;
  for (uint32_t i = 0; i < 1000; i++) yield();
  uint32_t elapsed = em;
  Serial.print(": ");
  Serial.println(elapsed, DEC);
  Serial.flush();
}

void loop() {
  while (Serial.read() != -1) ; // Make sure queue is empty.
  Serial.println("Press any key to run test");
  while (!Serial.available()) ; // will loop until it receives something
  while (Serial.read() != -1) ; // loop until queue is empty
  Serial.printf("start test yield_active_check_flags %x\n", yield_active_check_flags);
  Serial.printf("  systick ISR: %x\n", (uint32_t) _VectorsRam[15]);
  TimeYieldCalls("Start");

  // First try with immediate call.
  event.attachImmediate(&asyncEventResponder);
  Serial.printf("Test Immediate: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
  event.clearEvent();
  digitalWriteFast(CS_PIN, LOW);
  SPI.transfer(buffer, NULL, sizeof(buffer), event);
  while (!event_happened) ;
  TimeYieldCalls("After Immediate");

  // Use yield .
  event.detach();
  event.attach(&asyncEventResponder);
  Serial.printf("Test Immediate: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
  event.clearEvent();
  digitalWriteFast(CS_PIN, LOW);
  SPI.transfer(buffer, NULL, sizeof(buffer), event);
  while (!event_happened) ;
  TimeYieldCalls("After yield");

  // Use Interrupt .
  event.detach();
  event.attachInterrupt(&asyncEventResponder);
  Serial.printf("Test Interrupt: %x %x\n", yield_active_check_flags, (uint32_t) _VectorsRam[15]);
  event.clearEvent();
  digitalWriteFast(CS_PIN, LOW);
  SPI.transfer(buffer, NULL, sizeof(buffer), event);
  while (!event_happened) ;
  TimeYieldCalls("After Interrupt");
}

And test output...
Code:
SPI Test program

Press any key to run test

start test yield_active_check_flags 1
  systick ISR: 211d
Start: 57

Test Immediate: 1 211d
After Immediate: 57

Test Immediate: 5 211d
After yield: 64

Test Interrupt: 5 213d
After Interrupt: 63

Press any key to run test

I may go ahead and do PR on it.
 
@KurtE, @Frank B - I have spent today understanding how EventResponder works using SPI.* and @KurtE test sketch. I think I have a fairly good understanding of how I can use it with MSC. In one version of MSC I have a three stage transfer() function now that I can implement the EventResponder in. Maybe using that with queuing a fifo'd array of transfers. Still trying to minimize MSC transfer complete polling.

Anyway thanks guys:)
 
Unfortunately. as seems so often to be the case, there is no example (that I'm aware of). Documentation comes a poor last to (a) doing essential stuff, (b) coming up with some interesting concepts and implementing prototypes, and (c) merging the most deeply urgent Pull Requests. Minor bugfix or improvement PRs come nowhere. Documentation is behind all of those.

You'll just have to look at the comments in EventResponder.h and try stuff out. Essentially it's
  • create an EventResponderFunction erFn()
  • create an EventResponder object erObj
  • attach() the function to the object
  • call erObj.triggerEvent(), then ...
  • ...erFn() will get called on the next yield(). Maybe.
yield() gets called when loop() exits, or your sketch calls delay() or yield(). Despite the documentation implying otherwise, one yield() call results in exactly one EventResponderFunction being called, not all the pending ones that might have been triggered. Do not trigger an event before you have attached its EventResponderFunction, there's no check made and your program will crash.

Pity, because it could be a very powerful tool...
 
Reading above Priority came to mind - and scanning the linked thread : could be awesome or could be a time sink.

Could there be a 'priority' flag of some sort?
> Pri 1 - run this every time ... down to run in turn every '# event' turns (as it is now)
> or track us's and run no less / no more than as often as that # of us's has elapsed?

yield can be called 100K or 10M times per second - calling 10M with a big workload list quickly drops to 1M/sec and no work gets done in loop() and perhaps nothing to do in the events either if called too often.
 
Okay thanks for the feedback. Time for me to give back to the community, here the example I created. (seems to work as expected)
Please feel free to comment, especially in case I used EventResponder in a wrong way (or unintended way).

C++:
/*
 * Teensy's EventResponder example/demonstrator:
 *   based on timer_blink_print from arduino-timer library.
 *
 * > Blinks the built-in LED every half second, and prints a messages every
 * > second using the arduino-timer library.
 *
 * This is a demonstrator, so update the loop function to experience the behavior with delay vs a dummy while
 * case #1: delay: it is going to work correctly:
 *      - every 500ms toggle LED
 *      - every 1 sec print message
 *   since delay uses the 'yield' function during it is waiting.
 * case #2: dummy while: it does not work as one want (but it is expected like this; this is not a bug).
 *   In this case there is no yield function called.
 *   The Arduino 'main' function calls loop-function, and it calls 'yield' function before it calls again the loop function.
 *   Meaning there is a yield function called every 10 seconds.
 *   And that is what you can observe in this case #2.
 *
 */
#include <arduino-timer.h>
#include <EventResponder.h>
auto timer = timer_create_default(); // create a timer with default settings
EventResponder event;

bool
toggle_led(void *)
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED
  Serial.printf("toggle_led\n");
  //delay(1000);
  return true; // repeat? true
}

bool
print_message(void *)
{
  Serial.print("print_message: Called at: ");
  Serial.println(millis());
  delay(100);
  return true; // repeat? true
}

void
timer_responder(EventResponderRef event_responder)
{
  // do the thing!
  timer.tick();
  // and yes, make a new trigger for next yield-call.
  event.triggerEvent();
}

void
setup()
{
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT
  // add the function to the event, and trigger the event.
  event.attach(&timer_responder);
  event.triggerEvent();
  // call the toggle_led function every 500 millis (half second)
  timer.every(500, toggle_led);
  // call the print_message function every 1000 millis (1 second)
  timer.every(1000, print_message);
}

void
loop()
{
  // case #1: delay
  delay(10000);
  // case #2: dummy while
  // unsigned long stop, start = millis();
  // while (stop = millis(), stop-start < 10000) {}
}
 

Attachments

  • teensy_event_responder.ino
    2.2 KB · Views: 12
Reading above Priority came to mind - and scanning the linked thread : could be awesome or could be a time sink.

Could there be a 'priority' flag of some sort?
> Pri 1 - run this every time ... down to run in turn every '# event' turns (as it is now)
> or track us's and run no less / no more than as often as that # of us's has elapsed?

yield can be called 100K or 10M times per second - calling 10M with a big workload list quickly drops to 1M/sec and no work gets done in loop() and perhaps nothing to do in the events either if called too often.
Sure, all sorts of priority and time allowance mechanisms could be added. I don’t need them so I’m not taking the time to add them.

I don’t believe “it might go wrong, or be misused” is a valid reason for not adopting a backwards compatible update to a utility that’s rarely made use of. People do it all the time now, with the functionality they already have.

EventResponder adds a tiny overhead to any yield() that has no event triggered. So 10M/s might become 9.9M/s for an empty loop with no triggers - can’t test right now. I can’t see that as a huge issue.

If there are a lot of triggers then presumably there’s work to be done. Adding the thin veneer of EventResponder doesn’t materially change the amount done. It can conceal when it’s done, true. No-one should be relying on how fast loop() gets re-entered, of course. If EventResponder is in your program then you’re in control; if it’s used in a library then the documentation (that word again) should mention it and the possible consequences. The responsible library writer will try to keep the responder’s work spread out as much as possible.
 
Okay thanks for the feedback. Time for me to give back to the community, here the example I created. (seems to work as expected)
Please feel free to comment, especially in case I used EventResponder in a wrong way (or unintended way).
I’ll take a look in a bit - I think I can see some minor changes that may improve it slightly, but it’s an excellent start!
 
@karelv, here's a modified version of your demo code:
C++:
/*
 * Teensy's EventResponder example/demonstrator:
 *   using IntervalTimer objects to do work and trigger event responders.
 *
 * > Blinks the built-in LED every half second, and prints a messages every
 * > second using the arduino-timer library.
 *
 * This is a demonstrator, so update the loop function to experience the behavior with delay vs a dummy while
 * case #1: delay: it is going to work correctly:
 *      - every 500ms toggle LED
 *      - every 1 sec print message
 *   since delay uses the 'yield' function while it is waiting.
 * case #2: dummy while: it does not work as one want (but it is expected like this; this is not a bug).
 *   In this case there is no yield function called.
 *   The Arduino 'main' function calls loop-function, and it calls 'yield' function before it calls again the loop function.
 *   Meaning there is a yield function called every 10 milliseconds.
 *   And that is what you can observe in this case #2.
* case #3: dummy while with extra yield() call
 *   Improves response time with multiple events triggered
 *
 */

#include <EventResponder.h>

IntervalTimer timer500;
IntervalTimer timer1000;
EventResponder event500, event1000;

uint32_t isInISR(void) { return (SCB_ICSR & 0x1FF);} // 0 if in thread; vector number if in ISR

void printWhere(void)
{
  uint32_t vec = isInISR();

  if (0 == vec)
    Serial.print("Foreground: ");
  else
    Serial.printf("Vector %d: ", vec);   
}

//=============================================================================
// IntervalTimer callbacks: these are within Interrupt Service Routines,
// so MUST not delay, and really shouldn't print() as it could block. But
// this is just a demo, so we'll allow it.
volatile bool printLoop;

void
toggle_led(void)
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED
  printWhere();
  Serial.printf("toggle_led: called at: %d\n",millis());
  event500.triggerEvent(500,(void*) "toggle");
  printLoop = true;
}

void
print_message(void)
{
  printWhere();
  Serial.print("print_message: called at: ");
  Serial.println(millis());
  event1000.triggerEvent(1000,(void*) "print");
  printLoop = true;
}


//=============================================================================
// EventResponder callback: called from yield(), so any normal calls are safe,
// although a long-running function could cause issues.
void
event_response(EventResponderRef event_responder)
{
  int status = event_responder.getStatus();
  char* data = (char*) event_responder.getData();
  // do the thing!
  printWhere();
  Serial.printf("Event at %d from timer%d: data=%s\n", millis(), status, data);
}


//=============================================================================
void
setup()
{
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT); // set LED pin to OUTPUT

  // add the function to the events
  event500.attach(&event_response);
  event1000.attach(&event_response);

  // call the toggle_led function every 500 millis (half second)
  timer500.begin(toggle_led, 500'000);

  //delay(100); // uncomment to stop IntervalTimer callbacks overlapping
  // call the print_message function every 1000 millis (1 second)
  timer1000.begin(print_message, 1000'000);
}


//=============================================================================
int demoCase = 2; // select from various options in loop() below

void
loop()
{
  Serial.print('.'); // one dot per actual loop iteration

  switch (demoCase)
  {
    // case #1: delay
    // This demonstrates that even if loop() is not allowed to return
    // for a long time, so long as there are frequent calls to yield()
    // (which delay() has built-in), event responses occur in a
    // timely manner.
    
    case 1:
      delay(3000);
      break;
 
    // case #2: dummy while
    // This is the "interesting" case, as only one event response will be
    // processed per loop(), and we've set up the IntervalTimer
    // callbacks to run simultaneously. The "losing" response gets
    // delayed by an extra 10ms.
    //
    // The while() loop simulates e.g. a slow-ish SPI display update
    
    case 2:
    {
      unsigned long stop, start = millis();
      while (stop = millis(), stop-start < 10)
        ;
    }
      break;
      
    // case #3: dummy while with extra yield
    // Allows prompt event response during long operations,
    // BUT requires attention from the sketch or library
    // writer, so not ideal.

    case 3:
    {
      unsigned long stop, start = millis();
      for (int i=0;i<2;i++)
      {
        while (stop = millis(), stop-start < 5)
          ;
        if (0 == i) // just one extra yield
          yield();
        start = millis();     
      }
    }
      break;
  }
  //-------------------------------------------------------------
  // Print "Loop" at exit, if an IntervalTimer callback
  // has occurred recently. Shorter IntervalTimer is set
  // to 500ms, so this should print that often.
  if (printLoop)
  {
    Serial.printf("\nLoop exit at %d\n",millis());
    printLoop = false;
  }
}
  • I've used IntervalTimer because it's built in to Teensyduino, and the callbacks are from inside the Interrupt Service Routine, so ideal candidates for using EventResponder where your response code isn't interrupt-safe (e.g. SD card access)
  • Case #2 shows how slow non-yielding code can delay event responses
  • I added a case #3 where an extra yield() is put in to improve event response time. Kind of fine for code you write yourself, but not so good if a library has slow code with no yield calls built-in
  • Shows passing data from trigger to response
 
Back
Top