Bat detector

DD4WH

Well-known member
With the new possibility to set the sample rate of the Teensy audio board to higher rates, it should be possible to build a bat detector with the Teensy 3.5/3.6. Thanks chip and Pete for your suggestions and Frank B for the higher sample rate possibilities! :)

Bats use echolocation with ultrasound and use frequencies from 12 to 125kHz. Most bats, however can be detected, if you use a sample rate of 192ksps, that means the usable upper limit frequency is < 96kHz, which is quite nice and covers the vast majority of the bat species occuring worldwide (with the exception of most horseshoe bats). [Audio frequencies > 96kHz are very heavily attenuated in the air anyway, so you would have to be very close to a bat calling at 100kHz (a few meters!) to detect it]: --> so, I think a bat detector detecting frequencies < 96kHz is a very useful thing.

It would be very cool, if we could use the basic Teensy 3.5/3.6 [sorry, probably not possible with Teensy 3.2 and smaller] with its Teensy audio shield plus an electret mic (those tiny little ones) without further hardware to detect bats!

First, I think, we have to look at some basic questions, before starting to program the Teensy:


  1. the samples come in such a high rate from the audio lib in that sample rate, that we can only do some very light audio processing, we have only (128/192000) = 667µsec for a block of 128 samples. [FFT256point should be possible, but a 512point or 1024point FFT is probably not possible]
  2. Is there an analog anti-alias filter in front of the ADC of the SGTL5000 Teensy audio board? If yes, we do not need to work any further in this thread, because that would make it impossible for us to process audio > 48kHz through the audio board.
  3. To lower the processor usage, we could do the following: sample at 192ksps, decimate the audio, process the audio, and interpolate.
  4. Zoom FFT: To get high resolution to look at the bat calls, we need many many points in the FFT, > 1024. That is of course not possible in that high sampling rate, even with the fast Teensy 3.6. We could use a technique called Zoom FFT to have a detailed look at only a small portion of the frequency spectrum with a small FFT (say 256 points). ZOOM_FFT
  5. Before doing all that, a first start could be: get the audio at 196ksps (queue object) --> software DDS or sinewave object with user adjustable frequency (12 – 96kHz) --> multiply that with the incoming audio --> hear batsound at 0-20kHz : that is called a heterodyne detector

What do you think about this? Any answers to the above questions or any further suggestions/ideas greatly acknowledged!

Frank
 
Last edited:
hmm, does that mean, there is an analog anti-alias filter in the way, when we use the mic input of the audio board?
 
OK, just for me as a newbie to understand, please help:

internal ADC & DAC: no internal analog filters, but how do I input my mic audio to this ADC (oh dear, I fear this is a very silly question . . . ), just by using one of the analog input pins of the Teensy?

ADC/DAC of the SGTL5000: blackbox, but no analog filtering on the Teensy audio board and no mentioning of any input filters in the datasheet of the SGTL5000, MIC input on the audio board
 
Perhaps the best approach is to test it.... can we produce ultrasonic with a small speaker ?
Or..hm..i think i have some of these ultrasonic-distance-sensors.. somewhere... never used them..lol

On the other hand, with-line-input, we need no speaker :)

I'll setup a second teensy which outputs some ultrasonic noise :)
Maybe tomorrow or this evening..don't know.
 
Last edited:
Frank,

the easiest way to produce a lot of loud ultrasonic noise is a keyring with two or more keys ;-) Just shake it and you will have very loud and very broadband ultrasonic noise! This is how we test our bat detectors.

no need to setup a speaker system.

And most of the standard small electrete mics cover a large portion of the ultrasonic spectrum, at least up to 50kHz.
 
And here is the first version of the "bat detector":

it uses the MIC input of the audio shield and 96ksps sample rate.

It displays the input sound with an FFT on an ILI9341.

96ksps: display is from 0 to 37.5kHz
192ksps: display is from 0 to 75kHz

use keyring to simulate a bat ;-)

Have fun,

Frank


Code:
/***********************************************************************
 * 
 * first version ultrasound detector 
 * using higher sample rate code by Frank Boesing 
 * 
 * https://forum.pjrc.com/threads/38988-Bat-detector
 * 
 * Frank DD4WH 2016_10_31
 * 
 *  first experiment 
 */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>
#include "font_Arial.h"
#include <ILI9341_t3.h>

#define BACKLIGHT_PIN 0

#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

Metro five_sec=Metro(2000); // Set up a 0.5 second Metro

// this audio comes from the codec by I2S2

AudioInputI2S            i2s_in; 
           
AudioRecordQueue         Q_in_L;    
AudioRecordQueue         Q_in_R;    

AudioPlayQueue           Q_out_L; 
AudioPlayQueue           Q_out_R; 
AudioAnalyzeFFT256  myFFT;
AudioOutputI2S           i2s_out;           
AudioConnection          patchCord1(i2s_in, 0, Q_in_L, 0);
AudioConnection          patchCord2(i2s_in, 1, Q_in_R, 0);
AudioConnection      patchCord5(Q_out_R,0,myFFT,0); 
AudioConnection          patchCord3(Q_out_L, 0, i2s_out, 1);
AudioConnection          patchCord4(Q_out_R, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;
int n_L;
int n_R;
long int n_clear;

int peak[512];
int barm[512];

ulong samp_ptr = 0;
bool FFT_state = false;

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

// We're only processing one buffer at a time so the
// number of samples is fixed at 128
#define BUFFER_SIZE 128

float32_t float_buffer_L [BUFFER_SIZE];
float32_t float_buffer_R [BUFFER_SIZE];
float32_t float_buffer_L_3 [BUFFER_SIZE];
float32_t float_buffer_R_3 [BUFFER_SIZE];

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!
  setI2SFreq (96000); // this works perfectly
//  setI2SFreq (192000); this sucks with a Teensy 3.5 (because of the slow display driver)

  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Floating point audio processing");


 /****************************************************************************************
 *  begin to queue the audio from the audio library
 ****************************************************************************************/
    delay(100);
    Q_in_L.begin();
    Q_in_R.begin();    
} // END SETUP


int16_t *sp_L;
int16_t *sp_R;

