Teensy 4.1 timing accuracy help.

Status
Not open for further replies.

AndyCap

Well-known member
Hi Guys,

I'm looking at running code at set periods over time and would like the timing to be as stable as possible.

I have looked at IntervalTimer and elapsedMicros and on the 4.1 I am using here I get around 3ms drift in a minute.

I looked at the output of AnalogWrite and get the same error in frequency.

So I'm guessing this is just the crystal used being inaccurate? 1 part in 20,000 seems pretty bad though?

Is this the expected accuracy?

Any help would be greatly appreciated.

Cheers

Andy
 
No, timing has been seen as much better than that.

Interrupts are being disabled or something.

Can a simple sketch showing the issue for review and test be posted?

Is the current TeensyDuino 1.53 in use? What other devices are attached?
 
Hi,

Thanks for the interest, here is an example using IntervalTimer, 1ms, every 1000 send midi message:

Code:
#include <Arduino.h>

IntervalTimer myTimer;
elapsedMicros since = 0;
int val = 0;
bool trig = false;
unsigned int count = 0;
  
void trigger()
{
  digitalWrite(PIN_A9, val);
  val = !val;
  count++;
}

void setup() {
  pinMode(PIN_A9, OUTPUT);
  myTimer.begin(trigger, 1000);
}

void loop() 
{
  unsigned int delay = 1000;
  if(count == 1000)
  {
    usbMIDI.sendNoteOn(60, 99, 1);
    count = 0;
  }
}

Here are the midi messages arriving with timestamps:

Code:
8895.509	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8896.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8897.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8898.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8899.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8900.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8901.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8902.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8903.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8904.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8905.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8906.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8907.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8908.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8909.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8910.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8911.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8912.510	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
8913.511	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
 
Actually I think it might be the midi usb stuff that is playing up?

I just tested on a 4.0 as well:

If I use the timer to generate a 1khz wave and use a hardware freq counter I am seeing: 999.987 on my 4.1 and 999.996 on a 4.0.

Both suffer from the drifting midi messages though, nearly identical.

Even with my bad maths the error in the frequency doesn't add up to the drift on the midi messages.

p.s. The error on the frequency counter is meant to be 2ppm
 
Try this code and see what happens - took out the MIDI and added Teensy Self reporting of the timing.
This just shows the Teensy is running as expected here. Would take a true reference to 'time' to know what it looks like.
Code:
// https://forum.pjrc.com/threads/62768-Teensy-4-1-timing-accuracy-help?p=251159&viewfull=1#post251159
IntervalTimer myTimer;
elapsedMicros since = 0;
volatile int val = 0;
volatile unsigned int count = 0;
  
