TeensyTimerTool

@luni
I did a kludge to DCCWaveform.cpp to use the IntervalTImer for T3.x and GPT for T4.1. I just did a test with the arduino motor shield with a T3.5 and hook up a LogicAnalzer to pin2 power and pin 12 (signal) and got the following waveform:
Capture.jpg

The short pulses are 58.1us and the long about 116us - think within specs. 58 = digit 1 and 116 = digit 0. And Power pin goes high when I turn on track power. So think the kludge worked. Tomorrow may hook up a test track and see what happens. Here is the kludge (looking forward to see what you come up with):

Code:
/*
 *  © 2020, Chris Harlow. All rights reserved.
 *  © 2020, Harald Barth.
 *  
 *  This file is part of Asbelos DCC API
 *
 *  This is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  It is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with CommandStation.  If not, see <https://www.gnu.org/licenses/>.
 */
#include <Arduino.h>

#include "DCCWaveform.h"
#include "DIAG.h"
 
const int NORMAL_SIGNAL_TIME=58;  // this is the 58uS DCC 1-bit waveform half-cycle 
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;

DCCWaveform  DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform  DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);


bool DCCWaveform::progTrackSyncMain=false; 
bool DCCWaveform::progTrackBoosted=false;

#if !defined(TEENSYDUINO)
VirtualTimer * DCCWaveform::interruptTimer=NULL;      


void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
  mainTrack.motorDriver=mainDriver;
  progTrack.motorDriver=progDriver;

  mainTrack.setPowerMode(POWERMODE::OFF);      
  progTrack.setPowerMode(POWERMODE::OFF);
  switch (timerNumber) {
    case 1: interruptTimer= &TimerA; break;
    case 2: interruptTimer= &TimerB; break;
#ifndef ARDUINO_AVR_UNO  
    case 3: interruptTimer= &TimerC; break;
#endif    
    default:
      DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid.  DCC will not work.*** \n\n"), timerNumber);
      return;
  }
  interruptTimer->initialize();
  interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
  interruptTimer->attachInterrupt(interruptHandler);
  interruptTimer->start();

}

void DCCWaveform::setDiagnosticSlowWave(bool slow) {
  interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
  interruptTimer->start(); 
  DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); 
}

#else

void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
  mainTrack.motorDriver=mainDriver;
  progTrack.motorDriver=progDriver;

  mainTrack.setPowerMode(POWERMODE::OFF);      
  progTrack.setPowerMode(POWERMODE::OFF);

  // Initialise timer1 to trigger every 58us (NORMAL_SIGNAL_TIME)
  TimerA.beginPeriodic(interruptHandler, NORMAL_SIGNAL_TIME);



}

void DCCWaveform::setDiagnosticSlowWave(bool slow) {
  TimerA.beginPeriodic(interruptHandler, slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
  DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); 
}
#endif





void DCCWaveform::loop() {
  mainTrack.checkPowerOverload();
  progTrack.checkPowerOverload();
}


// static //
void DCCWaveform::interruptHandler() {
  // call the timer edge sensitive actions for progtrack and maintrack
  bool mainCall2 = mainTrack.interrupt1();
  bool progCall2 = progTrack.interrupt1();

  // call (if necessary) the procs to get the current bits
  // these must complete within 50microsecs of the interrupt
  // but they are only called ONCE PER BIT TRANSMITTED
  // after the rising edge of the signal
  if (mainCall2) mainTrack.interrupt2();
  if (progCall2) progTrack.interrupt2();
}


// An instance of this class handles the DCC transmissions for one track. (main or prog)
// Interrupts are marshalled via the statics.
// A track has a current transmit buffer, and a pending buffer.
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.


// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};


DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
  // establish appropriate pins
  isMainTrack = isMain;
  packetPending = false;
  memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
  state = 0;
  // The +1 below is to allow the preamble generator to create the stop bit
  // fpr the previous packet. 
  requiredPreambles = preambleBits+1;  
  bytes_sent = 0;
  bits_sent = 0;
  sampleDelay = 0;
  lastSampleTaken = millis();
  ackPending=false;
}

