Calculate decibels measured by Sparkfun Sound Detector?

How do I calculate the volume measured by the Sparkfun Sound Detector?

In this document it is described that changing the resistance between the two r17 pins changes the gain. I'm doing this using an X9C104 (which is just a digital potentiometer) so I can set the gain using my Arduino.

I've written a function that converts a given gain dB value into the resistance required at r17 to reach that gain level. But what I couldn't figure out is how to calculate the actual volume or sound pressure level that is picked up by the Sound Detector.

It's gotta be some equation that takes the gain and raw audio level read at the analog pin into account:

float getDbVolumeByRawVolume(float gain, int rawVolume) {
  // magic ?
}

This is my code so far:

#include <LapX9C10X.h>

#define SOUND_DETECTOR_AUDIO_PIN A0
#define SOUND_DETECTOR_ENVELOPE_PIN A1
#define POT_CS_PIN 8
#define POT_INC_PIN 9
#define POT_UD_PIN 10
LapX9C10X gainPot(POT_INC_PIN, POT_UD_PIN, POT_CS_PIN, LAPX9C10X_X9C104);

float gainDb = 33;

void setup() {
  Serial.begin(115200);
  pinMode(SOUND_DETECTOR_AUDIO_PIN, INPUT);
  pinMode(SOUND_DETECTOR_ENVELOPE_PIN, INPUT);
  gainPot.begin();
  float resistance = getResistanceByGain(gainDb); 
  gainPot.set(gainDb);
}

void loop() {
  int rawAudioVolume = analogRead(SOUND_DETECTOR_AUDIO_PIN);
  int rawEnvelopeVolume = analogRead(SOUND_DETECTOR_ENVELOPE_PIN);
  
  float dbVolume = getDbVolumeByRawVolume(gainDb, rawAudioVolume);

  Serial.print("Volume:");
  Serial.println(dbVolume);
}

float getDbVolumeByRawVolume(float gain, int rawVolume) {
  // magic
  return ?
}

// Sparkfun sound detector dB<>resistance-interpolation based on Sparkfun's data set
// https://learn.sparkfun.com/tutorials/sound-detector-hookup-guide/configuration
float getResistanceByGain(float dB) {
  const float infinityOhms = 40.0f;
  const float dBTable[] = {0.0f, 6.0f, 13.0f, 19.0f, 25.0f, 30.0f, 33.0f, 40.0f};
  const float resistanceTable[] = {0.0f, 2.2f, 4.7f, 10.0f, 22.0f, 47.0f, 100.0f, INFINITY};

  // Check if the dB value is out of range
  if (dB < dBTable[0] || dB > dBTable[7])
    return -1.0f; // Return an error value or handle the case accordingly

  // Find the two nearest dB values in the table
  int index = 0;
  while (dB > dBTable[index + 1])
    index++;

  // Perform logarithmic interpolation
  float dB1 = dBTable[index];
  float dB2 = dBTable[index + 1];
  float resistance1 = resistanceTable[index];
  float resistance2 = resistanceTable[index + 1];

  float resistance = resistance1 * pow(10, ((dB - dB1) * log10(resistance2 / resistance1)) / (dB2 - dB1));
  return resistance / 1000.0f; // Return resistance in kilohms (K)
}

Any ideas on how to do this?

The board is intended for audio pickup and relative sound measurement.

Since nothing on that board is calibrated, the only way to get reasonably accurate sound pressure level readings from it is to compare the measured sensor output with a calibrated, professional sound level meter. If you do that for several sound levels, it is simple to work out a calibration response curve.

I made an SPL Meter with a different (fixed gain) SparkFun board. There are LOTS of comments & notes and if you read & study carefully you should be able to get yours working.

...There are also LOTS of caveats. You can make something "useful", but you can't make a "real" SPL meter for regulatory, safety, or legal purposes.

Normally you DO need known-good SPL meter to calibrate yours, or you can get a calibrator. But sometimes you can make calculations from the microphone specs and amplifier gain. But there are tolerances in the specs and it's just a good idea to check/calibrate your readings.

You may not need variable amplifier gain unless you are reading very loud sounds. Then you might need 2 or 3 ranges. Very-quiet sound measurements will be limited by electrical noise in the circuitry and gain will just amplify that noise along with any sound signal you're trying to read.

The envelope output will be easier to use than the raw audio signal. The board I have doesn't have that so my code looks for the peak in the audio signal, making the code more complicated...

I couldn't find the sensitivity specs for the microphone capsule but if we assume (or pretend) it's the same as this one on the same SparkFun page the sensitivity is -46dBV at 1Pa (at 94dB SPL).

With 40dB of amplifier gain (100X) that brings it up to -6dBV, or 0.5V. That's RMS but with the envelope detector we'll get the peak so we can multiply by 1.4 and we get 0.7V. (Even without the envelope detector output you'd probably want the peak.) 0.7V should give you an ADC reading of about 143. That's your (calculated) calibration... 94dB SPL should read 143.

Now, let's say you read 286... We (the software) can calculate the dB DIFFERENCE between 143 and 268. (I happen to know that a factor of 2 is 6dB, so that's why I chose that example.) So the SPL level is 94+6 = 100dB.

NOTE - You are using the WRONG dB formula. The formula for amplitude or voltage is 20*log10(A/Aref). The factor of 10 is for power calculations.

The reason is that power (wattage) is related to the square of the voltage. If you double the voltage you also double the current. Power is calculated as Voltage x Current so double the voltage is 4 times the power.... Double the voltage is +6dB and 4 times the power is +6dB.

