Unexpected FFT Peak with Disconnected Sensors

All window functions heavily distort the frequency spectum
I wholeheartedly dispute this, have you used a modern S.A. with FFT mode?

Here's a plot of a 1kHz DDS oscillator recorded by a QA403 with no window:
a.png


And the same with a lowly Hann window:

h.png


Which one is useful? No contest at all. Without the window spectral leakage _completely_ _obliterates_ the rest of the spectrum - that's what I call heavily distorting the frequency spectrum....

In this instance the signal could not be synchronized to the FFT at all because it come from a separate unit with its own clock and used a DAC sampled at a different rate even, 44.1kSPS, whereas the QA403 analyzer is running at 192kSPS from a different quartz crystal.

Note that the Hann window is not a great choice for this spectrum since a flat-top window would give accurate peak heights only at the expense of some peak-broadening and higher noise floor - here that would not obscure the dominant harmonics or get in the way of accurate measurement of them. However the Hann window is good enough to let you see the harmonics properly, not hiding under a massive skirt...
 
Last edited:
I provide the information for that, 192kSPS, 256k point FFT, so ~1.3653s = 1365.3 cycles at 1kHz, +/- the error of two crystals...
 
Thanks. So this is irrelevant to what we were discussing (the effect on exactly one cycle or just less than one cycle) and to the OP's situation.

Applying a window fn is like applying amplitude modulation to the sample. if the components of your window frequency are 3 orders of magnitude lower than the sample most of the distorting effects are out of sight of the frequency range of interest and it works fine.

Because multiplication in time domain is equivalent to convolution in f.d. , you are effectively applying convolution filters to the f.d. data. All filters distort. You need to select the most suitable distortion for a particular job. Removing the wrap-around glitch, which destroys the phase information in the sample is the primary need but this has side effects.

As you state , different window functions produce different results. That is because the remaining distortions are visible and you need to chose which set of distortions are the most helpful.

Let's try to keep this relevant to Aaron's problem. Applying a window fn to one cycle of sample will not help because the window's fn main freq component will be the same as that of the sample.
 
Last edited:
Ok all I did a redesign of the example. I believe this is what @Aaron Z is really trying to do.
Screenshot 2024-08-21 152353.png


Now if there is no connections between input and output nothing is seen in the fft plot.

But once I connect Line in with line out
Figure_1.png


Now no clue how to apply a window function in the python example. If someone wants to mod the example code will be happy to try it.

EDIT: Forgot here is the sketch for you all to have fun with

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=294,318
AudioSynthWaveform       waveform;      //xy=294,449
AudioMixer4              mixer;         //xy=467,361
AudioOutputI2S           i2s2;           //xy=473,458
AudioRecordQueue         queue;         //xy=651,295
AudioConnection          patchCord1(i2s1, 0, mixer, 0);
AudioConnection          patchCord2(i2s1, 1, mixer, 1);
AudioConnection          patchCord3(waveform, 0, i2s2, 0);
AudioConnection          patchCord4(waveform, 0, i2s2, 1);
AudioConnection          patchCord5(mixer, queue);
AudioControlSGTL5000     sgtl5000_1;    //xy=467,541
// GUItool: end automatically generated code


const int myInput = AUDIO_INPUT_LINEIN;


void setup() {
  Serial.begin(9600);
  AudioMemory(60);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(1);
  sgtl5000_1.lineInLevel(15);    // 0-15, default 5, set input sensitivity
  sgtl5000_1.lineOutLevel(13);    // 13-31, default 29, set output voltage

  // configure the mixer to equally add left & right
  mixer.gain(0, 1.5);
  mixer.gain(1, 1.5);

  waveform.begin(1, 10000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

  queue.begin();
}

void loop() {
  if (queue.available() > 0) {
    int16_t *buffer = queue.readBuffer();
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      Serial.print(buffer[i]);
      if (i < AUDIO_BLOCK_SAMPLES - 1) Serial.print(",");
      delayMicroseconds(50);
    }
    Serial.println();
    queue.freeBuffer();
  }
}
 
Ok all I did a redesign of the example. I believe this is what @Aaron Z is really trying to do.
View attachment 35555

Now if there is no connections between input and output nothing is seen in the fft plot.

But once I connect Line in with line out
View attachment 35556

Now no clue how to apply a window function in the python example. If someone wants to mod the example code will be happy to try it.