POWERMODE DCCWaveform::getPowerMode() {
  return powerMode;
}

void DCCWaveform::setPowerMode(POWERMODE mode) {

  // Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away.  
  if (!interruptTimer) return; 
  
  powerMode = mode;
  bool ison = (mode == POWERMODE::ON);
  motorDriver->setPower( ison);
}


void DCCWaveform::checkPowerOverload() {
  
  static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static

  if (millis() - lastSampleTaken  < sampleDelay) return;
  lastSampleTaken = millis();
  int tripValue= motorDriver->getRawCurrentTripValue();
  if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
    tripValue=progTripValue;
  
  switch (powerMode) {
    case POWERMODE::OFF:
      sampleDelay = POWER_SAMPLE_OFF_WAIT;
      break;
    case POWERMODE::ON:
      // Check current
      lastCurrent = motorDriver->getCurrentRaw();
      if (lastCurrent <= tripValue) {
        sampleDelay = POWER_SAMPLE_ON_WAIT;
	if(power_good_counter<100)
	  power_good_counter++;
	else
	  if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
      } else {
        setPowerMode(POWERMODE::OVERLOAD);
        unsigned int mA=motorDriver->raw2mA(lastCurrent);
        unsigned int maxmA=motorDriver->raw2mA(tripValue);
        DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d  offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait);
	power_good_counter=0;
        sampleDelay = power_sample_overload_wait;
	if (power_sample_overload_wait >= 10000)
	    power_sample_overload_wait = 10000;
	else
	    power_sample_overload_wait *= 2;
      }
      break;
    case POWERMODE::OVERLOAD:
      // Try setting it back on after the OVERLOAD_WAIT
      setPowerMode(POWERMODE::ON);
      sampleDelay = POWER_SAMPLE_ON_WAIT;
      break;
    default:
      sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
  }
}





// process time-edge sensitive part of interrupt
// return true if second level required
bool DCCWaveform::interrupt1() {
  // NOTE: this must consume transmission buffers even if the power is off
  // otherwise can cause hangs in main loop waiting for the pendingBuffer.
  switch (state) {
    case 0:  // start of bit transmission
      setSignal(HIGH);
      state = 1;
      return true; // must call interrupt2 to set currentBit

    case 1:  // 58us after case 0
      if (currentBit) {
        setSignal(LOW);
        state = 0;
      }
      else  {
        setSignal(HIGH);  // jitter prevention
        state = 2;
      }
      break;
    case 2:  // 116us after case 0
      setSignal(LOW);
      state = 3;
      break;
    case 3:  // finished sending zero bit
      setSignal(LOW);  // jitter prevention
      state = 0;
      break;
  }

  // ACK check is prog track only and will only be checked if 
  // this is not case(0) which needs  relatively expensive packet change code to be called.
  if (ackPending) checkAck();

  return false;

}

void DCCWaveform::setSignal(bool high) {
  if (progTrackSyncMain) {
    if (!isMainTrack) return; // ignore PROG track waveform while in sync
    // set both tracks to same signal
    motorDriver->setSignal(high);
    progTrack.motorDriver->setSignal(high);
    return;     
  }
  motorDriver->setSignal(high);
}
      
void DCCWaveform::interrupt2() {
  // set currentBit to be the next bit to be sent.

  if (remainingPreambles > 0 ) {
    currentBit = true;
    remainingPreambles--;
    return;
  }

  // beware OF 9-BIT MASK  generating a zero to start each byte
  currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
  bits_sent++;

  // If this is the last bit of a byte, prepare for the next byte

  if (bits_sent == 9) { // zero followed by 8 bits of a byte
    //end of Byte
    bits_sent = 0;
    bytes_sent++;
    // if this is the last byte, prepere for next packet
    if (bytes_sent >= transmitLength) {
      // end of transmission buffer... repeat or switch to next message
      bytes_sent = 0;
      remainingPreambles = requiredPreambles;

      if (transmitRepeats > 0) {
        transmitRepeats--;
      }
      else if (packetPending) {
        // Copy pending packet to transmit packet
        for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
        transmitLength = pendingLength;
        transmitRepeats = pendingRepeats;
        packetPending = false;
        sentResetsSincePacket=0;
      }
      else {
        // Fortunately reset and idle packets are the same length
        memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
        transmitLength = sizeof(idlePacket);
        transmitRepeats = 0;
        if (sentResetsSincePacket<250) sentResetsSincePacket++;
      }
    }
  }  
}



// Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
  if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
  while (packetPending);

  byte checksum = 0;
  for (int b = 0; b < byteCount; b++) {
    checksum ^= buffer[b];
    pendingPacket[b] = buffer[b];
  }
  pendingPacket[byteCount] = checksum;
  pendingLength = byteCount + 1;
  pendingRepeats = repeats;
  packetPending = true;
  sentResetsSincePacket=0;
}

int DCCWaveform::getLastCurrent() {
   return lastCurrent;
}

// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...) 

void DCCWaveform::setAckBaseline() {
      if (isMainTrack) return;
      int baseline = motorDriver->getCurrentRaw();
      ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
      if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
			  baseline,motorDriver->raw2mA(baseline),
			  ackThreshold,motorDriver->raw2mA(ackThreshold),
                          minAckPulseDuration, maxAckPulseDuration);
}

void DCCWaveform::setAckPending() {
      if (isMainTrack) return; 
      ackMaxCurrent=0;
      ackPulseStart=0;
      ackPulseDuration=0;
      ackDetected=false;
      ackCheckStart=millis();
      ackPending=true;  // interrupt routines will now take note
}

byte DCCWaveform::getAck() {
      if (ackPending) return (2);  // still waiting
      if (Diag::ACK) DIAG(F("\n%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, 
           ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
      if (ackDetected) return (1); // Yes we had an ack
      return(0);  // pending set off but not detected means no ACK.   
}

void DCCWaveform::checkAck() {
    // This function operates in interrupt() time so must be fast and can't DIAG 
    
    if (sentResetsSincePacket > 6) {  //ACK timeout
        ackCheckDuration=millis()-ackCheckStart;
        ackPending = false;
        return; 
    }
      
    lastCurrent=motorDriver->getCurrentRaw();
    if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
    // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
        
    if (lastCurrent>ackThreshold) {
       if (ackPulseStart==0) ackPulseStart=micros();    // leading edge of pulse detected
       return;
    }
    
    // not in pulse
    if (ackPulseStart==0) return; // keep waiting for leading edge 
    
    // detected trailing edge of pulse
    ackPulseDuration=micros()-ackPulseStart;
               
    if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
        ackCheckDuration=millis()-ackCheckStart;
        ackDetected=true;
        ackPending=false;
        transmitRepeats=0;  // shortcut remaining repeat packets 
        return;  // we have a genuine ACK result
    }      
    ackPulseStart=0;  // We have detected a too-short or too-long pulse so ignore and wait for next leading edge 
}
 
Great, looks like it works! Let the trains departure :)

Just a comment: If this is only for you, your method of hacking the teensy timers into the code is certainly fine. If you want to have it integrated to the library it might be better to just use the interface they provide for extension to other timers. It is easy to use: you only need to add a teensy folder containing a Timer.h file where you derive a Timer class from the abstract VirtualTimer class and fill in the 4 or 5 functions (initialize, setPeriod etc) they need. Since IntervalTimer and the timers from the TimerTool provide all this functionality you just need to relay their calls to the corresponding Teensy Timer calls. The rest of the library can stay more or less untouched (at least that seems to be the idea).
The pull request I sent you yesterday shows how to do this. If you want to have different timers for different purposes (like you did), you can do that as well, just distinguish which one you create by the number they pass in the Timer constructor.
 
Great, looks like it works! Let the trains departure :)

