Detecting and Responding to High Frequency Audio

Hi, all. I am attempting a setup by which I can control LEDs remotely using audio signals, but I haven't ever worked with audio before.

I will generate a constant sine or square wave using my phone at a predetermined frequency (preferably above human hearing, so, 18kHz+) and I would like for that solid tone to be used an an on and off switch for the LED. Is this something that I could accomplish with an electret mic and an op-amp + an Arduino Uno? Would I need to implement FFT or would this be easier with hardware filters? Thanks for any help you can provide.

With only one tone transmitted at a time, you can use freq. measurement library :
http://interface.khm.de/index.php/lab/experiments/

That looks to be an excellent resource for this. Thanks!

Any advice on the hardware side of things to get a useful output from the electret?

After looking over various projects documented online I decided to go with the circuit outlined for the DIY Tengu DIY Tengu on a breadboard – tinkerlog.

http://www.flickr.com/photos/8123185@N02/1696720251/ is the schematic with the top right portion being what i've constructed (minus the 220u and the speaker).

I'm currently just trying to test the analogread from the op-amp to verify that I'm getting a response that will be sensitive and reliable enough to move forward detecting specific frequencies, and I'm not getting very good results. My test code is:

/*
 * Monitor for sound sensor
 */

int inPin = 2;    // select the input pin for sound sensor
int val = 0;
int amp = 0;
String stringOne = "LOUD";
String stringTwo = " ";

void setup() {
  Serial.begin(9600);
}

void loop() {
  val = analogRead(inPin);
  amp = (val >= 512) ? val - 512 : 512 - val;
  //Serial.println(val);
  if (amp > 120) {
    Serial.println(stringOne);
    delay(20);
  }
  else {
    Serial.println(stringTwo);
  }
}

There's so much feedback entering the circuit that it's only registering my voice as "LOUD" from about 2" away. The maker of the Tengu says that he used a battery pack to eliminate this, but I'm not entirely sure where to insert that for the best results (running the entire Arduino + Breadboard off of a pack, or just the breadboard). I'm also not familiar enough with the EE of it to know the optimal positions of the 10k and 100k pots. The 10k doesn't seem to affect anything, and the 100k is extremely touchy.

Could the DIY Tengu sound sensor circuit be a good fit for my ultimate goal of triggering an event based on a specific sound frequency? Are there any tips as to how to get the best results from this circuit (best being an analogread that distinctly shows an increase in "amp" when external audio is present)? Thanks!

Yea, I'd expect such low sensitivity from audio power amplifier IC. Better look here:
SparkFun Electret Microphone Breakout - BOB-12758 - SparkFun Electronics Schematic is posted, you can build yourself. Possible replacement for IC : LM324, LM358, NE5532
though pin numbering may be different
Also, you can check this link: http://fftarduino.blogspot.com/2011/02/color-organ-spectrum-analyzer-on.html, if you like it.

Would you expect just swapping out the current LM386 for an LM358 would increase the distance from which the mic picks up audio? Or could this extremely short range be a byproduct of the circuit i chose to construct?

Would you expect just swapping out the current LM386 for an LM358 would increase the distance from which the mic picks up audio? Or could this extremely short range be a byproduct of the circuit i chose to construct?

You can't swap one IC for another, as they belongs to different classes. Better to build from scratch with LM358.

Be careful about the choice of frequency - especially with a phone there may be a very agressive low-pass filter in its audio-out chain, so a tone that's properly out of human hearing may be filtered away. Start with a tone you can hear (easier to debug that way) and then try higher frequencies once the principle is proven.

The standard setup for the Arduino A->D converter allows sample rates up to about 9kHz, which is a problem. There are ways to configure it to go faster (but less accurately), using some of the control registers. Other approaches would be to make the opamp
circuit a resonant filter at the frequency of interest and then use envelope detector to measure the amplitude of the response.

Yet another approach is to amplify the audio signal then pass it through a comparator to turn it into a logic signal - this can then be read much faster with digitalRead or pulseIn, and the zero-crossing count method easily employed. So long as the frequency of interest is the loudest signal this ought to work.

I took a break from working on this project, but am back now. I twiddled with the pots on my previous circuit until i got the best "on/off" signal based solely on sound amplitude. I then went searching for some method of tone detection and found what seemed to be a very handy Arduino Goertzel library here: http://citizengadget.com/post/25868921072/goertzel-arduino-library.

However, as you pointed out, MarkT, even with the best possible results the detection peaks at 4.5kHz which is way below what my ultimate goal is. For what it's worth, the circuit i've been using gave far too many false positives with this library and its sample code (even after modifying the "magnitude" level).

I'm very interested in the comparator idea, but I will have to search for a documented circuit to test.

I've already found some new promising documentation:
http://www.fanjita.org/serendipity/archives/22-Instrument-Tuner-Audio-Input-Stage.html
is a post detailing a transistor amplifier circuit and