EDIT: Forgot here is the sketch for you all to have fun with

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=294,318
AudioSynthWaveform       waveform;      //xy=294,449
AudioMixer4              mixer;         //xy=467,361
AudioOutputI2S           i2s2;           //xy=473,458
AudioRecordQueue         queue;         //xy=651,295
AudioConnection          patchCord1(i2s1, 0, mixer, 0);
AudioConnection          patchCord2(i2s1, 1, mixer, 1);
AudioConnection          patchCord3(waveform, 0, i2s2, 0);
AudioConnection          patchCord4(waveform, 0, i2s2, 1);
AudioConnection          patchCord5(mixer, queue);
AudioControlSGTL5000     sgtl5000_1;    //xy=467,541
// GUItool: end automatically generated code


const int myInput = AUDIO_INPUT_LINEIN;


void setup() {
  Serial.begin(9600);
  AudioMemory(60);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(1);
  sgtl5000_1.lineInLevel(15);    // 0-15, default 5, set input sensitivity
  sgtl5000_1.lineOutLevel(13);    // 13-31, default 29, set output voltage

  // configure the mixer to equally add left & right
  mixer.gain(0, 1.5);
  mixer.gain(1, 1.5);

  waveform.begin(1, 10000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

  queue.begin();
}

void loop() {
  if (queue.available() > 0) {
    int16_t *buffer = queue.readBuffer();
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      Serial.print(buffer[i]);
      if (i < AUDIO_BLOCK_SAMPLES - 1) Serial.print(",");
      delayMicroseconds(50);
    }
    Serial.println();
    queue.freeBuffer();
  }
}
Thank you for your reply!
I tried your sketch, but sometimes I would have errors recording data, then I can't plot FFT. I think the reason might be "delayMicroseconds(50)", so what is the purpose for using this here? But those errors only occurred occasionally, sometimes I could plot the FFT. You mentioned you saw nothing on fft when nothing is connected to the input and output. Is this what you have (picture below)?
截图 2024-08-21 17-22-47.png

There's nothing on the FFT, which actually makes sense, since no sensors are connected to the input and output.
But when I connect both sensors to the board, just like the setup I posted, I still got this same FFT plot with nothing on it. I think it doesn't really make sense here cuz I suppose some signals(waves) or noise should show up. And also i suppose some signals should come from the sensor(receiver) on line_in.
When I delete "delayMicroseconds(50)", I could then see the peak at 10kHz, which is just what we were seeing before. Even when I disconnected both sensors, it still shows the same FFT with this peak, which means the signal on line_out is still leaking to line_in.
截图 2024-08-21 17-36-48.png

So, not sure if your sketch could solve my issue, and I'm little confused about "delayMicroseconds(50)" here.
Really appreciate your reply!
 
think the reason might be "delayMicroseconds(50)", so what is the purpose for using this here?
When running your sketch with read_serial and the ploting sketch I ran into the same issue but more often than not it the plot sketch was erroring out on reading the csv data. So I change the baud rate in the read serial sketch to 2000000 and added the delay on the output to get the data recorded more reliably - I am on a windows 11 pc. So if its working better for you without the delay then thats ok. For me I was having issues.
You mentioned you saw nothing on fft when nothing is connected to the input and output. Is this what you have (picture below)?
yep

But when I connect both sensors to the board, just like the setup I posted, I still got this same FFT plot with nothing on it. I think it doesn't really make sense here cuz I suppose some signals(waves) or noise should show up. And also i suppose some signals should come from the sensor(receiver) on line_in.

it still shows the same FFT with this peak, which means the signal on line_out is still leaking to line_in

The test sketch uses waveform is in the input data and assumes you are jumping line_in to line_out. If you are putting sensors in the loop you are going to need to delete the patchcords to waveform and delete waveform.begin. Then if you want to here it you are going to have to attach a patchchord between I2S1 and I2S2 or from the mixer to the output. Otherwise its not going to work.
 
Been playing a bit more and cant figure out how to record and play at the same - however, I you just use the on board mic with no output and play some frequencies on the PC from youtube:

1khz:
Figure_1.png


2600 hz
Figure_2.png


5000hz:

5khz.png