P.S.
With an known good SPL meter for calibration this is a lot easier... You just play a tone and adjust it for some SPL level. (You can generate tones with Audacity or you can download them.) 94dB at 1kHz is "standard" but it's uncomfortably LOUD so you might want to use 70 or 80dB or any "easy" round number. Then you make a note of the Arduino ADC reading and that reading and that SPL level are your references. When you take a reading you calculate the dB difference between the current ADC reading and the reference ADC reading, and add (or subtract) the dB calculation from the dB reference.

Thank you @DVDdoug I'm making progress and I've turned your code into a class (adding options to set the gain and mic sensitivity) to make it a bit easier to work with. But I'm still not sure if I understand what I'm doing.
The "dB formula" I've used in the first post was just to do some basic interpolation. I think my algorithm works pretty well or at least good enough. I'm not sure how I would incorporate the 20*log10(A/Aref) into the interpolation anyway. I'm using your formula in the actual spl meter code though:

    spl.peak = referenceSpl + 20 * log10((float)maxPeak / referencePeak) + sensitivity + gain;
    spl.average = referenceSpl + 20 * log10((float)average / referenceAvg) + sensitivity + gain;

I'm not sure if I'm doing this right. One thing in particular that I don't understand is how I get the reference average from the specs. Is it just roughly peak/2? And what about the factor of 1.4 when using the envelope pin? Where does that come from and what value do I use when using the raw audio pin?

This is my code so far:

// based on the work of DVDdoug: https://forum.arduino.cc/t/sound-sensor-which-one/962327/6
class SplMeter {
private:
  int micPin;
  int bias;
  int referenceSpl;
  int referencePeak;
  int referenceAvg;
  int micSensitivity;
  int gain;
  float operatingVoltage;
  bool usingEnvelope;
  
public:
  struct SplData {
    int peak;
    float average;
  };

  SplMeter(int micPin = A0, int bias = 512, int referenceSpl = 94, int micSensitivity = -46, int gain = 0, float operatingVoltage = 5, bool usingEnvelope = false)
      : micPin(micPin), bias(bias), referenceSpl(referenceSpl), micSensitivity(micSensitivity), gain(gain), operatingVoltage(operatingVoltage), usingEnvelope(usingEnvelope) {
    setGain(gain);
  }

  void setGain(int newGain) {
    gain = newGain;
    float envelopeFactor = usingEnvelope ? 1.4 : 1;
    referencePeak = 1023*((1 * pow(10,((micSensitivity+gain)/20)) * envelopeFactor)/operatingVoltage);
    referenceAvg = referencePeak/2;
  }

  SplData read(int loopTime = 1000) {
    int maxPeak = 0;
    unsigned long sum = 0;
    int readings = 0;

    unsigned long readStartTime = millis();

    while (millis() - readStartTime < loopTime) {
      int analogValue = abs(analogRead(micPin) - bias);
      if (analogValue > maxPeak)
        maxPeak = analogValue;

      sum += analogValue;
      readings++;
    }

    float average = (float)sum / readings;

    SplData spl;
    spl.peak = referenceSpl + 20 * log10((float)maxPeak / referencePeak) + micSensitivity + gain;
    spl.average = referenceSpl + 20 * log10((float)average / referenceAvg) + micSensitivity + gain;

    return spl;
  }
};
#define MIC_PIN A0
#define NOISE_BIAS 512
#define REFERENCE_SPL 94 // dB
#define MIC_SENSITIVITY -46 // dB
#define MIC_GAIN 40 // dB
#define OPERATING_VOLTAGE 5.0f // V
#define USING_ENVELOPE true

SplMeter splMeter(MIC_PIN, NOISE_BIAS, REFERENCE_SPL, MIC_SENSITIVITY, MIC_GAIN, OPERATING_VOLTAGE, USING_ENVELOPE);

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

void loop() {
  SplMeter::SplData spl = splMeter.read();
  Serial.print("Max Peak: ");
  Serial.print(spl.peak);
  Serial.println("dBSPL");
  Serial.print("Average: ");
  Serial.print(spl.average, 2);
  Serial.println("dBSPL");
}

Unfortunately I don't have access to any SPL meters. For my purposes I think it would be enough to calculate everything by spec. I believe the mic capulse you linked is the one they actually use.

Like the SPL Meter post (his first link) by DVDdoug explains, commercial meters will use A-weighting. The hardware you are using has a rectifier based on an op-amp built into the module. The big downside of that is the fact that you lose all frequency information in the audio you are measuring.

Even if you used a calibrator at 1kHz 94 dB SPL, you can only measure pure 1 kHz accurately. Other frequencies can measure with significant error. The microphone itself responds differently to different frequencies.
We took a long time to develop a module that takes care of everything and supports most functions that commercial meters have to offer:

I2C decibel meter
We already experimented with budget electret mics and their response was very bad for audio measurements. They can sway by 20 dB based on frequency of audio fed to them. So a MEMS mic + MCU was the reasonable option.

Have you taken the frequency response curve from the datasheet into account when testing with budget electret mics?

It should be possible to obtain the frequencies from the audio pin with enough compute power. My understanding is that the audio pin carries the raw audio signal.

Your I2C decibel meter is very impressive though with its built-in weighting and +-1dB accuracy. I just wish it was more affordable.
In terms of Arduino-compatibility, do you have an Arduino library or sample code for the Arduino? And what do you suggest to use in order to connect this 3.3V board to a 5V Arduino if there is only a 5V PSU? Would this work?

Another project I stumbled upon that might be of interest to future readers: ESP32-I2S-SLM | Hackaday.io

Really? $25? Seems pretty reasonable given the additional functionality over the Spark Fun unit.

Given that I'm not aware of equivalent alternatives, it may be reasonable, but using a cheaper MEMS mic with only +-3dB accuracy like the INMP441 mentioned in the article, you could most likely make this with less than $5 worth of hardware.
The Sparkfun unit I have is vastly overpriced for what it is imo btw.