Just a comment: If this is only for you, your method of hacking the teensy timers into the code is certainly fine. If you want to have it integrated to the library it might be better to just use the interface they provide for extension to other timers. It is easy to use: you only need to add a teensy folder containing a Timer.h file where you derive a Timer class from the abstract VirtualTimer class and fill in the 4 or 5 functions (initialize, setPeriod etc) they need. Since IntervalTimer and the timers from the TimerTool provide all this functionality you just need to relay their calls to the corresponding Teensy Timer calls. The rest of the library can stay more or less untouched (at least that seems to be the idea).
The pull request I sent you yesterday shows how to do this. If you want to have different timers for different purposes (like you did), you can do that as well, just distinguish which one you create by the number they pass in the Timer constructor.

Well ran a test this morning and the train has departed: https://forum.pjrc.com/threads/65956-DCC-for-Model-Train-Control-with-Teensy-3-x-and-Teensy-4-x !

Am planning to redo what I did to use a Teensy specific Time.h file to conform to their format but just wanted to test to see if it would work. So guess next up is to work on your merge and see what I can do. Really appreciate the help. Also glad with the EEPROM lib updated as well. So guess there was added value in me trying to port over the lib :)
 
Hi Luni,
Sorry to bother you again with what could be a Mat 101 issue. I have been experimenting more with timing routines and the Timer Tool, moving from the LC to a Teensy 3.5. with an Audio board.

The project is Radio based. I have a user programmable time delay (from 5 mins to about 15-20 minutes). When this timer expires it transmits some Morse code then starts a new count down.
There is another counter also running at a rate of 10ms.

My code uses a periodic timer set to 10ms from this I generate my 1s, 20ms and 10ms timers - my original code used the Periodic t1 (TCK); This works just fine and my code is doing as expected, when the countdown expires Morse is generated.

As my "project" (in a lose terms) grows I need to move away from the software interrupts. I started using the FTMx interrupt source. However it seams the Audio (I used the Audio design tool) breaks when I select FTMx. With a scope I see the data IN line (pin 22 A8PWM) becomes stuck until I change the code back to TCK. Is there any way around this FTMx resource issue?

As per my last post this is a steep learning curve for me so let me thank you in advance for any advise/guidance you can give.

Regards.
Mat.
 
AFAIK the audio library uses some FTM timers. Should be documented somewhere. Did you try to use another FTM module?
 
Hi there!
How can I change frequency without having to restart it?
i'm trying to use it as a BPM Clock.
Cheers!
 
@luni
I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
I see that my code is not working anymore. Here is the example that I was using earlier.
I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.
Screenshot_16.png

Code:
/********************************************************
 * Basic usage of the timer
 * 
 * Generates a timer from the timer pool and
 * starts it with a period of 250ms. 
 * The timer callback simply toggles the built in LED
 * 
 ********************************************************/

#include "TeensyTimerTool.h"

using namespace TeensyTimerTool;
PeriodicTimer t1(PIT);

void vStartTimer()
{
  t1.begin(callback, 10'000); //10mSec
}

void vStopTimer()
{
  t1.begin([] {}, 10'000); //10mSec  
}

void callback()
{
    digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));    
}

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

void loop()
{   
}
 
@luni
I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
I see that my code is not working anymore. Here is the example that I was using earlier.
I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.

Strange, this is the most simple application and should work of course. I'm currently not at home and don't have a T4 with me for testing I can have a closer look on Friday. Does it work with other timers (e.g. GPT1, TCK)?
 
I'm on a T3.2, so I unless I've misunderstood something, I only have FTM0, FTM1 and FTM2 to use?
(and now realizing that you probably replied to @HallMark and not me ;-) )
 
@luni TeensyStep works great, but I've run into a conflict with the PWM timer (as mentioned here https://github.com/luni64/TeensyStep/issues/51). I've tried doing as you suggest in this issue by changing

Code:
#define USE_TIMER TIMER_DEFAULT

to

Code:
#define USE_TIMER TIMER_FTM1

I'm on a T3.2, so I have limited timers available. I think the problem is that I'm using one RotateControl and one StepControl that need to work independently?
My project needs PWM, so I need to not use TIMER_FTM0 at all.