probably have to remove DC level and use a window function to clean up real time signal.


Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=294,318
//AudioSynthWaveform       waveform;      //xy=294,449
AudioMixer4              mixer;         //xy=467,361
AudioOutputI2S           i2s2;           //xy=473,458
AudioRecordQueue         queue;         //xy=651,295
AudioConnection          patchCord1(i2s1, 0, mixer, 0);
AudioConnection          patchCord2(i2s1, 1, mixer, 1);
//AudioConnection          patchCord3(waveform, 0, i2s2, 0);
//AudioConnection          patchCord4(waveform, 0, i2s2, 1);
AudioConnection          patchCord5(mixer, queue);
AudioControlSGTL5000     sgtl5000_1;    //xy=467,541
// GUItool: end automatically generated code


//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

void setup() {
  Serial.begin(9600);
  AudioMemory(60);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.75);
  sgtl5000_1.lineInLevel(15);    // 0-15, default 5, set input sensitivity
  //sgtl5000_1.lineOutLevel(13);    // 13-31, default 29, set output voltage

  // configure the mixer to equally add left & right
  mixer.gain(0, 1.5);
  mixer.gain(1, 1.5);

  //waveform.begin(1, 1000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

  queue.begin();
}

void loop() {
  if (queue.available() >= 1) {
    int16_t *buffer = queue.readBuffer();
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      Serial.print(buffer[i]);
      if (i < AUDIO_BLOCK_SAMPLES - 1) Serial.print(",");
      //delayMicroseconds(50);
    }
    Serial.println();
    queue.freeBuffer();
  }

}
 
Been playing a bit more and cant figure out how to record and play at the same - however, I you just use the on board mic with no output and play some frequencies on the PC from youtube:

1khz:
View attachment 35570

2600 hz
View attachment 35571

5000hz:

View attachment 35572

probably have to remove DC level and use a window function to clean up real time signal.


Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=294,318
//AudioSynthWaveform       waveform;      //xy=294,449
AudioMixer4              mixer;         //xy=467,361
AudioOutputI2S           i2s2;           //xy=473,458
AudioRecordQueue         queue;         //xy=651,295
AudioConnection          patchCord1(i2s1, 0, mixer, 0);
AudioConnection          patchCord2(i2s1, 1, mixer, 1);
//AudioConnection          patchCord3(waveform, 0, i2s2, 0);
//AudioConnection          patchCord4(waveform, 0, i2s2, 1);
AudioConnection          patchCord5(mixer, queue);
AudioControlSGTL5000     sgtl5000_1;    //xy=467,541
// GUItool: end automatically generated code


//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

void setup() {
  Serial.begin(9600);
  AudioMemory(60);

  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.75);
  sgtl5000_1.lineInLevel(15);    // 0-15, default 5, set input sensitivity
  //sgtl5000_1.lineOutLevel(13);    // 13-31, default 29, set output voltage

  // configure the mixer to equally add left & right
  mixer.gain(0, 1.5);
  mixer.gain(1, 1.5);

  //waveform.begin(1, 1000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

  queue.begin();
}

void loop() {
  if (queue.available() >= 1) {
    int16_t *buffer = queue.readBuffer();
    for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
      Serial.print(buffer[i]);
      if (i < AUDIO_BLOCK_SAMPLES - 1) Serial.print(",");
      //delayMicroseconds(50);
    }
    Serial.println();
    queue.freeBuffer();
  }

}
Thanks for your reply!
Yes, if you delete the "waveform" and "waveform.begin", it works only as a receiver, which works well.
But here I tend to make the receiver and speaker work simultaneously. Still don't know how to do that.
Thank you!!
 
Thanks for your reply!
Yes, if you delete the "waveform" and "waveform.begin", it works only as a receiver, which works well.
But here I tend to make the receiver and speaker work simultaneously. Still don't know how to do that.
Thank you!!
Point of the test was just to try and see in using the mic actually worked - more for curiosity.

As for what you really want to do - havent been able to figure it out. Sorry. I am not an audiophile person so maybe someone with more experiance can help out here.

See post below
 
Last edited:
Thanks for your reply!
Yes, if you delete the "waveform" and "waveform.begin", it works only as a receiver, which works well.
But here I tend to make the receiver and speaker work simultaneously. Still don't know how to do that.
Thank you!!
Think I found the solution but not an explanation of why. Check out this post: Audio lineIn reading data from SDplay output without any connections.

Basically comment out the following line:

Code:
  //sgtl5000_1.volume(1);
in your original sketch and then it starts working the way you want it work. I just gave it a try and it seems to work. If I hook up linein to lineout I am seeing the waveform, if I disconnect them the waveform goes away.

Let me know if it works for you.
 
Think I found the solution but not an explanation of why. Check out this post: Audio lineIn reading data from SDplay output without any connections.

