Close
0%
0%

ESP32-I2S-SLM

Sound Level Meter with Arduino IDE, ESP32 and I2S MEMS microphone

Public Chat
Similar projects worth following
The project aim is to design and build simple but relatively accurate 'Sound Level Meter' with Arduino IDE, ESP32, and commonly available I2S digital microphones.

The basic idea

Sample the sound via microphone, do some filtering and weighting, calculate noise level in real-time on ESP32 and display the result on small screen.

Should be quite simple, however, as usual, the devil is in the details.

Sampling sound with I2S digital microphone

The good thing about digital microphones is that you don't need to worry about the analog part like pre-amplification, linearity and speed of MCU ADC, etc... And the digital values you receive should be already referenced to sound pressure levels (SPL). Datasheet needs to list the amplitude value for certain SPL, for 1KHz pure sine wave tone (i.e. -26dBFS @ 94dB SPL). This is usually expressed as dBFS  (decibel relative to full-scale), so for i.e. -26dBFS you can convert this to absolute amplitude value based on the maximum value mic can send, and in case of 24bit data, this should be (2^23 − 1) * 10^(−26/20) ~= 420426. This is the amplitude you should expect to receive if you i.e. put the microphone inside 'Sound level calibrator'

The microphone of choice for this project is TDK/InvenSense ICS-43434, or more specifically its breakout board available at Tindie. One good thing about this microphone is that its sensitivity is specified as +/-1dB. This means our measurement of 94dB, 1KHz pure sine wave tone, should be -26dBFS, +/-1dB, without any additional calibration. This is pretty good considering I do not have access to any calibration equipment. You can also use the older INMP441 mic, widely available as cheap breakout board on i.e. Aliexpress, but that one has sensitivity specified as +/-3dB.

The hardware

Breadboard friendly, see the list of components

Equalization and weighting

MEMS microphones are usually not ideal and there should be frequency response plot in the datasheet. If this curve deviates outside from acceptable parameters, first we need to equalize (i.e flatten) the microphone native response in the measurement range (20Hz - 20KHz), before we measure the actual SPL levels (i.e. Z-weighted) and apply any weighting filter. We can do this with digital IIR filter designed to (inverse) match the datasheet frequency plot. See the 'ics43434.m' file for my humble attempt at filter design to equalize the ICS-43434. You can copy/paste the math in Octave Online to calculate the coefficients and display the IIR filter frequency response graphs. TLDR, the 'flattened frequency response should look like this (blue line):

Next step is to apply the frequency weighting, in this case the most common (but probably not the most correct) A-weighting, also implemented as IIR filter. The coefficient for this filter were taken from here, for sampling frequency of 48KHz.

Actual implementation of IIR filters is taken (and slightly modified for single-precision and performance) from the nice Arduino digital filter library, and ESP32 with its FPU has the required grunt to do the math continuously while sampling.

The measurement

And from there it is straight forward. I calculate the RMS of the sampled signal, calculate decibels referenced to datasheet value for 94dB and display the value.
Sound level measurements are only meaningful in context of duration of the sampling (see Wikipedia). The Arduino sketch, by default, displays the LAeq(125ms) measurements as horizontal line on top of the screen and LAeq(1sec) measurements as numeric value. It also prints the measured numeric value on the serial monitor and you can graph it with Arduino's 'Serial plotter'

Source code and IIR filter math are available on Github

sheet - 4.57 MB - 09/08/2019 at 16:12