So I wonder:
- Is it so that each instance will grab a new timer?
- Do you have a suggestion as to how to solve this?

Thanks!

Looks like you are in the wrong thread? This is TeensyTimerTool, not TeensyStep :)
Anyway, on a T3.2 FTM1 only has 2 channels. A controller needs one PIT and two FTMs so, you can only use one controller if you selected FTM1 (same with FTM2). Couldn't you switch your PWM pins so that you can use FTM0 for the Steppers and FTM1/FTM2 for PWM?
 
@luni
I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
I see that my code is not working anymore. Here is the example that I was using earlier.
I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.
View attachment 23659

Sorry for the delay, I finally found some time trying your code. I can not reproduce the problem. It generates nice 10ms calls here. Do you still have this problem?
 
Encapsulated timers

Hi,
I am having some trouble with using the timers encapsulated in a class. I am working on a simple LED control class that flashes LEDs. I am using a teensy 4.0 and teensytimertool V0.3.2.
Here is the class

Code:
//LEDcontrol.h
#include "Arduino.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;
class LED{
    PeriodicTimer PTimer;
    OneShotTimer OTimer;
    void callbackTriggerP1();
    void callbackP1();
public:
    LED(int, int);
    int pin;
    int selftrig=0, flash=0;
    void begin();
    void end();
};

Code:
//"LEDControl.cpp"
LED::LED(int p, int f){
    flash = f;
    pin = p;
}

void LED::callbackP1(){
  OTimer.trigger(100ms);
}
void LED::callbackTriggerP1(){
  if(selftrig < flash)
  {
    OTimer.trigger(100ms);
    selftrig++;
    digitalWriteFast(pin, !digitalReadFast(pin));
  }
  else{
    selftrig = 0;
    digitalWriteFast(pin,LOW);
  }
}

void LED::begin(){
    pinMode(pin, OUTPUT);
    flash = (flash-1)*2;
    OTimer.begin([this]() {this->callbackTriggerP1(); });
    PTimer.begin([this]() {this->callbackP1(); }, 1000ms);
}

void LED::end(){
    PTimer.end();
}

This is pretty simple and similar to the Blinker class example, except with some self calls to trigger multiple flashes. This works fine when only one is being used, ie in this main function:
Code:
#include "Arduino.h"
#include "LEDControl.h"
LED L1(23, 4);
//LED L2(22, 4);
void setup()
{ 
  Serial.begin(9600);
  L1.begin();
  //L2.begin();
}

void loop()
{

}

The problem occurs when you try to add a second LED (or more). The LED that was begun second will just come on and stay on, without flashing. This indicates that it is only triggering the callbacktrigger() function once.

Any help here would be appreciated.
 
not seeing a loop() making that a complete posted sketch?

It may be after the first call it is getting called TWICE? Not sure if that was resolved here - but a fast short _isr() on T_4.0 can trigger twice as it exits before realizing the interrupt was serviced.

Was a scope at hand to put on the LED?
 
I am not sure I follow. There is an empty loop after the setup function.

I guess it is possible that the _isr is being called twice, although it is strange that the problem only occurs when the TeensyTimerTool class is being generated from within another class. For example here is a working piece of code that flashes 2 LEDs. All the timers are defined as globals. It is the same as before just doesn't use the class. as soon as I wrap it in a class, it stops working, I also cannot specify to use the tick timers if it is in a class, which might be related.

Code:
//main.cpp
#include "Arduino.h"
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

#define LED1_PIN 23
#define LED2_PIN 22

PeriodicTimer PT1(TCK);
OneShotTimer OST1(TCK);
void callback1();
void callbackTrigger1();
int selftrig1=0, flash1=4;

PeriodicTimer PT2(TCK);
OneShotTimer OST2(TCK);
void callback2();
void callbackTrigger2();
int selftrig2=0, flash2=4;

void setup()
{ 
  Serial.begin(9600);
  flash1 = (flash1-1)*2;
  flash2 = (flash2-1)*2;
  pinMode(LED1_PIN, OUTPUT);
  pinMode(LED2_PIN, OUTPUT);
  PT1.begin(callback1, 1000ms);
  PT2.begin(callback2, 1000ms);
  OST1.begin(callbackTrigger1);
  OST2.begin(callbackTrigger2);
}

