Using a Microphone to Measure Volume

Hello, all! This is my first post as a member of the Particle community.
I’m working on a small project that alerts me if the noise in a room surpasses a certain level. I’ve tried a wide variety of microphone systems to make this work, and none of the ones that I’ve put together seem to do what I’d like them to do.

The system I have in mind provides 3.3V to a microphone module, which will output values to the Spark via one of the analogue pins. However, the systems I’ve purchased and assembled, such as the MAX4466, the op-amp design here, and a circuit very similar to this one, found here.

All of these devices output SOME voltage in the way that I’m hooking them up, but none of them seem to respond very much to changes in volume. On the 0-4095 scale that AnalogRead() pulls up (that I’m monitoring from my desktop using direct requests to the Core) no matter how much noise I make at the electrets, the value only seems to fluctuate within a range of 5 or so. I’ve tried feeding the MAX4466’s output through an op-amp subtractor to try and get rid of the ‘offset’ caused by a quiet room and then multiply the output of that by 100x to see if the changes are just too tinny to register without massive amplification, but that also doesn’t seem to be working.

Has anyone ever used electret mics with a Core? Do you have any tips, any suggestions on why what I’m doing isn’t working out?

I noticed that the MAX4466 says it will have a DC voltage offset of VCC/2. This is probably because the mic outputs a sine wave and you don’t want it to go negative.

How fast are you sampling the ADC? If your to slow you might not be capturing the highs and lows and only catching the mid points.

Make sure you don’t have and pull-up or pull-downs configured on the ADC pin.

Otherwise look at the signal with a oscilloscope.

@cyrilamethyst, @Bspranger, I’ve used that mic module in my RGBPongClock. I find that even at its highest gain, the mic is not as sensitive as I would expect. The Vcc/2 output is normal as @Bspranger pointed out. However, when doing analogRead() the firmware will override any pinMode() set by the user code.

If you are doing ad-hoc sampling of the ADC, it is unlikely you will see much activity. You may want to write a simple program to sample the ADC in a loop and print out the value using Serial.print(). You will have a better chance of seeing the value change that way. :smile:

@peekay123 @Bspranger
Unfortunately I don’t have an oscilloscope where I am to test the signal. The offset makes sense, though; I understand why you wouldn’t want it going negative. I’m sampling it every 60ms, which would also make sense given how quickly speech fluctuates.

The device in question is really only meant to determine if some volume of noise is present in the room. To that end, do you think a better solution would be to poll the analog pin rapidly, keeping track of its maximum, and then report that maximum back every 60ms? This would alert to the loudest noise heard over that interval, would it not?

@cyrilamethyst, can you post your sampling code? Sampling at a fixed, rapid interval and using a time-based threshold “trigger” would be an extension of your “maximum” approach. The idea is you look at each sample to see if it is higher than a set threshold for a fixed time interval. It’s almost like debouncing a button! :smile:

These are the functions the web client calls:

int readPort (String command) {
    int ret = -1;
    
    if (command == "port_a") {
        ret = portA.getLastReading();
    } else if (command == "port_b") {
        ret = portB.getLastReading();
    } else if (command == "port_c") {
        ret = portC.getLastReading();
    } else if (command == "port_d") {
        ret = portD.getLastReading();
    }
    
    return ret;
}

This is my loop, that updates what is on each of the ports every 60ms.

void loop () {
    if (millis() - lastTime >= 60) {
        portA.read();
        portB.read();
        portC.read();
        portD.read();
        lastTime = millis();
    }
}

Finally, the actual reading and getLastReading() functions:

 /**
         * Updates the lastReading variable, reading either as an analog or serial input.
         * Analog inputs can be interpreted by the client software as digital.
         */
        void read() {
            lastReading = digitalRead (notSerialPin) ? analogRead (dataPin) : serialRead ();
            
            // Serial modules cannot be hot-swapped. So, if one is connected after the system 
            // has started running, the Core must be reset. 
             if (digitalRead(notSerialPin))
            {
                hasBeenAnalog = true;   
                hasBeenSerial = false;
            }
            else
            {
                 if (hasBeenSerial && hasBeenAnalog)
                {
                System.reset();   
                }
                hasBeenSerial = true;
                
            }

        }
        
        /**
         * Returns the most recent value read on this port.
         */
        int getLastReading() {
            return lastReading;
        }
};

Can you explain your ‘trigger’ method?

@cyrilamethyst, I am not clear on your portX structure and how you use digitalRead(notSerialPin). The way I see it, though your loop() runs at 60ms, your web client can only sample at that speed or slower meaning it may miss sound “peaks”.

The other way to do it is to do the analog peak and timing logic on the device and return (or publish) a “triggered” condition when the sound has been above a certain level (which you can set via the Spark.function) for a set amount of time (again, set via Spark.function).