Basically comment out the following line:

Code:
  //sgtl5000_1.volume(1);
in your original sketch and then it starts working the way you want it work. I just gave it a try and it seems to work. If I hook up linein to lineout I am seeing the waveform, if I disconnect them the waveform goes away.

Let me know if it works for you.
Hi mjs513, thank you so much for your help!
I followed your instruction and commented out "sgtl5000_1.volume(1);". But still I could see the peak at 10kHz when disconnected line_in and line_out. Here are the two FFTs in linear scale and logarithmic scale.
截图 2024-08-27 14-45-34.png
截图 2024-08-27 14-45-52.png

Is that the same result you got? Seems like it becomes better since the peak value decreases comparing with my previous plots (posted before). But I still don't understand why we still see the peak here. I know that's the frequency I set for "waveform”, but I supposed line_in would detect nothing since no sensor is connected.
 
Is that the same result you got? Seems like it becomes better since the peak value decreases comparing with my previous plots (posted before). But I still don't understand why we still see the peak here. I know that's the frequency I set for "waveform”, but I supposed line_in would detect nothing since no sensor is connected.
Same thing. But I have no clue. Doesn't seem to be a good answer that I can find.
 
Capacitive pickup, or perhaps some breakthrough via the power rails?

BTW you really ought to scale the axes properly so the numbers are sensible. Also where does the 900000 high peak come from even with 16 bit values where the range is +/-32767? The maximum amplitude should be 32767!, which is 93.4dB if not scaled properly and -3dB if scaled to +/-1.0 range. perhaps you multiplied by 32767 rather than dividing?

Anyway if that's the case your peak is -64.5dB and the noise floor about -90dB. (Note that the apparent noise floor level depends on the FFT bin size so this isn't a measurement, just a very rough figure).
 
An interesting problem. I did pull out my old t3.6 + audio board and did some testing.
It seems there is quite a significant crosstalk between the headphone amp and the ADC.
This is my test program, slightly modified version of the posted test sketch:
C++:
#include <Arduino.h>

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

// GUItool: begin automatically generated code
AudioInputI2S i2s1;             // xy=294,318
AudioSynthWaveform waveform; // xy=294,449
AudioMixer4 mixer;             // xy=467,361
AudioOutputI2S i2s2;         // xy=473,458
AudioRecordQueue queue;         // xy=651,295
AudioConnection patchCord1(i2s1, 0, mixer, 0);
AudioConnection patchCord2(i2s1, 1, mixer, 1);
AudioConnection patchCord3(waveform, 0, i2s2, 0);
AudioConnection patchCord4(waveform, 0, i2s2, 1);
AudioConnection patchCord5(mixer, queue);
AudioControlSGTL5000 sgtl5000_1; // xy=467,541
// GUItool: end automatically generated code

const int myInput = AUDIO_INPUT_LINEIN;

int16_t data[AUDIO_BLOCK_SAMPLES * 128];
uint8_t buf_count = 0;
int16_t* dataPtr = data;

bool started = false;
uint8_t test_step = 0;

void setup()
{
    Serial.begin(9600);
    AudioMemory(25);

    if (!sgtl5000_1.enable())
    {
        while(1)
        {
            Serial.println("Codec init fail!");
            delay(1000);
        }
    }
    sgtl5000_1.inputSelect(myInput);
    sgtl5000_1.volume(0.5);
    //sgtl5000_1.muteHeadphone();
    sgtl5000_1.lineInLevel(15);     // 0-15, default 5, set input sensitivity
    sgtl5000_1.lineOutLevel(29); // 13-31, default 29, set output voltage

    // configure the mixer to equally add left & right
    mixer.gain(0, 1.0);
    mixer.gain(1, 1.0);

    waveform.begin(1, 10000, WAVEFORM_SINE); // Set amplitude and frequency for speaker
    queue.begin();
}

