Teensy 3.6 as FFT based knock sensor

Blu302

Member
I am building a knock sensor board based on a Teensy 3.6

I am utilising the onboard ADC and have a 4 pole MFB low pass @16Khz between the knock sensor and the ADC input.
The ADC is biased to 3.3v/2 with a ~0.5 amplifier output from the MFB because the sensor is ~6v at max output.

The FFT will be 256 bin(audio library) and I am wanting a window time of ~1ms to suit a 40Deg knock window at 6000rpm on a 6 cylinder engine.
The board is also an interface between the factory CAN bus and the aftermarket ECU. The factory CAN format can't be understood by the aftermarket ECU(Motec M800).

The aftermarket ECU sends a "high" trigger signal for the window time and when it goes "low" it expects the knock level(voltage 0-5v) on another input.
I will use the "low" time to measure the background noise level and subtract it from the "high" window to only send the signal that exceeds the background noise.

I have found how to change the ADC reference source away from the default to external/3.3v reference.
My main problem I am finding, is how to change the audio library away from the default 44.1Khz to something around 96Khz to get my desired time window.

Any help appreciated
 
I have been changing PDB0_MOD to see how fast I can get the FFT.256 going.

The teensy 3.5 I am testing on hangs when PDB0_MOD gets to 1023.
I have changed CPU speeds including overclock.
I have used USB 2.0 and 3.0 ports.
I have commented out Serial.print statements.
The lowest I have gotten to is 1010 on a once-off with PDB0_MOD.

Am I going about it the wrong way with speeding up the FFT?
Am I missing something?


Code:
/*
 * Object Oriented CAN example for Teensy 3.6 with Dual CAN buses 
 * By Collin Kidder. Based upon the work of Pawelsky and Teachop
 * 
 * Both buses are set to 500k to show things with a faster bus.
 * The reception of frames in this example is done via callbacks
 * to an object rather than polling. Frames are delivered as they come in.
 * 
 * analogReference(INTERNAL); // range 0 to 1.2 volts
 * analogReference(EXTERNAL); // range 0 to 3.3 volts
 * 
 * void analogReference(type);
 * Set type=0 for reference=VCC, type=1 for reference=internal (1.2V)
 */

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


int Count = 0;
int Mod = 1039;


// GUItool: begin automatically generated code
AudioInputAnalog         adc1(A0);           //xy=415,159 **Uses ADC0 pins**
AudioAnalyzeFFT256       fft256_1;       //xy=625,147
AudioConnection          patchCord1(adc1, fft256_1);
// GUItool: end automatically generated code

/*
available();
Returns true each time the FFT analysis produces new output data.

read(binNumber);
Read a single frequency bin, from 0 to 127. 
The result is scaled so 1.0 represents a full scale sine wave.

read(firstBin, lastBin);
Read several frequency bins, returning their sum. 
The higher audio octaves are represented by many bins, 
which are typically read as a group for audio visualization.

averageTogether(number);
New data is produced very radidly, approximately 344 times per second. 
Multiple outputs can be averaged together, so available() returns true at a slower rate.

windowFunction(window); 
Set the window function to be used. AudioWindowHanning256 is the default. 
Windowing may be disabled by NULL, but windowing should be used for all non-periodic (music) signals, 
and all periodic signals that are not exact integer division of the sample rate.

The raw 16 bit output data bins may be access with myFFT.output[num], where num is 0 to 127.
*/

class ExampleClass : public CANListener 
{
public:
   void printFrame(CAN_message_t &frame, int mailbox);
   void gotFrame(CAN_message_t &frame, int mailbox); //overrides the parent version so we can actually do something
};

void ExampleClass::printFrame(CAN_message_t &frame, int mailbox)
{
   Serial.print("ID: ");
   Serial.print(frame.id, HEX);
   Serial.print(" Data: ");
   for (int c = 0; c < frame.len; c++) 
   {
      Serial.print(frame.buf[c], HEX);
      Serial.write(' ');
   }
   Serial.write('\r');
   Serial.write('\n');
}

