Calculating decibels (SPL) from MAX4466 microphone input

With advice from here, I obtain a good range of input values, then calculate the rectified, peak-to-peak, and RMS values - see code below. However, there are many formulas for calculating dB(SPL) - a ratio of two levels - around, with differing units, and I can't find a best-practice answer. At the moment, I obtain values from -32 (silence) to -12 (nearby radio on), but audiodB = 20 * log10(audioRMSFiltered * (2.5 / 1024.0)); does not take the below into account

  1. Reference sound pressure auditory threshold 20 µPa
  2. Microphone sensitivity from data sheet -44 dBV (0,00631 VRMS)
  3. Amplifier gain set to 75 x (+37 dB)

I am not intending to build a precision instrument, but would like to come reasonably close to what this app or this device output. How should I approach this?

rectified, peak-to-peak, and RMS values

dB(SPL)

const byte pinAudioIn = 0;
const byte timeIntervalSampling = 30;
const int levelShift = 676; // 676 for 2,5V AREF, 512 for 3,3V AREF , without AREF 338

int sampleCounter;
int audioInRaw;
int audioInRectified;
int audioPeakToPeak;
float audioRMSPrecalc;
float audioRMS;
float audioRMSFiltered;
float audiodBSPL;

void setup()
{
  Serial.begin(115200);

  analogReference(EXTERNAL); // Use 3,3V AREF or 2,5V AREF as reference voltage
}

void loop()
{
  unsigned long timeSamplingStart = millis();

  uint16_t audioMin = 1023;
  uint16_t audioMax = 0;

  while (millis() - timeSamplingStart < timeIntervalSampling)
  {
    audioInRaw = analogRead(pinAudioIn) + 0; // ADC correction factor +-
    audioInRectified = abs(audioInRaw - levelShift);

    audioMin = min(audioInRectified, audioMin);
    audioMax = max(audioInRectified, audioMax);

    audioRMSPrecalc += ((long) audioInRectified * audioInRectified); // Square each value and add them
    sampleCounter ++;
  }

  audioPeakToPeak = audioMax - audioMin;

  audioRMSPrecalc /= sampleCounter; // Divide by number of samples taken
  audioRMS = sqrt(audioRMSPrecalc); // Take the square root
  audioRMSFiltered = 0.5 * audioRMSFiltered + (1 - 0.5) * audioRMS; // Leaky integrator, fast attack and slow decay

  audiodBSPL = 20 * log10(audioRMSFiltered * (2.5 / 1024.0)); // 3,3V AREF or 2,5V AREF

  //Serial.print("audioInRaw:"); Serial.print(audioInRaw); Serial.print(" ");
  //Serial.print("audioInRectified:"); Serial.print(audioInRectified); Serial.print(" ");
  //Serial.print("audioPeakToPeak:"); Serial.print(audioPeakToPeak); Serial.print(" ");
  //Serial.print("audioRMS:"); Serial.print(audioRMS); Serial.print(" ");
  //Serial.print("audiodBSPL:"); Serial.println(audiodBSPL);

  sampleCounter = 0;
}

I didn't really study your code...

Microphone sensitivity is normally referenced to 94dB SPL.

So with a gain of 75, you should get 0.47VRMS at 94dB. That's your reference (although you can calculate a different reference from that information).

20 x log(V/0.47) is the dB difference.

Add that dB calculation to 94dB and you've got your SPL level. (Of course if it's negative you'll be effectively subtracting.)

Here's an SPL meter I made with a different microphone board. I used the peak instead of RMS and I find a short-term peak and a short-term average. (If you assume sine waves, the peak-to-RMS ratio is known.)

is this the sampling rate your using for sampling a audio signal from a microphone or from external circuit that provides a voltage representing the level of the signal.

the following suggests it's sampling an audio signal

From a MAX4466 microphone, the output already amplified and biased at Vcc/2. The sampling rate works very well with a headphone line input and a decibel measuring breakout board, so I kept it for this third approach to sound input also.

if the input is the audio signal, it needs to be sampled at a much higher frequency (~8kHz)