void setup() {
  while (!Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  pinMode(PIN_A9, OUTPUT);
  myTimer.begin(trigger, 1000);
}

void trigger()
{
  digitalWrite(PIN_A9, val);
  val = !val;
  count++;
}


void loop() 
{
  if(count == 1000)
  {
  	uint32_t ss = since;
  	float cc=ARM_DWT_CYCCNT;
    since =0;
    // usbMIDI.sendNoteOn(60, 99, 1);
    Serial.printf( "val=%d at us==%d Since==%d Cycles==%f\n", val, micros(), ss, cc/F_CPU_ACTUAL );
    count = 0;
  }
}

Output should appear like this even when the usbMIDI_sendNoteOn() is restored?
T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 00:45:05
val=0 at us==1422001 Since==1122001 Cycles==1.421550
val=0 at us==2422001 Since==1000000 Cycles==2.421550
val=0 at us==3422001 Since==1000000 Cycles==3.421550
val=0 at us==4422001 Since==1000000 Cycles==4.421550
val=0 at us==5422001 Since==1000000 Cycles==5.421550
val=0 at us==6422001 Since==1000000 Cycles==6.421550
val=0 at us==7422001 Since==1000000 Cycles==0.263271
val=0 at us==8422001 Since==1000000 Cycles==1.263271
val=0 at us==9422001 Since==1000000 Cycles==2.263271
val=0 at us==10422001 Since==1000000 Cycles==3.263271
val=0 at us==11422001 Since==1000000 Cycles==4.263271
val=0 at us==12422001 Since==1000000 Cycles==5.263271
val=0 at us==13422001 Since==1000000 Cycles==6.263271
val=0 at us==14422001 Since==1000000 Cycles==0.104993
val=0 at us==15422001 Since==1000000 Cycles==1.104993
val=0 at us==16422001 Since==1000000 Cycles==2.104992
val=0 at us==17422001 Since==1000000 Cycles==3.104993
val=0 at us==18422001 Since==1000000 Cycles==4.104992
val=0 at us==19422001 Since==1000000 Cycles==5.104992
val=0 at us==20422001 Since==1000000 Cycles==6.104992
val=0 at us==21422001 Since==1000000 Cycles==7.104992
val=0 at us==22422001 Since==1000000 Cycles==0.946714
val=0 at us==23422001 Since==1000000 Cycles==1.946714
val=0 at us==24422001 Since==1000000 Cycles==2.946714
val=0 at us==25422001 Since==1000000 Cycles==3.946714
Just added the last cycles - 600MHz wraps every few seconds - but generally it is keeping pace with the crystal.
 
Thanks for that.

The timing output on teensy serial looks right, on midi monitor I see the message received in lock with the serial display but I see the drift in timing.

I'm thinking of attaching a function generator at 1khz and running off a pin interrupt to see if the same thing is happening on the midi side...
 
Perhaps the timing of the messages is just USB transmission variability?
It doesn't creep away it just bounces up and down from the initial value.
with SerialMidi - using SerMon with timestamp:
Code:
01:06:50.536 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:04:53
01:06:50.536 -> val=0 at us==5000002 Since==4700001 Cycles==4.999563
01:06:50.536 -> val=0 at us==6000002 Since==1000000 Cycles==5.999563
01:06:50.536 -> val=0 at us==7000002 Since==1000000 Cycles==6.999563
01:06:50.536 -> val=0 at us==9120002 Since==1120000 Cycles==1.961284
01:06:51.533 -> val=0 at us==10120002 Since==1000000 Cycles==2.961284
01:06:52.564 -> val=0 at us==11120002 Since==1000000 Cycles==3.961284
01:06:53.548 -> val=0 at us==12120002 Since==1000000 Cycles==4.961284
01:06:54.533 -> val=0 at us==13120002 Since==1000000 Cycles==5.961284
01:06:55.565 -> val=0 at us==14120002 Since==1000000 Cycles==6.961284
01:06:56.549 -> val=0 at us==15120002 Since==1000000 Cycles==0.803005
01:06:57.531 -> val=0 at us==16120002 Since==1000000 Cycles==1.803005
01:06:58.550 -> val=0 at us==17120002 Since==1000000 Cycles==2.803005
01:06:59.533 -> val=0 at us==18120002 Since==1000000 Cycles==3.803005
01:07:00.566 -> val=0 at us==19120002 Since==1000000 Cycles==4.803005
01:07:01.551 -> val=0 at us==20120002 Since==1000000 Cycles==5.803005
01:07:02.534 -> val=0 at us==21120002 Since==1000000 Cycles==6.803005
01:07:03.531 -> val=0 at us==22120002 Since==1000000 Cycles==0.644727
01:07:04.533 -> val=0 at us==23120002 Since==1000000 Cycles==1.644727
01:07:05.565 -> val=0 at us==24120002 Since==1000000 Cycles==2.644727

Here again with MIDI remove and Serial only as posted:
01:09:33.639 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:09:01
01:09:33.639 -> val=0 at us==5000001 Since==4700001 Cycles==4.999539
01:09:33.639 -> val=0 at us==6000001 Since==1000000 Cycles==5.999539
01:09:33.639 -> val=0 at us==7000001 Since==1000000 Cycles==6.999539
01:09:33.639 -> val=0 at us==16120001 Since==1000000 Cycles==1.802981
01:09:34.670 -> val=0 at us==17120001 Since==1000000 Cycles==2.802982
01:09:35.642 -> val=0 at us==18120001 Since==1000000 Cycles==3.802982
01:09:36.674 -> val=0 at us==19120001 Since==1000000 Cycles==4.802981
...
01:09:59.657 -> val=0 at us==42120001 Since==1000000 Cycles==6.328145
01:10:00.673 -> val=0 at us==43120001 Since==1000000 Cycles==0.169866
01:10:01.675 -> val=0 at us==44120001 Since==1000000 Cycles==1.169866
01:10:03.439 -> val=0 at us==45120001 Since==1000000 Cycles==2.169866 // this one got really delayed ????
01:10:03.642 -> val=0 at us==46120001 Since==1000000 Cycles==3.169866
...
01:12:11.658 -> val=0 at us==174120001 Since==1000000 Cycles==2.320847
01:12:12.672 -> val=0 at us==175120001 Since==1000000 Cycles==3.320847
01:12:13.642 -> val=0 at us==176120001 Since==1000000 Cycles==4.320848
...
01:12:30.673 -> val=0 at us==193120001 Since==1000000 Cycles==7.004290
01:12:31.658 -> val=0 at us==194120001 Since==1000000 Cycles==0.846011
01:12:32.642 -> val=0 at us==195120001 Since==1000000 Cycles==1.846011
01:12:33.673 -> val=0 at us==196120001 Since==1000000 Cycles==2.846011
01:12:34.659 -> val=0 at us==197120001 Since==1000000 Cycles==3.846011
01:12:35.644 -> val=0 at us==198120001 Since==1000000 Cycles==4.846011
01:12:36.673 -> val=0 at us==199120001 Since==1000000 Cycles==5.846011
 
Added :: Serial.flush();
After the printf(); and the times may have less variability - and might actually predate the first print at time - not sure if it helps MIDI?
01:21:40.642 -> T:\tCode\FORUM\TimeTestSlip\TimeTestSlip.ino Aug 30 2020 01:21:36
01:21:41.670 -> val=0 at us==3948001 Since==3648001 Cycles==3.947557
01:21:42.658 -> val=0 at us==4948001 Since==1000000 Cycles==4.947557
01:21:43.642 -> val=0 at us==5948001 Since==1000000 Cycles==5.947557
01:21:44.673 -> val=0 at us==6948001 Since==1000000 Cycles==6.947557
...
01:23:56.634 -> val=0 at us==138948001 Since==1000000 Cycles==2.940260
01:23:57.669 -> val=0 at us==139948001 Since==1000000 Cycles==3.940260
01:23:58.662 -> val=0 at us==140948001 Since==1000000 Cycles==4.940260
01:23:59.650 -> val=0 at us==141948001 Since==1000000 Cycles==5.940260
01:24:00.673 -> val=0 at us==142948001 Since==1000000 Cycles==6.940260
01:24:01.658 -> val=0 at us==143948001 Since==1000000 Cycles==0.781981
01:24:02.678 -> val=0 at us==144948001 Since==1000000 Cycles==1.781981
01:24:03.665 -> val=0 at us==145948001 Since==1000000 Cycles==2.781981

... /// and a few minutes later ...
01:28:13.674 -> val=0 at us==395948001 Since==1000000 Cycles==2.242222
01:28:14.658 -> val=0 at us==396948001 Since==1000000 Cycles==3.242222
01:28:15.641 -> val=0 at us==397948001 Since==1000000 Cycles==4.242222
01:28:16.673 -> val=0 at us==398948001 Since==1000000 Cycles==5.242222
01:28:17.659 -> val=0 at us==399948001 Since==1000000 Cycles==6.242222
01:28:18.642 -> val=0 at us==400948001 Since==1000000 Cycles==0.083943
 
Thanks for that.

The timing output on teensy serial looks right, on midi monitor I see the message received in lock with the serial display but I see the drift in timing.

I'm thinking of attaching a function generator at 1khz and running off a pin interrupt to see if the same thing is happening on the midi side...

No problem - was feeling a bit guilty/anxious not having found anything to program for 2-3 weeks :)
 
