Phase Shift of Pass Through Signal

Status
Not open for further replies.

Bob Larkin

Well-known member
With the Audio Network Analyzer project, I found I needed to recalibrate the phase of the ADC-DAC path. This is with the PJRC SGTL-5000 Audio Board (and a teensy 3.6). I have explored this issue enough to understand the limitations this presents. The details are here in case any one else is doing phase-sensitive work.

NOTE - Nothing here should be of any interest to those doing audio processing for the ear. The ear will never notice a few tens of microseconds of shift!

First the results, and then the test setup. A signal is introduced through both line inputs of the ADC. This is directly sent to the corresponding DAC outputs of the audio board. The delay of this path, for the default 44117.65 sample rate is about 6350 microseconds. If the power is removed and reapplied, the delay through this path is often the same, but not always. A trial of 5 times showed 4 times for 6352 microseconds and once at 6358 microseconds. If the sketch is reloaded, the results are basically random, over a range of about 35 microseconds. Note that 35 microseconds is most of the 50 microsecond period for a 20 KHz signal, and for what I am doing is a big deal! BTW, both L & R channels track the shifts together.

That is the question of start-up, and for me just says that calibration is needed after things are powered down. This is OK. A second issue is that I use a combination of a 44.117 kHz and 100 kHz sample rates. I was worried that changing the sample rate would change the delay. As best that I can test it, it does not ever change the delay. My experiment changed the sample rate back and forth thousands of times with always consistent results for both rates.

And the method of testing this. I hooked up a HP33120A waveform generator, set to produce a square wave. This instrument is crystal controlled and can be adjusted in 0.01 milli-Hz steps. This went into the ADC inputs, and also was used to trigger an analog 'scope. The output of the selected DAC went to a second channel of the 'scope. The frequency was adjusted until the half-voltage point of the rising DAC output exactly lined up with the rising edge of the square wave. By choosing the lowest frequency for which this will occur the period of the square wave is the delay through the path.

The sketch is pretty simple, but included anyway:
Code:
// passThruSync.ino   Bob Larkin 11 Feb 2017 
// Check synchronism of ADC and DAC.  Explores two issues for a signal using a ADC-DAC pass through.
//  1 - Is the phase shift the same at multiple startups or reloads?
//  2 - After changing sample rates, and back, does the phase shift return to the same value?

#include <Audio.h>

// Variable sample rate from Frank B., see
// https://forum.pjrc.com/threads/38753-Discussion-about-a-simple-way-to-change-the-sample-rate
// As pointed out by Frank, the various routines, like waveform generator, are calibrated
// for 44.11765 and need to be corrected when other sample rates are used, e.g.,
//      waveform1.frequency(AUDIO_SAMPLE_RATE_EXACT/samplefreq*freq); 
// Note Teensy 3.6:  F_CPU == 180000000, F_PLL == 180000000
// Returns exact sample frequency
double setI2SFreq(int freq)
  {
  typedef struct
    {
    uint8_t mult;
    uint16_t div;
    } __attribute__((__packed__)) tmclk;
  const int numfreqs = 5;
  // 44117 is nickname for 44117.64706
  const int samplefreqs[numfreqs] = { 44100, 44117, 48000, 96000, 100000 };
#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{151, 214}, {12, 17}, {96, 125}, {192, 125}, {0, 0} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{98, 625}, {8, 51}, {64, 375}, {128, 375}, {0, 0} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{147, 1250}, {2, 17}, {16, 125}, {32, 125}, {0, 0} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{205, 2179}, {8, 85}, {64, 625}, {128, 625}, {0, 0} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{49, 625}, {4, 51}, {32, 375}, {64, 375}, {0, 0} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{42, 625}, {8, 119}, {64, 875}, {128, 875}, {0, 0} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{196, 3125}, {16, 255}, {128, 1875}, {219, 1604}, {32, 225} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{147, 2500}, {1, 17}, {8, 125}, {16, 125}, {0, 0} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{98, 1875}, {8, 153}, {64, 1125}, {128, 1125}, {0, 0} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{147, 3125}, {4, 85}, {32, 625}, {64, 625}, {0, 0} };
#endif
  for (int f = 0; f < numfreqs; f++)
    {
    if ( freq == samplefreqs[f] )
      {
      while (I2S0_MCR & I2S_MCR_DUF)  ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return  (double)F_PLL * (double)clkArr[f].mult / (256.0 * (double)clkArr[f].div);
      }
    }
  }

// Audio Objects
AudioControlSGTL5000     audioShield;
AudioInputI2S            i2s1; 
AudioOutputI2S           i2s2;
AudioConnection          Connect1(i2s1, 0, i2s2, 0);
AudioConnection          Connect2(i2s1, 1, i2s2, 1);
  
void setup()
  {
  AudioMemory(25);          // Way below this
  // The next line will not always give the same phase shift to a pass through
  // signal.  Restarting is usually, but not always, the same.  Reloading the
  // sketch give random shifts.  The range of shifts is around 35 microseconds.  For a
  // 20 kHz signal, this is a major part of a cycle.
  setI2SFreq(100000);       // or 44117, etc
  delay(100);
  AudioNoInterrupts();      // Use to synchronize all audio
     // Enable the SGTL5000 audio CODEC
     audioShield.enable();
     // Headphone volume, 0.0 to 0.8 is undistorted over full input ADC range
     audioShield.volume(0.0);
     audioShield.lineInLevel(2, 2);     // 2.2 V p-p max
     audioShield.lineOutLevel(19);      // 2.2 V p-p FS
  AudioInterrupts();
  }

