Reset audio board codec SGTL5000 in realtime processing

Status
Not open for further replies.
Hi Brian,

sorry for being silent for so long, my professional work has been taking a lot of my time recently! Seems you made a lot of progress with your SDR! Very impressive screenshots and a nice analog S-meter emulation ;-). BTW, didn´t know how to make screenshots back in 1972 ;-).

@twinpeaks: yes, you are absolutely right, that is a bug in the code I will have to fix. Also, the determination of the phase difference for twinpeaks detection does not seem to be as accurate as I want it to be. Thats odd, because the automatic IQ correction works very well with mirror rejection > 50dB . . . will have to investigate that.
And I will think about the sgtl5000.cpp mod and will describe it in the github wiki, so people interested in building the Teensy Convolution SDR will know about it.

@transformer: good that you tested the frequency response.

@QSD and caps etc.: Yes, you are right, the sampling caps have to be smaller in order to allow for a 96kHz/192kHz bandwidth and additionally the lowpass filtering of the OPAMP has to be altered too. Have a look here, where I calculated that for the OLD version of the Teensy phasing SDR (have a look at page 9 of the pdf): https://github.com/DD4WH/Teensy-SDR-Rx/blob/master/Teensy.SDR.Documentation.DD4WH.pdf
OR HERE: https://github.com/df8oe/mchf-github/wiki/How-does-your-mcHF-software-DSP-work

@antenna: your signal levels seem quite reasonable. For investigating internal noise, consider putting 50 Ohms to the input before measurements, not a short circuit or an open circuit. Your wire antenna does not require some particular type of wire, every wire type is fine for a longwire antenna. However, the QSD expects 50 ohms and a longwire will typically have a higher impedance, so a preamp for impedance matching or a passive antenna tuner would be perfect and could improve your reception a lot. When it comes to magnetic loop antennas with a single loop of 70cm or 1m, the skin effect calls for thick loop elements like copper tube or a thick coax cable or just a thicker copper wire. Then you need a special very low impedance amp for that and you can have exceptional performance for receive with that setup. As you mentioned, that active loop is a wideband loop antenna, which is not selectively tuned. You can also build a narrowband selective loop antenna, just parallel a single turn loop with a variable capacitor and you have the bandfilter effect already with the antenna.

Would you mind sharing your source code? I would be very interested in potentially adding some waterfall code to the Teensy Convolution SDR ;-).

All the best, have fun with the Teensy Convolution SDR,

Frank
 
Hi Frank:
Glad to hear from you. You mention professional work so I guess you are employed- I have been retired for 6 years now- more free time but fewer brain cells !
If you perfect the twin peaks balance routine, and end up using the codec reset function, I can write a short routine that does the SGTL5000 reset as a function in the main SDR program. This will eliminate the need for others to play around with their SGTL5000.cpp and .h files. Its better not to depend upon forked audio lib. files, as one's additions/mods get lost when Paul pushes out a newer version of the library.
I like the info you provide in that SDR pdf file you gave me- didn't know that existed. On my own, the smaller values of the 4 caps in the QSD/amplifier that I came up with were close to the ones you selected.
But I see in this article that you use a preselector- that is what I figured I was missing/needed. I can wind the coils, but finding air variable caps is harder - normal distributors like Digikey etc. don't sell these anymore! I have a great "junkbox" but no HF RF components- haven't for many years. But, hams can get them, so I guess I can order them from somewhere too. In the meantime, I have to do without. ( I do have lots of small reed relays and an assortment of varicap diodes from 10- 100pf (max capacitance), so that might be another way to go)
My 25M end-fed antenna outdoors is indeed hi-Z. I had a MOSFET preamp between the antenna and the 1:1 QSD RF transformer, to handle this. I wasn't sure the MOSFET preamp was working properly- the nice signal I was feeding in from the Si5351/step attenuator was looking terrible once it hit the transformer (on my scope). Even so, the output of the I/Q QSD looked fine- as the image I sent you shows.Today, I decided to exchange this arrangement to a T-50 toroid with 30 turns primary, 3 turns secondary- and eliminate the MOSFET preamp altogether. I'll see if this works any better tonight when the signals are coming in.
Yesterday, I fed my QSD output to my PC computer's audio input/ SDR software last night. It worked about the same as your Teensy SDR software: i.e. I wasn't getting any better signal reception.
I looked up the AAA-1 loop antenna you quoted, and could see that a special lo-Z preamp would be needed for that.

Re the waterfall display. I'd be glad to send you my source code. The waterfall routine is very short- but that is because the FT800 display is doing all of the work. The waterfall display is a bitmap image, which the FT800 handles natively. In the SDR main "loop" routine, I check a loop counter and about 5 times per second I do my entire screen display update. I send out the same 256 8-bit signal level values that you use for your spectrum display. The FT800 has a high-level bargraph function that will provide me with virtually the same spectrum display you provide (minus all of the code you wrote to draw it on the ILI9341). But, I can optionally send those 256 values into a bitmap RAM array in the FT800. At each display update iteration, I adjust a pointer to put these 256 "new" values into the ram location corresponding to the next line of the waterfall. I also have to adjust the bitmap starting RAM address pointer each iteration.After the waterfall has filled its full window, I perform a memory copy function, native in the FT800, to move the whole block (90 lines X 256 values) to another section of the bitmap RAM, and move the write and read pointers accordingly. All in all, it gives a gradually moving display- exactly like the pro SDR software.
I have a Palette function in the FT800 which allows me to map the 8-bit signal level values into a 16 bit RGB color pallete for the color waterfall.
So, there is virtually no additional Teensy calculation overhead to do the waterfall- beyond sending out the 256 sig level values that I would do for the standard spectrum display (using the FT800 Bargraph function).
These high-level functions are the reason I wanted to use the FT800 in the first place (aside from having a few of them handy having written a series of magazine articles about them). What you see in the picture I sent takes a lot less code than you needed for the ILI9341, and provides touch screen functions too. Of course, you selected the most cost-effective TFT screen. FYI my whole SDR, with FT800 uses 250 ma- I believe it dropped to about 140 ma with the display off.
The waterfall routine is below.( the first few lines remove the artificially high values at the beginning/end) Can I email you my full SDR source code? I don't see how to attach a file in the forum.
Thanks for all of your help/attention
Brian
void FT800_drawWaterfall (int xoffset, int yoffset, int width, int height) {
FTImpl.Tag('S');
FT800_drawRect(xoffset-1, yoffset-1, xoffset + width+2, yoffset + height+2, 1);
FTImpl.Cmd_Memwrite(WaterfallPointer, 256UL);
spectrum[0] = spectrum[2];
spectrum[1] = spectrum[2];
spectrum[255] = spectrum[253];
spectrum[254] = spectrum[253];
for (int i = 0; i < 256; i = i + 4) {
union
{
uint32_t WaterfallData;
uint8_t s[4];
};
s[0] = 255-spectrum;
s[1] = 255-spectrum[i + 1];
s[2] = 255-spectrum[i + 2];
s[3] = 255-spectrum[i + 3];

FTImpl.WriteCmd(WaterfallData);
}
p_bmhdr = (Bitmap_header_t *)&Waterfall_Header[0];
FTImpl.BitmapSource(WaterfallGofs);//set the source address of bitmap to starting address of graphis ram
FTImpl.BitmapLayout(p_bmhdr->Format, p_bmhdr->Stride, p_bmhdr->Height);//set the layout of the bitmap
FTImpl.BitmapSize(FT_NEAREST, FT_BORDER, FT_BORDER, p_bmhdr->Width, p_bmhdr->Height);//set the bitmap size
FTImpl.Begin(FT_BITMAPS); // start drawing bitmaps
FTImpl.ColorRGB(255, 255, 255);//set the color of the bitmap to white
FTImpl.Vertex2ii(xoffset, yoffset, 0, 0);
WaterfallPointer -= 256;
WaterfallGofs -= 256;
if (WaterfallPointer == 0) {
WaterfallPointer = 0x5A00;
WaterfallGofs = 0x5A00;
FTImpl.Cmd_Memcpy(0x5A00, 0, 0x5A00); // move old data block into new display buffer area
}
}
 
