Arduino Frequency Analyzer

I am having trouble with my project. I am trying to use an Arduino Uno and a electret microphone (MAX 4466) to hear specific frequencies related to sound. For example I would like to turn on an LED when a 15KHz signal is picked up by the microphone. I am unsure how to implement this in code and have done a lot of research but haven’t been able to find something similar to what I am trying to accomplish. Any help on the matter would be appreciated.

Here is some code I’ve been testing but it does not seem to do what I want to accomplish. This code is able to read the analog input signal and convert it to voltage data but I need the values in frequency and then be able to identify when a certain frequency is heard.

const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;

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

void loop()
{
unsigned long startMillis= millis(); // Start of sample window
unsigned int peakToPeak = 0; // peak-to-peak level

unsigned int signalMax = 0;
unsigned int signalMin = 1024;

// collect data for 50 mS
while (millis() - startMillis < sampleWindow)
{
sample = analogRead(A0);
if (sample < 1024) // toss out spurious readings
{
if (sample > signalMax)
{
signalMax = sample; // save just the max levels
}
else if (sample < signalMin)
{
signalMin = sample; // save just the min levels
}
}
}
peakToPeak = signalMax - signalMin; // max - min = peak-peak amplitude
double volts = (peakToPeak * 5.0) / 1024; // convert to volts

Serial.println(volts);
}

sample = analogRead(A0);
      if (sample < 1024)

If it is greater than or equal to 1024, you’ve got bigger problems than can be solved by a simple “if”.

I would like to turn on an LED when a 15KHz signal is picked up by the microphone

Start with a 30kHz (or greater) sampling frequency.

Please remember to use code tags when posting code

The 1024 value isn't the frequency though. The arduino is taking an analog signal from the microphone and converting it to digital on pin A0. Then it assigns a value between 0 and 1024 and that can be then converted back into volts as done in the bottom piece of the code.

I want to set the sampling frequency to about 40kHz just to be sure. But I can't even read the signal yet and I see no change in the values when speaking into the microphone, I only see changes when I press on the mic or move it around.

I want to set the sampling frequency to about 40kHz just to be sure

The best you can hope for with analogRead is short of 10kHz, and that's going to drop drastically with a serial line rate of only 9600 bits per second.

Your code "looks at" peak amplitude but not frequency.

I can't give you much "direct help".

If you are using a "regular Arduino" the analog-to-digital converter is only accurate up to a sample rate of 15kHz which means your signal can't go over 7.5kHz ([u]Nyquist[/u]). The specs for the ATmega chip don't say how much accuracy you loose.

You'll also probably have difficulty digitizing the signal while analyzing it at the same time.

And in order to prevent [u]aliasing[/u] (false frequencies) you need to low-pass filter the signal before digitizing. That may, or may not, be important in your application. All soundcards and audio ADCs have anti-aliasing filters.

The Audacity website has a nice-easy [u]tutorial[/u] about how digital audio works.

If you just need to detect 15kHz, you can make a software (or hardware/op-amp) bandpass filter. If you need to "analyze" the audio band you can use [u]FFT[/u] (or FHT). There are Arduino libraries for this. If you want to see what can be done, Google or search YouTube for "Arduino Spectrum Analyzer). I've never done anything with FFT/FHT but from what I understand you have to read a short sample and then pause to analyze it and display the results (because the Arduino isn't fast enough to do it in real time). As a "spectrum analyzer display" this happens fast-enough that you never know it's pausing but in a "technical application" it could be a problem.

rsfrech:
I want to set the sampling frequency to about 40kHz just to be sure.

No chance with a regular Arduino. You need an external ADC that can do this frequency.

Getting a data stream of 40 kHz x 2 bytes per sample (anything 9-16 bits resolution has two bytes per sample) makes for 80 kB/s or 640 kbps. That's the data stream you get to analyse.

Indeed if you're just looking for a specific frequency, go for a band pass filter. Filter out all frequencies but that one; then you can amplify it big time and basically turn it into a digital on/off signal that is very easy for your Arduino to read.

wvmarle:
Indeed if you’re just looking for a specific frequency, go for a band pass filter. Filter out all frequencies but that one; then you can amplify it big time and basically turn it into a digital on/off signal that is very easy for your Arduino to read.

This is exactly what I am looking to do. I have built a physical bandpass filter but am unsure how to turn it into a digital signal that the arduino can read easily. Do you have any suggestions on how to amplify the signal and turn it into a digital on/off signal?

Something like this: OpAmp to amplify; filter signal through an RC filter to give the envelope; lead that through another OpAmp (as comparator) or Schmitt trigger buffer to get a clean on/off signal.

I have built a physical bandpass filter

Are you getting any useful/meaningful readings?

Note - A bandpass filter will remove the DC bias (zero Hz) from the microphone board output. The Arduino can't read the negative half of the (unbiased) AC signal, and in fact it can potentially be damaged by negative voltages. It would be wise to add a [u]bias circuit[/u] to the output of the bandpass filter so you're not feeding negative voltages into the Arduino.

Add a [u]peak detector[/u] (AKA envelope follower) between the bandpass filter and the Arduino input. That gives you a varying DC voltage (approximately) equal to the peak AC signal voltage so all you need is a straightforward analogRead().

I use that circuit for my audio-activated lighting effects and I can sample at about 10 times per second instead of sampling the actual waveform thousands of times per second.

