Measure WM8731 (and SGTL5000) CODEC ADC's

Status
Not open for further replies.

Bob Larkin

Well-known member
I posted hardware information on a basic WM8731 CODEC board, last summer https://forum.pjrc.com/threads/41356-Audio-Adapter-ADC-Square-wave-with-no-input. Then we moved into a new house, and all that experimentation was put on hold. I have finally gotten back to measuring the basic performance and am reporting the results here.

The WM8731 can be operated as either Codec master (Teensy slave) or Codec slave (Teensy Master) and the performance changes. The latter arrangement is conventional for Teensy and is the only one supported for the PJRC SGTL5000 board. One reason for getting into this project was for radio "SDR" and that runs more easily with 48 or 96 kHz sample rates, rather then the 44.1 kHz usually run with the Teensy library. But in the following measurements, there are small differences due to Codec slave running at 44.1 and Codec master at 48 kHz. All measurements were done with the CODEC plugged directly into a T3.6 and with a 47 Ohm resistor across the Left channel input.

To start out, here are three plots of the ADC time series observed for the WM8731 in Codec Master and Codec Slave along with the SGTL5000.
Codec3xA.gif
The first observation is the presence of a noisy square wave in all three. This has been a characteristic I have always observed. See https://forum.pjrc.com/threads/41356-Audio-Adapter-ADC-Square-wave-with-no-input. This square wave is stronger than the random noise by a fair bit, for all three.

The next observation is that the WM8731 square wave is substantially less in Codec Master than in Codec Slave. Likewise the square wave and noise of WM8731 is less than that of the SGTL5000. Quantifying these difference in the time domain is tricky as the processes are not statistically stationary. But the time plots give a feel to the levels. For reference, and to allow others to duplicate the measurements, the INO used for the time measurements was
Code:
// EvalWM8731_32K.ino   RSL  15 March 2020
// Gathers time series
// For comparisons, this can also be run with the SGTL5000 Teensy Aduio Board

// Select only one:
// #define USE_WM8731_CODEC_MASTER 2
// #define USE_WM8731_CODEC_SLAVE 3
#define USE_SGTL5000 4

// Number of 128 word blocks to save
#define NBLOCKS 8

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <math.h>

#ifdef USE_WM8731_CODEC_MASTER
AudioInputI2Sslave       i2sslave1;      //xy=150,270
AudioAnalyzeRMS          rms1;           //xy=380,170
AudioAnalyzePeak         peak1;          //xy=380,220
AudioRecordQueue         queue1;         //xy=386,262
AudioConnection          patchCord1(i2sslave1, 0, queue1, 0);
AudioConnection          patchCord2(i2sslave1, 0, peak1, 0);
AudioConnection          patchCord3(i2sslave1, 0, rms1, 0);
AudioControlWM8731master wm8731m1;       //xy=160,347
#endif

#ifdef USE_WM8731_CODEC_SLAVE
AudioInputI2S            i2s1;           //xy=151,269
AudioAnalyzeRMS          rms1;           //xy=377,176
AudioAnalyzePeak         peak1;          //xy=381,220
AudioRecordQueue         queue1;         //xy=386,262
AudioConnection          patchCord1(i2s1, 0, queue1, 0);
AudioConnection          patchCord2(i2s1, 0, peak1, 0);
AudioConnection          patchCord3(i2s1, 0, rms1, 0);
AudioControlWM8731       wm8731;         //xy=162,347
#endif