Hi Brian,

just a very quick update:

1. IQ correction and automatic check: seems to be working much more reliably now, I had to allow for much more time for the parameters to settle, because there is a lowpass filter in the algorithm. I have been running it for some days now and the number of false positives and false negatives has significantly gone down, although it is not perfect . . . latest version is in my github

2. I think I really have to try the FT800, will give it a try in my next version of the Teensy Convolution SDR, which I want to be a transportable pocket version ;-). Would be very interesting to have a look at your magazine papers on the FT800! Are they available online?

3. I was using a manual preselector in my old Teensy SDR. In my Teensy Convolution SDR I use relay-switched lowpass filters (nine-th order elliptic LP) using the kit by QRP labs. https://www.qrp-labs.com/ultimatelpf.html
The philosophy for the elliptic filters has been taking from the Fifi-SDR preselector: http://www.box73.de/download/bausaetze/DK5DN_FiFi-Preselektor.pdf
However, I do not use pin diode switching (because of the possible IMD problems), but use the relay kit by QRP labs instead. I also use a transistor in front of each relay in order not to crash the Teensy pins (the relays use 28mA each . . .)

4. Thanks for the waterfall code! I see that it is only usable with that particular type of display. I will think further about the waterfall, if I find some time . . .

5. power consumption of the FT800 seems to be very similar to my much smaller ILI9341! So there seems to be no more reason not to use the FT800 ;-).

Have fun!

Frank DD4WH
 
Hi Frank: Thanks for the latest info. Here is the link to my FT800 articles. They were originally published by a Slovenian friend Jure Mikeln (a HAM like you) in his magazine Svet Elektronike. That was in Slovenian, but a translated version is on SE website at:
http://www.svet-el.si/english/index.php/brians-corner
I should note that I wrote these just after the displays came out, and FTDI had written a "C" Hardware Abstraction Layer for the FT800 chip and wrapped it in a driver which you could access from Arduino (but not too easily!). The article tried to explain how to use the display, using this driver library.
Here in N.A., we say that something that is complicated/ hard to understand is "convoluted" (not to be confused with your math convolution routines!) . That early FTDI library was certainly "convoluted". Recently they re-wrote the driver from scratch, and it is now easier to understand and use with Arduino. Still, the whole "display list" concept of this controller is very different from your normal TFT display like the ILI9341. However, once you get used to it, it is very powerful as you can basically manipulate the graphic "engine" in the FT800 to off-load graphics chores, such as what I have done with the waterfall. While the waterfall is certainly possible with the ILI9341, it would need a lot of SPI transfers, and the DMASPI routines written by others on the forum, would probably be necessary.
My FT800 version of your TeensySDR are up on Github, if you want the source code, at
https://github.com/bmillier/TeensySDR_FT800
All hardware apart from the display is identical to yours, although some of the encoders/switches are no longer used. The QSD is defined as 25 MHz clock on Output 0.
When I swapped out my MOSFET preamp feeding 1:1 toroid RF coil with a 10:1 toroid RF coil and NO preamp, it didn't help anything- probably made it worse although conditions vary so much from night to night that its hard to say. I guess the MOSFET circuit was working OK. So, I broke down and ordered in an Elektor QSD RF front end- so I can see if there is something wrong with my hand-wired design, or not. I think I will build at least 1-2 bands of that preselector you linked to.
Music is one of my hobbies, and a guitar player friend of mine has been anxious to see if I could use those Teensy convolution techniques for guitar cabinet/speaker modeling. Its done in commercial software on the PC, so its possible with enough knowledge/MCU power. This is one of the reasons I built your TeensySDR- so I could ease my way into the DSP routines such as you are using. This is what I am playing with now (until my Elektor SDR board arrives from NL).
Thanks for all of your advice.
 
Frank & Brian,
I know I'm late to the party, but I want you to know that I am having the same problem with corresponding samples in data blocks from the I2S input apparently getting out of sync. Like you I'm working on phase/amplitude correction from the quadrature inputs from my completely home-brew SDR using the T3.6, and like you I am using the Moseley & Slump algorithms. However, I am not trying to do it in real-time because I don't see the need. I have developed two audio blocks, one of which will monitor the I/Q inputs and return the calibration constants c1 and c2, and a separate block that sits in the processing chain and corrects the amplitude and phase errors. I use a strong rf sinusoidal input to the SDR to find c1 and c2.

Like you I am finding random switching between two modes in the operation of the monitoring code:
a) It is stable and reports a gain imbalance of about 5%, and a phase error of about 6 deg. independent of the baseband input frequency, but it will randomly switch to a mode where
b) The reported phase error is proportional to the input frequency from the SDR, with a slope of 8 degrees/kHz. When this happens I have to turn the Teensy off and on again to clear it.

Now, the linear slope of the phase error is important because a pure time delay produces this effect: sin(w(t+T)) = sin(wt + wT) and the phase phi = wT where T is the delay.

If you do the math you will see that a slope of 8 degrees/kHz corresponds to a delay of 1/45*10^-3 secs - which is the sampling interval for the audio board (well it's actually 1/44.1*10^(-3) secs). My conclusion is that this confirms the I2S inputs (I and Q) are getting out of step by one sample, which is what you suggested.
A couple more bits of evidence for this conjecture:
1) With a Hilbert SSB demodulator my SDR has always shown bimodal behavior: sometimes the unwanted sideband rejection was much better than others. (I had always suspected an intermittent problem in my QSD wiring).
2) A couple of weeks ago I found that if I changed the Hilbert delay line by one time step, the sideband rejection got much better! I suspected my code writing prowess and spent a whole day trying to understand why - now I understand it!

Is there any more progress on a fix? It seems to me that this is a SGTL or audio board bug that might be escalated to Paul's attention if we can define it more clearly.

I'll post simple demo code with the monitoring block tomorrow. It runs fine with the I and Q generated by AudioSynthWaveformSine blocks, it appears to fail only with I2S input.
 
Last edited:
As promised here is the code to demonstrate the bug.
Main sketch:
Code:
//  ------------------------------------------------------------------------------------------
//   TestPhaseError.ino     Measure Teensy Audio board "bug" in which I2S inputs get randomly
//                          out of sync by one sample after a program reload.  The problem is
//                          resolved by powering down and up.

//    This program is part of a quadrature IQ channel amplitude and phase correction scheme in
//    QSD based SDR systems using the Teensy 3.6 and the Audio Library.   The code detects amplitude
//    and phase errors from two sources:
//    1) A quadrature signal generated by a pair of AudioSynthSine generatures, and
//    2) An externally generated quadrature pair input through the I2S inputs.
//    It is found that the internally generated pair is always accurately reported, while the
//    external source randomly misbehaves after a program reload.   It can be fixed by by powering
//    the Teensy down and up again.
//    The problem shows up as a phase error that is proportional to the frequency of the input,
//    corresponding to a pure time delay between the two I2S inputs.   The slope is 8.1 deg/kHz
//    corresponds to a delay equal to a single sample step at Fs = 44.1kHz.
//
//    For testing I have been using sinusoids with frequency 1kHz, and a phase error of 10 deg.
//
//    Note:  You cannot force the error to occur.   It might take several reloads.
//      
//    Author: Derek Rowell
//    Date:   June 23, 2017
//        
//  ------------------------------------------------------------------------------------------