Hi,

Thanks for all the info.

I think it seems to be a constant drift on the midid usb, I guess I need to keep it running for a while to check.

I attach a 1khz signal to a pin and run the trigger() from the interrupt, now on the frequency counter I see 1.00001Khz.

The serial output now looks like this:

Code:
val=1 at us==309391992 Since==999981 Cycles==1.585544
val=1 at us==310391973 Since==999981 Cycles==2.585525
val=1 at us==311391955 Since==999981 Cycles==3.585507
val=1 at us==312391936 Since==999981 Cycles==4.585488
val=1 at us==313391917 Since==999981 Cycles==5.585469
val=1 at us==314391899 Since==999981 Cycles==6.585450
val=1 at us==315391880 Since==999982 Cycles==0.427153
val=1 at us==316391861 Since==999981 Cycles==1.427134
val=1 at us==317391843 Since==999981 Cycles==2.427115
val=1 at us==318391824 Since==999982 Cycles==3.427097
val=1 at us==319391805 Since==999981 Cycles==4.427078
val=1 at us==320391787 Since==999981 Cycles==5.427059
val=1 at us==321391768 Since==999982 Cycles==6.427041
val=1 at us==322391749 Since==999981 Cycles==0.268743
val=1 at us==323391731 Since==999981 Cycles==1.268724
val=1 at us==324391712 Since==999982 Cycles==2.268706


Nice, I thought, this will fix the issue, but...

Code:
12922.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12923.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12924.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12925.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12926.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12927.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12928.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12929.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12930.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12931.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12932.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12933.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12934.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12935.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12936.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12937.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12938.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12939.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12940.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12941.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12942.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12943.921	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12944.922	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99
12945.922	From Teensy MIDIx16/Audio Port 1	Note On	1	C4	99

Nearly identical.

I will take your idea and leave it running for a bit to see if it bounces back at some point...
 
I have looked at IntervalTimer and elapsedMicros and on the 4.1 I am using here I get around 3ms drift in a minute.