void ExampleClass::gotFrame(CAN_message_t &frame, int mailbox)
{
    printFrame(frame, mailbox);
}

ExampleClass exampleClass;

// -------------------------------------------------------------
void setup(void)
{
// fft256_1.windowFunction(NULL);
Serial.begin(115200);
//analogReference(0);
AudioMemory(10);
fft256_1.averageTogether(1);
//analogReference(EXTERNAL); // used for DAC output referance


  
int CANMsg10[] = {0x100,0,0,0,0,0,0,0};
int CANMsg11[] = {0x200,0,0,0,0,0,0,0};
int CANMsg12[] = {0x300,0,0,0,0,0,0,0};
int CANMsg13[] = {0x400,0,0,0,0,0,0,0};

  
  delay(1);
  Serial.println(F("Hello Teensy Single CAN Receiving Example With Objects."));

  Can0.begin(500000);  

  //if using enable pins on a transceiver they need to be set on
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);

  Can0.attachObj(&exampleClass);
  exampleClass.attachGeneralHandler();
}


// -------------------------------------------------------------
void loop(void)
{
 // delay(1000);
   // print Fourier Transform data to the Arduino Serial Monitor
  if (fft256_1.available()) {
    
    Serial.print("FFT: ");
    for (int i=0; i<35; i++) {  // 0-25  -->  DC to 4.3 kHz
      float n = fft256_1.read(i);
      printNumber(n);
    }
      Serial.print(AudioProcessorUsage());
      Serial.print(" ");
      Serial.print(AudioProcessorUsageMax());
      Count++;
      Serial.print(" ");
    if (Count >= 100) {
      Serial.print(Count);
      Serial.print(" ");
      Serial.print(PDB0_MOD, DEC);
      Serial.print(" ");
      PDB0_MOD = Mod;
      Mod--;     
      PDB0_SC |= PDB_SC_LDOK;  // Load OK
      Count = (0);
    }
    Serial.println();
    Serial.send_now();  
    
}
}
void printNumber(float n) {
  
  if (n >= 0.025) {
    Serial.print(n, 3);
    Serial.print(" ");
  } else {
    Serial.print("   -  "); // don't print "0.00"
  }
  
  /*
  if (n > 0.25) {
    Serial.print("***** ");
  } else if (n > 0.18) {
    Serial.print(" ***  ");
  } else if (n > 0.06) {
    Serial.print("  *   ");
  } else if (n > 0.005) {
    Serial.print("  .   ");
  }
  */
}
 
Update,

I have used a Teensy 3.6 at various speeds and overclocks.

Freezes at PDB0_MOD = 1023

When run at overclock, the PDB0_MOD starts at higher values and freezes at the same 1023.
 
Update,

If I "under-clock" either the 3.5 or 3.6, I get lower values for PDB0_MOD before freeze.

T3.6
MhzPDB_MOD
1801023
1681023
144513
1201023
96514
72514
48514

T3.5

MhzPDB_MOD
120514
96514
72514
 
The FFT will be 256 bin(audio library) and I am wanting a window time of ~1ms to suit a 40Deg knock window at 6000rpm on a 6 cylinder engine.

Before answering your question about the need for a faster sampling rate, a few more details might help. At 44100 Hz sample rate and a 256 point FFT, the time window is about 5.8ms, not ~1ms. Also what do you mean by a 40 degree knock window at 6000rpm? What do you expect the spectrum to look like for a typical knock occurrence? A 44100Hz/256 point FFT bin resolution is about 172Hz. A 96000Hz/256 point FFT bin resolution is about 375Hz and has a time window of ~2.7ms. How fine a resolution do you need?
 
Hi Neal,