#include <Wire.h>
#include <Arduino.h>
#include <Audio.h>
#include <EEPROM.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "AudioIQerror.h"

  AudioInputI2S            I2Sinput;
  AudioSynthWaveformSine   sine1;         
  AudioSynthWaveformSine   sine2;         
  AudioIQerror             errorI2S;
  AudioIQerror             errorSine;
  AudioControlSGTL5000     audioShield;
//---
  AudioConnection c1(sine1, 0,   errorSine,0);
  AudioConnection c2(sine2, 0,   errorSine,1);
  AudioConnection c3(I2Sinput, 0, errorI2S, 0); 
  AudioConnection c4(I2Sinput, 1, errorI2S, 1); 
//-------------------------------------------------------------------------
void setup() {
//--
  Serial.begin(57600);
  while(!Serial);
//
  AudioMemory(20);
  audioShield.enable();
//---
  AudioNoInterrupts();
  sine1.frequency(1000.);
  sine2.frequency(1000.);
  sine1.amplitude(0.5);
  sine2.amplitude(0.5);
  sine1.phase(0.0);
  sine2.phase(80.0);
  AudioInterrupts();
}                           

//-----------------------------------------------------------------------
void loop(){ 
  Serial.print(" Sine Input:  Amplitude ratio r = "); Serial.print(errorSine.gainRatio(),4);
  Serial.print("\tPhase error (deg) = "); Serial.println(errorSine.phi()*180/3.14159, 4);
  Serial.print(" I2S Input:   Amplitude ratio r = "); Serial.print(errorI2S.gainRatio(),4);
  Serial.print("\tPhase error (deg) = "); Serial.println(errorI2S.phi()*180/3.14159, 4);
  Serial.println();
  delay(500);
}
AudioIQError.h
Code:
//-------------------------------------------------------------------------------------
//                                 *** AudioIQerror.h ***
//
// AudioIQerror:  A Teensy audio library  compatible block processor to report quadrature
//               (IQ) amplitude and phase errors in quadradure signal processing
//               applications.
//               Based on the algorithm described by Mosely and Slump, download at:
//                             http://doc.utwente.nl/66726
// 
// Author:  Derek Rowell
// Updated: June 22, 2017
//  
//--------------------------------------------------------------------------
#ifndef AudioIQerror_h_
#define AudioIQerror_h_
#include "AudioStream.h"
#include "Arduino.h"
  class AudioIQerror : public AudioStream {
    public:
      AudioIQerror() : AudioStream(2, inputQueueArray) {}
//------------------------------------------------------------------------
      virtual void update(void);
      void enable(void) {             // Enable processing
        enabled = true;
      }
//-------
      void disable(void) {            // Disable processing
        enabled = false;
      }
//-------
      float gainRatio(void) {         // Return IQ gain ratio (g in M&S)
        return g;
      }
//-------
      float phi(void) {                
        return asinf(sin_phi);        // Return IQ phase error (radians)
      }
//-------
      float correction_1(void) {       // Return M&S IQ correction factor c_1
        return c_1;
      }
//-------
      float correction_2(void) {       // Return M&S IQ correction factor c_2
        return c_2;
      }
//-------
      float Theta_1(void) {           // Return M&S intermediate vaiable theta_1
        return theta_1;               // (for debugging)
      }
//-------
      float Theta_2(void) {           // Return M&S intermediate vaiable theta_2
        return theta_2;               // (for debugging)
      }
//-------
      float Theta_3(void) {           // Return M&S intermediate vaiable theta_3
        return theta_3;               // (for debugging)
      }
//-------
    private:
      audio_block_t *inputQueueArray[2];
      boolean       enabled = true;
      float         theta_1, theta_2, theta_3, g, sin_phi, c_1, c_2;
 };
#endif
AudioIQerror.cpp :
Code:
//-----------------------------------------------------------------------------------------
//                                 *** AudioIQerror.cpp ***
//
// AudioIQerror:  A Teensy audio library  compatible block processor to report quadrature
//               (IQ) amplitude and phase errors in quadradure signal processing
//               applications.
//               Based on the algorithm described by Mosely and Slump, download at:
//                               http://doc.utwente.nl/66726
// 
// Author:  Derek Rowell
// Updated: June 22, 2017
//-----------------------------------------------------------------------------------------
//
#include "AudioIQerror.h"
audio_block_t *blockI, *blockQ;

void AudioIQerror::update(void) { 
  int32_t       sum_1, sum_2, sum_3;
  const float   alpha = 0.99, beta = 1.0-alpha;
  blockI = receiveReadOnly(0);
  blockQ = receiveReadOnly(1);
//
  if (blockQ && !blockI){
    release(blockQ);
    return;
  }
  if (blockI && !blockQ){
    release(blockI);
    return;
  }
  if (!blockI && !blockQ) return;
//-------------------------------------------
  if(enabled) {  
    sum_1 = 0;
    sum_2 = 0;
    sum_3 = 0;
    g = 0.0;
    c_1 = 0.0;
    c_2 = 0.0;
 // Accumulate sums for IQ error correction
    for (int i=0; i<128; i++) {
        if (blockI->data[i] > 0) {
          sum_1 -= blockQ->data[i];
        } else {
          sum_1 += blockQ->data[i];
        }
       sum_2 += abs(blockI->data[i]);
       sum_3 += abs(blockQ->data[i]);
    }
//  Update the IQ error correction terms with 1st order IIR low-pass smoothing    
    theta_1 = alpha*theta_1 + beta*float(sum_1)/128.0;
    theta_2 = alpha*theta_2 + beta*float(sum_2)/128.0;
    theta_3 = alpha*theta_3 + beta*float(sum_3)/128.0;
     g       = theta_3/theta_2;
     sin_phi = theta_1/theta_3;
     c_1     = theta_1/theta_2;
     c_2     = sqrt(((theta_3*theta_3) - (theta_1*theta_1))/(theta_2*theta_2));
  }
  release(blockI);
  release(blockQ);
  return;
}
The code uses two instances of AudioIQerror, the first is connected to a pair of Audio sine blocks to simulate a quadrature pair, the second takes inputs through the I2S inputs form a hardware quadrature pair generated by my digital function generator. I've been using signals at 1 kHz, and a test phase error of 10 deg. The bug never appears on the internal signal, but about 40-50% of the time on the I2S inputs (always after a program reload). When the bug is there it shows up as a phase error of 18 deg, when it is not the reported error is accurately 10 deg.

These results strongly support the notion that the I2S channels are out of sync by a single step.

For most of the day I've been playing with the low-level SGT5000 registers. Ive been trying to reset the audio board, but nothing seems to do any good... Oh well, I guess we can just cycle the power to the Teensy after each software update.
 
Last edited:
@Derek. Seems like you have nailed it down to that 1 sample mis-synchronization from your tests.
I modified the SGTL5000 library to include a disable function, which turned off the power control bits for the ADC, etc. (The existing enable function sets them back on, by default.) Whether this solves the sync problem or not, I don't know, but it definitely shuts down the SGTL when my disable function is called, as it will hang the background audio activity, if it is running when you call the disable function. I think Frank used this when he was testing his "twin-peaks" eliminator, which is his way of labelling this problem in terms of SDR image rejection.
I'm not as proficient in Teensy software as others, but I've done lots of hardware troubleshooting. Given the random nature of this problem, I would be inclined to try and break the problem down into two possibilities:
1) The SGTL codec is acting strange
2) The DMA-driven I2S routines may have a bug in them.
No one seems to have figured out what the SGTL codec might be doing wrong. I wonder if running it at sample rates much in excess of the standard 44.1/48 KHz rates might be an issue. I've looked over the datasheet, but have not found any specs on the recommended upper limit for sample rate.
For 2), it should be possible to generate 2 quadrature sine waves, as you do in your internal tests, but instead feed the outputs to the Teensy Audio libraries I2S output block. Then, if you connect the Teensy I2S receive and transmit pins together (and disconnect the Audio board), you should be getting a proper quadrature sine wave pair into the Teensy I2S. If you continue to see random occurrences of 1 sample mis-synching, then it would seem to point to the Teensy I2S routines themselves, and not the SGTL Codec.
Just a thought, for what it's worth.
 