According to @nickgammon the ADC sampling rate of ATmega based boards at an analogue input is around 9.6 kHz.

a typical audio codec samples repeatedly, as soon as it completes one sample is starts the next. but your code is calling adcRead() in between do other things.

All examples I found, good or bad, use a sampling window of 20 ms to 125 ms, for example, and the sampling window size was never questioned. In my case, within the sampling window of 30 ms, the ADC reads as fast as it can with the default prescaler of 128. Matlab or Analog Devices for example, as well as other sources, place much emphasis on the temporal length of the sampling window when dealing with audio input - so why do you think using a sampling window is wrong?

sampling for 30 msec seems. it's not clear to me that this code is sampling the signal fast enough and uniformly

It should be fast enough and uniform (why would it not be the latter?) like the many other examples that actually do more - some rudimentary FFT for example, or flashing RGB LEDs, which I do not.

if you capture a tone and plot it does it look like a tone?

@gcjr Looks like expected. A pure 1 kHz tone. Remember the ADC's sampling frequency is not correlated to the tone frequency. And noise from trains or people in an office provides no pure tone, for it is a superposition of countless tones, constantly in flux.

@DVDdoug The human hearing threshold reference is 0 dB(SPL) or 0.00002 Pa. Most sound-level measurements will be made relative to this reference, meaning 1 Pa equals 94 dB(SPL). The microphone's sensitivity from the data sheet is -44 dBV which makes 0.00631 VRMS at 94 dB(SPL) or 1 Pa, according to the Analog Devices calculator and general notes. Likewise, an amplifier gain of 75 x would make a difference of 37.5 dB.

  • The way I understand things so far is that I should calculate like so dB(SPL) = 20 * log10(audioRMS/0.00631) + 94.
  • But if that is correct so far, where does the 25 x/75 x/125 x gain factor in? How do you arrive at 0.47 VRMS for 75 x gain? Would I not have to subtract it like so dB(SPL) = 20 * log10(audioRMS/0.00631) + 94 - 37.5?
  • And at what point should I re-calculate my input into volts - already here audioInRaw = volts = (audioInRaw * 2.5) / 1024?

is this a 30 msec capture?

@gcjr It is.

how many 1kHz cycles should there be in 35 msec?

1000 Hz = 1000 periods in 1000 milliseconds, so that'd be 35 periods in 35 milliseconds.

how many cycles do you see in you 35 msec capture?

I don't have an oscilloscope. Can only borrow one every now and then.

In any case, the RMS calculation is correct, right? But how to take the amplifier gain, and other factors into account?

const byte pinAudioIn = 0;
const byte timeIntervalSampling = 30; // Some suggest 50, others 125...
const int levelShift = 676; // 676 for 2,5V AREF, 512 for 3,3V AREF , without AREF 338

int sampleCounter;

int audioInRaw;
float audioInRectified;
float audioRMSPrecalc;
float audioRMS;
float audiodB;

void setup()
{
  Serial.begin(115200);

  analogReference(EXTERNAL); // Use 3,3V AREF or better 2,5V AREF as reference voltage
}

void loop()
{
  unsigned long timeSamplingStart = millis();

  while (millis() - timeSamplingStart < timeIntervalSampling)
  {
    audioInRaw = analogRead(pinAudioIn) + 0; // ADC correction factor +/-
    audioInRectified = abs(audioInRaw - levelShift) * (2.5 / 1024); // Rectify and convert to volts, AREF is 2,5V
    audioRMSPrecalc += (audioInRectified * audioInRectified); // Square each value and add

    sampleCounter ++;
  }

  audioRMSPrecalc /= sampleCounter; // Divide by number of samples taken
  audioRMS = sqrt(audioRMSPrecalc); // Take the square root

  audiodB = 20 * log10(audioRMS / 0.00631); // RMS calculated correctly? Gain? Other factors?

  Serial.print("audiodB:"); Serial.println(audiodB);

  sampleCounter = 0;
}

you have a plot. you're code is oscilloscope.

this is what a 1kHz waveform sampled at 9600 samp/sec for 35 msec should look like

what you have may be correct. but it doesn't seem to match the parameters