5

I’m trying to build a custom baby monitor for my wife who is deaf. It needs to detect loud noises and listen for a specific series of tones from a feeding pump our daughter uses. It will then send a radio signal to the other room to trigger a bed shaker.

I do have a loop which samples the MAX4466 microphone and uses a “peakToPeak” calculation to determine volume, which I can use to trigger for loud noises (pasted below). However, I cannot figure out how to determine the frequency of a noise sample. I’ve tried searching for other sketches and tutorials that would work, but haven’t found anything. The feeding pump, when it has an error puts out a series of tones which alternates between 4.1 kHz and 2.1 kHz. What I wanted to do is see if the first tone is sounding, and then if the second tone shows up within a couple seconds afterwards, then it would trigger sending the signal to the other device.

I’ve tried for two days now to find a way to determine the frequency, but haven’t figured it out. Any help would be greatly appreciated. The closest I was able to get was this: Instructables - Arduino Frequency Detection, but when I use this script, the Arduino can no longer do analogRead() to get the status of various setting knobs and such I have on the device.

The script I use to get the sound level / loudness is:

  #define micPin 0                      // Microphone
  const int sampleWindow = 96;          // Sample window width in mS (50 mS = 20Hz)
  unsigned int sample;
    while (millis() - startMillis < sampleWindow) {
      sample = analogRead(micPin);
      // Check peakToPeak mins and maxs
        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
4
  • I don't have enough info to write a proper answer, but I suspect you're going to have to use something like a (fast) Fourier transform or a discrete cosine transform to get frequency information from real-world inputs. The article you link to uses (from a quick skim) variations on zero-crossing, which will go wrong as soon as you get background noise or multiple frequencies. A quick Google for "arduino fft" gave some hopeful-looking results. I hope that gives you something to get started. Good luck.
    – Mark Smith
    Commented Jun 18, 2017 at 7:33
  • Might be able to use a Frequency-to-Voltage Converter analog.com/media/en/technical-documentation/data-sheets/… Commented Jun 18, 2017 at 13:54
  • it would be a lot more practical to pipe the audio to a real computer and use fancy audio libs to achieve your goal. i guess a raspPI is "real" enough. look for "frequency bargraph" examples since they do everything you need and a tiny bit more (visualization)
    – dandavis
    Commented Jun 18, 2017 at 20:42
  • 2
    I think the FFT solution is the best for your solution but... I usually find it more reliable to open the device I want to monitor and hook up a sensing point there (be it a transistor, an optocoupler, an op-amp, ...). If the alarm sound is the only one the feeder plays, you can just get the signal of the buzzer, filter it out a bit and you will have a digital signal for your arduino..
    – frarugi87
    Commented Jun 19, 2017 at 8:24

4 Answers 4

3

As stated in previous answers, frequency measurement is unlikely to be reliable because the microphone is going to pick up ambient noise, which is a combination of many individual frequencies. You want instead to see how much sound amplitude you have at the specific frequency of the feeding pump. Fourier analysis seems like the natural tool for the job.

However, a Fourier transform is heavy math, and overkill for your application. You are only interested in the Fourier amplitude at one specific frequency. Computing that single amplitude is way easier and faster than doing a full-fledged Fourier transform.

My preferred method for doing that is called homodyne detection. It is fast enough to be done in real time by an Arduino Uno sampling at 9.6 kHz. Basically you multiply the input signal by both a sine and a cosine at the target frequency, then you low-pass filter both products. The two resulting signals are the real and the imaginary part of the complex amplitude you are looking for.

Here is a small program of mine that does almost exactly what you are looking for: homodyne.ino. It is set for one specific frequency (1 kHz, but it's trivial to change), and you will have to adapt it to be able to change the target frequency between 4.1 and 2.1 kHz.

2

You can use a DFT(Discrete Fourier transform) that just checks the component frequency magnitude at 4.1kHz and 2.1kHz. Below is some code that works for my Arduino UNO. When I put a signal generator on A0 with a 2.3V(peak to peak) sine wave, the result goes above 0.5 at the respective frequencies.

float Single_Point_DFT(int16_t inputArray[], int startingIndex, int endingIndex, float sampleFreq, float freqToSampleFor){
  float N = endingIndex - startingIndex + 1; //N=number of samples
  float k = freqToSampleFor * N / sampleFreq; 
  float radiansPerSample = 2.0 * 3.14159 * k / N;

  float RealSum = 0;
  float ImaginarySum = 0;
  for (int n = 0; n <= endingIndex - startingIndex; n++){
    float angle = n * radiansPerSample;
    float voltage = inputArray[startingIndex + n] * (5.0 / 1023.0);

    RealSum += voltage * cos(angle);
    ImaginarySum -= voltage * sin(angle);
  }

  return sqrt(RealSum*RealSum + ImaginarySum*ImaginarySum) / N;
}

const int analogInPin = A0;
uint32_t startTime_uS;
uint32_t endTime_uS;

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

void loop() {
  int16_t values[128];

  while(1)
  {
    startTime_uS = micros();
    for(uint8_t n = 0; n<128; n++)
    {
      values[n] = analogRead(analogInPin);
    }
    endTime_uS = micros();

    float sampleFreq = 128.0*1000000.0/(endTime_uS - startTime_uS);
    float result = Single_Point_DFT(values, 0, 127, sampleFreq, 2100);
    Serial.print(result);
    Serial.print(", ");
    result = Single_Point_DFT(values, 0, 127, sampleFreq, 4100);
    Serial.println(result);
  }
}

This calculation is pretty slow due to the float arithmetic and the sine/cosine calculations, But is is fast enough if the beeps last longer than a quarter of a second.

1
  • single-frequency DFT or related goertzel algorithm is indeed a good idea. But doing it with floating point math on a system without hardware support of that is a very bad idea. Also there's no need to take the square root, you can just compare the squared value. Commented Jun 21, 2017 at 20:57
0

With a microphone listening for one frequency in ambient audio you have to do far more than just count cycles or measure time between crossings.

You have to sample a burst of audio into a buffer at a minimum of double the rate of the highest frequency you are interested in. Then you need to analyse that audio sample to determine the amplitudes of the different frequencies within it. FFT (Fast Fourier Transform) is the most popular method of doing that, and there are integer maths based libraries and examples around on the internet suitable for running on an Arduino.

1
  • Yes, in general, however 1) Undersampling and looking for the aliased frequencies is not out of the question and might be used to advantage on an underpowered system 2) computing a full FFT is not needed and probably not efficient when the desire it to find two alternativing frequency components. Commented Jun 21, 2017 at 20:59
0

A few ways, fft, 1057, or sin multification. Fft is quite calculation and space intensive, even in interfere form. For the detection of a known frequency, 1057 can be very efficient.

Multification is iterative so space and mips efficient. But you will need to filter and it detects with a delay.

A more efficient way is hardware filtering followed by rectification .

1
  • 2
    Could you give more details on “1057”? Commented Jun 21, 2017 at 21:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.