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.
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
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
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
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
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
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.
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
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
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