PDM captured data from mic is distorted

Hello, we are capturing sound with a PDM mic, and sending it to a codec through I2S. We have tested the I2S with software generated pure tones, and it is working properly. When we send the mic sound to the coded it is distorted so we think the problem is related to the microphone missconfiguration.

There are two interrupts, one for I2S, another for the PDM. We are using nordic drivers for both. The PDM buffer is copied in a "for loop"  into the I2S when both interrup handlers mark the buffer release event.

The microphone is configured for data capture on clk rise, mono. Desired sampling is 16KHz.

Thanks in advance for reviewing the code.

#include "pdm-microphone.h"
#include "I2S_port.h"  
#include <nrfx_pdm.h>
#include <nrfx.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(PDMport);

//Definir el tipo del array igual que word_size
int16_t pdm_data_frame[NUM_SAMPLES];
int16_t pdm_data_frame2[NUM_SAMPLES];
int16_t *pdm_active_buffer = pdm_data_frame;
int16_t *pdm_previous_buffer = pdm_data_frame2;

bool pdm_ready_flag = false;

//uint16_t pdm_buffer_size = PDM_N_WORDS;
uint16_t pdm_buffer_size = NUM_SAMPLES;

ISR_DIRECT_DECLARE(pdm_isr_handler)
{
    nrfx_pdm_irq_handler();
    ISR_DIRECT_PM(); // PM done after servicing interrupt for best latency
             
    return 1;
}

static void pdm_data_handler(nrfx_pdm_evt_t const * p_evt)
{
        nrfx_err_t err = 0;
        LOG_INF("PDM data Handler");  

        if(true == p_evt->buffer_requested){
            err = nrfx_pdm_buffer_set(pdm_active_buffer, pdm_buffer_size);
            if(err != NRFX_SUCCESS)
            {
                printk("PDM buffer init error: %d\n", err);
            }
        }

        if(p_evt->buffer_released != 0){

            // Swap active and next buffer
            uint32_t *pdm_tmp = pdm_active_buffer;
            pdm_active_buffer = pdm_previous_buffer;
            pdm_previous_buffer = pdm_tmp;

            pdm_ready_flag = true;
            LOG_INF("PDM buffer released, dataflag =TRUE");

        }
}

void init_microphone ()
{
    nrfx_pdm_config_t config_pdm = NRFX_PDM_DEFAULT_CONFIG(PDM_CLK_PIN, PDM_DIN_PIN);

    //***Run from 32MHz
    //config_pdm.clock_freq = NRF_PDM_FREQ_1280K;
    //config_pdm.ratio = NRF_PDM_RATIO_80X;
   
    //***Run from ACLK, PDM_CLK =1.024MHz (fsample= PDM_CLK/Ratio = 16KHz)
    //***PDM_CLK= fsource / [(4096**1048576)/(PDMCLKCTRL_regval)];, fsample= PDM_CLK / ratio.
    config_pdm.clock_freq = 0x15555555;
    config_pdm.mclksrc = NRF_PDM_MCLKSRC_ACLK;

    //Select-->GND , MIC captura dato en flanco de subida.
    config_pdm.edge = NRF_PDM_EDGE_LEFTRISING;

    IRQ_DIRECT_CONNECT (PDM0_IRQn, 0, pdm_isr_handler, 0);

    nrfx_err_t err = nrfx_pdm_init(&config_pdm, pdm_data_handler);
    if(err != NRFX_SUCCESS){
        printk("PDM init error: %d\n", err);
    }

    err = nrfx_pdm_start();

    if (err == NRFX_SUCCESS)
    {
        printk("Pdm start was successfull\n");
    }
    else {
        printk("Pdm start was NOT successfull\n");
    }

}

this is the main code:

int main(void)
{

    nrfx_err_t err_code;
    err_code = get_sound_init();
    //***Configura I2C
    init_i2c_device();
    //***Configura microphono PDM
    init_microphone();

        while (true) {

            if (I2S_ready_flag)
             {
                if (pdm_ready_flag)
                {
                convert_PDM_buffer_to_I2S_output();
                // Reset the data_ready_flag
                data_ready_flag = false;
                pdm_ready_flag = false;
                }
            }

        k_msleep(10);

        }

return 0;
}
Parents Reply Children
  • Hi Rafael

    PCM samples are signed values, and can be both positive and negative, but I doubt it will make a difference if they are declared as signed or unsigned unless you are planning to perform any mathematical operations on the data. 

    The EasyDMA mechanism of the PDM and I2S peripherals don't care how the buffers are declared, all you configure is the start address of the buffer, and the data will be written to or read from there. 

    One potential issue with your code is that you are trying to do everything in lock step (one after the other), rather than using a ring buffer to buffer the PDM data before you send it over I2S. 
    In applications like this you typically use a ring buffer that is filled up by the data producer (PDM), and then you empty it in parallel by the data consumer (I2S). Then you can ensure that you don't start playback until you have at least enough data in the buffer to set up double buffering in the I2S driver. 

    While not exactly the same I made an example some time back where audio data is moved from SD card to I2S on the audio DK, and it is based on the same principle. A ring_buffer is continuously filled up by the SD card interface, and emptied by the I2S driver. The relevant code can be found here.

    Best regards
    Torbjørn 

  • Hi Ovrebekk, I attach a few seconds of the output sound; I would say it might be related to the decimation filter, but of course we are testing tomorrow the ring buffer as you suggest. Please let me know your opinion, regards.

  • Hi Rafael

    Thanks for sharing the sound. The distortion is quite interesting, none of the clicks and pops you often get if there are buffering issues. 

    Still, possibly you can hear some kind of frequent, rapid interference happening, which could imply some kind of issue when the buffer swap happens (with a buffer size of 1024 you should get 16 buffer swaps every second). 

    What happens if you increase NUM_SAMPLES by a significant amount, try 8192 for instance. Does it affect the distortion? 

    Best regards
    Torbjørn

  • Hi Torbjorn,

    I found the problem and partially fixed it with the buffer ring; As the frequencies of I2S and PDM are slightly different, the interrupts secuence was not constant, so some times the PDM_handler swapped the buffers for a second time before the I2S_data_flag_ready was true.

    Now with the buffer ring, sound is clear till it overflows, because at some point there will be more data from the PDM than the I2S requested.

    I am using both Nordic drivers (I2S,PDM), both have same IRQ priority=7, and both run from ACLK.

    Which is the best approach to solve this?, If the numbers on the register "CONFIG.MCKFREQ" and "PDMCLKCTRL" are multiple, should we reach a perfect sampling synchronization?

    thanks in advance.

  • Hi Rafael

    Using a ring buffer is a good idea, then you should be able to handle the data handover in a more reliable manner. 

    If the I2S and PDM run from the same clock source and use the same audio settings they should run at the same speed. Can you let me know what kind of sample rate, bitrate and channel configuration you are trying to use? 

    Best regards
    Torbjørn

Related