http://www.fanjita.org/serendipity/archives/23-Instrument-Tuner-Zero-Crossing-analysis.html
is a follow-up post on using that circuit in conjunction with ATmega328 (Arduino) chips' built in analog comparator to determine the frequency of a sine wave.

Does this look like a potential solution to my problem? I will try and digest the info and then try the circuit + code for myself soon.

Its worth trying - you still have to amplify the audio signal enough and make sure the signal of interest has a greater amplitude than background noise...

Alright. Now i have this simple transistor pre-amp set up using a 2N4401

and it's giving me surprisingly good AnalogRead results considering how poorly the previous op-amp circuit worked. A "quiet" environment tends towards 450 and doesn't fluctuate more than ~30 in either direction.

I have also successfully put together a 5v to trim pot circuit to control my reference voltage for the comparator interrupt so that I can set the zero-crossing right around the 490 mark.

The problem is that when I attempt to run the code found here

// Count zero crossings using analog comparator
// atmega328p:
//   Uses pin 7 as the anacomp -ve input (AIN1)
//   Uses pin 6 as the anacomp +ve input (AIN0)
// attiny85:
//   Uses pin 1 as AIN1
//   Uses pin 0 as AIN0

#ifdef __AVR_ATtiny85__
#define AIN1 1
#define AIN0 0
#else
#define AIN1 7
#define AIN0 6
#endif

#define SAMPLE_WINDOW_SIZE 5
#define SAMPLE_MILLIS 50

volatile unsigned int crossings;
volatile unsigned int crossing_counts[SAMPLE_WINDOW_SIZE];
unsigned char timerLoadValue;
unsigned char latency;
unsigned char n;
int timer;
volatile unsigned long timer2;
volatile char sample_ready;
long oldmicro;
int diags;
int oldfreq;

#define TIMER_CLOCK_FREQ 16000000.0 //2MHz for /8 prescale from 16MHz

#ifdef __AVR_ATtiny85__
// TBD: Will need to use timer 1 or timer 0 for ATTiny
#error ATTiny clock code not yet written
#endif

//Setup Timer2.
//Configures the 8-Bit Timer2 to generate an interrupt
//at the specified frequency.
//Returns the timer load value which must be loaded into TCNT2
//inside your ISR routine.
//See the example usage below.
unsigned char SetupTimer2(float timeoutFrequency){
  unsigned char result; //The timer load value.
  
  
  /* We need to work out what divisor of the chip clock can be
     used to get an 8-bit counter preload value that represents
     the requested frequency.

      timer preload_val = scaled_freq / requested_freq
  =>  scaled_freq = clock_freq / scaler
  =>  preload_val = clock_freq / scaler / requested_freq
  =>  256 > clock_freq / scaler / requested_freq
  =>  clock_freq / scaler < 256 * requested_freq
  =>  1 / scaler < 256 * requested_freq / clock_freq
  =>  scaler > clock_freq / (256 * requested_freq)
    */

  Serial.print("clock/requested:");
  Serial.println(TIMER_CLOCK_FREQ / timeoutFrequency);

  int min_scaler = TIMER_CLOCK_FREQ / (256 * timeoutFrequency);
  Serial.print("Min scaler:");
  Serial.println(min_scaler);  
  
  // Need to convert min_scaler into a power-of-2 value to use.
  // Allowable values are actually 1, 8, 32, 64, 128, 256, 1024 -
  // see data sheet, section 18.10.
  int scaler = 1;
  while ((scaler < min_scaler) && (scaler < 1024))
  {
    scaler <<= 1;
    // skip disallowed values
    if ((scaler == 2) || (scaler == 4) || (scaler == 16) || (scaler == 512))
    {
      scaler <<= 1;
    }
  }
  
  if (scaler < min_scaler)
  {
    // Output a warning.
    Serial.println("Requested timer frequency too low - unable to find a suitable divider");
  }
  else
  {
    Serial.print("Chosen prescaler:");
    Serial.println(scaler);
  }
  
  long scaled_freq = TIMER_CLOCK_FREQ / scaler;      

  // Convert the scaler value into register settings
  switch (scaler)
  {
    case 1:
      TCCR2B = 0b00000001;
      break;
    case 8:
      TCCR2B = 0b00000010;
      break;
    case 32:
      TCCR2B = 0b00000011;
      break;
    case 64:
      TCCR2B = 0b00000100;
      break;
    case 128:
      TCCR2B = 0b00000101;
      break;
    case 256:
      TCCR2B = 0b00000110;
      break;
    case 1024:
      TCCR2B = 0b00000111;
      break;
    default:
      Serial.print("Unrecognised scaler: ");
      Serial.println(scaler);
      break;
  }  
  
  //Calculate the timer load value
  result=(int)((256.0-(scaled_freq/timeoutFrequency))+0.5);

  //Timer2 Settings: Timer mode 0
  TCCR2A = 0;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 = 1<<TOIE2;

  //load the timer for its first cycle
  TCNT2=result;

  Serial.print("Timer2 reload value:");
  Serial.println(result);
  return(result);
}
  
void setup()
{
  Serial.begin(57600);
  
  crossings = 0;
  n = 0;
  timer = 0;
  timer2 = 0;
  sample_ready = 0;
  diags = 0;
  oldfreq = 0;
  
  // Set up the comparator.
  pinMode(AIN0, INPUT);
  pinMode(AIN1, INPUT);
  // ACI : Ana Comp Interrupt flag - writing a 1 clears it, hardware autoclears it when calling ISR
  // ACIE : Ana Comp Int Enable
  // ACIS1, ACIS0 : Ana Comp Int mode select:
  //   0      0     Int on output toggle
  //   0      1     Reserved
  //   1      0     Int on falling output edge
  //   0      1     Int on rising output edge
  // (See section 16 of ATTiny85 datasheet)
  ACSR = 0 | (1 << ACI) | (1 << ACIE) | (1 << ACIS1);  // Enable interrupts; falling edge mode.
  
  // Set up a timer interrupt.
  timerLoadValue = SetupTimer2(1000);
}

void loop()
{
  // Calculate average crossings and output once we have a full sample window.
  // (This is indicated by the timer ISR setting sample_ready == true.
  while (!sample_ready);
  
  // Reset so that we'll wait again on the next loop.
  sample_ready = 0;
  
  // Grab a copy of the data as calculation could take a while.
  int copy[SAMPLE_WINDOW_SIZE];
  memcpy(copy, (const void *)crossing_counts, sizeof(crossing_counts));

  // Debugging : Print contents of the copy[] array every 10 cycles.
  /*
  diags ++;
  if (diags % 10 == 0)
  {
    for (int ii = 0; ii < SAMPLE_WINDOW_SIZE; ii++)
    {
      Serial.print(copy[ii]);
      Serial.print(',');
    }
    Serial.println("\x08.");
  }
  */
  
  long avg_count = 0;
  for (int ii = 0; ii < SAMPLE_WINDOW_SIZE; ii++)
  {
    avg_count += copy[ii];
  }
  avg_count *= (1000 / SAMPLE_MILLIS) / SAMPLE_WINDOW_SIZE;  
  
  
  /* Debugging : check that the interval between full samples is
     as expected:  
  long micro = micros();
  long microdiff = micro - oldmicro;
  oldmicro = micro;

  Serial.print(microdiff);
  Serial.print(' ');
  */
  
  // Filter noise.  If we're within 100Hz of our previous value,
  // then assume that we're reading a steady tone.
  if ((abs(avg_count - oldfreq) < 100) && (avg_count != 0))
  {
    Serial.println(avg_count);
  }
  oldfreq = avg_count;
}

ISR(ANALOG_COMP_vect)
{
  crossings ++;
}

//Timer2 overflow interrupt vector handler, called once per ms.
ISR(TIMER2_OVF_vect) {
  timer++;
//  timer2++;
  
  // Sample input freq every 100ms
  if (timer == SAMPLE_MILLIS)
  {
    crossing_counts[n++] = crossings;
    timer = 0;
    crossings = 0;
    if (n == SAMPLE_WINDOW_SIZE)
    {
      n = 0;
      sample_ready = 1;
    }
  }
 
  //Capture the current timer value. This is how much error we
  //have due to interrupt latency and the work in this function
  latency=TCNT2;

  //Reload the timer and correct for latency.
  TCNT2=latency+timerLoadValue;
}

it slowly prints out unintelligible characters such as:
" ¯ÌarÌúÄpÂ0 Dz?²?ù???Åñ?ûÉ?ó²Éó?ÉøÉóûáÉÍ?àÉÉÉ?Dz?áñ?óÁ????ÉûáûÉÉÉóÉ?ÉÉÉÍÍüÆóÇÆÆÅñÅÌ?#773¢'¦¢¢¢¢?'"

I don't expect my found code to work perfectly, but I'm also not sure where this strange output is coming from. Any ideas as to what's happening?

the gibberish was due to baud rate of serial data transmission. the code from my previous post is now working effectively to print out the frequency of a generated tone from my phone. there's a certain amount of troubleshooting to be done about finding the best combination of audio input circuit + reference voltage, but beyond that the rest of my project is trivial. thanks.

i have a two pin microphone...and i want that microphone listens to my clap,and then arduino process this audio to do some action.... plzz help,how to connect and how to do that....

this r the pics of this microphone...

Hi grizzlby

How did this project go? I want to do something very similar.

Thanks in advacne