void loop()
  {
  // Test multiple changes of sample rate for shifts in phase.  24 hour tests
  // of the following has ALWAYS given the same phase shift for both rates.
  //  Comment all out for single rate
  setI2SFreq(100000);
  delay(1000);
  setI2SFreq(44117);
  delay(1000);
  }
 
Wow, you must have some really impressive gear to make these precise delay measurements. I'm curious to know what you're using?
 
Hi Paul - Here is a bit more on the experiment to supplement what was in the original post. Really not all that fancy.
DelayTest558.jpg
The HP waveform generator uses DDS synthesis and is thus tied to an internal 10 MHz crystal oscillator. This makes using the period of a full square wave as the reference both exact and, for practical purposes, jitter free. The 'scope is my long-time friend, "Old Faithful," a Tek7704. The scope could be almost anything that had two traces, though. All we want to do is make the rising edge of square wave line up with the middle of the DAC output waveform by changing the frequency of the square wave. When this is done, you don't see much, as half of the DAC waveform is off the screen. So it looks like:
DelayTestGraph559.jpg
In this case the HP says the frequency 356.656 Hz with a period of 2803.8 microseconds (this is at 100 kHz sample rate). The trace is at 1 microsecond per division, so the accuracy of the measurement really must be a part of a microsecond. And, of course, the scope shows the DAC output from an input that occurred a full square-wave cycle earlier. One more shot shows the full waveform of the DAC output. This was achieved by changing the generator frequency by a few Hz. This picture is from a few days ago, and may not be in full agreement with the 'scope picture just above. For sure the scale has changed.
DelayTestGraph556.jpg
Unrelated to all this is the obvious pre and post rise ringing. Nobody shroud be concerned---this is good! The symmetrical "pre" and "post" means it is a linear phase FIR filter with symmetrical coefficients. The ringing is just a result of the ADC/DAC fast frequency cutoffs.
 
6 ms Delay ?

Where does this 6ms delay come from ? I wanted to make a delay for loudspeakers with control from 1-100ms delay...so i never get < 6ms delay if the teensy audio system already have >6ms delay. Is there a way to have < 6ms delay from input to output ?
 
At 44.1Khz, 128 samples is is 2.9 ms. For K66 the AUDIO_BLOCK_SAMPLES is set to 128, and there is one buffer each in the input and output I2S modules. A brief look at the code suggest the buffer is divided into two, and the DMA engine ping pongs between them, sending the data after it is filled. This suggests a minimum delay of two half-buffers = 128 samples total. (Note: I just took a quick 10 second look at the I2S code, I could be totally interpreting it wrong).

That would explain approximately half of your 6 ms delay. There will also be at least few dozen samples of delay in the ADC and DAC filters. That explains only roughly 3 ms of delay.

I'm confused where the other 3 ms came from, (perhaps I'm misinterpreting the delay causing by the DMA transfers in the I2S modules) but either way, changing the sampling rate to 100 KHz should definitely cause latency to go down. Really confused why it's still 6 ms after changing the sampling frequency.
 
Thank you very much for the explanation ! I tried to adapt the AUDIO_BLOCK_SIZE to 64, 32. This reduces the delay to minimum of about 2ms when using the 32 Value.
But... as i am using the delay functionality, the real delay was about 82ms and not the 100ms which i programmed the delay.
The numer of used AudioMemory increases when using smaller audio block sizes. What i found is, that there is a maximum value of about 118 which is used, even when i set the AudioMemory(200). This maximum value of 118 is somehow connected to the DELAY_QUEUE_SIZE 117 which is defined in "effect_delay.h" in the Audio library. I do not know how to adapt this in a way, that the audio library will use more AudioMemory than 118.

I tried with normal AUDIO_BLOCK_SIZE of 128 and also get a maximum of AudioMemory 118. This gives me a maximum Delay of about 330 ms and NOT the 425 ms delay which is written for the delay element in the Audio System Design Tool page.

We want to make a professional product with the Teensy 3.2 and the audio board for a delay which we want to use for active loudspeaker system. Now we will also test the small Sure ADAU1701 DSP board for the delay functionality. I would prefer the Teensy solution but then i would need a solution with about 2ms delay from input to output.
 
Opps, this is a bug in the audio library. It has a fixed maximum delay that doesn't increase on the newer boards, and doesn't scale properly for alternate block sizes.... an oversight on my part.

Here's a fix. Put this into hardware/teensy/avr/libraries/Audio in your copy of Arduino (replacing the old file). If using a Mac, control-click Arduino, select Show Package Contents, then look for the hardware folder inside Contents/Java.
 

Attachments

  • effect_delay.h
    3.9 KB · Views: 155
Opps, this is a bug in the audio library. It has a fixed maximum delay that doesn't increase on the newer boards, and doesn't scale properly for alternate block sizes.... an oversight on my part.

Here's a fix. Put this into hardware/teensy/avr/libraries/Audio in your copy of Arduino (replacing the old file). If using a Mac, control-click Arduino, select Show Package Contents, then look for the hardware folder inside Contents/Java.

I have tried your fix and replaced the old file. I am using Windows 7. The fix does increase the maxium usable amount of AudioMemory used, but when i change the AUDIO_BLOCK_SIZE to a value of 64, the sound after the delay gets corrupted. It seams as if there are blocks of audio missing, even when the delay time is short like 20ms. When i use the old value of 117 for the DELAY_QUEUE_SIZE it is working with 64 AUDIO_BLOCK_SIZE.
I am using the latest Teensyduino.
 
Status
Not open for further replies.
Back
Top