I looked at the output of AnalogWrite and get the same error in frequency.

So I'm guessing this is just the crystal used being inaccurate? 1 part in 20,000 seems pretty bad though?

That's 50ppm, which is believable for a low tolerance crystal, typical ones are spec'd at +/-100, +/-50,
+/-30, +/-20ppm or so. For better accuracy a TCXO is needed (temperature compensated crystal oscillator).

See if you get noticably different results with the pcb actively cooled perhaps?
 
I ran the code of msg #3 on a Teensy 4.1. Here's what my frequency counter with GPS disciplined 10 MHz reference sees on pin 23.

freq.jpg

Error of 0.007169 out of 500 is 14.3 ppm. That's well within the crystal's 30 ppm spec.

I also tried with the usbMIDI.sendNoteOn(60, 99, 1) commented out. Exactly the same frequency measured both ways.
 
Hi Paul and Mark,

Thanks for the info and for looking into this.

The timings I see on the generated waveform are:

Teensy 4.0: 999.996

Teensy 4.1: 999.987

Teensy 4.1 Clocking from from 1Khz function generator by interupt: 1000.00001

All these are actually pretty good.

The issue seems to be on drift from the USB midi, for each of these different clocks the drift stays around the same at 1ms per 20 seconds.

I have looked at drift from a couple of midi sequencers here to check it isn't anything to do with the Mac that is logging the metrics I am basing this on.

One gives 1ms drift per 4 seconds and the other 1 ms per 60 seconds.

I have a Cirklon sequencer that is meant to be the bees knees when it comes to midi timing unfortunately this is elsewhere, I'm going to go get it tomorrow and do some tests with that as well to see what that gives.

From the existing two sequencers I have tried though I'm guessing we cannot totally blame the Mac as one of the sequencers is running with 3 times less drift.

I also have another 4.1 here, tomorrow I will solder the headers and see what the timing is like on that, to be honest though I'm not sure this is totally down to the timing as I am seeing similar drift on the USB midi irrespective of the small differences in the frequency...
 
Increasing the count in the Intervall timer callback, and setting the count to 0 in the main loop looks like dangerous practice.

count should at least be declared volatile
 
Can you guarantee that there are no conditions, yield function activity, handling of USB interrupts, timers or other background activities, so that the main loop handling of (count==1000) condition is finished before the next timer interrupt arrives ?? Otherwise you have a race condition and will lose at seemingly random times 1ms by setting count to 0 after the timer interrupt has ticked on.

You could try move setting count = 0 up before sending the midi meassage
 
Here you go, different code same problem:

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

AudioSynthSimpleDrum     drum1;
AudioOutputUSB           usb1;
AudioConnection          patchCord2(drum1, 0, usb1, 0);
AudioOutputI2S           out1;

void togglePinA9()
{
  static int val = 0;
  digitalWrite(PIN_A9, val);
  val = !val;
}

void setup() {
  while (!Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  pinMode(PIN_A9, OUTPUT);
  pinMode(PIN_A8, INPUT_PULLDOWN);
  
  AudioMemory(12);
  drum1.frequency(1200);
  drum1.length(150);
  drum1.secondMix(0.0);
  drum1.pitchMod(0.0);
}

void loop() 
{
  static bool           needNoteOff  = false;
  static elapsedMicros  since        = 0;
  const int             duration     = 1000000;
  const int             durationDiv2 = duration/2;

  if(since >= durationDiv2/2 && needNoteOff)
  {
    usbMIDI.sendNoteOff(60, 99, 1);
    needNoteOff = false;
  }

  if(since >= duration)
  {
  	uint32_t ss = since;
  	float cc=ARM_DWT_CYCCNT;
    since -= duration;

    usbMIDI.sendNoteOn(60, 99, 1);
    usbMIDI.send_now();
    needNoteOff = true;
    drum1.noteOn();
    
    Serial.printf( "us==%d Since==%d Cycles==%f\n", micros(), ss, cc/F_CPU_ACTUAL );
  }
}

p.s. Reading back here my tone was not good, sorry about that and thanks for the help.
 
Last edited:
So with that code above and recording the audio and midi, the midi drifts but the audio doesn't:

At the start of test:

screenshot_664.jpg


A few minutes in:

screenshot_665.jpg
 
Actually it is even worse, the audio is drifting backwards in time while the midi is drifting forward in time:

screenshot_666.jpg
 
Shot in the dark, discard any eventual incoming MIDI messages:

Code:
  // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read()) {
  }
 
Thanks, I will stick that in and see if there is any difference.

The midi looks like it may be behaving better than the audio though!
 
Status
Not open for further replies.
Back
Top