IntervalTimer is not precise

Status
Not open for further replies.

lehtym

New member
Hello,

I am a newbie and I'd like to ask for a piece of advice.

I am trying to play a binary sequence at a sample rate of 32768 Hz.
Manual says that IntervalTimer is for precise timing control, so I tried to use it.
In the example code below the sequence {1 0 1} is repeated once a second.
This is just to simplify illustration: in real application I need to play long MLS (M-sequence).

Code:
int pin = 9; // output pin
int L = 3;   // length of sequence
boolean seq[3] = {1 0 1}; // sequence to be played
int Fs = 32768; // sampling rate

IntervalTimer t;

volatile int ind = 0;

void next_digit(){
  if (ind<L){
    digitalWrite(pin,seq[ind]);
  }
  else if (ind<=Fs){
    digitalWrite(pin,0);
  }
  else{ 
    ind=-1;
  }
  ind++;  
}

void setup() {
  pinMode(pin, OUTPUT);
  t.begin(next_digit,1./Fs);
}


void loop() {
}

In a perfect world I'd expect to see at output pin a signal looking like this:
ideal.jpg
I checked the real life output by connecting oscilloscpope to the ouput pin and recording waveform a few times.
What I saw is like this:
real.jpg
And here is zoom of the zone around the first drop:
real_zoom.jpg
As one can see, sometimes timing is off up to about 3 us which is 10% of my sampling period and is not acceptable.

Am I doing something wrong?
How can I get more precise timing?

Thank you!
 
Gave it a go then and for what it's worth t.priority(number); with lower numbers does reduce the jitter but doesn't suppress it fully, though may start conflicting with other timing critical functions depending on what the remaining code does.

In terms of being able to stream data with high precision I'm thinking the next step is to utilise one of the hardware FIFOs to allow data to be lined up and reducing the CPU impact to just coming back often enough to keep the buffer full. The default serial ports will insist on a start bit and character gaps, unsure if this is configurable. An alternative would be to look at the OCTO library which uses DMA clocked from a PWM out to achieve a custom bit chain out which I think would be the 'right' way to achieve this, but suspecting the coding overhead for that would be non trivial.

You could also do similar thing with hardware by using a high precision oscillator clocking shift register. Use one pin to interrupt the code, with an ISR that outputs the next bit to the shift register. Possibly with a very small delay in there to ensure you don't toggle the output while the shift register is clocking.

hopefully tweaking the interrupt priority gets you 'close enough' to real time. without needing the more adventurous options above. Was surprised by the amount of jitter since 3us is quite a lot of commands running in something with an empty loop.
 
I would check that your sample rate is an exact multiple of the clock. I forget, but I think the clock is not a power of 2.
This was discussed early on with the IntervalTimer. See if you can go back and find that thread. I would do it by ticks of the clock, and see what that looks like.
 
Did you try the priority function? If you don't raise the priority, your interval interrupt can be blocked by others.

Some code temporarily disables all interrupts. No matter what the priority, if your program is causing such code to run, it'll delay the interval interrupt.

If you need low-jitter waveforms, the PWM hardware (eg, analogWrite & analogWriteFrequency) is the best way.

If you need the ADC to sample on a precise schedule, the triggering it with a hardware timer is the best way. Trying to run code to do it will always add some jitter.
 
I'm trying to reproduce this problem, but the code you posted does not even compile. This line is a syntax error, missing commas.

Code:
boolean seq[3] = {1 0 1}; // sequence to be played

Even when I fix that, I still get no output.

This line is obviously wrong:

Code:
  t.begin(next_digit,1./Fs);

The interval is specified in microseconds, and Fs is 32768. IntervalTimer can not implement a 30.5 picosecond interval!

If you'd like me to investigate, please post the actual code you're running on Teensy. It must be a complete program I can copy into Arduino and run on a board here, to reproduce the problem you're seeing.
 
I modified the code to this:

Code:
  t.begin(next_digit, 1000000.0/Fs);

Then I set my scope to 5 us/div, 30 us delay from rising edge trigger, and infinite persistence. I let it run for a few minutes. I got this:

file.png
(click for full size)

Again, I can't reproduce the problem.

My guess is you tested with a very different program, which was probably generating quite a lot of other interrupt activity, with interrupts having a higher (lower numerical setting) priority than the IntervalTimer object you're using, which defaults to 128 if you don't use the priority() function.
 
I did even more experimenting. After observing for longer times, I did see the Systick interrupt cause the waveform to jitter by about 1 microsecond.

Writing to the SCB_SHPR3 register to lower the Systick priority, and of course IntervalTimer's priority function to make it the highest proirity, solves that.

But still quite a bit of jitter happens. I managed to solve most of it by using FASTRUN on the interrupt routine, and replacing digitalWrite() with digitalWriteFast(). When executing code from flash memory, the timing can vary, because the flash is slow and the timing depends on whatever is in the small cache memory between the flash and processor. The RAM runs without any delays.

There's still a jitter of approx 40 ns, which I believe corresponds to 4 processor cycles at 96 MHz. I still can't figure out exactly what's causing that...

Anyway, here's a copy of the code with the lowest IntervalTimer to digitalWrite jitter.

Code:
#define PIN  9   // output pin

int L = 3;   // length of sequence
boolean seq[3] = {1, 0, 1}; // sequence to be played
int Fs = 32768; // sampling rate

IntervalTimer t;

volatile int ind = 0;

FASTRUN void next_digit(){
  if (ind<L){
    digitalWriteFast(PIN, seq[ind]);
  }
  else if (ind<=Fs) {
    digitalWriteFast(PIN, 0);
  }
  else {
    ind=-1;
  }
  ind++;  
}

void setup() {
  SCB_SHPR3 = 0x20200000;  // Systick = priority 32 (defaults to zero)
  pinMode(PIN, OUTPUT);
  t.priority(0);
  t.begin(next_digit, 1000000.0/Fs);
  t.priority(0);
}


void loop() {
}

Edit: Here's what I see with this code. Notice the time scale is only 20 ns/div

file-2.png
 
Last edited:
Well, as it turns out, the teensy 3.2 is duplication the code nicely. It compares to the original handset really well..
 
Status
Not open for further replies.
Back
Top