The project actually has four sensors built in, one of which being the noise sensor. The ‘notSerialPin’ is used to determine if said sensor runs on analog or serial communication and either interpret what comes in on the sensor’s dataline as an analog voltage or a string of bits, but for the sake of this piece, the noise sensor is not serial, so it would just do an analog read as normal.

Thank you so much for your assistance, though! I think the problem is as stated before, we’re just sampling far too slowly. I have a few ideas to step things up a little, I’ll look into those today and tomorrow if I don’t finish today, and post what I deduce or write up after.

Okay, so currently I have this method set up for reading off the mic; all I really care about is the highest amount of noise detected at any point in time during the reading.

void noiseRead() {
    int temp = analogRead(noiseData);
    int temp2;
    for(int i = 0; i < 1000000; i++) {
        temp2 = analogRead(noiseData);
        if (temp2 > temp) temp = temp2;
    }
    noiseLastRead = temp;
}

void loop() {
    noiseRead();
    delay(10);
}
}
int getLastNoiseReading() {
    return noiseLastRead;
}

Wherein getLastNoiseReading is what’s called by the client requesting data. This samples many, many times and takes the max of them, and then makes that into the lastRead variable. However, it’s still barely responding to noise, if at all. It’s also frequently crashing, and I can’t reflash it via wifi, so I’m about to clear it and manually reflash. Any tips? I suspect the gigantic for loop is an issue.

@cyrilamethyst, first, you need to make sure your mic gain is set to maximum. Second, and the reason you cannot do OTA is the for loop you have which prevents the background firmware from running. Without that, you will look connectivity.

Remove the for loop in noiseRead and make noiseLastRead the variable you compare with and set to the highest value. Yo could reset noiseLastRead every second or more to make sure you always have a “fresh” peak. :smile:

Yeah you definitely need to reset noiseLastRead at some interval otherwise your only going to see the highest point ever captured. So every second it should see ~1.65 volts and then go up from there when you make noise.

You could also capture the minimum because the highest point could be on the negative side. But that is probably not needed.

@peekay123, I’m trying to adjust the gain with what I assume is the potentiometer on the back of the MAX4466, but it seems like I can just turn clockwise forever. Is it supposed to offer some form of resistance whenever I max it out?
For a while I had it working in such a way that it was reading about 4080 out of 4095, and virtually unchanging no matter how noisy I got. Then I turned it some more and it was about 2200, which is around 1.77V, close to what @Bspranger suggested, and it did go up a little bit when I made some noise. I tried to turn it a little more to up the gain, but now it’s back to sitting very close to max.

Code that I changed:

 void loop() {
    lightRead();
    motionRead();
    temperatureRead();
    noiseRead();
    delay(10);
	
	if (millis() - currentTime > 1000)
		{
		 noiseLastRead = 0;
		 currentTime = millis();
		}
}

void noiseRead() {
    int temp = analogRead(noiseData);
    if (temp > noiseLastRead)
		{
		 noiseLastRead = temp;
		}
}

EDIT:
One of my peers had a good idea. Create an array of shorts that stores the most recent 100 or so analog readings, and then find the max of that array any time the client requests if any noise was heard. Going to try to implement that!

@cyrilamethyst, that little pot is a pain in the butt to set! I have an oscilloscope so it was easy for me. Looking at the pot is not obvious either as to what the limit positions are.

By running a simple loop() app that does nothing but read and print the analog value, you could set the pot to an optimal position by looking at the output and making sound (I whistled a note!).

The collection bin method is a great idea though you can use a simple circular buffer with a head and tail pointer to have a rolling collection of data. Make sure to zero the buffer once you’ve “used” it. :smile:

I’ve swapped over to a breadboarded electret mic/op amp with a gain of 1-101 to make testing easier and it’s going much better.

Thank you so much for all the help, you made a real difference for me! And thanks to everyone else who posted with assistance.

1 Like

Op-amps are real brain twisters, and sound waveforms are fast, but there are lots of tricks to op-amps. A peak-detect might help. It uses a R/C circuit on the output to slow down the level so you can capture the recent levels averaged over time since you don’t actually need clear audio. Here’s an example I found, but not sure it’s the best fit:

https://www.st-andrews.ac.uk/~www_pa/Scots_Guide/audio/part8/Page3.html

Another good thing might be an op-amp circuit known as “integrate-and-dump” like this one:

This lets you accumulate voltage (loudness) over a longer period and then measure it once. After you measure it, you use a Core output to “dump” the accumulated charge.

This would reduce the work you have to do in software by adding hardware, so it depends on what else your Core is doing whether this makes sense or not.

4 Likes