void loop()
{
    if (queue.available() > 0 )
    {
        if (buf_count < 128+5 && started)
        {
            if (buf_count > 5)
            {
                int16_t *buffer = queue.readBuffer();
                memcpy(dataPtr, buffer, AUDIO_BLOCK_SAMPLES*sizeof(data[0]));
                dataPtr += AUDIO_BLOCK_SAMPLES;
            }
            buf_count++;
        }
        queue.freeBuffer();
    }
    if (buf_count == 128)
    {
        for (uint8_t i = 0; i<128; i++)
        {
            for (uint8_t j=0; j<AUDIO_BLOCK_SAMPLES; j++)
            {
                Serial.print(data[i*128 + j]);
                Serial.print(",");
            }
            Serial.println();
        }
        memset(data, 0, AUDIO_BLOCK_SAMPLES*sizeof(int16_t));
        buf_count = 0;
        dataPtr = data;
        started = false;
    }
    if (Serial.available() && started == false)
    {
        uint8_t serIn = Serial.read();
        if(serIn == 'R')
        {
            test_step = 0;
            memset(data, 0, AUDIO_BLOCK_SAMPLES*sizeof(int16_t));
            buf_count = 0;
            dataPtr = data;
        }
        else if (serIn == 'S')
        {
            if(test_step > 3) test_step = 0;
            switch(test_step)
            {
                case 0:
                    waveform.amplitude(1.0f);
                    // sgtl5000_1.unmuteHeadphone();
                    // sgtl5000_1.volume(1);
                    delay(50);
                    break;
                case 1:
                    waveform.amplitude(0.8f);
                    // sgtl5000_1.unmuteHeadphone();
                    // sgtl5000_1.volume(0.8);
                    delay(50);
                    break;
                case 2:
                    waveform.amplitude(0.4f);
                    // sgtl5000_1.muteHeadphone();       
                    delay(50);
                    break;
                case 3:
                    waveform.amplitude(0.0f);
                    // sgtl5000_1.unmuteHeadphone();
                    // sgtl5000_1.volume(0.5);                                   
                    delay(50);
                    break;
                default:
                    test_step = 0;
                    break;
            }
            test_step++;
            started = true;
            queue.clear();
        }
    }
}
It waits for a signal from the python script (char 'S') to start gathering the ADC data into a buffer, (128 audio data blocks). Once filled, it prints it via Serial.
I've set the headphone volume to 0.5f, which is a normally used value. Each test step changes the amplitude of the generated 10kHz sin from 1.0 to 0.0.
Before getting into FFTs i wanted to see what the source ADC signal looks like. Here is a plot, this is the case where the Line inputs are floating:
audioBoard_stock_t36.png
And this is in more real world scenario, were the inputs will be either shorted to GND (via jack contact if nothing is plugged in) or driven from a buffer:
audioBoard_stock_t36_inputMuted.png
Python script:
Python:
#!/usr/bin/env python3
import serial
import csv
import sys
import matplotlib.pyplot as plt

# Set the serial port and the recording duration (in seconds)
ser = serial.Serial('/dev/ttyACM0', 9600)

reset_sig = bytearray('R', 'ascii')
start_sig = bytearray('S', 'ascii')


def get_signal(port, file_name):
    sig = []
    port.write(start_sig)
    with open(file_name, 'w', newline='') as file:
        writer = csv.writer(file)
        buffer_count = 0  # Initialize the buffer counter
        while buffer_count < 128:
            line = ser.readline().decode().strip()  # Read a line from the serial port
            if line:
                line_split = line.split(',')
                writer.writerow(line_split)
                for element in line_split:
                    if element:
                        sig.append(int(element))
                buffer_count += 1  # Increment the sequence number

        print(f"Data recording completed, file: {file_name}")
    return sig

def __main(argv):
    ser.write(reset_sig)
    print('Test Step 1')
    data1 = get_signal(ser, 'test1.csv')
    #time.sleep(0.01)
    print('Test Step 2')
    data2 = get_signal(ser, 'test2.csv')
    #time.sleep(0.01)
    print('Test Step 3')
    data3 = get_signal(ser, 'test3.csv')
    #time.sleep(0.01)
    print('Test Step 4')
    data4 = get_signal(ser, 'test4.csv')
    #time.sleep(0.01)
    ser.close()  # Close the serial port

    fig, axs = plt.subplots(5, figsize=(10, 5))
    fig.suptitle('SGTL5000 HP amp crosstalk test, volume=0.5')

    axs[0].plot(data1, alpha=0.5)
    axs[0].plot(data2, alpha=0.5)
    axs[0].plot(data3, alpha=0.5)
    axs[0].plot(data4, alpha=0.5)
    axs[0].legend(["1.0", "0.8", "0.4", "0.0"], loc='center right')
    axs[0].grid()

    axs[1].plot(data1, label="ampl=1.0")
    axs[1].legend(loc='center right')
    axs[2].plot(data2, label="ampl=0.8")
    axs[2].legend(loc='center right')
    axs[3].plot(data3, label="ampl=0.4")
    axs[3].legend(loc='center right')
    axs[4].plot(data4, label="ampl=0.0")
    axs[4].legend(loc='center right')

    plt.tight_layout()
    plt.show()


