Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 36

Thread: Need examples of how to use eventResponder properly

  1. #1
    Senior Member
    Join Date
    Aug 2017
    Posts
    261

    Need examples of how to use eventResponder properly

    I have been working with MSC trying to queue or FIFO data reads and writes. I finally have a simple circular FIFO system setup that can FIFO data reads and writes. But I need a non-blocking way to process the FIFO's in the background. I am wondering if using the eventResponder would be the best way to go. I have seen this in other programs but after two days working on this my brain is fried and I can't remember where I saw it used Just need some direction and reference.

    Thanks

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    Haven't seen much activity with EventResponder.

    The USBHost Ethernet dongle was running with TeensyThreads to keep a running update.

    Latest move to NativeEthernet does it without that. But TThreads gives a regular time slice can exit early to minimize time away when not needed.

  3. #3
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    And IIRC eventResponder triggers from yield() so it can act in loop() or non_isr() or delay() context on a regular basis

  4. #4
    Senior Member
    Join Date
    Aug 2017
    Posts
    261
    Quote Originally Posted by defragster View Post
    And IIRC eventResponder triggers from yield() so it can act in loop() or non_isr() or delay() context on a regular basis
    Thanks - I think that may have been where I have seen it. In any event (excuse the PUN) I just want to trigger transfers in the background until all transfers are complete up to FIFO size.

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    Yes, yield() triggers serialEvent() processing and that is where eventResponder is last I saw it. Will feed those event()'s every loop() pass and yield call { which is embedded in delay() as well }

    Long loop() time can thwart - and calling it 100K to 5 or more million times for each loop() can render it on a scale of { useless ... perfect ... overdone } depending on the sketch. But it doesn't have interrupt baggage either.

  6. #6
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    The eventResponder code is something I know @Paul wants to some day go through and flesh out more.

    The one main place it was integrated into earlier on was the Async SPI transfers.

    Code:
    	bool transfer(const void *txBuffer, void *rxBuffer, size_t count,  EventResponderRef  event_responder);
    I am pretty sure I have posted some of my test sketches for this earlier. example:
    Code:
    #include <SPI.h>
    #include <EventResponder.h>
    #define SPIT SPI2
    #define DBGSerial Serial4
    #define CS_PIN 10
    #define SMALL_TRANSFER_SIZE 128
    //#define BUFFER_SIZE 70000 //(320*240*2) // size of ILI9341 display...
    #define BUFFER_SIZE 0x12000l   // More than 64K
    //uint8_t buffer[BUFFER_SIZE];
    uint8_t *buffer;  // lets malloc it...
    //uint8_t rxBuffer[SMALL_TRANSFER_SIZE];
    DMAMEM uint8_t rxBuffer[BUFFER_SIZE];
    //uint8_t *rxBuffer;
    uint8_t *foo_bar = nullptr;
    uint8_t static_buffer[16];
    
    #define SERIAL_tt Serial1
    // Trigger spare interrupts
    IntervalTimer ITtest;
    volatile uint32_t kk, jj = 0;
    uint32_t tt = 0;
    void TimeSome() {
      jj++;
      kk = micros();
      if ( !(jj % 10000) )   SERIAL_tt.print("*");
      if ( !(jj % 130000) )   SERIAL_tt.print("!\n");
    }
    #define CHANGE_SPEED // define to cycle the CPU speed
    extern "C" uint32_t set_arm_clock(uint32_t frequency);
    #define MAX_SPEED 800000000
    #define MIN_SPEED 96000000
    uint32_t ArmSpeed = 6 * 100000000;
    
    EventResponder event;
    volatile bool event_happened = false;
    void asyncEventResponder(EventResponderRef event_responder)
    {
      digitalWriteFast(CS_PIN, HIGH);
      event_happened = true;
    }
    
    void setup() {
      // debug pins
      uint8_t stack_buffer[10];
      pinMode(0, OUTPUT);
      pinMode(1, OUTPUT);
      digitalWrite(0, LOW);
      digitalWrite(1, LOW);
      extern unsigned long _heap_start;
      extern unsigned long _heap_end;
    
      pinMode(CS_PIN, OUTPUT);
      digitalWriteFast(CS_PIN, HIGH);
      while (!Serial && millis() < 4000) ;  // wait for Serial port
      DBGSerial.begin(115200);
      SPIT.begin();
      DBGSerial.println("SPI Test program");
    
      buffer = (uint8_t *)malloc(BUFFER_SIZE);
      //rxBuffer = (uint8_t *)malloc(BUFFER_SIZE);
    
      SERIAL_tt.begin( 115200 );
      SERIAL_tt.println("\n********\n T4 connected Serial1 *******\n");
    
      DBGSerial.print("Buffer: ");
      DBGSerial.print((uint32_t)buffer, HEX);
      DBGSerial.print(" RX Buffer: ");
      DBGSerial.print((uint32_t)rxBuffer, HEX);
      DBGSerial.print(" ");
      DBGSerial.println(BUFFER_SIZE, DEC);
      DBGSerial.printf("Static buffer: %x, Stack Buffer: %x\n", (uint32_t)static_buffer, (uint32_t)stack_buffer);
      DBGSerial.printf("Heap Start: %x, Heap End: %x\n", (uint32_t)&_heap_start, (uint32_t)&_heap_end);
      event.attachImmediate(&asyncEventResponder);
      ITtest.begin( TimeSome, 2);
    }
    int nn = 0;
    void loop() {
      // put your main code here, to run repeatedly:
      while (DBGSerial.read() != -1) ; // Make sure queue is empty.
      DBGSerial.println("Press any key to run test");
      //while (!DBGSerial.available()) ; // will loop until it receives something
      while (DBGSerial.read() != -1) ; // loop until queue is empty
      DBGSerial.println("Ready to start tests");
      DBGSerial.print("IntvTimer jj=");
      DBGSerial.print( jj );
      DBGSerial.print("\tIntvTimer jj=");
      DBGSerial.print( jj );
      DBGSerial.print("\tms Time=");
      DBGSerial.println( millis() - tt );
      DBGSerial.print("\tms Time=");
      DBGSerial.print( millis() - tt );
      tt = millis();
      jj = 0;
      DBGSerial.printf( "    deg C=%2.2f\n" , tempmonGetTemp() );
      delay( 500 );
    #ifdef CHANGE_SPEED
      if ( !(nn % 4) ) {      // Change Clock Speed
        ArmSpeed -= 100000000;
        if ( ArmSpeed < MIN_SPEED ) ArmSpeed = MAX_SPEED;
        set_arm_clock( ArmSpeed );
        if ( F_CPU_ACTUAL < MIN_SPEED ) ArmSpeed = MAX_SPEED;
        set_arm_clock( ArmSpeed );
        DBGSerial.printf("\t>>> Clock Speed is:%u", F_CPU_ACTUAL);
      }
    #endif
      delay( 500 );
      nn++;
    
    
      SPIT.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
      DBGSerial.println("After Begin Transaction");
    
      //=================================================================
      // Transfer Sync
      //=================================================================
    
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
      DBGSerial.println("Transfer Small"); //DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, rxBuffer, SMALL_TRANSFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      DBGSerial.println("*** Completed ***"); DBGSerial.flush();
      dumpBuffer(buffer, SMALL_TRANSFER_SIZE);
      DBGSerial.println();
      dumpBuffer(rxBuffer, SMALL_TRANSFER_SIZE);
      validateTXBuffer(0);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      DBGSerial.println("write Small"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, NULL, SMALL_TRANSFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      DBGSerial.println("*** Completed ***"); DBGSerial.flush();
      validateTXBuffer(0);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      DBGSerial.println("read Small"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(NULL, rxBuffer, SMALL_TRANSFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      DBGSerial.println("*** Completed ***"); DBGSerial.flush();
      dumpBuffer(rxBuffer, SMALL_TRANSFER_SIZE);
      delay(5);
    
      SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)buffer[i] = i / 1024;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
    
      DBGSerial.println("Transfer Full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, rxBuffer, BUFFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      validateTXBuffer(1);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i / 1024;
      DBGSerial.println("write full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, NULL, BUFFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      validateTXBuffer(1);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      DBGSerial.println("read full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(NULL, buffer, BUFFER_SIZE);
      digitalWriteFast(CS_PIN, HIGH);
      delay(5);
      //=================================================================
      // Transfer Async
      //=================================================================
      for (uint32_t i = 0; i < 5; i++) {
        digitalWriteFast(CS_PIN, LOW);
        delay(1);
        digitalWriteFast(CS_PIN, HIGH);
        delay(1);
      }
      event_happened = false;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      DBGSerial.println("Async write Small"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, NULL, SMALL_TRANSFER_SIZE, event);
      DBGSerial.println("After write call, waiting for event");
      while (!event_happened) ;
      event_happened = false;
      validateTXBuffer(0);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
      DBGSerial.println("Async Transfer Small"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, rxBuffer, SMALL_TRANSFER_SIZE, event);
      DBGSerial.println("After Transfer call, waiting for event");
      while (!event_happened) ;
      event_happened = false;
      dumpBuffer(buffer, SMALL_TRANSFER_SIZE);
      DBGSerial.println();
      dumpBuffer(rxBuffer, SMALL_TRANSFER_SIZE);
      validateTXBuffer(0);
      delay(5);
    
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
      DBGSerial.println("Async read Small"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.setTransferWriteFill(0x42);
      SPIT.transfer(NULL, rxBuffer, SMALL_TRANSFER_SIZE, event);
      //arm_dcache_delete(rxBuffer, SMALL_TRANSFER_SIZE);
      while (!event_happened) ;
      event_happened = false;
      dumpBuffer(rxBuffer, SMALL_TRANSFER_SIZE);
      validateTXBuffer(0);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i / 1024;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
      DBGSerial.println("Async Transfer Full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, rxBuffer, BUFFER_SIZE, event);
      while (!event_happened) ;
      event_happened = false;
      dumpBuffer(rxBuffer, 512);
      validateTXBuffer(1);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i / 1024;
      DBGSerial.println("Async write full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(buffer, NULL, BUFFER_SIZE, event);
      while (!event_happened) ;
      event_happened = false;
      validateTXBuffer(1);
      delay(5);
    
      for (uint32_t i = 0; i < BUFFER_SIZE; i++) buffer[i] = i & 0xff;
      for (uint32_t i = 0; i < BUFFER_SIZE; i++)  rxBuffer[i] = 0x5a;
      DBGSerial.println("Async read full"); DBGSerial.flush();
      digitalWriteFast(CS_PIN, LOW);
      SPIT.transfer(NULL, rxBuffer, BUFFER_SIZE, event);
      while (!event_happened) ;
      event_happened = false;
      dumpBuffer(rxBuffer, 512);
      validateTXBuffer(0);
      delay(5);
    
    
      DBGSerial.println("Tests completed");
      SPIT.endTransaction();
    }
    
    void dumpBuffer(uint8_t *pb, int cb) {
      uint8_t i = 0;
      while (cb) {
        DBGSerial.print(*pb++, HEX);
        cb--;
        DBGSerial.print(" ");
        i++;
        if (i == 16) {
          DBGSerial.println();
          i = 0;
        }
      }
      DBGSerial.println();
    }
    void validateTXBuffer(uint8_t test)
    {
      uint8_t error_count = 0;
      for (int i = 0; i < BUFFER_SIZE; i++) {
        if (((test == 0) && (buffer[i] != (i & 0xff)))
            || ((test == 1) && (buffer[i] != (i / 1024)))) {
          DBGSerial.print("Tx Buffer validate failed Index: ");
          DBGSerial.print(i, DEC);
          DBGSerial.print(" Value: ");
          DBGSerial.println(buffer[i], HEX);
          error_count++;
          DBGSerial.print("Tx Buffer validate failed Index: ");
          DBGSerial.print(i, DEC);
          if (error_count == 10)
            break;
        }
      }
    }
    In most cases here I tell the event object to call immediate... But there are other options.
    event.attachImmediate(&asyncEventResponder);


    The one mentioned is from yield, which also has an optional priority parameter which allows you to control the order they are called.
    event.attach(&asyncEventResponder);

    There is also an ability to setup to be called by interrupt...

    Also the ability to not be called, but you can manually clear the event and then call to get the status to see if a new event has happened.

  7. #7
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    In the current version it is not good for interrupt latency. That really needs a fix - esp the millitimer and some other things that run...
    Often an isssue here in the forum, when people ask why loop() is slow or why interrupts take longer time than expected to be called.
    Most issues could problably fixed..

    Easiest way would be #ifdef eventsresponder_enabled .. if ..we had a way to set and store such settings in the "IDE". or even better to set that in the sketch.

  8. #8
    Senior Member vjmuzik's Avatar
    Join Date
    Apr 2017
    Posts
    525
    Quote Originally Posted by defragster View Post
    Haven't seen much activity with EventResponder.

    The USBHost Ethernet dongle was running with TeensyThreads to keep a running update.

    Latest move to NativeEthernet does it without that. But TThreads gives a regular time slice can exit early to minimize time away when not needed.
    It's true I didn't use a separate thread in NativeEthernet like before, but I did use an IntervalTimer every millisecond to update it instead. I remember hearing about the EventResponder, but I never looked into it myself, I don't know if this would be preferable since it would run in yield instead of interrupting the sketch.

  9. #9
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    Quote Originally Posted by Frank B View Post
    In the current version it is not good for interrupt latency. That really needs a fix - esp the millitimer and some other things that run...
    Often an isssue here in the forum, when people ask why loop() is slow or why interrupts take longer time than expected to be called.
    Most issues could problably fixed..

    Easiest way would be #ifdef eventsresponder_enabled .. if ..we had a way to set and store such settings in the "IDE". or even better to set that in the sketch.
    I am not disagreeing here. As I also believe per sketch settings can be very useful...

    But assuming that is not in the immediate future cards... What can we do to help minimize this.

    I know in the Teensy4.x case, when I was working on the SerialEvent stuff, with having up to 8 serial ports, I did not want to make 8 calls every... time....

    So there are some hacks in place. Right now part of HardwareSerial (again T4.x) there is a bitmask of those possible Serial objects that the sketch has called begin on.
    And only those will be checked by calling their Serial.available()... Also then the default weak code for SerialEventX is setup to say disable calling me, which removes it's bit from the list to be checked.
    So hopefully if you don't use the SerialEventX functions this code will be fully disabled. Unfortunately it only does this after it first sees a character, as I don't know a way to say (Are you the default one?) and I don't want to call the event code unless it has something, as I am not sure what some random sketch gets called and it fails to retrieve something...

    The other benefit of the hacks is if your code does not use a Serial object, none of the buffers and code is brought in for that Serial object.

    I don't think I have yet setup to bypass the main USB Serial event code yet, as that code was not working when I did the other stuff.

    The SerialEvent code in yield simply checks of a pointer is NULL, if it is it returns.

    As for the millis interrupt. I wonder how much overhead does it have when there are no events? Maybe it just looks and sees a NULL pointer and exits...

    At some point might be interesting again to see how much overhead there is today with the T4.x with the return from the loop function...

  10. #10
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    The main problem are the disabled interrupts, not the other code.
    Even the systick (do I remember correctly?) disables them. 1000 times per second.
    Yield disables them, too(do I remember correctly?). This may add some other 1000 times per second.
    Not to mention the USB Code (But it is needed there, so at least for a first pass, we should leave it as it is and maybe later switch this, with caution, from a global interrupt-disable to the more specific USB-int disable.)

    If we fix these details, it will be much better!
    (And we should optimize that for the T3.x too)

    In a perfect scenario, yeld() would check a single flag only - one IF - not more - at least in the loop() code. We can have different yields().

    ...but I will not invest a single second on that until I know the needed change have a chance to be merged
    Last edited by Frank B; 05-18-2020 at 04:32 PM.

  11. #11
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    Quote Originally Posted by Frank B View Post
    The main problem are the disabled interrupts, not the other code.
    Even the systick (do I remember correctly?) disables them. 1000 times per second.
    Yield disables them, too(do I remember correctly?). This may add some other 1000 times per second.
    Are we talking about this code:
    Code:
    void MillisTimer::runFromTimer()
    {
    	MillisTimer *timer = listActive;
    	while (timer) {
    		if (timer->_ms > 0) {
    			timer->_ms--;
    			break;
    		} else {
    			MillisTimer *next = timer->_next;
    			if (next) next->_prev = nullptr;
    			listActive = next;
    			timer->_state = TimerOff;
    			EventResponderRef event = *(timer->_event);
    			event.triggerEvent(0, timer);
    			if (timer->_reload) {
    				timer->_ms = timer->_reload;
    				timer->addToActiveList();
    			}
    			timer = listActive;
    		}
    	}
    	bool irq = disableTimerInterrupt();
    	MillisTimer *waiting = listWaiting;
    	listWaiting = nullptr; // TODO: use STREX to avoid interrupt disable
    	enableTimerInterrupt(irq);
    	while (waiting) {
    		MillisTimer *next = waiting->_next;
    		waiting->addToActiveList();
    		waiting = next;
    	}
    }
    Maybe one could simply change it to be:
    Code:
    void MillisTimer::runFromTimer()
    {
    	MillisTimer *timer = listActive;
    	while (timer) {
    		if (timer->_ms > 0) {
    			timer->_ms--;
    			break;
    		} else {
    			MillisTimer *next = timer->_next;
    			if (next) next->_prev = nullptr;
    			listActive = next;
    			timer->_state = TimerOff;
    			EventResponderRef event = *(timer->_event);
    			event.triggerEvent(0, timer);
    			if (timer->_reload) {
    				timer->_ms = timer->_reload;
    				timer->addToActiveList();
    			}
    			timer = listActive;
    		}
    	}
    	if (listWaiting) 
    		bool irq = disableTimerInterrupt();
    		MillisTimer *waiting = listWaiting;
    		listWaiting = nullptr; // TODO: use STREX to avoid interrupt disable
    		enableTimerInterrupt(irq);
    		while (waiting) {
    			MillisTimer *next = waiting->_next;
    			waiting->addToActiveList();
    			waiting = next;
    		}
    	}
    }

  12. #12
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    I recall that on T_3.x micros() disables interrupts.

    For that Logger library I've been looking at it had disable interupts - I rewrote that to use STREX - like is used in T_4.x micros().

    T4 micros() was nice in that it was read only of millis() systick info.

    For the logger lib it was writing something the timer _isr() read - hopefully that got resolved in the STREX:: Generic-data-logger-object ... STREX

  13. #13
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    Quote Originally Posted by KurtE View Post
    As for the millis interrupt. I wonder how much overhead does it have when there are no events? Maybe it just looks and sees a NULL pointer and exits...
    https://forum.pjrc.com/threads/60831...-the-core-code

    Removing the obvious parts reduced the jitter from > 100ns to 25ns (And this without! using the events)
    (So there might be still room for more improvements)

    The jitter will be much more with the T3.x series.

    Sadly, even a short check for null-ptr with disabled interrupts can be responsible if a project is possible or not.
    I think we agree that a 600MHZ controller should be able to read a 2MHZ signal - and without the disabled interrupts, it is.
    Last edited by Frank B; 05-19-2020 at 08:51 AM.

  14. #14
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    @defragster, would be good to have your T4-Systick in the T3 core, too.

    A yield for loop() could just look like this:

    void yield() {
    if (eventResponderEnabled) { call all the other stuff}
    }
    Systick could do the same.

    It just need a flag "eventResponderEnabled".
    Then, the responder can be disabled with a little

    void disableEventresponder(void) {
    eventResponderEnabled = false;
    //+ optional switch to a shorter systick - interrupt without the check for eventResponderEnabled
    }

    That's all.
    You'll need if (SerialX.available()) after a disableEventResponder(), but this is no big issue.
    Last edited by Frank B; 05-19-2020 at 09:25 AM.

  15. #15
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    ...and if you're smart, you use the 32 Bits of uint32_t eventResponderEnabled in a intelligent way:

    i.e.
    bit 0 = global events enabled flag
    bit 1 = event for USB Serial pending
    bit 2 = event for USB Serial1 pending
    bit 3 = event for USB Serial2 pending
    ...
    bitx = event for MillisTimer pendig
    bity...

    So you call the corresponding events without further checking by just checking one bit.
    -> Not needed to disable any interrupt if there is no event.
    -> very fast. You can check (eventResponderEnabled & 1) > 0 in yield(). Not more.

    -> no need to replace yield() and do other hacks anymore.
    -> possible to switch it on and off at runtime.
    Last edited by Frank B; 05-19-2020 at 09:33 AM.

  16. #16
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    Quote Originally Posted by Frank B View Post
    @defragster, would be good to have your T4-Systick in the T3 core, too.
    With regard to micros() usage?

    I thought so too - mentioned it and missed any reply so never got back to it after confirming T_3.x's support ARM_DWT_CYCCNT and __STREXW - but T_LC won't do either.

    Only change/breakage is it would have to start ARM_DWT_CYCCNT that is not done now? - though some may use it and may be zeroing it?

    In use :: It won't flip interrupts off/on and should run faster than the current method of part tick inclusion - and even down to 8 Mhz ARM_DWT_CYCCNT gives better us resolution ... not to mention 96 or 120 or 180+ Mhz.

    ... but I just put all the T_3's off my desk and a couple of T_4's too ... though they are still just boxed beside me

    Replied on p#13 linked thread. Testing KurtE's T_4 multi serial I played with limiting yield() calls and it helped loop() have more time - and was using serialEvent#()'s on seven serial ports at 2 or 5 Mbaud. Even at 5 Mbaud that only yields 500K chars per second - and with FIFO's and buffers - all seven come in in parallel so no value in calling .available() 1 to 5 million times per second as it keeps other work from getting done. USB Serial a bit faster - but those often arrive in 64 or 512 byte packets. That would be under 94,000 packets of 512 bytes in a second at full 480 Mbps speed - and fewer than 19,000 64 packets at 12 Mbps - so they can get called even less.

    Not sure what goal timing resolution for eventResponder is? Not sure of a super efficient way to limit yield() processing [ limit to ~100k/sec on T-4's and less on T_3's? ] doing multiple .available() tests - especially when the serialEvent code isn't widely used [ assumed ] certainly not on 3,5,7 or 9 ports at once. KurtE did effort to limit more expensive calls - but still some conditional(s) and another call to get here:
    Code:
    void HardwareSerial::processSerialEvents()
    {
    	if (!serial_event_handlers_active) return;	// bail quick if no one processing SerialEvents.
    	uint8_t handlers_still_to_process = serial_event_handlers_active;
    	for (uint8_t i = 0; i < 8; i++) {
    		if (serial_event_handler_checks[i]) {
    			(*serial_event_handler_checks[i])();
    			if (--handlers_still_to_process == 0) return;
    		}
    	}
    }

  17. #17
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    As reseting the cyclecounter is wrong anyway and bad coding style I woulnd't care about it.
    Efficient way is shown in the post before.

    void yield(void) {
    if ((eventResponderEnabled & 1) == 0 ) return;

    //then , perhaps ( not important)
    //Check if any event is pending:
    if (
    eventResponderEnabled > 1) {

    //yes, one of these:
    if (
    eventResponderEnabled & 2) usb event
    if
    (eventResponderEnabled & 4) Serial1 event..
    ....

    }

    In 99.9999....% yield will now return immediately - with events used. And in 100% if no events are used.. And this is the goal.
    It can be further optimized with a jumptable and CLZ.

    Again...a better name would be good

  18. #18
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,571
    Quote Originally Posted by Frank B View Post
    As reseting the cyclecounter is wrong anyway and bad coding style I woulnd't care about it.
    Efficient way is shown in the post before.

    void yield(void) {
    if ((eventResponderEnabled & 1) == 0 ) return;

    //then , perhaps ( not important)
    //Check if any event is pending:
    if (
    eventResponderEnabled > 1) {

    //yes, one of these:
    if (
    eventResponderEnabled & 2) usb event
    if
    (eventResponderEnabled & 4) Serial1 event..
    ....

    }

    In 99.9999....% yield will now return immediately - with events used. And in 100% if no events are used.. And this is the goal.
    It can be further optimized with a jumptable and CLZ.

    Again...a better name would be good
    CycCnt - Indeed it shouldn't be reset with wrapping int32_t math - but the first example I found showed it used that way ... years back on the forum. But better they fix their code indeed.

    Prior efficient post looks like a good path indeed - it came in between browser refresh - if that could be implemented to get a fast return it would beat any method of counting calls/second to force early return.

    For sure the current yield() scheme seems to be the only non-bare metal thing about Teensy when it comes to perf loss for Arduino support. eventResponder seems a good direction especially if it goes away when unused, but provides good use when desired.

  19. #19
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    Ok, I'll do a PR for the dumb variant (as step #1) this evening and we'll see what happens.
    If it gets merged, we can add the addition with checking the individual bits, count leading zeros "CLZ" and jumptable (step #2)

  20. #20
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    Personally instead of adding the extra code to yield, one could probably simply mark processSerialEvents as inline...
    Then no extra calls.

    Also please do not put anything in, like: if (Serial1.available()) ...
    Or call off directly to serialEvent1 or the like.
    As this will cause Serial1 to be always defined with it's buffers and....

  21. #21
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    Nothing like this is my intention.
    Perhaps read my posts...

  22. #22
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    Just tested the "dumb" version a little (with switching to the shorter systick without millistimer)

    loop() is ~3 times faster (when eventresponder is disabled),
    interrupts have less jitter.

    Edit: this also helps for lower priority interrupts - if yield is faster (it gets called from interrupts, too) , they'll return faster..
    Last edited by Frank B; 05-19-2020 at 01:16 PM.

  23. #23
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    Quote Originally Posted by Frank B View Post
    Nothing like this is my intention.
    Perhaps read my posts...
    Yep sorry - The problem was that there are two threads going on with some of the information...
    And what concerned me a little was:
    Code:
    if (eventResponderEnabled & 2) usb event
    if (eventResponderEnabled & 4) Serial1 event..
    So I was trying to make sure that we did not put any direct links to each of the SerialX objects into code like as it will bring in all of their code, data... Into every sketch.

  24. #24
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,901
    No, my shortcut can use your way to call things.
    It will not use Serial.available().

    Kurt, I really don't mind how its done.
    If you have a better solution- just create a pullrequest.

    Anyhow, I hope we'll have a good solution, soon.

  25. #25
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    6,720
    Me too

    Earlier I tried to minimize the overhead as much as possible, but again a lot of this was done only for T4.x... My earlier rework of T3.X serial code to use one class instead of C functions with individual classes for each serial port (PR) was never integrated, so I never went back to optimize the memory usage or...

    There are a few gotchas here, that I did not know any way around...

    Example current yield:
    Code:
    void yield(void)
    {
    	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();
    	
    };
    So the code: if (usb_enable_serial_event_processing && Serial.available()) serialEvent();
    So in theory this code will only be called if the user supplies their own version of serialEvent...
    BUT: this is only true after the first time we call serialEvent. As the default one will clear usb_enable_serial_event_processing

    Dito for: if (HardwareSerial::serial_event_handlers_active) HardwareSerial:rocessSerialEvents();
    If your code does any SerialX.begin() calls, The serial_event_handlers_active member variable will become set and will continue to be set until our default implementation for serialEventX is called, which will clear out its handler... Which typically only happens when there is something on it's input queue... Which is if that input queue never receives anything will be the lifetime of that sketch.
    Which for example for a sketch that only does output...

    So if you wish to neuter this in your own sketch you should do something like:
    Code:
    void setup() {
        Serial.begin(115200);
        serialEvent();
    ... 
        Serial1.begin(115200);
        serialEvent1();
    Obviously don't do this for serial objects that you don't use... These won't be installed...

Posting Permissions

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