void loop()
{

}

void callback1(){
  OST1.trigger(100ms);
}

void callbackTrigger1(){
    if(selftrig1 < flash1)
  {
    OST1.trigger(100ms);
    selftrig1++;
    digitalWriteFast(LED1_PIN, !digitalReadFast(LED1_PIN));
  }
  else{
    selftrig1 = 0;
    digitalWriteFast(LED1_PIN,LOW);
  }
}

void callback2(){
  OST2.trigger(100ms);
}

void callbackTrigger2(){
  if(selftrig2 < flash2)
  {
    Serial.println("led2");
    OST2.trigger(100ms);
    selftrig2++;
    digitalWriteFast(LED2_PIN, !digitalReadFast(LED2_PIN));
  }
  else{
    selftrig2 = 0;
    digitalWriteFast(LED2_PIN,LOW);
  }
}

I don't have access to a scope today, but will on Sunday and will post the results if we haven't worked it out by then.
 
Last edited:
Somehow the empty loop() wasn't seen :(

Ran the code and the behavior described seems to be there.

Adding this to the callback didn't change the behavior to make sure the interrupt was cleared :: asm volatile ("dsb");

The second LED L2 is never being called after the first instance - code below:
{edit}: the SECOND one with .begin is called only once - swap .begin of L1 and L2 and it changes
Code:
T:\tCode\TimerTool\TimerTest\TimerTest.ino Jul  8 2021 20:20:21
T:\tCode\TimerTool\TimerTest\TimerTest.ino Jul  8 2021 20:22:35
millis=1000	PIN=22	count=0
millis=1000	PIN=23	count=1
millis=2000	PIN=22	count=3
millis=2000	PIN=23	count=1
millis=3000	PIN=22	count=9
millis=3000	PIN=23	count=1
millis=4000	PIN=22	count=15
millis=4000	PIN=23	count=1
millis=5000	PIN=22	count=21
millis=5000	PIN=23	count=1
millis=6000	PIN=22	count=27
...


Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;
class LED {
  PeriodicTimer PTimer;
  OneShotTimer OTimer;
  void callbackTriggerP1();
  void callbackP1();
public:
  void showcnt();
  LED(int, int);
  int pin;
  int selftrig = 0, flash = 0, acnt = 0;
  void begin();
  void end();
};


//"LEDControl.cpp"
LED::LED(int p, int f) {
  flash = f;
  pin = p;
}

void LED::callbackP1() {
  OTimer.trigger(100ms);
}
void LED::showcnt() {
  Serial.print( "millis=" );
  Serial.print( millis() );
  Serial.print( "\tPIN=" );
  Serial.print( pin );
  Serial.print( "\tcount=" );
  Serial.println( acnt );
  //acnt = 0;
}
void LED::callbackTriggerP1() {
  if (selftrig < flash)
  {
    OTimer.trigger(100ms);
    selftrig++;
    //digitalWriteFast(pin, !digitalReadFast(pin));
    digitalToggleFast(pin);
    acnt++;
  }
  else {
    selftrig = 0;
    digitalWriteFast(pin, LOW);
  }
  asm volatile ("dsb");
}

void LED::begin() {
  pinMode(pin, OUTPUT);
  flash = (flash - 1) * 2;
  OTimer.begin([this]() {this->callbackTriggerP1(); });
  PTimer.begin([this]() {this->callbackP1(); }, 1000ms);
}

void LED::end() {
  PTimer.end();
}




//#include "LEDControl.h"
LED L1(22, 4);
LED L2(23, 4);
void setup()
{
  Serial.begin(9600);
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  L1.begin();
  L2.begin();
}

void loop()
{
  if ( !(millis() % 1000)) {
    L1.showcnt();
    L2.showcnt();
    while ( !(millis() % 1000)) yield();

  }

}
 