Another advantage of the peak detector is that it throws-away the negative half of the AC signal. That means you don't have to bias the input and without a biased input you can use the optional 1.1V ADC reference (or an external reference) to increase the ADC sensitivity (if that's helpful to you).

  1. You'll probably want a microphone with more gain than provided by the Max4446 modules unless your signal source is very loud or very close to the microphone. I've had better success with the Max9814 modules.

  2. If one is detecting a handful of frequencies the Goertzel filter algorithm is a approach commonly used for telephone touch-tone detection.

  3. An issue, as others have noted above, is that a 15 kHz target signal (presuming this is a real requirement) is well above the sampling rate achievable by the standard Arduino analogRead() function. One can get around this (with caveats) by using a faster ADC library or direct manipulation of the ADC control registers.

MrMark:

  1. You'll probably want a microphone with more gain than provided by the Max4446 modules unless your signal source is very loud or very close to the microphone. I've had better success with the Max9814 modules.

I am going to use another microphone when applying this project but for testing purposes I thought the MAX 4466 was good enough. The idea is just have a digital output signal when a certain frequency is heard. For the purposes of testing it now I don't need to use 15KHz.

However, I'm not getting a reading at all that responds to sound on the arduino. I just tested the Goertzel filter algorithm and it didn't work. Nothing changes however when I change the sound. I play the sound through my computer and put it as loud as possible in addition to holding it close to the microphone so hopefully it is picking it up.

The bandpass filter I set up currently will filter out any signal outside of 10KHz to 20KHz. I am willing to try using an external ADC set up, that way the arduino can easily interpret the signal. If you have any other suggestions please let me know.

rsfrech:
. . . I play the sound through my computer and put it as loud as possible in addition to holding it close to the microphone so hopefully it is picking it up. . .

A reasonable debugging strategy at this point would start by bypassing all the code except the portion that collects the sound sample, adding code that dumps the sample buffer to the serial port, plot it with the Arduino IDE plotter, and stare at it long enough to satisfy one's self that this portion of the hardware/software is in fact working.

Once that works one can proceed with debugging the filter algorithm having some confidence that it is processing a reasonable representation of the sound.

The Goertzel program actually seems to be working. I took a screenshot of the serial plotter and will add it as an attachment. When I added the bandpass filter I designed all of the signal went away. So everything is essentially working correctly, the next thing I am trying is to change the frequency of the bandpass filter to see if I can filter out any frequency the arduino can read. The Goertzel program is preset to have a target frequency of 700Hz but the output on the serial plotter is between 800 and 820. Also if I try to change the value of the target frequency the values become very skewed.

rsfrech:
The Goertzel program is preset to have a target frequency of 700Hz but the output on the serial plotter is between 800 and 820. Also if I try to change the value of the target frequency the values become very skewed.

If you’re running the example from that library, the output to serial is a measure of the power detected at the target frequency, it is not the frequency.

The library is a bit odd in that it’s set up to do detection at a single frequency. It would be more useful in my opinion if the detection frequency were passed as an argument to the “detect” method as follows.

In the library the definition of “detect” in Geortzel.cpp would become something of the form:

float Goertzel::detect(float TARGET_FREQUENCY)
{
  float magnitude;
  float omega = (2.0 * PI * TARGET_FREQUENCY) / _SAMPLING_FREQUENCY;
  coeff = 2.0 * cos(omega);

  /* Process the samples. */
  for (int index = 0; index < _N; index++)
  {
    ProcessSample(testData[index]);
  }

  /* Do the "standard Goertzel" processing. */
  magnitude = sqrt(Q1*Q1 + Q2*Q2 - coeff*Q1*Q2);

  ResetGoertzel();
  return magnitude;
}

The header file Goertzel.h would change to match:

// library interface description
class Goertzel
{
  // user-accessible "public" interface
  public:
    Goertzel(float,float,float);
    Goertzel(float,float);
        void sample(int);
        float detect(float);

  // library-accessible "private" interface
  private:
        void GetRealImag(float*,float*);
        void ProcessSample(int);
        void ResetGoertzel(void);

} ;

And the call in the example code would include a target frequency argument:

float magnitude = goertzel.detect(700);  //check magnitude at 700 Hz

I guess the question why do you want to do this and what is the project is worth an ask . There may be other ways

hammy:
I guess the question why do you want to do this and what is the project is worth an ask . There may be other ways

I am trying to detect metal by the sound that it makes. A metal detector or magnet are not applicable options because of the environment but metal makes a distinct metallic sound at a certain frequency. I want to detect that metal based on the sound it makes.

rsfrech:
I am trying to detect metal by the sound that it makes. A metal detector or magnet are not applicable options because of the environment but metal makes a distinct metallic sound at a certain frequency. I want to detect that metal based on the sound it makes.

I don’t see how this is going to work.

A particular metal object may be distinctly resonant at a particular frequency, but there isn’t a unique frequency for all metal objects.

To wit: Handbell Sets & Cases – Schulmerich Bells, LLC

Metal objects can have many different frequencies. Just look at church bells.

I am able to test the metal beforehand to see what frequency range the metal is in. I have been using my keys to simulate the sound some metals might make and after analyzing the frequency with a Matlab program the keys were making a frequency sound around 15-17kHz. If I can identify a range similar to this that the metal makes then I can use that range and light up an LED for example when this frequency is heard by the microphone.