This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

MEMS microphone via PDM is noisy

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?
  • Hi,

    Have you started the high frequency xtal oscillator, or are you using the RC? If so, can you test again with the xtal oscillator started?

  • Hi Einar,

    In the project the clocks are managed by the soft device. The sdk_config.h is based on the ble_app_template example and configures this:

    // RC 
    // XTAL 
    // Synth 
    #define CLOCK_CONFIG_LF_SRC 1 
    #define NRF_SDH_CLOCK_LF_SRC 1 
    #define NRFX_CLOCK_CONFIG_LF_SRC 1Code

    I could not find anything that starts the high frequency oscillator. I tried to disable the soft device and manually start LF and HF clocks like this:

    int main(void)
    {
    
    /* Start 16 MHz crystal oscillator */
    NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_HFCLKSTART = 1;
    
    /* Wait for the external oscillator to start up */
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
    {
    // Do nothing.
    }
    
    /* Start low frequency crystal oscillator for app_timer(used by bsp)*/
    NRF_CLOCK->LFCLKSRC = (CLOCK_LFCLKSRC_SRC_Xtal << CLOCK_LFCLKSRC_SRC_Pos);
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    NRF_CLOCK->TASKS_LFCLKSTART = 1;
    
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0)
    {
    // Do nothing.
    }
    
    // ...
    }

    In this case the noise issue is still exists though and I also do not know if the HF clock is based on xtal or RC, as I could not find the way how this would need to be configured.

    By the way I also thought that there might be some noise induced by the board being connected to the computer so I hooked it up to a battery and ran the firmware, but it is still the same.

  • Hi,

    Writing 1 to the TASKS_HFCLKSTART register like you do here will ensure that the HFXO is started. However, the SoftDevice will only start the HFXO when needed by the radio and disable it immediately after (so the RC is running instead). It is your responsibility to start it when you need a high accuracy clock for other peripherals. If you start it with direct register access like this, then enable the SoftDevice, it will turn it off again after the first radio event. You should use the SoftDevice API function sd_clock_hfclk_request() (or even better clock driver, which in turn use the SoftDevice API) after enabling the SoftDevice to prevent this from happening.

  • So it turned out that the issue I was seeing was merely related to a broken reference device, to which my code had been calibrated. After calibrating it to a new device I was seeing a noise floor around 34 dB SPL, which I am happy with. Thanks for the help!

  • Do you know if it can work with the Thingy 52 ?

Related