Last edited:
The second LED L2 is never being called after the first instance - code below:

Good to know it is repeatable and not just my machine...

Any idea why this is? or what steps I can take to find the root cause?

I have simplified the class to only call the periodic timer, and to start the OneShot timer, simply by changing the following:
Code:
void LED::callbackP1() {
  //OTimer.trigger(100ms);
  digitalToggleFast(pin);
}

And this works as expected. So it seems the issue is in either the OneShotTimer class, or in the logic in callbacktrigger().

The logic in callbacktriggerP1() works outside the class so I can't see why it wouldn't work inside...

Thanks heaps for your help.
 
Short answer:
You use 16bit hardware timers for the second LED which can not handle your 1s delay.


Details:
If you don't specify which timer module to use, the TimerTool chooses the next free timer from a timer pool which is defined in the config file. Here the default definition of the pool for a Teensy 4.0:

Code:
#if defined(ARDUINO_TEENSY40)
    TimerGenerator* const timerPool[] = {GPT1, GPT2, TMR1, TMR2, TMR3, TMR4, TCK};

It starts using GPT1 and GPT2 (both 32bit) and then the first two channels of the TMR1 module (16bit).

It is always a good idea to activate the error handling of the library during development (see https://github.com/luni64/TeensyTimerTool/wiki/Error-Handling):
Code:
void setup()
{
    while(!Serial){}

    [B][COLOR="#FF0000"]TeensyTimerTool::attachErrFunc(ErrorHandler(Serial));[/COLOR][/B]

    L1.begin();
    L2.begin();
}

This will print the following warning messages
Code:
W-100: Period overflow, set to maximum
W-100: Period overflow, set to maximum

To fix this you can either adjust the timer pool definition in the config file (see here for instructions: https://github.com/luni64/TeensyTimerTool/wiki/Configuration), or (recommended) explicitly define which timer module to use in the constructor of your class. I recommend to use the TCK module which has 20 32bit channels and is implemented in software. In case you really need a hardware timer you can use the PIT module which has four 32bit channels.

For a quick check you can change your timing from milliseconds to microseconds, i.e.

Code:
//"LEDControl.cpp"
#include "LEDControl.h"

LED::LED(int p, int f)
{
    flash = f;
    pin   = p;
}

void LED::callbackP1()
{
    OTimer.trigger(100[B][COLOR="#FF0000"]us[/COLOR][/B]);
}
void LED::callbackTriggerP1()
{
    if (selftrig < flash)
    {
        OTimer.trigger(100[B][COLOR="#FF0000"]us[/COLOR][/B]);
        selftrig++;
        digitalWriteFast(pin, !digitalReadFast(pin));
    }
    else
    {
        selftrig = 0;
        digitalWriteFast(pin, LOW);
    }
}

void LED::begin()
{
    pinMode(pin, OUTPUT);
    flash = (flash - 1) * 2;
    OTimer.begin([this]() { this->callbackTriggerP1(); });
    PTimer.begin([this]() { this->callbackP1(); }, 1000[B][COLOR="#FF0000"]us[/COLOR][/B]);
}

void LED::end()
{
    PTimer.end();
}

Which will generate:

Screenshot 2021-07-09 070228.jpg
 
Last edited:
Thanks @luni this was the issue.

I can't get specify the timer module in the class constructor, when I change the following I get an error.
Code:
class LED {
   PeriodicTimer PTimer(TCK);
   OneShotTimer OTimer(TCK);
  //PeriodicTimer PTimer;
  //OneShotTimer OTimer;
  void callbackTriggerP1();
  void callbackP1();
public:
  void showcnt();
  LED(int, int);
  int pin;
  int selftrig = 0, flash = 0, acnt = 0;
  void begin();
  void end();
};

variable "TeensyTimerTool::TCK" is not a type name

If I define the timer as a global this isn't an issue.

Have I specified it incorrectly?

Thanks,
 
In c++ you can not initiate a member class directly in the parents class declaration if the member class has a constructor. You need to do this in the definition of the parent class (your LEDControl.cpp). Specifically, in the initializer list of the class constructor. This sounds much more complicated than it really is :)

I.e., leave your LEDControl.h header as it was and just add the following line to your constructor code in your LEDControl.cpp file:
Code:
LED::LED(int p, int f)
    [B][COLOR="#FF0000"]: PTimer(TCK), OTimer(TCK)[/COLOR][/B]
{
    flash = f;
    pin   = p;
}
 
Last edited:
That did the trick!

you are a lifesaver. Thank you and @defragster for the quick and helpful replies!
 
Luni,

recently i've updated my libraries coz of a new computer, and i have an issue with your tool.
here is my application : pulse generator

the problem is, your timerTool is trigging some pulses directly after the incoming trigger signal, without any delay (but the width/duration is ok).
"Some" pulses is maybe 1/100 or 1/1000, dont know exactly, it happens after some persistence on my oscilloscope, under a hi rep rate external trigger (> 1 khz). The others 99/100 or 999/1000 are ok.
also, the jitter is about 2µs !

during my tests, i've generated pulses with delay from 2 to 5 µs, and duration from 2 to 5 µs.
for one, or multiple channels : each channels are affected by this bug.

i've tried each of your versions, and it works fine from the 0.1.11. But any newer version than 0.1.11 has the bug.
and the jitter is better with this version ~1 µs


Another point:
in the TCK.cpp, i use now an empty yield() function.
and i'm calling the tick() directly from the loop(), that improve the jitter (~500ns)


thanks!
 
greetings dear..

please tell a beginner, in 500 ms I need to make a request for Serial port,
if I do so:
Code:
Serial7.print("IF;");
Serial7.flush();
delay(500);
then everything works correctly, but I don't want to use Delay()
if I do this:
Code:
if ((millis() - last_time) > 500)
{
Serial7.print("IF;");
Serial7.flush();
last_time = millis();
}
then this code does not work for me, what am I doing wrong, or how to do it right ?

board Teensy 4.1
 
@Croc: I used the code from your linked thread and added some timing functionality:

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

uint32_t t0, t1, t2;
elapsedMillis stopwatch;

class PulseGen
{
public:
    PulseGen() : delayTimer(TCK), durationTimer(TCK) // use the tick timers for this task, we have 20 per default, can be increased in config
    {}

    void begin(unsigned pin, unsigned delay, unsigned duration)
    {
        this->pin = pin;
        this->delay = delay;
        this->duration = duration;

        pinMode(pin, OUTPUT);
        delayTimer.begin([this]    {t1 = ARM_DWT_CYCCNT; digitalWriteFast(this->pin, HIGH);  this->durationTimer.trigger(this->duration);});
        durationTimer.begin([this] {t2 = ARM_DWT_CYCCNT; digitalWriteFast(this->pin, LOW);});
        // t1: start of pulse, t2: end of pulse
    }

    void trigger() { delayTimer.trigger(delay); }

protected:
    unsigned pin, delay, duration;
    OneShotTimer delayTimer, durationTimer;
};

//-------------------------------------
PulseGen pg;

void setup()
{
  pinMode(0,OUTPUT);
  pg.begin(1, 2, 5);  // output on pin1 2µs delay, 5µs duration
}

void loop()
{
  if(stopwatch > 100) // generate a pulse every 100ms
  {
      Serial.printf("delta: %.2f µs, duration: %.2f µs\n", (t1 - t0)/600.0f, (t2 - t1)/600.0f); // print out timing in microseconds

      digitalWriteFast(0, HIGH);   // marker for the LA
      t0 = ARM_DWT_CYCCNT;         // start time = trigger time
      pg.trigger();
      digitalWriteFast(0, LOW);
      stopwatch -= 100;
  }
}

Here the output of the LA:

Screenshot 2021-08-10 201529.jpg

And here the printout: EDIT: (delta should read "delay" of course)
Code:
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs
delta: 2.42 µs, duration: 5.17 µs

I don't see any jitter or wrong delay here.

Can you prepare a minimal example showing your problem?
 
Last edited:
Back
Top