@Derek. I may have misinterpreted your message- did you actually produce a digital I2S signal of a quad sine pair to feed into the Teensy I2S, or an analog signal which you fed into the SGTL's ADC?
 
Hi Brian,
The Teensy itself doesn't have I2S ports. The I2s inputs go directly into the codec on the audio board. So no, I didn't produce a digital I2s quad pair, I used an external function generator to create a phase locked pair of sinusoids and fed those to the I2S input on the audio board. I had thought about feeding the internally generated quad pair out through the I2S outputs and back in through the inputs, but I don't know what extra info that would give me.

Geez - in reading your post I just had an idea, I'm going to try doing a AudioInputI2s.begin() to reset the dma I2S system and see if that will fix it. I'll get back to you.
 
I am presently using Frank, DD4WH's Convolution SDR receiver that has been discussed on this Forum. Relative to this discussion topic, Frank has been helping me to understand the "Twin Peaks" problem that causes the Audio Adaptor to need restarts. As a result, I have done some tests that show the basic nature of the problem. This report shows it to be associated with the Adaptor hardware or software, and basically always present for some percentage of start-ups. All on T3.6.

I think this is all consistent with the observations of Derek and Brian, but is a more direct measurement.

First the issue is fundamental and not peculiar to the radio application. After any reset, the Left and Right channels may be misaligned by one sample period. For example, here are 10 sequential ADC sample pairs from a problematic startup. This happens to be done at a 96 kHz sample rate on a 3 KHz signal applied to both L & R inputs.
232 <L R> -722
1176 <L R> 239
2077 <L R> 1175
2894 <L R> 2072
3608 <L R> 2897
4170 <L R> 3607
4585 <L R> 4167
4821 <L R> 4582
4869 <L R> 4819
4730 <L R> 4868

Next are measurements from a different start-up showing the alignment as desired. The measurement conditions were the same, just a different start-up.
-1833 <L R> -1829
-914 <L R> -912
35 <L R> 36
983 <L R> 981
1894 <L R> 1897
2738 <L R> 2736
3474 <L R> 3474
4075 <L R> 4072
4516 <L R> 4517
4781 <L R> 4778

This misalignment case occurs whether the start-up comes from a new program load, a power up, or a push of the Teensy reset button.

The misalignment was seen at all data rates that I tested, 96K, 48K, 44K and 16K samples/sec.

The frequency of occurrence depends on something that is not obvious, perhaps the use of interrupts. It occurs something around half of the time for my current version of Frank's radio. It occurs about 5 or 10 percent of the time for the test program below. In addition, the inclusion of a cli(), sei() pair around the enable of the Audio Adaptor seems to make the problem worse. Having a AudioNoInterrupts(), AudioInterrupts() pair around the enable seems to make the misalignment less frequent. Nothing stops the problem, it would seem.

Code:
/* Twin Peaks Test Bed    Bob Larkin  15 April 2018
 * This is Frank, DD4WH's Convolution SDR with everything removed that is 
 * not needed to create "Twin Peaks" problem.  Issue is the arrays from 
 * AudioRecordQueue do not always line up L & R data.
 * Test with roughly 3 kHz sine wave applied to both L & R line inputs.
 */
#include <Audio.h>
#include <Wire.h>

AudioInputI2S            i2s_in;
AudioRecordQueue         Q_in_L;
AudioRecordQueue         Q_in_R;
AudioConnection          patchCord1(i2s_in, 0, Q_in_L, 0);
AudioConnection          patchCord2(i2s_in, 1, Q_in_R, 0);
AudioControlSGTL5000     sgtl5000_1;

#define SAMPLE_RATE_MIN               6
#define SAMPLE_RATE_8K                0
#define SAMPLE_RATE_11K               1
#define SAMPLE_RATE_16K               2
#define SAMPLE_RATE_22K               3
#define SAMPLE_RATE_32K               4
#define SAMPLE_RATE_44K               5
#define SAMPLE_RATE_48K               6
#define SAMPLE_RATE_88K               7
#define SAMPLE_RATE_96K               8
#define SAMPLE_RATE_100K              9
#define SAMPLE_RATE_176K              10
#define SAMPLE_RATE_192K              11
#define SAMPLE_RATE_MAX               11

uint8_t SAMPLE_RATE =            SAMPLE_RATE_96K;

typedef struct SR_Descriptor
{
  const uint8_t SR_n;
  const uint32_t rate;
  const char* const text;
  const char* const f1;
  const char* const f2;
  const char* const f3;
  const char* const f4;
  const float32_t x_factor;
  const uint8_t x_offset;
} SR_Desc;
const SR_Descriptor SR [12] =
{
  //   SR_n , rate, text, f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
  {  SAMPLE_RATE_8K, 8000,  "  8k", " 1", " 2", " 3", " 4", 64.0, 11}, // not OK
  {  SAMPLE_RATE_11K, 11025, " 11k", " 1", " 2", " 3", " 4", 43.1, 17}, // not OK
  {  SAMPLE_RATE_16K, 16000, " 16k",  " 4", " 4", " 8", "12", 64.0, 1}, // OK
  {  SAMPLE_RATE_22K, 22050, " 22k",  " 5", " 5", "10", "15", 58.05, 6}, // OK
  {  SAMPLE_RATE_32K, 32000,  " 32k", " 5", " 5", "10", "15", 40.0, 24}, // OK, one more indicator?
  {  SAMPLE_RATE_44K, 44100,  " 44k", "10", "10", "20", "30", 58.05, 6}, // OK
  {  SAMPLE_RATE_48K, 48000,  " 48k", "10", "10", "20", "30", 53.33, 11}, // OK
  {  SAMPLE_RATE_88K, 88200,  " 88k", "20", "20", "40", "60", 58.05, 6}, // OK
  {  SAMPLE_RATE_96K, 96000,  " 96k", "20", "20", "40", "60", 53.33, 12}, // OK
  {  SAMPLE_RATE_100K, 100000,  "100k", "20", "20", "40", "60", 53.33, 12}, // NOT OK
  {  SAMPLE_RATE_176K, 176400,  "176k", "40", "40", "80", "120", 58.05, 6}, // OK
  {  SAMPLE_RATE_192K, 192000,  "192k", "40", "40", "80", "120", 53.33, 12} // not OK
};

int16_t *sp_L;
int16_t *sp_R;

void setup() {
  AudioMemory(170);
  Serial.begin(115200);
  delay(1000);
                                                                                              
  setI2SFreq (SR[SAMPLE_RATE].rate);
  delay(200);

  // cli();
  AudioNoInterrupts();
  // cli();
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  // sei();
  AudioInterrupts();
  // sei();
  Q_in_L.begin();
  Q_in_R.begin();
}