void loop() {
 
 elapsedMicros usec = 0;
/**********************************************************************************
 *  Get samples from queue buffers
 **********************************************************************************/

    // this is supposed to prevent overfilled queue buffers
    if (Q_in_L.available() > 3 || Q_in_R.available() > 3) {
      Q_in_L.clear();
      Q_in_R.clear();
      n_clear ++; // just for debugging to check how often this occurs
    }
    // is there at least one buffer in each channel available ?
    if (Q_in_L.available() >= 1 && Q_in_R.available() >= 1)
    {   
    sp_L = Q_in_L.readBuffer();
    sp_R = Q_in_R.readBuffer();

      // convert to float
     arm_q15_to_float (sp_L, float_buffer_L, BUFFER_SIZE); // convert int_buffer to float 32bit
     arm_q15_to_float (sp_L, float_buffer_R, BUFFER_SIZE); // convert int_buffer to float 32bit
     Q_in_L.freeBuffer();
     Q_in_R.freeBuffer();

/**************************************************************************
 * From here, all the 32 bit float audio processing can start
 * ************************************************************************/

      
/**************************************************************************
 * END of 32 bit float audio processing
 * ************************************************************************
 */
    sp_L = Q_out_L.getBuffer();
    sp_R = Q_out_R.getBuffer();
    arm_float_to_q15 (float_buffer_L, sp_L, BUFFER_SIZE); 
    arm_float_to_q15 (float_buffer_R, sp_R, BUFFER_SIZE); 
      Q_out_L.playBuffer(); // play it !
      Q_out_R.playBuffer(); // play it !

/**********************************************************************************
 *  PRINT ROUTINE FOR ELAPSED MICROSECONDS
 **********************************************************************************/
 
      sum = sum + usec;
      idx_t++;
      if (idx_t > 1000) {
          tft.fillRect(240,50,90,20,ILI9341_BLACK);   
          tft.setCursor(240, 50);
          mean = sum / idx_t;
          tft.print (mean);
          Serial.print (mean);
          Serial.print (" microsec for 2 stereo blocks    ");
          Serial.println();
          idx_t = 0;
          sum = 0;
         
      }

     }
/**********************************************************************************
 *  PRINT ROUTINE FOR AUDIO LIBRARY PROCESSOR AND MEMORY USAGE
 **********************************************************************************/
          if (five_sec.check() == 1)
    {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
      Serial.print("Cleared the audio buffer ");    
      Serial.print(n_clear); Serial.println (" times. ");

/*      tft.fillRect(100,120,200,80,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
     */ 
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
   spectrum();
}


 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 2;
  for (int16_t x=2; x < 100; x+=1) {

     int bar = (abs(myFFT.output[x]) * scale);
     if (bar >180) bar=180;
     // this is a very simple IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * barm[x]; 
//     if (bar > peak[x]) peak[x]=bar;
//     tft.drawFastVLine(x, 210-bar,bar, ILI9341_PURPLE);
//     tft.drawFastVLine(x*2+10, 210-bar,bar, ILI9341_PINK);

     tft.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     tft.drawFastVLine(x*2+10, 20, 210-bar-20, ILI9341_BLACK);    

//     tft.drawPixel(x*2+10,209-peak[x], ILI9341_YELLOW);

     if(peak[x]>0) peak[x]-=1;
     barm[x] = bar;
  }
  } //end if

   } // end void spectrum

// sample rate routine by Frank Boesing
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}
 
Last edited:
sample at 192ksps, decimate the audio, process the audio, and interpolate.
Wouldn't the decimation process remove the audio you're interested in? E.g. decimation by two would require that you lowpass filter out everything above 48kHz.

You probably wouldn't need the interpolation though because you won't need to upsample the audio back to its original frequency range - I don't think you can hear it anyway :D

Pete
 
Wouldn't the decimation process remove the audio you're interested in? E.g. decimation by two would require that you lowpass filter out everything above 48kHz.

So the beautiful thing with decimation is that it doesn't inherently eliminate any portion of the signal. if you don't apply a pre-decimation low-pass filter, any signal content above the new Nyquist frequency will be aliased down. It's a poor-man's way of dropping the frequency of a signal...just decimate the signal!

The problem, of course, is that the audio already in the lower frequencies will still be there. They will probably cover up your aliased-down ultrasonic signal. So, the solution is actually to high-pass filter the audio prior to the decimation. That'll clear out the lower band so that your decimation process can fold the high frequency audio down into the lower band.

As a concrete example...decimation by 2:
1) Digitize audio at 96 kHz. The Nyquist is 48 kHz. You have audio (including ultrasound) spanning 0-48 kHz.
2) High-pass filter at 24 kHz to keep 24(ish) kHz up to 48 kHz.
3) Decimate by a factor of two to get a sample rate of 48 kHz. Your new Nyquist is 24 kHz. Everything above 24 kHz is now below 24 kHz. If you previously had a tone at 25 kHz (ie, 24 kHz +1 kHz), it will now be 24 kHz - 1 kHz = 23 kHz. If you previously had 34 kHz (ie, 24 kHz + 10 kHz), it'll now be 24 kHz - 10 kHz = 14 kHz. And so on. Reversed. Like a mirror at 24 kHz.

As a 2nd concrete example...decimate by 3:
1) Digitize audio at 96 kHz. The Nyquist is 48 kHz. You have audio (including ultrasound) spanning 0-48 kHz.
2) High-pass filter at 32 kHz to keep 32(ish) kHz up to 48 kHz.
3) Decimate by a factor of 3 to get a sample rate of 32 kHz. Your new Nyquist is 16 kHz. Here, because of the triple-folding, your frequencies will appear in the correct order (not reversed as in the decimate by 2 case). Here, 32 kHz becomes 0 kHz. 33 kHz becomes 1 kHz. 48 kHz becomes 16 kHz.

(Sorry if this is old news, but I think that it is cool that you can purposely manipulate aliasing to achieve your desired goals)

Chip
 
Frank (DD4WH),

I see that you're piping your audio back out to the I2S output. I'm having a little difficulty tracing through the code quickly...are you doing anything to the audio to make the ultrasound audible? It doesn't look like it.

You could use the decimation trick, or you could use the single sideband shifting (Weaver or otherwise) discussed in another thread. You could also do it in the frequency domain, but I don't recommend that, when one of these simpler approaches are, well, simpler.

Chip
 
Pete, Chip,

thanks for your suggestions and comments. Yes, the decimation bit is something I have to think about a little more. Did not know about that tricky usage of alias products, thanks for that, Chip!

The preliminary code was meant to graphically detect ultrasound by showing the sound in the spectrum display, which works somehow ;-). So the audio queue was not in use anyway, it was just bits from old code, sorry for leaving that in the code.

I plan to have an alpha version of a heterodyne detector (with audio ;-)) built from a Teensy 3.5 with audio board and a simple electret mic ready in the next days, hopefully. Will post it here, once it works. Preliminary tests indicate detection up to about 50kHz (as shown on the FFT spectrum display), the upper limit probably being the mic specs (or an alias filter on the SGTL???), but this requires careful testing.

The Teensy 3.5 is at the extreme limit with 192ksps (however, a naked audio-only version works nicely), but it does not seem to be able to use 192ksps audio PLUS FFT, so I am waiting for the Teensy 3.6 to arrive, so I can test that.

Frank
 
Here is the first version of the Teensy Bat and Ultrasound Detector (v 0.1).

Needed:
- Teensy 3.5 or Teensy 3.6
- Teensy audio board
- small electret mic soldered to the MIC input of the audio board
- six push buttons that connect GND to pins 33-38 when pushed
- optional (if you want to additionally SEE the bat sound FFT): ILI9341 display

This is a VERY SIMPLE Heterodyne type bat detector.

It simply takes the audio from the MIC and mixes that with a frequency produced by a local oscillator in the Teensy.

If I say "mix", I mean multiplication of frequencies:

Let the mic input be F_bat, let the local oscillator frequency be F_LO

then the multiply produces:

F_bat - F_LO --> this is what interests us! If you want to hear a bat with 45kHz, put the LO to 46kHz and you hear it with 1kHz (In reality, because of bats movements and doppler effects, the frequency of a bat never stays constant in the field, so you will optimally hear a bat if you adjust the detector to the bats frequency)
F_bat + F_LO --> this is not interesting, because it will be way to high for everyone to hear . . .

The display shows the frequency bins of a 256point FFT, I have also implemented a display of the max frequency of the y-axis, because that changes as you change the sample rate.

Users can alter the following on-the-fly with buttons:

freq = ultrasound frequency that you would like to hear (in fact, you will hear everything about 10kHz above and below that frequency)
gain = MIC GAIN
SR = sampling rate: this can also be altered on-the-fly during bat hearing, the higher the sample rate - the higher the frequencies the detector will be able to detect (audio and spectrum display) --> this is indicated by the frequency at the right side of the spectrum display

If you are not familiar with bat detectors and would like to know more about heterodyne bat detecting, look here:

https://en.wikipedia.org/wiki/Bat_detector

If you happen not to have a bat at hand for testing, take your key ring and shake it. Keys produce a whole lot of loud ultrasound. Also test switching power supplies, energy saving bulbs etc. ;-).

Please note, that this bat detector has only been possible to make because Frank Boesing made available the code for setting the sample rate and overclocking the codec SGTL5000! Thanks a lot, Frank B !

If you set the sample rate > 96kHz, you run your audio board OUT OF SPECS and that could potentially damage it! It is your own responsibility if you do so!

Please test and comment !

Frank

Code:
/***********************************************************************
 * 
 *  Teensy Bat & Ultrasound detector 
 *         
 *  Version 0.1       
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 * 
 *  Frank DD4WH 2016_11_01
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN          33 + 34 -
 *  FREQUENCY         35 + 36 -
 *  (SAMPLE RATE)     37 + 38 -
 *  
 *  this detector was made possible by the development of the samplerate code by Frank Boesing, thanks Frank!
 *  Audio sample code (c) f.boesing (at) gmx.de
 *
 *  MIT license   
 */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <Bounce.h>

//#include <Metro.h>

#include <ILI9341_t3.h>
#include "font_Arial.h"

#define VERSION     "v 0.1"

#define BACKLIGHT_PIN 0
#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);

#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in; // MIC input
           
AudioSynthWaveformSine   sine1; // local oscillator

AudioEffectMultiply     mult1; // multiply = mix

AudioAnalyzeFFT256  myFFT; // for spectrum display

AudioOutputI2S           i2s_out; // headphone output          

AudioConnection      patchCord5(i2s_in,0,myFFT,0);
AudioConnection          patchCord6(i2s_in,0, mult1,0); 
AudioConnection          patchCord7(sine1,0, mult1,1); 
AudioConnection          patchCord3(mult1, 0, i2s_out, 1);
AudioConnection          patchCord4(mult1, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;  

int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;

int peak[512];
int barm[512];

int8_t mic_gain = 30; // start detecting with MIC_GAIN 30dB
int freq_real = 30000; // start detecting at 30kHz
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
String FFT_limit = "37.5kHz";
int freq_LO = 7000;

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

void setup() {
  Serial.begin(115200);
  delay(1000);

  //setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!
  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.fillRect(0,222,320,17,ILI9341_BLACK);
  tft.setCursor(0, 222);
  tft.print("gain: "); tft.print (mic_gain); 
  tft.print("   "); 
  tft.print("freq: "); tft.print (freq_real); 
  tft.fillRect(200,222,120,17,ILI9341_BLACK);
  tft.setCursor(200, 222);
  tft.print("SR: "); tft.print (text);

  delay(1000);

  set_sample_rate (sample_rate);

  set_freq_LO (freq_real);

} // END SETUP


void loop() {
   controls();
   spectrum();
}

void controls() {

  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();

  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change FREQUENCY of the local oscillator
  if ( freq_P.fallingEdge()) { 
      freq_real = freq_real + 1000;
      if (freq_real > 85000) {
        freq_real = 85000;
      }
      set_freq_LO(freq_real);
  }
  if ( freq_M.fallingEdge()) { 
      freq_real = freq_real - 1000;
            if (freq_real < 10000) {
        freq_real = 10000;
      }
      set_freq_LO(freq_real);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate -1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }

} // END function "controls"


void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain


void       set_freq_LO(int freq) {
    // audio lib thinks we are still in 44118sps sample rate
    // therefore we have to recalculate the frequency of the local oscillator
    freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 
    AudioNoInterrupts();
    sine1.frequency(freq_LO);
    AudioInterrupts();
    display_settings();
} // END of function set_freq_LO

void      display_settings() {
    tft.fillRect(0,222,200,17,ILI9341_BLACK);
    tft.setCursor(0, 222);
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("   "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("   "); 
    tft.fillRect(200,222,120,17,ILI9341_BLACK);
    tft.setCursor(200, 222);
    tft.print("SR: "); 
    tft.print (text);
    tft.fillRect(212, 200, 108,17,ILI9341_BLACK);
    tft.setCursor (212, 200);
    tft.print (FFT_limit);
       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    FFT_limit = "17.2kHz";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    FFT_limit = "18.8kHz";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    FFT_limit = "34.4kHz";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    FFT_limit = "37.5kHz";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    FFT_limit = "69kHz";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    FFT_limit = "75kHz";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    set_freq_LO (freq_real);
    AudioInterrupts();
    delay(20);
    display_settings();
} // END function set_sample_rate


 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 5;
  for (int16_t x=2; x < 100; x+=2) {

     int bar = (abs(myFFT.output[x]) * scale);
     if (bar >180) bar=180;
     // this is a very simple IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * barm[x]; 
//     if (bar > peak[x]) peak[x]=bar;
//     tft.drawFastVLine(x, 210-bar,bar, ILI9341_PURPLE);
//     tft.drawFastVLine(x*2+10, 210-bar,bar, ILI9341_PINK);

     tft.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     tft.drawFastVLine(x*2+10, 20, 210-bar-20, ILI9341_BLACK);    

//     tft.drawPixel(x*2+10,209-peak[x], ILI9341_YELLOW);

//     if(peak[x]>0) peak[x]-=1;
     barm[x] = bar;
  }
  } //end if
} // end void spectrum

// samplerate code (c) by Frank Boesing  f.boesing (at) gmx.de 
// MIT license
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}
 
Last edited:
Here is the first version of the Teensy Bat and Ultrasound Detector (v 0.1).

Needed:
- Teensy 3.5 or Teensy 3.6
- Teensy audio board
- small electret mic soldered to the MIC input of the audio board
- six push buttons that connect GND to pins 33-38 when pushed
- optional (if you want to additionally SEE the bat sound FFT): ILI9341 display

This is a VERY SIMPLE Heterodyne type bat detector.

Frank,
nice job. It again demonstrates that getting a ham-radio license helps a lot for advanced signal processing.

I only wanted to mention that if you do not wanted to visualize the spectrum and are only interested in 'detecting' the presence of bats, the following line will do it also

Code:
	    // HF click detector
	    //------------------
		// IIR HP filter (2nd order)
	    // [b,a]=butter(2,0.5,'high')
    	for(ii=2;ii<NSAMP;ii++)
    	{	yy[ii]  =(xx[ii] - 2.0f*xx[ii-1] + xx[ii-2])*0.2929f
    					- yy[ii-2]*0.1716f;
    	}

    	// TKO
    	for(ii=1;ii<NSAMP-1;ii++)
    	{	vv[ii]  =yy[ii]*yy[ii] - yy[ii-1]*yy[ii+1];
    	}

    	// LP filter (1st order)
    	// [b,a]=butter(1,0.1,'low');
    	for(ii=1;ii<NSAMP;ii++)
    	{	ww[ii]  =(vv[ii] + vv[ii-1])*0.1367f
    				- ww[ii-1]*0.7265f;
    	}
This is a cut-paste of my own "more complicated" system (without proper declarations etc)
This assumes that the sampling frequency is about 4 times the frequency of interest
1) all frequencies below 0.5 nyquist will be attenuated.
2) use a Taeger-Kaiser operator (TKO) to approcimate the Envelope
3) low-pass filter (0.1 Nyquist) to remove residual ripples (may not ne needed)

The suggestion of fsamp 4 times frequency of interest, is due to the TKO, as for this frequency TKO follows the envelope of the signal)

Obviously this does not generate an audio signal directly and has limited capability for spectrum-based classification, but it is the processing, I'm using to detect HF clicks
In my case not echolocating bats but echolocating harbour porpoises. They echolocate at 130 kHz and I sample at 375 kHz (NO, no sgtl5000, but ADS8881) and T3.6 (for FPU)
 
thanks, WMXZ, for your code snippet ! I understand the IIR low- and highpass filtering in your code, but I have never heard of the TKO!?

In the output, does that mean, you would hear all signals regardless of their frequency with that envelope detector (well, if they are higher than 0.5 nyquist) ?

That would be nice, because one of the drawbacks of the heterodyne method is that you only hear bats in a range of +- 5kHz around the oscillator freq. On the other hand, with a little experience, you can tell bat species apart by their typical sound in heterodyning (appearing "dry"/"wet" etc.) AND by identifying their frequency by altering the oscillator frequency up to the "zero beat" --> F_bat minus F_oscillator = zero.

Maybe it would help to have a general "click detector" like the TKO on one ear and the heterodyne sound on the other!? I have to think about that.

Can the TKO be used to estimate the frequency where most energy occurs within a block of samples? That would be great, if we could have audio from heterodyning and simultaneously a display showing at which frequencies bats are calling at the moment ;-).

Frank
 
A little off-topic, OK, but now that the bat detector works, you need some info about bats and their bioacoustical properties and how to identify them by their ultrasound calls.

This is the "bible" of bat bioacoustics, if you want to know everything about bats and their calls:

Barataud et al. (2015): Acoustic ecology of European bats: Species identification, study of their habitats and foraging behaviour. -
http://www.nhbs.com/title/199366/acoustic-ecology-of-european-bats --> not only for european bat friends, the bioacoustics & methods section is universal

There are many many nice websites on batcalls, here is a subjective choice:

http://www.batcalls.com/

http://blog.batdetective.org/

http://www.batecho.eu/

https://www.researchgate.net/profil...cf24e50e94036cf.pdf?origin=publication_detail

If you live in the northern hemisphere, we are very near to the end of the bat season, before the bats go to their wintering grounds. But if you live in a city, it could be worth going outside when the sun goes down and try to detect the last bats of the mating season doing their courtship calls.

Have fun with the Teensy and with bat detection,

Frank
 
Just a random comment here, these bat monitoring devices could be interesting to researchers in ecology/biology so whoever "owns" this idea/prototype (DD4WH) should approach their local universities and offer the designs/assistance in producing a batch of these with a mesh networking topology for data collection and recording en-masse.
 
Hi kyranzor,

as I am a biologist involved in training environmental scientists at the university, this is exactly my motivation and my plan, how did you know ? ;-).

At the moment however, the Teensy bat detector is not at all a bat monitoring device, it is just a local oscillator and a multiplier with an FFT ;-).

But the code is open source, so everybody can hop on and use it/improve it.

Recording would be a nice thing, maybe write the RAW data with the queue object onto the SD? But what should we record ? Everything, or pick those samples where there is a bat (which we have identified by using the Taeger-Kaiser operator ;-))?

Frank
 
Hi kyranzor,

as I am a biologist involved in training environmental scientists at the university, this is exactly my motivation and my plan, how did you know ? ;-).

At the moment however, the Teensy bat detector is not at all a bat monitoring device, it is just a local oscillator and a multiplier with an FFT ;-).

But the code is open source, so everybody can hop on and use it/improve it.

Recording would be a nice thing, maybe write the RAW data with the queue object onto the SD? But what should we record ? Everything, or pick those samples where there is a bat (which we have identified by using the Taeger-Kaiser operator ;-))?

Frank

haha, that's cool. I worked as an engineer for a university's robotics lab and one of the projects I did was a GPS flying-fox (large bat) monitoring device that is glued to the back of the animal and logs their flight paths, for the purpose of population monitoring. I think there was also plans to include sound recording devices that were triggered to begin recording by being close to a certain GPS coordinate (where the bats come home each night) in order to get the most use of power and proximity.

The project worked well. We used Xbee-Pros with the largest antenna/gain option, on each device. "base stations" with receiver Xbee Pros were littered around in areas that were known flight paths and would capture and record the periodic data-dumps from the bat-mounted devices if they were close enough.

For your sampling behaviour, it depends how much processing you can reasonably do on-board. Reducing the amount of data it outputs by doing some pre-processing would help a lot. Simply recording short sections that contain the detected bat call with as little wasted time before/after the bat call will give you the most amount of recordings for whatever storage you have, plus as you mention, down-sampling the data so that the general structure of the call's waveform is still intact and can be identified by the unique patterns is another good way to compress the data.

If you have static locations with the monitoring devices, and if bandwidth isn't too much of an issue for getting the data from remote devices, then as long as you have enough memory for a reasonable number of recordings you could record all night and then slowly transmit back to a base station/internet uplink all day (maybe taking advantage of a small solar panel attached).
 
Kyranzor, sounds quite sophisticated and interesting! I did not know that flying foxes emitt ultrasound!? I thought they had excellent eyes permitting them to see at night? But maybe I am wrong.

Some very good points you make, let´s see how far we can get with the tiny Teensy that is often compared to Raspberrys and Xbee Pros (just kidding . . .).

I think we will have to see whether we can discriminate bats from other ultrasounds in realtime and if the Teensy is fast enough to write sound on an SD card while processing the sound by heterodyning in realtime and doing an FFT at the same time, lets see . . .

Will issue version 0.2 of the Teensy bat detector soon.

Frank
 
The flying foxes make audible sounds, and yes they have good eyes.

If you do a sensor network comprised of teensy logging devices, then a base station from a raspberry pi with GPRS module or SIM card with 3G/4G internet data connection you could instead of comparing these devices, make them work in harmony :)

You may consider using RAM chips to buffer recordings and then slowly write to SD card, if the SD card write speeds become an issue.
 
Here is version 0.2 of the Teensy Bat detector code.

Thanks to Frank Boesing for putting his sample rate code into the public domain! That enables us to have the whole code under the MIT license.

New:
- x-axis indicators for the spectrum display (dynamically adjusted when changing sample rates)
- logo (sorry, couldn´t resist ;-))
- minor fixes
- now local oscillator frequencies down to 0 Hz are allowed (not useful for bat detection, just for the user to be able to understand and interpret the different heterodyne tones that you hear: F_bat - F_LO and F_bat + F_LO can be heard, if you tune below 10kHz, try it)

Have fun with the Teensy and with ultrasound conversion,

Frank



Code:
/***********************************************************************
 *  (c) 2016  Frank DD4WH 2016_11_01 - MIT license 
 *  Teensy Bat & Ultrasound detector 
 *         
 *  Version 0.2       
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 *        made possible by the samplerate code by Frank Boesing, thanks Frank!
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN          33 + 34 -
 *  FREQUENCY         35 + 36 -
 *  SAMPLE RATE     37 + 38 -
 *  
 * Audio sample rate code - function setI2SFreq  
 * Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <Bounce.h>

//#include <Metro.h>

#include <ILI9341_t3.h>
#include "font_Arial.h"

#define VERSION     "v 0.2"

#define BACKLIGHT_PIN 0
#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);

#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in; // MIC input
           
AudioSynthWaveformSine   sine1; // local oscillator

AudioEffectMultiply     mult1; // multiply = mix

AudioAnalyzeFFT256  myFFT; // for spectrum display

AudioOutputI2S           i2s_out; // headphone output          

AudioConnection      patchCord5(i2s_in,0,myFFT,0);
AudioConnection          patchCord6(i2s_in,0, mult1,0); 
AudioConnection          patchCord7(sine1,0, mult1,1); 
AudioConnection          patchCord3(mult1, 0, i2s_out, 1);
AudioConnection          patchCord4(mult1, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;  

int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;

int peak[512];
int barm[512];

int8_t mic_gain = 30; // start detecting with MIC_GAIN 30dB
int freq_real = 30000; // start detecting at 30kHz
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
int freq_LO = 7000;

typedef struct SR_Descriptor
{
    const int SR_n;
    const char* const f1;
    const char* const f2;
    const char* const f3;
    const char* const f4;
    const float32_t x_factor;
} SR_Desc;

// Text and position for the FFT spectrum display scale
const SR_Descriptor SR [SAMPLE_RATE_MAX + 1] =
{
    //   SR_n ,  f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
    {  SAMPLE_RATE_44K,  "5", "10", "15", "20", 58.05},
    {  SAMPLE_RATE_48K,  "5", "10", "15", "20", 53.33},
    {  SAMPLE_RATE_88K,  "10", "20", "30", "40", 58.05},
    {  SAMPLE_RATE_96K,  "10", "20", "30", "40", 53.33},
    {  SAMPLE_RATE_176K,  "20", "40", "60", "80", 58.05},
    {  SAMPLE_RATE_192K,  "20", "40", "60", "80", 53.33}
};    


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

void setup() {
  Serial.begin(115200);
  delay(1000);

  //setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!
  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(14, 7);
  tft.setTextColor(ILI9341_ORANGE);
  tft.setFont(Arial_12);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.setTextColor(ILI9341_WHITE);

  display_settings();
  // sorry, couldn´t resist ;-)
  logo_head();
  for (int v = 0; v < 20; v++) {
  logo(true);
  delay(200);
  logo(false);
  delay(200);
  }

  set_sample_rate (sample_rate);

  set_freq_LO (freq_real);

} // END SETUP


void loop() {
   controls();
   spectrum();
}

void controls() {

  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();

  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change FREQUENCY of the local oscillator
  if ( freq_P.fallingEdge()) { 
      freq_real = freq_real + 1000;
      if (freq_real > 85000) {
        freq_real = 85000;
      }
      set_freq_LO(freq_real);
  }
  if ( freq_M.fallingEdge()) { 
      freq_real = freq_real - 1000;
            if (freq_real < 0) {
        freq_real = 0;
      }
      set_freq_LO(freq_real);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate - 1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }

} // END function "controls"

void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain

void       set_freq_LO(int freq) {
    // audio lib thinks we are still in 44118sps sample rate
    // therefore we have to scale the frequency of the local oscillator
    // in accordance with the REAL sample rate
    freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 
    // if we switch to LOWER samples rates, make sure the running LO 
    // frequency is allowed ( < 22k) ! If not, adjust consequently, so that
    // LO freq never goes up 22k, also adjust the variable freq_real  
    if(freq_LO > 22000) {
      freq_LO = 22000;
      freq_real = freq_LO * (sample_rate_real / AUDIO_SAMPLE_RATE_EXACT) + 9;
    }
    AudioNoInterrupts();
    sine1.frequency(freq_LO);
    AudioInterrupts();
    display_settings();
} // END of function set_freq_LO

void      display_settings() {
    tft.fillRect(14,32,200,17,ILI9341_BLACK);
    tft.setCursor(14, 32);
    tft.setFont(Arial_12); 
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("     "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("    "); 
    tft.fillRect(232,32,88,17,ILI9341_BLACK);
    tft.setCursor(232, 32);
    tft.print("       "); 
    tft.print (text);
       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    set_freq_LO (freq_real);
    AudioInterrupts();
    delay(20);
    display_settings();
    prepare_spectrum_display();
} // END function set_sample_rate

void prepare_spectrum_display() {
    int base_y = 211; 
    int b_x = 10;
    int x_f = SR[sample_rate].x_factor;
    tft.fillRect(0,base_y,320,240 - base_y,ILI9341_BLACK);
//    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_PURPLE);  
//    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_PURPLE);  
    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_MAROON);  
    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_MAROON);  
    // querstriche
    tft.drawFastVLine(b_x - 4, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine(b_x - 3, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 0.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 1.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    // text
    tft.setTextColor(ILI9341_WHITE);
    tft.setFont(Arial_9);
    int text_y_offset = 16;
    int text_x_offset = - 5;
    tft.setCursor (b_x + text_x_offset + 256, base_y + text_y_offset);
    tft.print("kHz");
    // zero
    tft.setCursor (b_x + text_x_offset, base_y + text_y_offset);
    tft.print("0");
    tft.setCursor (b_x + x_f + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f1);
    tft.setCursor (b_x + x_f * 2 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f2);
    tft.setCursor (b_x + x_f *3 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f3);
    tft.setCursor (b_x + x_f *4 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f4);
    tft.setFont(Arial_14);
} // END prepare_spectrum_display

 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 5;
  for (int16_t x=2; x < 128; x+=2) {

     int bar = (abs(myFFT.output[x]) * scale);
     if (bar >175) bar=175;
     // this is a very simple first order IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * barm[x]; 
//     if (bar > peak[x]) peak[x]=bar;
//     tft.drawFastVLine(x, 210-bar,bar, ILI9341_PURPLE);
//     tft.drawFastVLine(x*2+10, 210-bar,bar, ILI9341_PINK);

     tft.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     tft.drawFastVLine(x*2+10, 20, 210-bar-20, ILI9341_BLACK);    

//     tft.drawPixel(x*2+10,209-peak[x], ILI9341_YELLOW);

//     if(peak[x]>0) peak[x]-=1;
     barm[x] = bar;
  }
  } //end if
} // end void spectrum


// set samplerate code by Frank Boesing 
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}

//  bat logo taken from:  
//  http://sisterzpsptreasures.freeforums.org/easy-animated-bat-t77.html
void logo(bool wing1) {
      // Logo ;-)      
     int x = 265;
     int y = 10; 
//      grey background and white line rectangle around it 
     tft.fillRect(x + 23, y - 2,16,14,ILI9341_DARKGREY);
     tft.fillRect(x - 2, y - 2,16,14,ILI9341_DARKGREY);
//     tft.fillRect(x - 2, y - 2, 41, 14,ILI9341_DARKGREY);
    if (wing1) {
     //Wing1
     tft.drawFastHLine(x + 2, y + 0, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 0, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 1, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 2, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 2, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 2, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 2, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 2, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 2, 2, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 5, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 2, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 7, ILI9341_BLACK);
    }
    else {
     //Wing2
     tft.drawFastHLine(x + 2, y + 2, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 2, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 3, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 4, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 4, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 4, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 4, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 4, 2, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 7, 3, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 9, ILI9341_BLACK);
    }
}

void logo_head() {
     int x = 265;
     int y = 10; 
     tft.fillRect(x + 14, y - 2,9,14,ILI9341_DARKGREY);
     // Head 
     tft.drawPixel(x + 15, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 4, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 17, y + 4, 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 5, ILI9341_RED);
     tft.drawPixel(x + 20, y + 5, ILI9341_RED);
     tft.drawPixel(x + 17, y + 6, ILI9341_RED);
     tft.drawPixel(x + 19, y + 6, ILI9341_RED);
     tft.drawPixel(x + 14, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 15, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 17, y + 8, ILI9341_WHITE);
     tft.drawPixel(x + 19, y + 8, ILI9341_WHITE);
     tft.drawFastHLine(x + 17, y + 9, 3, ILI9341_BLACK);
}
 
Heterodyn detectors only can detect in range of +-10kHz below/above the local oscillator frequency. Thus, if you tune your detector to a 27kHz bat and a bat calling at 45kHz approaches, you will not hear the higher calls.

However, the Teensy can "see" the bat by the FFT based spectrum display that is running continuously. So, if I have a peak detector running all the time, that compares the output of the bins of the FFT with a threshold, and if a single bin exceeds the amplitude threshold (that the user can set or it is 4 * mean of all FFT bins or something similar), you could indicate that ultrasound has been detected. You could even roughly calculate at which frequency that ultrasound was detected (with an accuracy of the bin bandwidth, which is 375Hz at the highest sample rate of 192k with a 256 point FFT --> that accuracy is more than enough for bat detection, as the doppler effects are much higher than that accuracy). That would not take much processor time, as the FFT bins have already been calculated for the FFT! And it would be very fast (bat calls can be as short as a few ms, so you have to be very fast, so Ying and other algorithms would not work here, as far as I know).

Frank
 
Last edited:
This is version 0.3 of the Teensy Bat Detector.

I added two separate detectors that scan the FFT results for ultrasound peaks in realtime. These peaks are indicated by a RED "BAT!" sign in the display AND the estimated frequency of the peaks is being displayed for some time until it is deleted again, when no bat is present anymore. The frequency of the peaks is very easy to determine, as the location of the peak bin of the FFT can easily be determined and indicates the frequency of the peak. It is more than adequately accurate for bat detection (bin bandwidth is 375Hz at 196k sample rate), but could potentially made much more accurate by three-point-interpolation.

However, we would have to tweak the detection threshold by empirical testing in the field. At the moment I made it quite sensitive for testing. I could reliably identify short peaks of frequencies between 14 and 17kHz (produced by my smartphone) with an accuracy of < 400Hz! (to reproduce, increase local oscillator frequency first to > 27kHz, otherwise those frequencies are not covered by the peak detector which is designed to only work OUTSIDE the local oscillator frequency +-10kHz AND only identifies peaks > 14kHz --> so that environmental noise does not trigger the detector)

The processor usage is like this (and does not change at all when changing sample rate!?):

Code:
Proc = 9.40 (10.49),  Mem = 5 (7)

However, at 192k, the Teensy 3.5 is clearly at its limits and the audio is not clear anymore. It would be good, if someone could test the performance on the Teensy 3.6 especially at the highest sample rate.

Now, the idea by Frank B and Frank is to extend the FFT SPECTRUM display (showing a realtime representation of power level (y) vs. frequency (x)) to a SPECTROGRAM display showing frequency (y) vs. time (x) and power level/amplitude (z) is indicated by the color of the pixel/lines.

Have fun with the Teensy Bat Detector and please post your suggestions / comments / experiences,

Frank

Code:
/***********************************************************************
 *  (c) 2016  Frank DD4WH 2016_11_02 - MIT license 
 *  Teensy Bat & Ultrasound detector 
 *         
 *  Version 0.3       
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 *        made possible by the samplerate code by Frank Boesing, thanks Frank!
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN          33 + 34 -
 *  FREQUENCY         35 + 36 -
 *  SAMPLE RATE       37 + 38 -
 *  
 * Audio sample rate code - function setI2SFreq  
 * Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <Bounce.h>
#include <Metro.h>

#include <ILI9341_t3.h>
#include "font_Arial.h"

#define VERSION     " v0.3"

#define BACKLIGHT_PIN 0
#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);

#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in; // MIC input
           
AudioSynthWaveformSine   sine1; // local oscillator

AudioEffectMultiply     mult1; // multiply = mix

AudioAnalyzeFFT256  myFFT; // for spectrum display

AudioOutputI2S           i2s_out; // headphone output          

AudioConnection      patchCord5(i2s_in,0,myFFT,0);
AudioConnection          patchCord6(i2s_in,0, mult1,0); 
AudioConnection          patchCord7(sine1,0, mult1,1); 
AudioConnection          patchCord3(mult1, 0, i2s_out, 1);
AudioConnection          patchCord4(mult1, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;  

// Metro 1 second
Metro second = Metro(1000);
elapsedMillis since_bat_detection1;
elapsedMillis since_bat_detection2;

int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;
int16_t FFT_bin [128]; 
int16_t FFT_max1 = 0;
uint32_t FFT_max_bin1 = 0;
int16_t FFT_mean1 = 0;
int16_t FFT_max2 = 0;
uint32_t FFT_max_bin2 = 0;
int16_t FFT_mean2 = 0;
//int16_t FFT_threshold = 0;
int16_t FFT_bat [3]; // max of 3 frequencies are being displayed
int16_t index_FFT;
int l_limit;
int u_limit;
int index_l_limit;
int index_u_limit;

int peak[512];
int barm[512];

int8_t mic_gain = 40; // start detecting with this MIC_GAIN in dB
int freq_real = 22000; // start detecting at this frequency
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
int freq_LO = 7000;

typedef struct SR_Descriptor
{
    const int SR_n;
    const char* const f1;
    const char* const f2;
    const char* const f3;
    const char* const f4;
    const float32_t x_factor;
} SR_Desc;

// Text and position for the FFT spectrum display scale
const SR_Descriptor SR [SAMPLE_RATE_MAX + 1] =
{
    //   SR_n ,  f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
    {  SAMPLE_RATE_44K,  "5", "10", "15", "20", 58.05}, // which means 58.05 pixels per 5 kHz
    {  SAMPLE_RATE_48K,  "5", "10", "15", "20", 53.33},
    {  SAMPLE_RATE_88K,  "10", "20", "30", "40", 58.05},
    {  SAMPLE_RATE_96K,  "10", "20", "30", "40", 53.33},
    {  SAMPLE_RATE_176K,  "20", "40", "60", "80", 58.05},
    {  SAMPLE_RATE_192K,  "20", "40", "60", "80", 53.33} // which means 53.33 pixels per 20kHz
};    


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

void setup() {
  Serial.begin(115200);
  delay(200);

  //setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!
  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(14, 7);
  tft.setTextColor(ILI9341_ORANGE);
  tft.setFont(Arial_12);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.setTextColor(ILI9341_WHITE);

  display_settings();

  // sorry, couldn´t resist ;-)
  logo_head();
  for (int v = 0; v < 6; v++) {
  logo(true);
  delay(200);
  logo(false);
  delay(200);
  }

  set_sample_rate (sample_rate);

  set_freq_LO (freq_real);

} // END SETUP


void loop() {
   controls();
   spectrum();
   check_processor();
}

void controls() {

  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();

  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change FREQUENCY of the local oscillator
  if ( freq_P.fallingEdge()) { 
      freq_real = freq_real + 1000;
      if (freq_real > 85000) {
        freq_real = 85000;
      }
      set_freq_LO(freq_real);
  }
  if ( freq_M.fallingEdge()) { 
      freq_real = freq_real - 1000;
            if (freq_real < 0) {
        freq_real = 0;
      }
      set_freq_LO(freq_real);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate - 1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }
} // END function "controls"

void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain

void       set_freq_LO(int freq) {
    // audio lib thinks we are still in 44118sps sample rate
    // therefore we have to scale the frequency of the local oscillator
    // in accordance with the REAL sample rate
    freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 
    // if we switch to LOWER samples rates, make sure the running LO 
    // frequency is allowed ( < 22k) ! If not, adjust consequently, so that
    // LO freq never goes up 22k, also adjust the variable freq_real  
    if(freq_LO > 22000) {
      freq_LO = 22000;
      freq_real = freq_LO * (sample_rate_real / AUDIO_SAMPLE_RATE_EXACT) + 9;
    }
    AudioNoInterrupts();
    sine1.frequency(freq_LO);
    AudioInterrupts();
    display_settings();
} // END of function set_freq_LO

void      display_settings() {
    tft.fillRect(14,32,200,17,ILI9341_BLACK);
    tft.setCursor(14, 32);
    tft.setFont(Arial_12); 
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("     "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("    "); 
    tft.fillRect(232,32,88,17,ILI9341_BLACK);
    tft.setCursor(232, 32);
    tft.print("       "); 
    tft.print (text);
       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    set_freq_LO (freq_real);
    AudioInterrupts();
    delay(20);
    display_settings();
    prepare_spectrum_display();
} // END function set_sample_rate

void prepare_spectrum_display() {
    int base_y = 211; 
    int b_x = 10;
    int x_f = SR[sample_rate].x_factor;
    tft.fillRect(0,base_y,320,240 - base_y,ILI9341_BLACK);
//    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_PURPLE);  
//    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_PURPLE);  
    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_MAROON);  
    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_MAROON);  
    // vertical lines
    tft.drawFastVLine(b_x - 4, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine(b_x - 3, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 0.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 1.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    // text
    tft.setTextColor(ILI9341_WHITE);
    tft.setFont(Arial_9);
    int text_y_offset = 16;
    int text_x_offset = - 5;
    tft.setCursor (b_x + text_x_offset + 256, base_y + text_y_offset);
    tft.print("kHz");
    // zero
    tft.setCursor (b_x + text_x_offset, base_y + text_y_offset);
    tft.print("0");
    tft.setCursor (b_x + x_f + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f1);
    tft.setCursor (b_x + x_f * 2 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f2);
    tft.setCursor (b_x + x_f *3 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f3);
    tft.setCursor (b_x + x_f *4 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f4);
    tft.setFont(Arial_14);
} // END prepare_spectrum_display

 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 5;
  for (int16_t x = 2; x < 128; x++) {
     FFT_bin[x] = abs(myFFT.output[x]); 
     int bar = (FFT_bin[x] * scale);
     if (bar >175) bar=175;
     // this is a very simple first order IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * barm[x]; 
//     if (bar > peak[x]) peak[x]=bar;
//     tft.drawFastVLine(x, 210-bar,bar, ILI9341_PURPLE);
//     tft.drawFastVLine(x*2+10, 210-bar,bar, ILI9341_PINK);

     tft.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     tft.drawFastVLine(x*2+10, 20, 210-bar-20, ILI9341_BLACK);    

//     tft.drawPixel(x*2+10,209-peak[x], ILI9341_YELLOW);

//     if(peak[x]>0) peak[x]-=1;
     barm[x] = bar;
  }
     search_bats();     
  } //end if
} // end void spectrum

void  search_bats() {
    // the array FFT_bin contains the results of the 256 point FFT --> 128 magnitude values
    // we look for bins that have a high amplitude compared to the mean noise, indicating the presence of ultrasound
    // 1. only search in those parts of the array > 14kHz and not around +-10kHz of the LO freq -->
    //    thus it is best, if I search in two parts --> 14kHz to freq_real-10k AND freq_real+10k to sample_rate/2
    // 2. determine mean and max in both parts of the array
    // 3. if we find a bin that is much larger than the mean (take care of situations where mean is zero!) --> identify the no. of the bin
    // 4. determine frequency of that bin (depends on sample_rate_real)
    //    a.) by simply multiplying bin# with bin width
    //    b.) by using an interpolator (not (yet) implemented)
    // 5. display frequency in bold and RED for 1-2 sec. (TODO: also display possible bat species ;-)) and then delete
    // goto 1.    

    // search array in two parts: 
    //  1.)  14k to (freq_real - 10k)
    // upper and lower limits for maximum search
     l_limit = 14000;
     u_limit = freq_real - 10000;
     index_l_limit =  (l_limit * 256 / sample_rate_real); 
     index_u_limit =  (u_limit * 256 / sample_rate_real); 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);

     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max1, &FFT_max_bin1);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean1);
            // shift bin_max because we have not searched through ALL the 256 FFT bins
        FFT_max_bin1 = FFT_max_bin1 + index_l_limit;
     }
//     Serial.print(FFT_max1); Serial.print ("  "); Serial.println(FFT_mean1);

    //  2.)  (freq_real + 10k) to 256 
    // upper and lower limits for maximum search
     l_limit = freq_real + 10000;
     if (l_limit < 14000) {
      l_limit = 14000;
     }
     index_l_limit = (l_limit * 256 / sample_rate_real); 
     index_u_limit = 128; 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);
     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max2, &FFT_max_bin2);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean2);
            // shift bin_max because we have not searched through ALL the 128 FFT bins
        FFT_max_bin2 = FFT_max_bin2 + index_l_limit;
     }
//         Serial.print(FFT_max2); Serial.print ("  "); Serial.println(FFT_mean2);

      display_found_bats();
    FFT_max1 = 0;
    FFT_mean1 = 0;
    FFT_max_bin1 = 0;
    FFT_max2 = 0;
    FFT_mean2 = 0;
    FFT_max_bin2 = 0;
}  // END function search_bats()

void display_found_bats() {
    int sign_x = 148; 
    int sign_y = 57; 
    if (since_bat_detection1 > 6000 && since_bat_detection2 > 6000) {
// DELETE red BAT sign
//      Serial.println("RED SIGN DELETED");      
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_BLACK);
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 40, ILI9341_BLACK);
//      since_bat_detection = 0;
    }

// DELETE FREQUENCY 1
    if (since_bat_detection1 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection1 = 0;
    }

// DELETE FREQUENCY 2
    if (since_bat_detection2 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection2 = 0;
    }
// PRINT RED BAT SIGN    
    if(FFT_max1 > (FFT_mean1 + 5) || FFT_max2 > (FFT_mean2 + 5)) {
//      Serial.println("BAT");
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_RED);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.setCursor(sign_x + 2, sign_y + 4);
      tft.print("B A T !");      
//      since_bat_detection = 0;
    }
// PRINT frequency 1
    if(FFT_max1 > FFT_mean1 + 5) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 4);
      tft.print(FFT_max_bin1 * sample_rate_real / 256);
      tft.print(" Hz");      
      since_bat_detection1 = 0;
    
//      Serial.print ("max Freq 1: ");
//      Serial.println(FFT_max_bin1 * sample_rate_real / 256);
    }
    
// PRINT frequency 2    
    if(FFT_max2 > FFT_mean2 + 5) { 
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 30);
      tft.print(FFT_max_bin2 * sample_rate_real / 256);
      tft.print(" Hz");      
//      Serial.print ("max Freq 2: ");
//      Serial.println(FFT_max_bin2 * sample_rate_real / 256);
      since_bat_detection2 = 0;
    }
      tft.setTextColor(ILI9341_WHITE);
} // END function display_found_bats


// set samplerate code by Frank Boesing 
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}

//  bat logo taken from Shezzy:  
//  http://sisterzpsptreasures.freeforums.org/easy-animated-bat-t77.html
//  copyright free graphics
//  "The image you create by following this tutorial belongs to you and you may do whatever you want with it."
void logo(bool wing1) {
      // Logo ;-)      
     int x = 265;
     int y = 10; 
//      grey background and white line rectangle around it 
     tft.fillRect(x + 23, y - 2,16,14,ILI9341_DARKGREY);
     tft.fillRect(x - 2, y - 2,16,14,ILI9341_DARKGREY);
//     tft.fillRect(x - 2, y - 2, 41, 14,ILI9341_DARKGREY);
    if (wing1) {
     //Wing1
     tft.drawFastHLine(x + 2, y + 0, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 0, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 1, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 2, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 2, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 2, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 2, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 2, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 2, 2, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 5, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 2, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 7, ILI9341_BLACK);
    }
    else {
     //Wing2
     tft.drawFastHLine(x + 2, y + 2, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 2, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 3, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 4, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 4, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 4, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 4, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 4, 2, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 7, 3, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 9, ILI9341_BLACK);
    }
}

void logo_head() {
     int x = 265;
     int y = 10; 
     tft.fillRect(x + 14, y - 2,9,14,ILI9341_DARKGREY);
     // Head 
     tft.drawPixel(x + 15, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 4, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 17, y + 4, 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 5, ILI9341_RED);
     tft.drawPixel(x + 20, y + 5, ILI9341_RED);
     tft.drawPixel(x + 17, y + 6, ILI9341_RED);
     tft.drawPixel(x + 19, y + 6, ILI9341_RED);
     tft.drawPixel(x + 14, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 15, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 17, y + 8, ILI9341_WHITE);
     tft.drawPixel(x + 19, y + 8, ILI9341_WHITE);
     tft.drawFastHLine(x + 17, y + 9, 3, ILI9341_BLACK);
}

void check_processor() {
      if (second.check() == 1) {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
/*      tft.fillRect(100,120,200,80,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
     */ 
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
} // END function check_processor
 
I guess, if you disable the window, it is a bit faster: fft256_1.windowFunction(NULL); But i don't know if that is good for this application..
 
Back
Top