if __name__ == "__main__":
    __main(sys.argv[1:])
My audio board has an added 10uF cap to the existing 2u2 on the analog 3.3V rail. Did not really help if the crosstalk is happening via the power line. Setting the volume to 1 and not using the Headphone out (will probably be heavily clipped anyway) is really a bad idea.
This might explain the presence of the 10kHz peak. If the Headphone output is not used, mute it, or even better disable to save power.

On a side note: piezo transducers are high impedance devices and require a special preamp if the signal they output should at least resemble what they are sensing. Here is a bit more info about piezo sensors and a few examples of suitable preamps:
Piezo sensor expects input impedance in range 1M to hundreds of Mohms, Line input on the audio board is 10k. Way too low.
Line outputs are also not really suitable to drive piezo transducers.
 
It seems there is quite a significant crosstalk between the headphone amp and the ADC.
Which makes perfect sense when you realize the low impedance headphone amp will take significant current from the supplies under load, imposing a current signal on them. The resistance of the bond-wires and on-chip power distribution metalization means this translates to a voltage signal (which can't be eliminated by external decoupling or regulation). Unless the ADC is on a separate die or uses a precision voltage reference this will inevitably mean some amount of cross-talk. Poor regulation of supply on the PCB will add to the issue - for lower audio frequencies decoupling isn't practical and you are limited by the voltage regulator performance (output impedance).

Consider a 10mA signal and a regulator with output impedance of 0.01 ohms. That's going to lead to 100µV signal on the supply, only 80dB down from a 1V ADC signal... ADCs need really clean supply rails (or reference voltage) to perform at their best.
 
Well, it seems a proper power rail decoupling does partially help.
More tests:
i've switched to a 100Hz square wave, as it will be better seen on the plots and harder to filter out.
Interestingly, after disabling the HP amp power, i'm still getting the crosstalk - this means it happens between the DAC and the ADC, too.
I have desoldered the ferrite bead and fed the analog 3.3V from the Nordic PPK2.
The 100Hz square wave edges are clearly seen on the current draw plot as peaks:
sgtl5000_analog3v3_100HzSQR.png
And in the ADC signal:
sgtl5000_crosstalk_100HzSQR_HPdisabled_ExtV33A_10uMLCC.jpg
The crosstalk is clearly caused by the current draw peaks, resulting in a voltage drops on the analog supply rail (= also ADC reference).
Next step: supply the analog 3.3V from a linear bench PSU - got the same crosstalk results. This is still with a 10uF cap soldered ontop of the default 0603 2.2uF.
Only after adding a 100uF low ESR cap across the analog 3.3V rail the peaks are gone:
sgtl5000_crosstalk_100HzSQR_HPdisabled_ExtV33A_100ulowESR.jpg
Headphone amp was disabled in the above test. After enabling it with volume = 0.5 we get this:
sgtl5000_crosstalk_100HzSQR_HPvol0_5_ExtV33A_100ulowESR.png
All the above tests were made with ADC Line inputs shorted to GND.
The results were the same as with bench supply (which has a series shunt resistor) when using an LF33CV voltage regulator and two 100uF decoupling caps.

Here is a snippet to disable the HP amp power, it's not available in the default audio lib driver:
C++:
bool sgtl5000_en_HP(bool state)
{
    uint16_t val = 0x40EB;         // disabled HP power
    uint16_t reg = 0x0030;         // CHIP_ANA_POWER
    if (state) val = 0x40FF;    // enable HP power
    Wire.beginTransmission(0x0A);
    Wire.write(reg >> 8);
    Wire.write(reg);
    Wire.write(val >> 8);
    Wire.write(val);
    if (Wire.endTransmission() == 0) return true;
    return false;
}
And a few conclusions:
for application where the crosstalk might be critical, as DAC providing a test signal and the ADC analyzing the response it's better to avoid using the headphone amp, if there is a load to drive, as a speaker/transducer, use an external amp. Use separate 3.3V regulator for the analog supply rail with enough decoupling capacitance.
 
On STM32, when noise is critical on an analog input, all the other pins in the same port should not be used for digital signals.
Could be similar on the NXP.
 
Back
Top