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;
}
  • check this code ! 

    #include <stdint.h>
    #include "nrfx_pdm.h"
    
    #define PDM_BUFFER_SIZE 256
    
    
    static int16_t pdm_buffer[PDM_BUFFER_SIZE];
    
    uint16_t *p_buffer;
    uint16_t pdm_buffer1[PDM_BUFFER_SIZE];
    uint16_t pdm_buffer2[PDM_BUFFER_SIZE];
    uint8_t data_ready_flag;
    
    ISR_DIRECT_DECLARE(pdm_isr_handler)
    {
        nrfx_pdm_irq_handler();
        ISR_DIRECT_PM(); /* PM done after servicing interrupt for best latency
                  */
        //printk("pdm_isr_handler\n");
    
        return 1; /* We should check if scheduling decision should be made */
    }
    
    
    void pdm_event_handler(nrfx_pdm_evt_t const *p_evt) {
      
            
            nrfx_err_t err = 0;
    
            if(true == p_evt->buffer_requested){
                if(buffer_no)   err = nrfx_pdm_buffer_set(pdm_buffer2, PDM_BUFFER_SIZE);
                else            err = nrfx_pdm_buffer_set(pdm_buffer1, PDM_BUFFER_SIZE);
    
                buffer_no = !buffer_no;
                
                if(err != NRFX_SUCCESS)
                {
                    printk("PDM buffer init error: %d\n", err);
                }
                else
                {
                    //LOG_DBG("PDM buffer init OK\n");
                }
            }
            if(p_evt->buffer_released != 0){
                printk("buffer_released: %p\n", p_evt->buffer_released);
                p_buffer = p_evt->buffer_released;
                data_ready_flag = true;
    
            }
        }
    
    void i2s_data_handler(nrfx_i2s_buffers_t const *p_released, uint32_t status) {
        if (status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED) {
    
    		printk("i2s_handeller CB ! \n");
    
    		
            // Handle I2S errors or other status conditions
            return;
        }
    }
    
    
    int pdm_init(){
    
    	 nrfx_pdm_config_t pdm_cfg = NRFX_PDM_DEFAULT_CONFIG(25,26);
    
    	 IRQ_DIRECT_CONNECT(PDM0_IRQn, 0, pdm_isr_handler, 0);
    
         
        nrfx_err_t err = nrfx_pdm_init(&pdm_cfg, pdm_event_handler);
        if(err != NRFX_SUCCESS){
            printk("PDM init error: %d\n", err);
        }
        else{
            printk("PDM init OK\n");
        }
    }
    
    
    void check_data(void)
    {
        /* print PCM stream */
        while(!data_ready_flag){
            k_sleep(K_MSEC(1));
        }
    
        data_ready_flag = false;
    
        for(int i = 0; i < PDM_BUFFER_SIZE; i++) {
                printk("%x ", p_buffer[i]);
    
            }
            printk("\n");
    
       // printk("PINRT DATA\n");
    }
    
    nrfx_i2s_buffers_t i2s_buffers;
    
    int main(void) {
    
    	// Initialize PDM
    	pdm_init();
    
    	
    	// Start PDM and I2S
    	 nrfx_err_t err = nrfx_pdm_start();
    	 if(err != NRFX_SUCCESS){
    	 printk("PDM start error: %d\n", err);
    	 }
         else{
    	 	printk("PDM start OK\n");
    	 }
    
    		while (1) {
    			// Your application logic here
    			check_data();
    			k_msleep(2);
    		}
    }

  • Hi 

    In case the code shared by Ajaysinh doesn't fix the issue, could you provide a bit more details on the sound distortion? 

    Are you getting repeated clicks, or is the distortion more random in nature? 

    What is the size of NUM_SAMPLES in your test? 

    Best regards
    Torbjørn

  • Hi, I updated the pdm_event_handler and pdm_init as in the code provided, but same result.

    NUM_SAMPLES=1024

    I see you declare pdm_buffer1 and pdm_buffer2 as unsigned int, are pcm samples saved as unsigned?

  • 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.

Related