Go Down

Topic: Detecting and Responding to High Frequency Audio (Read 4297 times) previous topic - next topic

grizzlby

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.

Magician

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

grizzlby

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?

grizzlby

After looking over various projects documented online I decided to go with the circuit outlined for the DIY Tengu http://tinkerlog.com/2007/10/22/diy-tengu-on-a-breadboard/.

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:

Code: [Select]
/*
* 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!

Magician

Yea, I'd expect such low sensitivity from audio power amplifier IC. Better look here:
http://www.sparkfun.com/products/9964 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.

grizzlby

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?


Magician

Quote
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.

MarkT

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 won't respond to messages, use the forum please ]

grizzlby

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.

grizzlby

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.

MarkT

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...
[ I won't respond to messages, use the forum please ]

grizzlby

#11
Jul 10, 2012, 02:56 am Last Edit: Jul 10, 2012, 03:02 am by grizzlby Reason: 1
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
Code: [Select]
// 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?

grizzlby

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...

Go Up