Unexpected FFT Peak with Disconnected Sensors

Aaron Z

Member
Hi everyone,

I'm doing an audio project on Teensy 4.0 integrating with Audio Shield. Two sensors are connected on the Audio Shield with one emitting signals and the other receiving them.
IMG_7580.jpeg
截图 2024-08-15 18-51-43.png

For the emitter, I use "waveform" from audio lib to generate signal at a specific frequency, and I use "queue" to record data for the receiver. I tend to record the received data and use them to plot FFT spectrogram, in order to see what their FFT looks like.

The problem is I could always "collect " the emitting signals, even when both of the sensors are disconnected. I mean no input or output to the audio shield, but still I could always see the corresponding peak on FFT at the frequency that I set for the emitting signal. Seems like "queue" would always capture some info from "waveform", even when the audio shield connects to nothing. What I expect here is I should only record signals that emitted by the emitter, and received from the other sensor (receiver).

Is Teeny able to emit and receive signals simultaneously, just like what I've been doing here? Or it's the problem with the code?

Here is the code for Teensy:
Code:
#include <Audio.h>
#include <SerialFlash.h>

// Setup for receiver
AudioInputI2S            i2s1;
AudioRecordQueue         queue;    // Use a queue object to record data into the buffer
AudioConnection          patchCord1(i2s1, queue);
AudioControlSGTL5000     sgtl5000_1;

// Setup for speaker
AudioSynthWaveform       waveform;     
AudioOutputI2S           i2s2;           
AudioConnection          patchCord2(waveform, 0, i2s2, 0);  // L LINE_OUT

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

  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(",");
    }
    Serial.println();
    queue.freeBuffer();
  }
}
I also attached the python files for saving data ("read_serial") and for plotting FFT ("plot_fft"), in case you'd like to play around!

Please leave message if you got any idea!

Thank you!
 
It would be interesting to see how this works. I love FFTs like Kamal Harris loves Venn diagrams !!
 
I did not express any political opinion. She is famous for saying how much she loooooosves Venn diagrams. I made a play on that that I similarly try to apply FFTs where ever I can.
 
I also don't see any attachments.

At what levels exactly are we looking here in the FFT plot? Is it really 5e8? Or 5e-8? If it should be a very low level there could be some crosstalk on the analog lines which is totally normal. What levels do you read when the sensors are connected?
 
Its always much more informative to plot a spectrum logarithmically, there's probably clues in the data you cannot see in a linear plot...
 
Sorry guys! Here are the attached python files, they can only be attached in txt format.
One is for saving data ("read_serial") and the other is for plotting FFT ("plot_fft").
 

Attachments

  • read_serial.txt
    1 KB · Views: 35
  • plot_fft.txt
    964 bytes · Views: 40
I also don't see any attachments.

At what levels exactly are we looking here in the FFT plot? Is it really 5e8? Or 5e-8? If it should be a very low level there could be some crosstalk on the analog lines which is totally normal. What levels do you read when the sensors are connected?
Yes, the level here is exact 5e8 which is really high!
When sensors are connected, the level of the peak lies from 2e8 to 5e8 while I set the frequency (of the emitter) from 20Hz to 20kHz. So, if sensors are disconnected, there is still a dominant peak (5e8).
 
Its always much more informative to plot a spectrum logarithmically, there's probably clues in the data you cannot see in a linear plot...
Good idea! Here's what the logarithmic spectrogram looks like. There's still a dominant peak at 10kHz, when nothing is connected.
截图 2024-08-16 17-25-13.png
 
Well its pretty clean, unlikely to be a software artifact in that case.
The large values suggest you are not using norm='forward', which is correct for spectral analysis (scipy.fft defaults to norm='backward' alas, not very intuitive).
The skirts on the peak are due to not using a window function (leading to spectral leakage) - normally a window function is essential in spectral analysis unless the signal source is strictly locked to a precise fraction of the sampling frequency. A 'flattop' window is the correct choice for measuring signal peaks, 'Hann' window is often chosen for general use where peak measurements aren't needed to be accurate.

I would imagine there is simply signal breakthrough on the PCB due to poor grounding or something, leaking some 10kHz signal. The 2nd harmonic suggests the 10kHz signal is not a precision audio oscillator.
 