The 256 Bin FFT at 44100 is around 2.9 ms because there are only 128 "real" samples. The other 128 samples are "imaginary" for phase angle. If I want to turn the FFT back into sound, I would need them(won't happen, no recording).

The FFT only needs to pick the differing frequencies from engine noise. Actual frequency really does not matter after filtering/gating etc.
The 40 degree window is the typical angle for "sensing" knock after ignition. Using RPM, the time for 40 degrees of crank rotation is around ~1ms.

By doing some maths from my PDB_MOD counts, the sample time is down to around ~1.3 ms for 128 samples using the inbuilt millis timer function.
 
The 2.9ms is incorrect. If you are sampling at 44100 Hz and you take 256 samples, all the samples are real. And 256 samples cover 256/44100 sec = 5.8ms. You are correct however that the FFT output yields 128 frequency bins each with a real component and an imaginary component. The Audio Library function returns 128 magnitude values. It does that by calculating sqrt(real^2 + imag^2). The frequency bins are spaced 44100/256 ~ 172 Hz apart.

So a 256 point FFT gives you plenty of time coverage. If 172 Hz isn't enough frequency resolution, you can change the sampling frequency. It is a bit counterintuitive, but if you DECREASE the sampling rate the resolution INCREASES. For example, if you sample at 22050 Hz, the resolution is 22050/256 ~ 86 Hz.

I hope that helps.
 
The sample window needs to be 1.5ms or less, preferably under 1ms. Anything longer and the background noise will wash out/dilute the knock noise.

A sample time of 3ms+ will render the FFT useless at ~6000rpm.
 
Looks like you have a few ways to get the short duration analysis window you want.

1: Increase the sample rate. Requires diving into the low level audio library ADC input code.

2: Use fft.windowFunction() to replace the default Hanning window with something slightly narrower. Perhaps AudioWindowFlattop256 would be more to your liking? Or maybe Hanning will be good enough?

3: Use the record queue to gain direct access to the raw audio samples, and then compute a shorter FFT.
 
Regarding option #2, please consider the 256 point FFT in the audio library runs with 50% overlap. So even though the FFT is computed over 256 samples spanning 5.8 ms, you get a new analysis every 128 samples, or every 2.9ms. The assumption is you will be using one of the windows functions which attenuates the input near the begin and end of those 256 samples, so you're mainly analyzing just the middle of the window. 50% overlapping is done (computes twice as many FFT256) so you're not losing analysis of the input during those moments the window function is attenuating the input.

If you're not familiar with this overlapping and the window functions, I'd recommend reading page 29 of the tutorial PDF.

https://www.pjrc.com/store/audio_tutorial_kit.html

Especially if you're looking for specific spectral content or changes, you'll need to use some sort of window function to prevent spectral leakage problems. It's a very well known issue of using FFT to analyze real-world sensors, where the input signal isn't perfectly periodic and in perfect lock-step sync with the FFT analysis window. Hopefully the explanation in the tutorial can help.
 
Thanks Paul,

I was looking at the rectangular(null) or a Tukey type windowing because I don't know where in the window the knock will happen.

The measured frequency is not important, just the amplitude peak. The knock amplitude will be much greater than the normal operating noise. The spectral leakage will blend into the background noise but the amplitude peak should be contained to as little bins as possible to get the most accurate single peak. The knock peak will be anything up to 3x or more above the normal operating "noise".

I have been attempting option#1 with changing the PDB0_MOD value. I would like to change the multiplier, but I have not managed to find where it comes from. I only need to change for example 128 to 64 clock divisor to get me in the ballpark and adjust PDB0_MOD to get the rest.

With the overlapping, I will use the FFT produced during/after the closing gate signal to capture the end of the possible knock window at high rpm.
 
I will measure the background noise between engine firings/outside the measurement "window" and minus it from the measurement window to give the "noise above background" during ignition.
 
Maybe you might be underestimating the troublesome nature of spectral leakage? Without a window function to gradually attenuate towards the edges of the window, on each FFT the error from discontinuity can vary from almost nothing to a large amount. Which bins get all the phantom energy is unpredictable.
 
Paul,

The reason I was looking at the Tukey window or similar is that it has a large non-attenuated area in the middle to account for the movement of the knock(moves around due to load, rpm, temp etc).

WMXZ,

The 1st problem is that the ECU expects a "gated" time based signal(based around crank degrees of rotation for a given rpm)

The 2nd is that background noise in different frequencies can "override" the peak value of the knock signal, hence the need of "signal minus background". Think of it as a possible side lobe to the normal engine running noise.
What usually happens with the knock signal is that it gets reflected as orders of frequency inside of the engine block. The 2nd order is usually the best for measurement because the first order often falls within the engine noise.
 
Paul,

Do you have a simple few lines I can add to Setup() that I can use to change the master clock speed away from the default in the Audio library?

I will play with different windowing types to see what works after I get the FFT to process within the required timeframe.
 
Does anyone know which file in the audio library sets the master clock?

It isn't just 1 place. The answer depends on which audio library objects you've instantiated and the order they where created, and of course which Teensy you're using.
 
The FFT is not a panacea - this sounds more like a problem for correlation/convolution techniques. (Having said that you can use the FFT to implement fast correlation/convolution, even with low-latency, but it gets fairly complicated.)
 
Thanks Paul,

I am using just the ADC1 as an input to the FFT_256 object in the audio library as can be seen in the GUItool block as shown in my 2nd post.

I am using T3.6 in the final project but have tested T3.5 to see if I get different results.
 
Engine-noise-spectrogram-frequency-Hz-as-an-engine-rpm.jpg

This image shows how engine background noise changes frequency with increasing RPM

36276076380_5bbd302997_o.jpg

This image shows how background noise can mask the fundamental frequency of the knock signal when kept at a constant RPM.
The tall vertical lines towards the right are the reflected frequency of the knock signal.

When I subtract background noise from the knock signal, the 1st harmonic at the bottom will be attenuated to almost nothing due to the background noise being so high in those frequencies.
The upper frequencies will be "cleaned-up" because the background noise is minor.

When a knock-free engine is running, the total signal outputted will be close to zero(signal will almost be completely made up of background noise).
When the engine encounters a knock event, the signal level will jump sharply due the the different(higher) frequencies not being attenuated.



All of this needs to be completed in ~1ms at high rpm because, if the background "window" period contains knock, the knock event will effectively "not be recorded".
 
In spectral processing you can't cheat the window. You will need a decent window whether you think so or not. The problem is spectral leakage is just plain awful with a rectangular window, like 13 dB sidelobes decaying extremely slowly. When I did radar processing, I used Blackmann-Harris windows or things like it (high order Kaiser filters). Yes, these windows don't have the resolution that a perfectly centered rectangular window has, or the peak amplitude, but they have 80+ dB sidelobes. That allows you to see tiny stuff in the presence of big stuff. Like an incoming missile, or a pedestrian child. If the knock event or any large signal, like the engine noise, is truncated by a rectangular window a lot of the energy will splatter everywhere blinding your ability to resolve the characteristic signal you are looking for. It's even worse if you are looking for phase information from a small peak in the presence of a large one. In that case, it is essential to have a Blackmann-Harris class window, because the phase of the small peak is strongly polluted by the spectral leakage of the larger peak, even if they are 50 or more bins away. I had to do an analysis of what windows to use to for this issue. In the real world you can rarely guarantee peak spacing.

We used to have a saying in our radar group: "Friends don't let friends use rectangular windows." Rectangular windows are for textbook illustrations, but rarely are used successfully in the field. Good luck with your project, it is an interesting one!
 
For a definitive paper on window functions and their properties, there is the paper by Fredric Harris. Hope this is helpful.
Notice: US Gov't work, not protected by US copyright.
 

Attachments

  • Harris on Windows.zip
    1.6 MB · Views: 37
clinker8,

Thanks for all that.

With my windowing, I am most worried about what will be attenuated with the window with the 50% overlap.
I take it as the 128th sample(assuming a 256 sample FFT) will be the "peak" in the centre of the window.
I am concerned with the 64th and 192nd sampled areas. They will be the trough in the 50% overlapped FFTs.
If the knock event appears at one of these points, it will be heavily attenuated if I use a Hann/Blackman/Hamming type/shape window(under 50% remaining).

If I use a Tukey/Welch/Sine type window, they will be attenuated to a lesser extent(~70%+ of original).
 
Back
Top