#ifdef USE_SGTL5000
AudioInputI2S            i2s1;           //xy=151,269
AudioAnalyzeRMS          rms1;           //xy=377,176
AudioAnalyzePeak         peak1;          //xy=381,220
AudioRecordQueue         queue1;         //xy=386,262
AudioConnection          patchCord1(i2s1, 0, queue1, 0);
AudioConnection          patchCord2(i2s1, 0, peak1, 0);
AudioConnection          patchCord3(i2s1, 0, rms1, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=162,347
#endif

int16_t  k,i1;
int16_t  dt[128*NBLOCKS];   // Keep one vector of the time series

void setup()
  {
  Serial.begin(300);  // Any rate will do
  delay(1000);        // Wait for serial
  AudioMemory(50);    // Safe number

  #ifdef USE_WM8731_CODEC_MASTER
  wm8731m1.enable();
  wm8731m1.inputSelect(AUDIO_INPUT_LINEIN);
  wm8731m1.inputLevel(1.0);
  Serial.println("WM8731 CODEC Master Enabled");
  #endif

  #ifdef USE_WM8731_CODEC_SLAVE
  wm8731.enable();
  wm8731.inputSelect(AUDIO_INPUT_LINEIN);
  wm8731.inputLevel(1.0);
  Serial.println("WM8731 CODEC Slave Enabled");
  #endif

  #ifdef USE_SGTL5000
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  // 2 & 19 sets 2.2 v p-p range for Line in and out
  sgtl5000_1.lineInLevel(2);
  sgtl5000_1.lineOutLevel(19);
  Serial.println("SGTL5000 Enabled");
  #endif

  i1 = -1000;
  queue1.begin();
  }

void loop()
  {
  int16_t *pd, *pq;
  // To prevent transients, toss out the first points:
  if (queue1.available() >= 1  &&  i1<=-1) // First 128 words
    {
    i1++;
    queue1.readBuffer();
    queue1.freeBuffer();
    }

  // Collect 128xNBLOCKS samples and output to Serial
  // This "if" will be active for i1 on (0, NBLOCKS-1)
  if (queue1.available() >= 1  && i1>=0 && i1<NBLOCKS) // See if 128 words are available
    {
    pq = queue1.readBuffer();
    pd = &dt[128*i1++];
    for(k=0; k<128; k++)
      {
      *pd++ = *pq++;        // Save 128 words in dt[]
      }
    queue1.freeBuffer();
    }

  // If NPOINTS words are saved, send them
  if(i1 == NBLOCKS)
    {
    i1 = NBLOCKS + 1;   // Should stop all this
    queue1.end();  // No more data to queue1
    Serial.println("128 x NBLOCKS Data Points:");
    for (k=0; k<128*NBLOCKS; k++)
      {
      Serial.println(dt[k]);
      }
    Serial.println("RMS, Peak both on (0.0, 1.0):");
    }

  // Finally, once every 2 seconds report RMS and Peak levels
  if(i1 == NBLOCKS+1)
    {
    if(rms1.available())
      Serial.print(rms1.read(),7);
    Serial.print(",");
    if(peak1.available())
      Serial.println(peak1.read(),7);
    delay(2000);
    }
  }

Moving to the frequency domain, I used the Teensy Audio Library 1024 point FFT. There is no signal input, and so the noise levels are low. To make sure that we are not looking at FFT residual noise, a gain of 100 was added after the ADC input. A power average over 16 FFT's was done to better see the noise levels. The values are converted to dB in the INO. Also, the spectral data has been reduced by 40 dB to compensate for the x100 gain. A plot of the spectrum, done in Gnumeric, follows
Codec3xNoSigSpectrumA.gif
The scallops are the frequency domain version of the "square waves." Also in the frequency domain, it is easier to quantify the difference between the three cases. Notable is the lowest frequency peak at about -78 dB for the SGTL5000, -88 dB for the WM8731-Slave and -93 dB for the WM8731-Master.

The INO for the frequency domain measurements is
Code:
// EvalWM8731_FFT2B.ino   RSL  15 March 2020
// For comparisons, this can also be run with the SGTL5000 Teensy Aduio Board

// Select only one:
#define USE_WM8731_CODEC_MASTER 2
// #define USE_WM8731_CODEC_SLAVE 3
// #define USE_SGTL5000 4

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <math.h>

#ifdef USE_WM8731_CODEC_MASTER
AudioInputI2Sslave       i2sslave1;      //xy=150,270
AudioAnalyzeRMS          rms1;           //xy=380,170
AudioAnalyzePeak         peak1;          //xy=380,220
AudioMixer4              gain0;         // Just used as a gain block
AudioAnalyzeFFT1024      fft1024;        //xy=580,320
AudioConnection          patchCord1(i2sslave1, 0, gain0, 0);
AudioConnection          patchCord2(i2sslave1, 0, peak1, 0);
AudioConnection          patchCord3(i2sslave1, 0, rms1, 0);
AudioConnection          patchCord4(gain0, fft1024);
AudioControlWM8731master wm8731m1;       //xy=160,347
#endif

#ifdef USE_WM8731_CODEC_SLAVE
AudioInputI2S            i2s1;           //xy=151,269
AudioAnalyzeRMS          rms1;           //xy=377,176
AudioAnalyzePeak         peak1;          //xy=381,220
AudioMixer4              gain0;          // Just used as a gain block
AudioAnalyzeFFT1024      fft1024;        //xy=580,320
AudioConnection          patchCord1(i2s1, 0, gain0, 0);
AudioConnection          patchCord2(i2s1, 0, peak1, 0);
AudioConnection          patchCord3(i2s1, 0, rms1, 0);
AudioConnection          patchCord4(gain0, fft1024);
AudioControlWM8731       wm8731;         //xy=162,347
#endif

#ifdef USE_SGTL5000
AudioInputI2S            i2s1;           //xy=151,269
AudioAnalyzeRMS          rms1;           //xy=377,176
AudioAnalyzePeak         peak1;          //xy=381,220
AudioMixer4              gain0;         // Just used as a gain block
AudioAnalyzeFFT1024      fft1024;        //xy=580,320
AudioConnection          patchCord1(i2s1, 0, gain0, 0);
AudioConnection          patchCord2(i2s1, 0, peak1, 0);
AudioConnection          patchCord3(i2s1, 0, rms1, 0);
AudioConnection          patchCord4(gain0, fft1024);
AudioControlSGTL5000     sgtl5000_1;     //xy=162,347
#endif

int16_t  k;
double powerSum[512];     // Accumulate sums of power for each bin

void setup()
  {
  Serial.begin(300);  // Any rate will do
  delay(1000);        // Wait for serial
  AudioMemory(50);    // Safe number

  #ifdef USE_WM8731_CODEC_MASTER
  wm8731m1.enable();
  wm8731m1.inputSelect(AUDIO_INPUT_LINEIN);
  wm8731m1.inputLevel(1.0);
  Serial.println("WM8731 CODEC Master Enabled");
  #endif

  #ifdef USE_WM8731_CODEC_SLAVE
  wm8731.enable();
  wm8731.inputSelect(AUDIO_INPUT_LINEIN);
  wm8731.inputLevel(1.0);
  Serial.println("WM8731 CODEC Slave Enabled");
  #endif

  #ifdef USE_SGTL5000
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
  // 2 & 19 sets 2.2 v p-p range for Line in and out
  sgtl5000_1.lineInLevel(2);
  sgtl5000_1.lineOutLevel(19);
  Serial.println("SGTL5000 Enabled");
  #endif

  // Allow for this extra 40 dB in post-processing
  gain0.gain(0,100);
  
  for (k=0; k<512; k++)
     powerSum[k] = 0.0;
  k = 0;
  }
 
void loop()
  {
  int i;
  float d[512];

  if ( fft1024.available() && k>=0 )
    {
    k++;
    for (i=0; i<512; i++)          // Get a full data set quickly
      {
      d[i] = fft1024.read(i);       // Get rms voltage
      }
    // Let things settle.  Wait 150 ffts
    if(k>=150 && k<166)     // Average 16 of them
       {
       Serial.print(k-150);
       for (i=0; i<512; i++)
          {
          // To be safe, do the sum as doubles, adding big and small with accuracy
          powerSum[i] += (double)(d[i] * d[i]);  // d[] are f32 rms voltage
          }
        }
     if (k==166)           // Average is done.  Print in dB
        {
	      k = -1;            // We are done, mark it
        Serial.println();
        Serial.print("Peak, rel FS at ADC Output: ");
        if(peak1.available() )  Serial.println(peak1.read(), 7 );
        Serial.print("RMS, rel FS at ADC output: ");
        if(rms1.available() )   Serial.println(rms1.read(), 7 );
        for (i=0; i<512; i++)
          {
		      // Convert power to dB; -40 dB accounts fpr gain of 100,
		      // So outputs are dB relative to full scale. Sum=16x -> 12.0412 dB
          Serial.println(10.0*log10f((float)powerSum[i]) -40.0 -12.0412, 3);
          }
       }
    }
  }

Finally, a signal was introduced across the 47 Ohm resistor. This was generated by a HP33120 DDS Function Generator with a 1:1 transformer to prevent ground loops. The frequency was chosen to hit in the middle of FFT bin 22 to make the center of bin22 contain the most power. For the Codec slaves this is 947.46 Hz and for the master case it is 1031.25 Hz. The three spectrums are
Codec3xSigSpectrumA.gif
Careful examination shows which spikes come from the generator and those from the ADC process. The gain of the two boards was not set perfectly and they differ by a few dB. Note that the spectrum of the ADC "square wave" is reduced by the presence of an external sine wave. Also, it appears that the SGTL5000 is introducing harmonics of the generator signal. That would be good to investigate further, but I have not done so.

That is the fast summary. I would appreciate if anyone could reproduce (some) of these results. Likewise, there are many parameters that can be varied and this is only a sampling; more data might show other characteristics. Please report on what you observe.

Finally, note that the levels of all of these ADC artifacts are small. For most audio applications, they can be ignored. For radio and instrumentation applications, they can be much more important. Pay attention according to the application.

Bob
 
By accident, I discovered that the presence of the square wave in the SGTL5000 depends on the analog audio gain inside the CODEC, as set by lineInLevel(x). Here are two cases for x=10(top) and x=0 (bottom).
TimeVsLineIn.jpg
The overall level as a fraction of full scale is not all that different, but the energy shifts between the square wave and random noise. This could be explored in detail with FFT and the like, but the trend is obvious without all that.

What is happening here? Have others seen this?
 
Yes, I've seen similar effects in the CS42448 board I'm building.

Distortion sets in quite heavily well before the theoretical maximum. Interestingly, the bottom of the waveform flattens first. It might well be internal PGA gain-related, but I haven't researched this yet on the CS42448.

I'm also starting to feel that the input impedance to the CODECS may not be linear, and buffers (Z < 100 ohms) may be needed on the inputs to drive them properly.

I'm dealing with a bunch of CoVid operational re-thinking at work at the moment, so it might be a week or so before I get back to the project, which I'm tracking in https://forum.pjrc.com/threads/58836-CS42448-board-update-for-T4-pinouts-and-improved-performance?highlight=cs42448.
 
I was unaware of your CS42488 project, and it looks like fun stuff.

You show a couple of spectrum plots on 2-25-2020. The one of noise only is interesting. How many points in the FFT (because, that varies the noise bandwidth)? It doesn't look like you see strong spectral spikes, but are there a bunch of small spikes at higher frequencies, or is that an illusion? When the FFT is plotted on a log frequency scale, it gets hard to interpret the right side.

Also, I might note that the plots of two time series I posted this morning (above) were with the input terminated in a 47-Ohm resistor, and no other wires connected to the audio board. So it is a simple set of the T-3.6 and a PJRC audio adapter plugged into the T3.6. The 47 Ohm is soldered to the audio board. That still leaves the possibility of digital stuff getting coupled into the audio board and the connecting pins. Why the square wave gets bigger when the analog audio gain is reduced remains puzzling.

Thanks for the note, Bob
 
Bob,

What are you driving the input with? The 47ohm needs to be added to its source impedance to get a true value of the driving Z.

Other things to look out for - is the offset/reference voltage set correctly for the interface (i.e. at 0.5 x the audio V+ rail) in the SGTL/WM driver code? PGA and other internal gain settings are important, as you have already noted. I'm not sure whether the I2S is running native 16 bit, or truncating 24/32 bit transfers into the Teensy 16 bit format. I know the TDM code does a few interesting things with the lower 16 bits of the data stream (as they are presented as odd-numbered channels in the Library), still working this out.

My test images are from RightMark, and I think the FFTs are 16384 buckets (2.7Hz resolution), but I have the free version so those settings are hidden.

What I did see as I turned up the level is strong peaks start to appear above the noise at odd & even multiples of the test frequency.
thd.png

These started to appear well below the theoretical maximum input level. I re-ran the tests at a lower input level and S/N suffered, while THD improved.
thd.png.

These are newer (21 March) than the ones in the blog, and I didn't keep track of the absolute values.

BTW: My set up is a FocusRite audio interface @24 bits/48kHz, as that's what I use for audio recording. A loopback test on the Focusrite has S/N > 100dB and THD < 0.005%.

My current S/N of 70dB is disappointing, and I'm trying to work out where this is happening (noise on the board, CODECS, audio library). Theoretically, I should be able to get close to 85dB with 16 bit data for the audio library portion. My SGTL5000 measurements on Paul's standard RevD audio board suggest that this is possible.
thd.png

My guess is that the Focusrite output impedance is around 200 ohms, as this is pretty much an industry standard, but I haven't measured it yet using the standard open circuit/sink resistor method.

Hope all this helps.
 
@palmerr, It is interesting to see the other data and thanks for posting that. On most of the plots I have shown, there is no input. The signal like spikes are coming from the CODEC board and possibly the T3.6 it is plugged into. The 47-Ohm resistor I referred to serves as a short across the input and prevents stray pickup. It also is convenient when I do connect my audio generator as is a HP33120A with a 50-Ohm output. The output level display is correct when working into 50-ish Ohms.

Here are more plots of spectrum, measured using the EvalWM8731_FFT2B.ino shown in post #1 above. The reason for these additional measurements is the change in time series in post #2 that occurred with the SGTL5000 when changing the analog gain. It is hard to see what is happening in the time domain of post #2. So here are the observed spectra. First are three curves for the SGTL5000 CODEC with LevelIn settings:
LineInLevel(0) 3.12 Vp-p
LineInLevel(2) 2.22 Vp-p
LineInLevel(10) 0.56 Vp-p
B_SGTL5000.gif
The higher gain of (10) does indeed have more noise, spread across the spectrum, and for some reason the coherent spikes are reduced, corresponding to the time domain observations. The total power does not change much between the three curves.

My next concern was whether the WM8731 signal-free spectrum changes with gain setting, and the quick answer is, "very little." The next plot is for the WM8731 running as CODEC Master with two gain settings 0.0 and 1.0, the full limits:
B_WM8731_CodecMaster.gif
There are two curves in red and blue for the two gain limits, but they overlap quite a bit. This one does not change with gain setting.

Next is the WM8731 still without any input signal:
B_WM8731_CodecSlave.gif
It appears that there is a change in the spectra with gain change, but further measurements showed this was unlikely. I made 10 more runs of the same no noise and WM8731 Codec-Slave. The results of each run was different and they ranged between the red and blue curves of the last plot---not a lot but enough to be obvious. One would expect to have the noise from the CODEC ADC to look the same from time to time in a statistical sense, referred to as "stationary statistics." This does not have that for the case of "CODEC slave," where the timing comes from the T3.6. The why is not obvious, but the effect is small.

Conclusion is that all three cases of SGTL5000, WM8731 master and WM8731 codec-slave have small square-wave-like artifacts that add to the random ADC noise. The total noise for all three is small but does limit performance for some applications. In order of most quiet to least quiet is the WM-8731 Codec master, WM8731 as Codec-slave and the SGTL-5000 which is always Codec slave (T3.6-master). The differences are small and the use of Codec-master is burdensome on other audio timing, as has been pointed out.

Or so it seems. Comments are welcomed! Bob
 
Bob

Fascinating. The harmonic frequencies of the artefact are close, but not identical for both interfaces, and the SGTL is only showing odd harmonics!

Some thoughts (I suspect you have already checked all of these, but nevertheless):
  • Timebase jitter - MCLK, BCLK, LRCLK analysis should show this. It's unlikely, as jitter usually shows up as spread-spectrum noise.
  • Audio library artefact - I'd expect this at some multiple of 2.9ms (~344Hz) rather than 2.x kHz.
  • Codec Digital filter artefact - This is quite a possibility - see if you can turn off the internal filters. I know there's been forum discussion about the HPF on the SGTL 5000 producing funny artefacts.
  • Actual 2.x kHz getting into the audio - a disconnected CRO probe might sniff this - or simply probing every teensy pin for something with a 2.x kHZ fundamental. Unlikely as the frequencies are a bit different.
  • Something in the generator or test setup - running the analysis using a physical bypass will show this up. Again, unlikely.

Sorry, nothing else comes to mind immediately. Looking at my noise readings on the SGTL5000, they do show similar peaks. I must admit I ignored them as they're well below audibility at -90dB (and below the theoretical maximum for 16 bit audio ~-86dB). Now you've piqued my interest. The CS42448 tests show a broader noise spectrum, though I'm currently looking at getting 24 bit pass-through happening on the TDM interface - as Paul's TDM object presents the lower 16 bits on odd numbered channels (but they're all reading as value 1.0).


SGTL5000 Noise spectrum:

noise.png

Happy hunting!

Richard
 
Hi Richard - Thanks for the thoughts.

Bob

Fascinating. The harmonic frequencies of the artefact are close, but not identical for both interfaces, and the SGTL is only showing odd harmonics!

Some thoughts (I suspect you have already checked all of these, but nevertheless):
  • Timebase jitter - MCLK, BCLK, LRCLK analysis should show this. It's unlikely, as jitter usually shows up as spread-spectrum noise.
  • Audio library artefact - I'd expect this at some multiple of 2.9ms (~344Hz) rather than 2.x kHz.
  • Codec Digital filter artefact - This is quite a possibility - see if you can turn off the internal filters. I know there's been forum discussion about the HPF on the SGTL 5000 producing funny artefacts.
  • Actual 2.x kHz getting into the audio - a disconnected CRO probe might sniff this - or simply probing every teensy pin for something with a 2.x kHZ fundamental. Unlikely as the frequencies are a bit different.
  • Something in the generator or test setup - running the analysis using a physical bypass will show this up. Again, unlikely.
I will explore your idea of probing for spectral stuff associated with the hardware. One way is to use a general purpose spectrum analyzer running in the PC like Spectrum Labhttps://www.qsl.net/dl4yhf/spectra1.html or similar SA with many turns on a ferrite core probe. Or something of that sort.

I will also explore the internal HPF's of the CODEC's.

I was able to look at the detailed FFT output and interpolate the frequencies of the spike. From this it checked "exactly" that the three cases are
SGTL5000 Codec Slave Fundamental 1838 Hz, sample rate/24
WM8731 Codec-Master Fundamental 2090 Hz, Sample Rate/23
WM8731 Codec Slave Fundamental 1918 Hz, Sample Rate/23
Cute huh? One of the CODECs is x24 and the other is x23 suggesting that the artifact is part of the delta-sigma ADC process. It can't relate to the T3.6 wiring since theT3.6 doesn't know who is hooked to the I2S. Or so it would seem.

I gave out a spectrum plot with an error in the frequency scale in post #6. The WM8731 Codec Slave is running at the 44117 sample rate, the regular thing and the same as the SGTL5000. Here is a corrected plot:
B_WM8731_CodecSlave_2.gif
Bob
 
Hi Frank - It appears that there is a small difference. With LineInLevel set to 10, it is the same as before, but with LineInLevel set to 0, the spectrum is now about the same as it was for LineInLevel 10. So, it looks like there is a change, but it is minor. Here are the 4 curves, two with the SCLK x64 0x0030 change
C_SGTL5000_WithSCLK64.gif

Bob
 
Status
Not open for further replies.
Back
Top