void loop() {
  uint16_t  k;
  if (Q_in_L.available() &&  Q_in_R.available())
        {
        sp_L = Q_in_L.readBuffer();
        sp_R = Q_in_R.readBuffer();
        // Get sampling of raw data to the Serial Monitor.
        for(k=0; k<10; k++)
             {
             Serial.print(sp_L[k]); Serial.print(" <L  R> "); Serial.println(sp_R[k]);
             }
        Serial.println();
        // Recycle the buffers
        Q_in_L.freeBuffer();
        Q_in_R.freeBuffer();
        }
}

// set sample rate code by Frank Boesing
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 15;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 100000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {1, 1}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {1, 1}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {1, 1}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {1, 1}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {1, 1}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {1, 1}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {224, 1575}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {1, 1}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {1, 1}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {1, 1}, {89, 473}, {16, 85}, {128, 625} };
#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;
    }
  }
}

Do these measurements help anyone to see a complete solution? Can I measure anything else? Thoughts?

A couple of other observations. The phasing radio is extremely sensitive to this type of data error. Also, I believe that many audio applications would not be disturbed at all by an L/R time shift of 10+ microseconds.

Bob Larkin
 
Bob,
I have continued to be plagued by this bug, and after a year of trying have still not found a true solution. It sure plays hell with my own SDR work, and in ultrasonic imaging work, where inter-channel delays are disastrous. I still can't truly isolate it to the Teensy or the codec.
So I have had to measure the delay, and resort to compensating for it in an Audio Block that I wrote for the purpose. I have attached code that I wrote last year to document the problem. It computes the cross-correlation function between the two I2S inputs, and includes some code to compensate for any delay.
The cross-correlation is a DSP technique to specifically estimate delays between a pair of of waveforms. It compares the similarity between two signals as a function of delay between them. For example, in an echolocation system (radar or sonar) the optimal estimate of delay is computed by measuring the location of the peak in the cross-correlation. Here is the sketch:
Code:
//-------------------------------------------------------------------------------------
//                                 *** TestXcorr.ino ***
//
// Sketch to demonstrate  Audio board I2S channel synchronization problem.  Computes the
// cross-correlation function between the I2S inputs at lags -2,-1, 0, 1, 2 samples,
// for complete blocks, and prints the lag with maximum correlation, and the values of
// the xcorr estimates for each lag.
//
// Usage:   Connect the two line-in inputs together, and drive with a wide-band input.
//          (I use the "random" waveform from my digital function generator)   If the
//          channels are in sync the software should report a lag of 0, however I find
//          that
//          1) on program reload it randomly reports either 0, or -1 with 50% probability
//          2) on power-up it reports 0 with about 70% probability, -1 with 30%.
//
// Author:  Derek Rowell
// Updated: July 1, 2017
//  
//-------------------------------------------------------------------------------------
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "AudioXcorr.h"
#include "AudioLagComp.h"

const int myInput = AUDIO_INPUT_LINEIN;
AudioInputI2S    I2S_in;
AudioOutputI2S   I2S_out;
AudioXcorr       xcorr_raw_data;
AudioXcorr       xcorr_compensated;
AudioLagComp     compensator;
AudioSynthNoiseWhite Noise;
AudioControlSGTL5000 audioShield;
//----
AudioConnection c1(Noise, 0, I2S_out, 0);
AudioConnection c2(I2S_in, 0, xcorr_raw_data, 0);
AudioConnection c3(I2S_in, 1, xcorr_raw_data, 1);

AudioConnection c4(I2S_in, 0, compensator, 0);
AudioConnection c5(I2S_in, 1, compensator, 1);

AudioConnection c6(compensator, 0,   xcorr_compensated, 0);
AudioConnection c7(compensator, 1,   xcorr_compensated, 1);
//----------------------------------------------------------------------------
void setup() {
  Serial.begin(57600);
   delay(1000);
   audioShield.enable();
   AudioMemory(12);
   audioShield.inputSelect(myInput);
   Noise.amplitude(.6);
}
//----------------------------------------------------------------------------------
void loop() {
  Serial.print("Lag between I2S input channels (samples):  "); Serial.print(xcorr_raw_data.XcorrLag());
  Serial.print("\tLag after Compensation:  ");  Serial.println(xcorr_compensated.XcorrLag());
  compensator.setLag(xcorr_raw_data.XcorrLag());
  delay(100);
}
and here is an Audio Block to measure the cross-correlation:
Code:
//-------------------------------------------------------------------------------------
//                                 *** AudioXcorr.h ***
//
// Used in debug sketch to evaluate Audio board I2S channel synchronization problem
// 
// Author:  Derek Rowell
// Updated: July 1, 2017
//  
//--------------------------------------------------------------------------
#ifndef AudioXcorr_h_
#define AudioXcorr_h_
#include "AudioStream.h"
#include "Arduino.h"
  class AudioXcorr : public AudioStream {
  public:
    AudioXcorr() : AudioStream(2, inputQueueArray) {}
    virtual void update(void);
    int16_t XcorrLag(void) 
    {return lagMax;}
//
  private:
    audio_block_t *inputQueueArray[2];
    int32_t sum, xcorr[5] = {0,  0,  0,  0,  0}, max;
    int16_t lagMax;
  };
#endif

//-------------------------------------------------------------------------------------
//                                 *** AudioXcorr.cpp ***
//
// Used in debug sketch to evaluate Audio board I2S  chaneel synchronization problem
// 
// Author:  Derek Rowell
// Updated: July 1, 2017
//  
//--------------------------------------------------------------------------
#include "AudioXcorr.h"

void AudioXcorr::update(void) { 
  audio_block_t *blockI, *blockQ;
  blockI = receiveReadOnly(0);
  blockQ = receiveReadOnly(1);
  if (blockQ && !blockI) {release(blockQ); return;}
  if (blockI && !blockQ) {release(blockI); return;}
  if (!blockI && !blockQ) return;
//-------------------------------------------
  max = 0;
  // Compute the cross-correlation between channels for lags -2, -1, 0, 1, 2
  for (int lag = -2; lag < 3; lag++) {
    sum = 0; 
    for (int i = 3; i < 125; i++) { 
      sum += blockI->data[i+lag] * blockQ->data[i];
    }
    xcorr[lag+2] = sum;
    if (sum > max) {
       max = sum;
       lagMax = lag;
    }
  }
// This version simply prints the results 
//  Serial.print(lagMax); Serial.print("\t");
//  for (int i=0; i<5; i++)  Serial.print(xcorr[i]); Serial.print("\t\t");
//  Serial.println();
  release(blockI);
  release(blockQ);
  return;
}
and an Audio Block to compensate for any delay
Code:
//-------------------------------------------------------------------------------------
//                                 *** AudioLagComp.h ***
//
// Used in debug sketch to evaluate Audio board I2S channel synchronization problem
// 
// Author:  Derek Rowell
// Updated: July 1, 2017
//  
//--------------------------------------------------------------------------
#ifndef AudioLagComp_h_
#define AudioLagComp_h_
#include "AudioStream.h"
#include "Arduino.h"
  class AudioLagComp : public AudioStream {
  public:
    AudioLagComp() : AudioStream(2, inputQueueArray) {}
    virtual void update(void);
    void setLag(int16_t Lag) {
      lag = Lag;
      return;
    }
//
  private:
    audio_block_t *inputQueueArray[2];
    int16_t lag = 1;
    int16_t temp, oldData;
  };
#endif

//-------------------------------------------------------------------------------------
//                                 *** AudioLagComp.h ***
//
// Used in debug sketch to evaluate Audio board I2S  chaneel synchronization problem
// 
// Author:  Derek Rowell
// Updated: July 1, 2017
//  
//--------------------------------------------------------------------------
#include "AudioLagComp.h"

void AudioLagComp::update(void) { 
  audio_block_t *blockI, *blockQ;
  blockI = receiveWritable(0);
  blockQ = receiveWritable(1);
  if (blockQ && !blockI) {release(blockQ); return;}
  if (blockI && !blockQ) {release(blockI); return;}
  if (!blockI && !blockQ) return;
//-------------------------------------------
  if (lag == 1) {
    temp = blockQ->data[127];
    for(int i = 127; i>0; i--) blockQ->data[i] = blockQ->data[i-1];
    blockQ->data[0] = oldData;
    oldData = temp;
  } else if (lag == -1) {
    temp = blockI->data[127];
    for (int i = 127; i>0; i--) blockI->data[i] = blockI->data[i-1];
    blockI->data[0] = oldData;
    oldData = temp;
  }
  transmit(blockI, 0);
  transmit(blockQ, 1);
  release(blockI);
  release(blockQ);
  return;
}
As I say in the sketch header, a single sample delay is found in 50% of program reloads and about 30% of power-ups.

Paul, if you read this, I really wish you might help us with this. It is a real bug... (I submitted a bug report last year)
 
Derek, thanks for the observations and work-around. Yes, I too have a need to get this

Today I started on a similar path to that of yours. I connected a 2.2nF cap to pin A21 and a pair of 100 K resistors go to the L & R inputs. This is programmed to have a 22 kHz sine wave (just alternate +/- 32767) This pushes against the low output impedance from the I-Q amps and so the signal input to the ADC is quite weak, ranging +/- 100 counts. The resistors can stay as they do nothing when the 22 kHz wave is stopped.

The software first turns off the Si5351 to disable the receiver, generates the 22 kHz signal, and then measures the cross correlation (without a divide by N) over 128 samples. This is highly reliable for detecting the 1 sample shift. The sum of products is less than 15,000 for the bad 1-sample shift case and around 100,000 for the properly aligned case. So I am taking the bad case to call for a Codec reset. This works to get rid of the problem in a few seconds. Once the problem is fixed it stays fixed, and so there is no overhead, except at startup.

I will post code and results in the next day. At this moment it is too ugly to be useful. The approach is probably pretty obvious, anyway.

All the best, Bob
 
Here is the test .INO referred to above for resetting the Codec until the cross correlation between the L and R channels is high. It uses the 22 kHz signal coming from the A21 DAC connection.
Code:
/* Twin Peaks Correction Test Bed    Bob Larkin  17 April 2018
 * This started as DD4WH's Convolution SDR with everthing removed that is 
 * not needed to create "Twin Peaks" problem.  Issue is the arrays from 
 * AudioRecordQueue do not always line up L & R data.
 *
 * This code uses a hardware kludge of a 2.2nF cap from the Teensy A21 DAC output
 * going to a parir of 100K resistors that connect to the L and R line inputs of the
 * codec.  Because of the output impedance of the amplifiers driving the L & R inputs,
 * the 22 KHz test signal is low  in level, and the 100K resistors can remain in
 * place for radio operation.
 * 
 * The good/bad threshold TP_THRESHOLD needs to be adjusted according to 
 * the signal level at the ADC inputs.  Use the Serial.print's in measureTP() to
 * determine the best value and place in the #define.
 * 
 * The test for data shift is a measurement of the cross correlation between the L & R
 * ADC outputs.  These should be the same and correlation should be high.  Shifting a 22 kHz
 * signal by a single sample cuts the correlation output by about 7:1.  This is used to
 * determine a successful start of the codec, or the need to restart.
 * 
 * This is a stand-alone test bed.  It may require an additional logical variable,
 * or 2, to integrate into a full program.  Most issues, such as not using a delay() inside
 * loop() are dealt with.
 */
 
#include <Audio.h>
#include <Wire.h>

AudioPlayQueue           queueTP;        // Output packets for Twin Peak test
AudioOutputAnalog        dac1;           // Generate 24 KHz signal
AudioConnection          patchCord3(queueTP, 0, dac1, 0);

AudioInputI2S            i2s_in;
AudioRecordQueue         Q_in_L;
AudioRecordQueue         Q_in_R;
AudioConnection          patchCord1(i2s_in, 0, Q_in_L, 0);
AudioConnection          patchCord2(i2s_in, 1, Q_in_R, 0);
AudioControlSGTL5000     sgtl5000_1;

#define TP_THRESHOLD 50000

#define SAMPLE_RATE_MIN               6
#define SAMPLE_RATE_8K                0
#define SAMPLE_RATE_11K               1
#define SAMPLE_RATE_16K               2
#define SAMPLE_RATE_22K               3
#define SAMPLE_RATE_32K               4
#define SAMPLE_RATE_44K               5
#define SAMPLE_RATE_48K               6
#define SAMPLE_RATE_88K               7
#define SAMPLE_RATE_96K               8
#define SAMPLE_RATE_100K              9
#define SAMPLE_RATE_176K              10
#define SAMPLE_RATE_192K              11
#define SAMPLE_RATE_MAX               11

uint8_t SAMPLE_RATE =            SAMPLE_RATE_96K;

typedef struct SR_Descriptor
{
  const uint8_t SR_n;
  const uint32_t rate;
  const char* const text;
  const char* const f1;
  const char* const f2;
  const char* const f3;
  const char* const f4;
  const float32_t x_factor;
  const uint8_t x_offset;
} SR_Desc;
const SR_Descriptor SR [12] =
{
  //   SR_n , rate, text, f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
  {  SAMPLE_RATE_8K, 8000,  "  8k", " 1", " 2", " 3", " 4", 64.0, 11}, // not OK
  {  SAMPLE_RATE_11K, 11025, " 11k", " 1", " 2", " 3", " 4", 43.1, 17}, // not OK
  {  SAMPLE_RATE_16K, 16000, " 16k",  " 4", " 4", " 8", "12", 64.0, 1}, // OK
  {  SAMPLE_RATE_22K, 22050, " 22k",  " 5", " 5", "10", "15", 58.05, 6}, // OK
  {  SAMPLE_RATE_32K, 32000,  " 32k", " 5", " 5", "10", "15", 40.0, 24}, // OK, one more indicator?
  {  SAMPLE_RATE_44K, 44100,  " 44k", "10", "10", "20", "30", 58.05, 6}, // OK
  {  SAMPLE_RATE_48K, 48000,  " 48k", "10", "10", "20", "30", 53.33, 11}, // OK
  {  SAMPLE_RATE_88K, 88200,  " 88k", "20", "20", "40", "60", 58.05, 6}, // OK
  {  SAMPLE_RATE_96K, 96000,  " 96k", "20", "20", "40", "60", 53.33, 12}, // OK
  {  SAMPLE_RATE_100K, 100000,  "100k", "20", "20", "40", "60", 53.33, 12}, // NOT OK
  {  SAMPLE_RATE_176K, 176400,  "176k", "40", "40", "80", "120", 58.05, 6}, // OK
  {  SAMPLE_RATE_192K, 192000,  "192k", "40", "40", "80", "120", 53.33, 12} // not OK
};

int16_t *sp_L;
int16_t *sp_R;
int16_t *outBufferPtr;
uint16_t  countBegin, k;
boolean okLR, doWait;
int64_t xcorr;
unsigned long endTime;

void setup() {
  AudioMemory(100);
  Serial.begin(115200);
  delay(1000);
                                                                                              
  setI2SFreq (SR[SAMPLE_RATE].rate);
  delay(200);
  resetCodec();
  Q_in_L.begin();
  Q_in_R.begin();
   
  countBegin = 0;
  okLR = false;
  endTime = millis() + 5UL;
  doWait = true;
}

void loop()
  {
  // Toss out the first packets until 22KHz is up and out
  // of Codec
  if ( (countBegin < 20) && Q_in_L.available() &&  Q_in_R.available() )
    {
    sp_L = Q_in_L.readBuffer();
    sp_R = Q_in_R.readBuffer();
    Q_in_L.freeBuffer();
    Q_in_R.freeBuffer();
    countBegin++;
    }

  // okLR means that correction is done.  Until then evaluate cross
  // correlation if data available, and update 22 kHz if needed.
  if( !okLR )
    {
    measureTP();
    generate22kHz();
    }

  if ( !doWait && !okLR && countBegin >= 20)
     // Need to reset Codec and hope for L-R alignment
     // This includes a delay.
     resetCodec();

  if(okLR)
     printSuccess();

/*   Uncomment to print samples of goood data
  if ( !doWait && okLR && Q_in_L.available() &&  Q_in_R.available() )
        {   
        sp_L = Q_in_L.readBuffer();
        sp_R = Q_in_R.readBuffer();
        // Get sampling of raw data to the Serial Monitor.
        for(k=0; k<10; k++)
           {
           Serial.print(sp_L[k]); Serial.print(" <L  R> "); Serial.println(sp_R[k]);
           }
        Serial.println();
        // Recycle the buffers
        Q_in_L.freeBuffer();
        Q_in_R.freeBuffer();
        }
 */
 
  doMyWait();    // Check delay, without stopping process
  }       // End loop()


void measureTP()
  {
  uint16_t k;
  int64_t xcorr;

  if (Q_in_L.available() &&  Q_in_R.available())
    {
    sp_L = Q_in_L.readBuffer();
    sp_R = Q_in_R.readBuffer();
    xcorr = 0LL;
    for(k=0; k<128; k++)
      xcorr += (int64_t)( sp_L[k]*sp_R[k] );
    // Recycle the buffers
    Q_in_L.freeBuffer();
    Q_in_R.freeBuffer();
    if(xcorr > TP_THRESHOLD)   //  <== Determine experimentally
      {
      // Serial.print("Single Peak X-Correlation = ");
      // Serial.println( (int32_t)(xcorr/1000LL) );
      okLR = true;
      }
    else
      {
      // Serial.print("Twin Peaks  X-Correlation = ");
      // Serial.println( (int32_t)(xcorr/1000LL) );
      }
    }
 }

void resetCodec(void)
  {
  AudioNoInterrupts();
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  AudioInterrupts();
  // Need a delay after reset.  How much?
  endTime = millis() + 200UL;
  doWait = true;
  Serial.println(" Reset Codec ");
  }

void generate22kHz(void)
  {
  uint16_t k;
  
  //getBuffer() - Returns a pointer to an array of 128 int16_t or
  // 0 if buffer not available.
  if( (outBufferPtr = queueTP.getBuffer()) )    // assignment & logic
      {
      //Generate a 22 kHz "sine wave".  It is a big amplitude to allow
      // large value resistors going to L and R inputs.
      for(k=0; k<64; k++)
          {
          *(outBufferPtr+k*2)   = -32767;
          *(outBufferPtr+k*2+1) =  32767;
          }
      queueTP.playBuffer();   // Transmit *outBufferPtr
      }   
  }

// Enable delay within loop() by setting global endTime to millis()+d
// where d is the desired millisec of delay.  Then, also set do_wait=1.
// Use doWait to test for a wait happening.
void doMyWait(void)
  {
  if (doWait == 0)          // No delay now
    return;
  if(endTime <= millis())    // Delay is over
    doWait = 0;
  }

void printSuccess()
  {
  static uint16_t ps = 0;
  if(ps ==0)
    {
    Serial.println("Success!!");
    ps =1;
    }
  }

// set sample rate code by Frank Boesing
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 15;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 100000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {1, 1}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {1, 1}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {1, 1}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {1, 1}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {1, 1}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {1, 1}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {224, 1575}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {1, 1}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {1, 1}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {1, 1}, {89, 473}, {16, 85}, {128, 625} };
#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;
    }
  }
}

This takes 1 to 2 seconds to get the L & R channels aligned.

Note that this needs manual measurements to determine TP_THRESHOLD. See the comments in the program. A separate .INO could be written to determine that value by experiment, but the experimental method works fine for me.

I plan to integrate this into Frank's C-SDR to speed the start-up.. More later.
 
Bob/Derek. Sounds like you've determined for sure that a 1 sample offset can occur randomly. Does this single sample error make enough of a phase difference to cancel out the image rejection feature of the quadrature detection, ie give the "twin peaks" problem that DD4WH experienced/posted about in this thread? If so, then Bob's auto-correlation routine would seem to be the most simple way to work around it. I wasn't able to get DD4WHs I,Q correction routine to work on my SDR- the twin peaks persisted regardless. My Elektor QSD front end is putting out proper I,Q signals so the problem wasn't there.
I have the gut feeling that if one were to re-configure the SGTL5000 to run as an I2S master, instead of slave, this intermittent problem may disappear. Surely Freescale tested the design thoroughly, but probably in the Master mode.
There are also issues with the SGTL5000/Teensy library with respect to the 44100 sample rate- The Teensy clocks the SGTL5000 at 44118, since its onboard i2S clock generator can't generate the exact 44100 master clock rate. Granted, this is a different issue then the subject of this thread.
THe SGTL5000 has its own dedicated PLL and fractional divider chain, which will produce accurate 44100 rate with an incoming clock of 12 MHz, which the teensy should easily be able to produce, given its 24 mhz clock.
I wonder if running SGTL5000 in master mode, where it produces all of the 3 clocking signals MCLK, BCLK and LRCLK, might eliminate this random sample problem. Switching SGTL to master mode would mean some changes to the SGTL initialization routine in the audio library, as well as using the I2S slave mode in the teensy audio library.
Any thoughts on this idea?
 
Derek and Brian -

First, Derek
I ran your TestXcorr.ino and got the expected result of bunches of
"Lag between I2S input channels (samples): -1 Lag after Compensation: 0"
and the other bunches of "0 Lag". All quite consistent with my other measurements.

Do you ever see a value besides 0 and -1?

Comment - When I first hooked the noise generator output (Left codec, I think) to L&R inputs, I was getting random lags, like
0, 1, 0, -1, -2, -1, -2, 0, -1, 0, -1, 1, -2, 0, 0 etc., down the screen.
When I changed "Noise.amplitude(0.6);" to (0.1) everything was fine. I was over-driving the correlation sum, I think.

Another comment - When I switched the input to a 20 kHz sine wave from the audio generator with 0.2V p-p, I got precisely the same lag numbers as the random noise gave. For both they were always the same scrolling down the screen.

It all looks good.

And Brian, yes a half of a degree of phase error creates a -40 dB response, and the 1 sample shift is 10's of degrees at higher frequencies, such as the i-f of the receiver.

Your idea of making the codec "master" is interesting. It might also have a side benefit of reduced spurs? How does one do this? Or, has somebody already done this? What are the hidden gottchas?

Bob
 
Bob: yes, a little math confirmed that 1 sample offset would give about 36 degrees phase shift on a 9.6 KHz audio signal (@ 96 kHz SR)
To change the SGTL to master means writing some new values into SGTL configuration registers, as well as setting up the PLL divider chains for 96 kHz SR ( more involved if you wanted to pick different SRs as you can do in the DD4WHs SDR software.) I modified the official audio library to add a codec reset function which Frank used in the SDR. Making the above changes to SGTL registers would be more work, but it is documented in the SGTL datasheet. It's been 6 months or so since I worked on the SDR project, but I may be able to get back in and try to implement the master mode. I now have a logic analyser, and I expect if I analysed the I2S lines, and got the software to decode the I2S stream, it should prove conclusively whether the codec is at fault, or the Teensy I2S hardware/audio library routines.
I mentioned the 44100 SR problem mainly for interest. I know that the Teensy I2S clock generation at 44100 has clock jitter that you can see on a scope, and that does result in spurs. But, off the top of my head, I don't know if the (integer) divisor ratios in the Teensy's I2S clock generators for 96 KHz SR are such that the clock jitter is problematic at 96 KHz.
Since the SGTL has its own PLL dedicated to the ADC /I2S clock generation, I would think that it would provide a better clock/reduced Spurs in master mode.
 