Without any meaningful units for the level it is difficult to get a clear impression.
I would suggest:
Normalize the level between 0.0 and 1.0 (isn't that the numbers you get from your audio samples anyway?
Convert to dBFS (decibel full scale) by 20*log10(level_data).
Then we would have useful numbers for the level, e.g. -10 or -80 to interpret the data.
As already suggested, make a plot from the scenario when the sensors are connected.
As third, connect line in and line out directly.
Then we would have numbers to compare and there is a better chance to understand the problem.
 
Have a very basic question. In the sketch you are creating a 10khz sine wave:
Code:
  waveform.begin(1, 10000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

have you tried disabling the this or varying the frequency of the waveform - like changing it to 1khz and see if you are getting a 1khz peak. If so you are picking up the wave form whether you have anything attached or not
 
Have a very basic question. In the sketch you are creating a 10khz sine wave:
Code:
  waveform.begin(1, 10000, WAVEFORM_SINE);   // Set amplitude and frequency for speaker

have you tried disabling the this or varying the frequency of the waveform - like changing it to 1khz and see if you are getting a 1khz peak. If so you are picking up the wave form whether you have anything attached or not
This is exactly what I'm doing here. I want the speaker (emitter) and receiver work simultaneously. So, the "waveform" here is what I used to generate a signal on Line_Out. I could set the frequency to any value, and still we could see the peak at the value I set. For example, if I set it to 1kHz, I would see a peak at 1kHz on the FFT.
 
Could you have not said before that you did already this and that the spectrum peak you see corresponds to the signal you transmit. Now we needed 13 replies to understand that you record always the outgoing signal.
In other words there is a loopback, which could be in hardware (somewhere line-out is connected to line-in), in sgtl5000 (not found any loopback instruction), in I2S module (could not find any SAI loopback instruction), or in your code.
I cannot see any loopback in your code, so only possibility is your hardware. Assuming you disconnected both line-in and line-out cables, then the most likely possibility is a solder bridge between line-out and line-in, which you could easily check visually and electrical.
 
Could you have not said before that you did already this and that the spectrum peak you see corresponds to the signal you transmit. Now we needed 13 replies to understand that you record always the outgoing signal.
In other words there is a loopback, which could be in hardware (somewhere line-out is connected to line-in), in sgtl5000 (not found any loopback instruction), in I2S module (could not find any SAI loopback instruction), or in your code.
I cannot see any loopback in your code, so only possibility is your hardware. Assuming you disconnected both line-in and line-out cables, then the most likely possibility is a solder bridge between line-out and line-in, which you could easily check visually and electrical.

@Aaron Z

Are jumping Linein to lineOUt - i see wires but not sure if the are connected to form a loop
IMG_7631.jpeg
IMG_7628.jpeg
IMG_7627.jpeg

Sorry for the confusion!
Here are my current setup where "line_out" is connected to the speaker(emitter) and "line_in" is connected to the receiver. I used the same type of sensors for this, which is a piezoelectric sensor. They are just directly connected to the "line_in" and "line_out".
And I think the soldering on the shield are isolated. So, when I disconnected both "line_in" and "line_out" cables, there shouldn't be a solder bridge I guess.
 
Ok all.

Since I don't have the appropriate sensors I just created a loop-back by connecting LR_lineIN to LR_LineOut and ran the sketch and the Python code:
Figure_1.png

If I only attach - one channel, left_lineIN to Left_lineOUT
1724097420835.png


Pretty much the same.
But no way as noisy as shown in original plot. Sorry didn't convert to db's - wanted to compare apples to apples.
 
Last edited:
As a follow up if nothing is
1724097698451.png


My guess is that you are attaching the waveform to the output:
Code:
AudioConnection          patchCord2(waveform, 0, i2s2, 0);  // L LINE_OUT

So its still being processed. So even if you don't have a hardwired connection or sensors attached you are going to get the waveform you attached.
 
As a follow up if nothing is
View attachment 35534

My guess is that you are attaching the waveform to the output:
Code:
AudioConnection          patchCord2(waveform, 0, i2s2, 0);  // L LINE_OUT

So its still being processed. So even if you don't have a hardwired connection or sensors attached you are going to get the waveform you attached.
Hi. Thanks for your reply!
Yes, I'm attaching "waveform" to the output, so the speaker(emitter) could keep generating signals.
Do you mean the "line_in" could always capture the signals(waves) I attached on "line_out", even when nothing is connected to "line_in" & "line_out"? So the signals(waves) just go through the board or air?
 
scipy.fft defaults to norm='backward' alas
Hi Mark. That seems like a good choice if your buffer is a data stream. You probably want to analyse the most recent data, not what happened when you turn it on !

Your point about window function is not relevant to this test case where he intends to have one complete sine wave cycle in the buffer. Window functions are to reduce spurious frequencies created by the step created by looping the last sample to the first sample (which is what FFT effectively does). The window fn acts as a low pass filter on the frequency domain and introduces a whole new bunch if distortions which you hope are less obstructive than the wrap-around glitch.

In the case being tested here they would introduce a lot of mess and distort the freq spectrum. In this special case of a sample which is an integral number of cycles you definitely to not want a window function.
 
Last edited:
Yes, I'm attaching "waveform" to the output, so the speaker(emitter) could keep generating signals.
Do you mean the "line_in" could always capture the signals(waves) I attached on "line_out", even when nothing is connected to "line_in" & "line_out"? So the signals(waves) just go through the board or air?
Well look like what it happening when you specify waveform as output it goes to the record queque that is why you are seeing output. I really don't know the ins and outs of the Audio library so maybe someone can help you more
 
Last edited:
The window fn acts as a low pass filter on the frequency domain and introduces a whole new bunch if distortions which you hope are less obstructive than the wrap-around glitch.
They are, almost always, way better! Basically a discontinuity spreads a wide skirt across the entire spectrum, hiding the noise floor completely and many small peaks too... Post #10 seems to have such a skirt, albeit fairly modest, I suspect a window will simply lose it.

Read this document carefully to learn more about windows and how to chose between them, its complicated but worth it: https://holometer.fnal.gov/GH_FFT.pdf
In the case being tested here they would introduce a lot of mess and distort the freq spectrum. In this special case of a sample which is an integral number of cycles you definitely to not want a window function.
Doesn't matter, if it _is_ an integral number of cycles the window function won't lose anything significant in a typical signal(*), and if it happens to _not quite_ be an integral number of cycles it will be extremely useful.

I think its best to use a window by default, and switch it out if you think its not needed - it will be obvious if it is!

(*) unless you are looking for very close in phase noise, or down at the noise floor.
 
if it _is_ an integral number of cycles the window function won't lose anything significant in a typical signal(*),
Incorrect. Multiplying the time domain by a window function has the effect of convoluting the frequency domain by the FT of the window function.
It will severely distort the frequency spectrum. You would generally expect this be better than nothing but it is a distortion. (In fact the "nothing" is a rectangular window fn whose FT in the sinc fn, which includes negative lobes) In the case of an integral number of cycles perfect sinewave the FT is an impulse function. When you convolute that, you do NOT get the unperturbed single frequency, you get the FT of your window fn.
and if it happens to _not quite_ be an integral number of cycles it will be extremely useful.
Wrong again. [ I suspect that may be what he has here, though I have not looked in depth. ]
In that case there would be some small distortions but basically a fairly clean peak. But if you multiply that with a window fn you will convolute the simple spike in freq domain by the FT of the window itself and get more information about the window fn than about the sample !

A lot of people who have practical experience of using FT analysis but do not understand the mathematics of how it works make similar erroneous conclusions.

Many window fns are some kind of bell curve which flattens both ends to zero and thus kind of does a fade in and fade out which avoids any glitch at wrap around. Since the FT of a gaussian is also a gaussian centred on zero frequency , it acts like a crude LP filter on the frequency output. Other windows are generally other mathematical forms which have somewhat different bell profile. In F.D. these may have better filter roll off but at the cost of introducing ringing or other distrotions. Some will be too heavy and smother a lot of detail in the spectrum, others introduce horrible ringing artefacts. It's horses for courses and the choice depends on what you need to preserve and what you can afford to lose or distort. All window functions heavily distort the frequency spectum and this needs to be understood when looking at the resulting spectrum which is no longer the spectrum of the signal.
 
Back
Top