Hello,
I am trying to build a device that can detect the surrounding sound level and display it on the screen. For this I am using this MEMS microphone in conjunction with the Nordic PDM interface.
The problem
It seems that the noise floor is very high, at around ~63 dB SPL. In a quiet room it should be around 35-40 db SPL when it is relatively silent (according to a reference meter). This is quite a significant difference.
I put together this graphic from the information in the datasheet that illustrates why I am expecting a noise floor at ~33 dB SPL.
So here is my code to determine the value.
- The nordic pdm interface converts the PDM signal to PCM
- Continuously fills a buffer of size 512
- These values are then copied into a larger buffer
- RMS value is determined and converted to a float value
- dBFS value is calculated with 20.0 * log10(fabs(x))
- This is offset by a value of 130 *
* The number of 130 was determined by calibrating the audio input against another sound level meter with a 1kHz sine wave at 94dB.
#define RMS_BUFFER_SIZE 4096 int16_t rms_buffer[RMS_BUFFER_SIZE]; #define MIC_BUFFER_SIZE 512 int16_t mic_buffer[MIC_BUFFER_SIZE] = {}; double rms_db_spl = 0.0; // <-- The result I am interested in double rms_normalized = 0.0; uint16_t rms_buffer_cursor = 0; /** * Returns dBFS value (negative number) from normalized RMS snapshot */ double calculate_dbfs_from_normalized(double rms_normalized) { return 20.0 * log10(fabs(rms_normalized)); } /** * Converts normalized RMS snapshot to dB SPL */ double get_db_spl(double normalized_rms) { const uint8_t db_spl_offset = 130; return calculate_dbfs_from_normalized(normalized_rms) + db_spl_offset; } /** * Applies buffer block to rms buffer and extracts root mean square value */ double calculate_rms(int16_t *buffer, uint16_t num_samples) { uint64_t sum = 0; for (uint16_t j = 0; j < num_samples; j++) { rms_buffer[rms_buffer_cursor] = buffer[j]; rms_buffer_cursor = (rms_buffer_cursor + 1) % RMS_BUFFER_SIZE; } for (uint16_t j = 0; j < RMS_BUFFER_SIZE; j++) { sum += rms_buffer[j] * rms_buffer[j]; } return sqrt(sum / (double) RMS_BUFFER_SIZE); } /** * Audio callback triggered from PDM event handler */ void audio_callback(int16_t *buffer, uint16_t size) { // Calculate RMS of last 250 ms double rms = calculate_rms(buffer, size); // Convert int16_t to double rms_normalized = rms / (double) 32768.0; // Convert rms to dB value rms_db_spl = get_db_spl(rms_normalized); } /** * PDM event handler */ nrfx_pdm_event_handler_t data_handler_pdm(nrfx_pdm_evt_t const *const p_evt) { if(p_evt->error) { NRF_LOG_INFO('pdm handler error %d', p_evt->error); return; } if(p_evt->buffer_requested) { nrfx_pdm_buffer_set(mic_buffer, MIC_BUFFER_SIZE); } if(p_evt->buffer_released) { audio_callback(mic_buffer, MIC_BUFFER_SIZE); } }
What about the gain config?
Since the gain stage only affects samples after AD conversion, it does not affect the reading in any way. At +20 or -20 dB I get the same noise level when I readapt the offset from 130 to 110 or 150 respectively.
Is the resolution of 16 bit audio simply too low?
According to different sources on the internet e.g. this one the dynamic range of 16 bit audio should be 96dB, which should be enough.
So my questions are
- What could be the cause of this?
- Is there any way to obtain a higher bit depth?
- Are there any other in between steps that add noise?