I have the same exact problem with SGTL5000 I/Q offset in my own superhet with DSP AF. Caused me tons of time and frustration - I initially thought that it is in my main code, then tried various tweaks to the codec init routine etc.

The frequency of this event is even worse at ~50% if I use Snooze library for the deepSleep standby (disabling and then re-enabling the codec). Happens less frequently on the power-up, I guess it depends on the power ramp rate and/or glitches.

I am seriously thinking of cutting the 3.3V trace on the audio adapter and adding a HW power switch (or an LDO with enable), controlled by the Teensy pin.
 
Software switching the codec power supply eliminated the occasional offset errors during full power-up, but did not help neither during USB downloads nor during Snooze deepSleep (I use it for standby). In addition to power cycling the codec I tried wrapping the deepSleep with Teensy I2S RX/TX enable/disable (with wait for frame completion) and still get the 50% offset probability. So I resorted to the least favorite solution - the cross correlation method. However instead of a restart loop I just modified the ISR in the input_i2s.cpp with an extra static memory variable to use as a delay line during DMA buffer copy. Hence the overall algorithm runs in one pass, once I determine if the offset is 0 or 1 sample (I get only two offsets).

It is fairly straigntforward to make SGTL a master on LRclk and Bclk, taking Mclk from the Teensy. But it is not trivial to make Teensy I2S module a proper slave on LRclk and Bclk, while supplying Mclk.
BTW the jitter on the Teensy Mclk is low if you use fractional synth multiplier 1 or 2, beyond that you do not want to look at the trace on the scope :(
 
@vladn: Looks like you've been pretty busy working on this.:) The cross-correlation + 1 sample offset in the ISR, while a workaround, is probably the only easy way to correct the problem.
While I have nothing to back it up, my hunch was that running the SGTL5000 in Master mode might cure things. Likely Freescale did their design testing in the master mode- if they had encountered this error, I doubt it would be sold that way. (I could be wrong, it isn't sold as an "audiophile" Codec and for many uses, it wouldn't really affect things much)
I expect you're right that the Teensy can't run in Slave mode AND also produce an MCLK signal on GPIO11. What I had in mind was switching GPIO11 pin to an input, and using a 12.288 MHz oscillator module (Digikey XC1643TR-ND for example) to feed the SGTL5000's MCLK line. That would make it easy for the internal PLL as it would be exactly 256 X sample rate (48000).
I agree that the Teensy MCLK signal can be bad depending upon what divisor/multiplier ratios you choose.
 
For our SDR applications the sampling frequency does not have to be exact as long as we know it exactly :) I actually use Si5338 synth on my board with a spare 4th output wired via 0-ohm to the I2S Mclk line. I do not use it currently (Teensy is a master and it's Mclk frac multiplier set to 2, one of the only two jitter-clean options).

Turning things around on the codec is easy. But the Kinetis has a myriad of registers in the I2S module and all needs to be set correctly to make it a proper slave - few days of work at least. And even then I am not sure it will recover after waking up from a standby mode when most peripherals on the Teensy are disabled and the codec CHIP_ANA_PWR set to 0.

The cross-correlation method does not require any normalization BTW. I just take first differences of the input (to put the emphasis on higher frequencies and remove flicker noise/transients) and look at the ratio of their accumulated cross products with the lag of 0 and the lag of 1. Works like a charm when the RF noise or signal of any sort is present. Does not work at all without antenna - the RX own AWGN is dominated by the I/Q amps, hence there is no 90 degree relationship.
 
Yes, the exact SR doesn't matter of course. This thread was started by DD4WH in reference to his SDR, which uses the PJRC Audio library (which is based upon standard SRs like 44.1, 48K, 96K etc.) Some of those need high I2S clock generator mult/div ratios to produce the correct SR.
The PJRC audio library contains both I2S Master and Slave functions, so the Kinetis code for Slave mode has already been written by someone. But, your point is well taken- if you are shutting everything down including the codec's internal blocks then who knows how things will come back up afterward. And since your routine will work on just raw RF noise, it looks like the best way to go.
Frank, DD4WH must be busy with other projects or I assume he would be posting on this thread about incorporating your ideas into his program.
 
I've tried to use the Teensy audio library in the slave mode recently and it did not work properly. I modified the latest slave code such that Teensy was generating Mclk, but the codec was generating LRclk and Bitclk. The clocks had the correct frequencies and levels (no bus conflicts) on the scope but still it did not work. I gave up for a while and decided to return to the library slave code later, when I have plenty of spare time to kill (usually winter).

BTW it is trivial to generate high frequency Mclk using Teensy frac mult of only 1 and 2. But the frac div will be very small and the SR non-standard (very coarse resolution). I do not see this as a problem though for DSP radios.
 
BTW it is trivial to generate high frequency Mclk using Teensy frac mult of only 1 and 2. But the frac div will be very small and the SR non-standard (very coarse resolution). I do not see this as a problem though for DSP radios.

Hi Vlad,

very interesting! I am very interested in using a low jitter sample rate for the codec! However, my knowledge of these things is too poor to make proper use of your suggestion to use "frac mult of one or two". How could I achieve that? Does that refer to Frank Boesings sample rate change code? Would be nice to have some more information on that!

Brian, you are right, the SDR does not need exact sample rates, the Teensy Convolution SDR does only use the queue object of the Audio library. I would need three rates (low, medium, high) in the range about 40-50k, 90-110k, 200k-250k, is that achievable with low jitter? [at the moment I use 48k, 100k, 192k]

All the best,

Frank DD4WH
 
Frank,

I am usually using the Audio library "liberally" in the sense that I create a reduced copy of it in my local project library directory (only the files that I need) and then change them directly. I also do not care much about supporting all Teensy frequencies options (F_CPU).

Look into the middle of the output_i2s.cpp file - there is an #if / #else table that sets MCLK_MULT and MCLK_DIV based on the F_CPU. Check the F_CPU that you use. The table is calculated to get SR as close to the standard 44.1...ksps as possible. Figure out how much faster SR you need, then scale the MULT/DIV ratio by that factor, then find the closest MULT/DIV pair that has 1 or 2 in the nominator, then calculate the exact SR. But do not forget to account for a non-standard SR in your SDR math ! It is also helps to change the library SR constants to get proper audio CPU usage reporting. You can do a similar thing based on Frank Boesings override function.
 
Hi Vlad,

thanks for your info!

I did a little math and tried to find MULT and DIV values, but I failed to find any values with denominator (=DIV) of one or two :-(. Maybe I did something wrong!?

I figured out that this is the way to calculate sample rate SR:

SR = (F_CPU * MULT / DIV) / 256

So for my case of F_CPU 180MHz, it can be simplified:

SR = 703125 * MULT / DIV

So, if DIV can be two or one, I can have 703.125ksps or 351.5625ksps (or higher SRs). I fear, thats a bit too fast ;-).

But maybe I am on the wrong path!? Which F_CPU and MULT / DIV values did you choose for your purpose?

All the best,

Frank DD4WH
 
Status
Not open for further replies.
Back
Top