Download

  • 1 × ESP32 Development board of choice
  • 1 × ICS-43434 breakout Digital I2S MEMS microphone
  • 1 × Small OLED display
  • 1 × Breadboard and jumper wires

  • Back to single-precision with ‘Second-Order Sections’

    Ivan Kostoski11/22/2019 at 16:13 0 comments

    It turns out that if you 'breakup' the higher order IIR filters into series of Second-Order Sections (i.e. biquads), the filtering error does not accumulate as much. With 24-bit data, broken down filters and single-precision, the error induced by the A-weighting filter should be less than 0.5dB in worst case (20Hz). The monolithic 6th order ‘B/A’ transfer function had >7dB error at 20Hz with single-precision. 

    As we are here to hack stuff, It is more likely that parts of this project will be integrated into other more complex projects, with many more sensors and functionality, instead of being used as standalone SLM, so I set my (over)optimization goal to leave as much as possible CPU time for other tasks. And instead of fighting with GCC to produce the code that I know is possible, the end result is going all the way to ESP32/Xtensa assembler...

    Well, now you can lower the frequency of ESP32 down to 80MHz (i.e. for battery operation) and filtering and summation of I2S data will still take less than 15% of single core processing time. At 240MHz, filtering 1/8sec worth of samples with 2 x 6th-order IIR filters takes less than 5ms. The ESP32/GCC assembler implementation is in ‘sos-iir-filter.h’ and in the comments you can find more or less equivalent C code. The sources from esp-dsp were quite helpful. The CPU ISAs these days… let’s just say that my beginnings involved writing 6502 assembler…

    Support for Knowles SPH0645LM4H-B 

    I received a sample of this microphone so I decided to try it out. First, it is not quite compatible with ESP32 I2S peripheral and you need to apply ‘dirty hack’ (directly manipulating ESP32 registers, see the code) to even receive the MSB of I2S data. Additionally, the received values have ‘DC bias’ (or offset) so calculating SPL RMS directly is not possible. If you apply DC-blocker filter (or more specifically, DC-Blocker SOS section), this bias can be filtered out.

    While it finally works, in my humble opinion, this microphone is not very well suited for sound level measurement, mostly due to its limited dynamic range (18 valid bits in I2S data) and the errors that will be added by any IIR filters at lower amplitudes, no matter what kind of arithmetic precision you use.

  • When signle-precision is just not good enough

    Ivan Kostoski09/29/2019 at 07:08 0 comments

    It turns out that doing single-precision (24 bits mantissa) math on IIR filters may not work well for frequencies below 40Hz (sampling rate 48Khz). The A-weighting filter was not attenuating the signal enough (error in range of >10dB),

    Easily fixable by replacing 'float' with 'double'. That however, on ESP32 which only has single-precision hardware FPU, comes with big performance penalty. While using only one 6th order IIR filter was still OK, using 2x 6th order filter needed ~122ms just for filtering 125ms sampled data, i.e. just a bit too slow.

    ESP32 silicon should have the 'double precision FP acceleration pkg' (more info here) and 'XCHAL_HAVE_DFP_ACCEL' macro is defined in esp-idf. That would have taken care of the problem. Sadly, it seems GCC doesn't know how to use these instructions on and there is no public record of the accelerated libraries mentioned in the application note. Due to the extra registers involved, I assume this may also have impact on FreeRTOS context switching...

    Another way around this is to use fixed-point math, with i.e. .32 precision. Again, GCC doesn't have __int128 implementation on 32bit platforms, so that had to be added as well...

    Anyway, I have updated the sources with .32 fixed-point implementation of the IIR filters which is fast enough (2x compared to software emulated double-precision) and good enough so error at low frequencies (down to 10Hz) is <=0.1dB. As we are dealing with 24bit microphone values, there should be low risk of fixed-point overloading. The code is not very well tested, but seems to work for microphone sampled data.

  • Comparative measurements with B&K 2250

    Ivan Kostoski09/08/2019 at 16:26 0 comments

    If you are wondering if the theoretic accuracy of this simple and cheap SLM has any practical meaning, here are some measurements in comparison with IEC-61672-1, class 1, Brüel&Kjær 2250 sound level meter, courtesy of D-r Enrico Armelloni.  

    The MEMS microphone used in the test is ICS-42432 (slightly older, and perhaps more accurate model), in protective shell which also acts as 1.27mm adapter, connected to ESP32 running the GitHub sketch. D-r Armelloni went to great length testing the MEMS+ESP32 setup, including various sound amplitudes, frequencies, pink and white noise, etc...  


    The detailed calibrated results so far are in the excel file in the ‘Files’ section.

    Please have in mind that this is probably the best-case scenario, i.e. it is not expected that every single piece of MEMS microphone will produce such close results. And if you wish to be confident in the measurements, you will need to do similar calibration on your setup. Also note that the range of the used MEMS microphones is about 35dB to 116dB and not suitable for i.e. low noise measurements.

    What test does validate is the principle of how noise is calculated based on sampled sound from I2S microphone. On D-r Armelloni's advice, I also removed all misleading references to 'Fast' and 'Slow' in the project description, as the code never did any time-weighting on the sampled values. It only calculates LAeq values for various periods, which I believe is currently the most useful metric.

View all 3 project logs

Enjoy this project?

Share

Discussions

RSTjordan wrote 07/03/2020 at 09:39 point

Hello Ivan,

Why are you shifting the sampels 8 bit right? is it for converting the 32 bit to 24? but how is this how you do it? it doesnt lead to data loss?

thank you very much!

  Are you sure? yes | no

Ivan Kostoski wrote 07/07/2020 at 19:14 point

Hi Jordan, the microphone datasheet specifies the number of valid bits (i.e. 24 for ICS-43434) in the I2S data, and rest are zeroes anyway. I am just 'normalizing' the data before  converting to 'float' and filtering...

  Are you sure? yes | no

belovictor wrote 05/13/2020 at 14:49 point

Great project first of all! Can you please elaborate on how have you put the MEMS microphone into the shell to fit it into the coupler?

  Are you sure? yes | no

Enrico Armelloni wrote 05/16/2020 at 20:42 point

Hi belovictor,

In order to use the 1/2" microphone calibrator I designed the PCB for the MEMS microphone, so I mounted it in an aluminum case (specially made in the workshop) with the diameter of a 1/2" microphone.
You can see the picture of the case here:

https://forum.pjrc.com/threads/44005-Teensy-3-6-and-MEMS-mic-ICS-43432?highlight=earmello

  Are you sure? yes | no

Walter wrote 10/09/2019 at 19:30 point

Hi, I have acces to a professional acoustic calibration laboratory and can help you further. I am using a MEMS microphone from Infineon. Also have some other ideas which I already implemented and tested using my own code, like a second channel and a DAC output. Let me know if you want to work together and can let me join the project.

  Are you sure? yes | no

Elliot Williams wrote 08/27/2019 at 07:44 point

Great idea to find a (cheap) calibrated part and fill out the rest yourself.  Very cool.

  Are you sure? yes | no

Ivan Kostoski wrote 08/27/2019 at 08:58 point

Thanks. I am waiting for some comparison results with 'real' SLM. Initial test with Bruel&Kjaer 2250 look very promising...

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates