Digital MEMS microphone (IM72D128V01) interface with nrf5430 via PDM

Hi,

   Iam currently working with NRF5340 module for the integration of MEMS microphone (IM72D128V01). need to configure PCM sampling rate as 2KHz.

                .min_pdm_clk_freq =  120000,
                .max_pdm_clk_freq = 160000,

this is the configuration that I have given to configure PCM as 2Khz. but while probing the data and clock pins. Iam able to view the clock but no data out.

 

note: have use the DMIC example code.

1) is there any way to use this microphone (IM72D128V01)?

      attaching the screenshot of MEMs microphone.

2) can u suggest the step to validate the data?

        2.1) how to convert the hex data to audio. and validate?

Parents
  • I have been able to use a similar MEMS microphone from Infineon with the 5340. If you want to configure the sampling rate to 2 KHz you should set the pcm_rate field in the dmic_cfg structure:

    cfg.streams[0].pcm_rate = <SAMPLE_RATE>;

    Regarding point number 2, you could stream the 16-bit data over the serial port and have a python script convert it into a .wav file. You should then be able to listen to the recorded data or analyze it using some kind of software.

    Also I think the maxmium and minimum clk frequencies you have specified are incorrect. They should reflect the microphone's specification, which I think, if reading the datasheet correctly is 440 KHz to 5.04 Mhz.

  • thanks for the reply.

    1) is there any way to down sample (pcm) using any api?

             

  • Are you referring to the conversion between PDM to PCM? Because the DMIC api will do this for you. dmic_read will give you a "usable" format (PCM) that can be directly used to create a .wav file.

    Does this answer your question or did I misinterpet it?

  • how we create the .wave file .. can u explain in detail. 
    share example if any.

  • This code should work, it has been a while since I used it. I think it expects binary data (not hex string). I hope this helps.

    import serial
    import numpy as np
    from scipy.io import wavfile
    
    # Configuration: duration in seconds and sampling rate in Hz
    duration = 5  # Record for 5 seconds
    Fs = 2000    # Sampling rate of 2000 Hz
    
    # Open the serial port
    ser = serial.Serial('/dev/ttyACM0', baudrate=115200, timeout=1)
    
    # Number of samples to read (calculated from duration and sampling rate)
    num_samples = int(duration * Fs)
    
    # Buffer to store audio data
    audio_data = np.zeros(num_samples, dtype=np.int16)
    
    bytes_per_sample = 2
    samples_read = 0
    i = 0
    
    # Read audio data from the serial port
    while i < num_samples:
        # Read two bytes (16-bit integer)
        data_bytes = ser.read_all()
        
        # Ensure there are exactly 2 bytes to read
        if len(data_bytes) % 2 != 0:
            print(f"Error: {len(data_bytes)} bytes read")
            continue
    
        # Convert bytes to integer (little-endian)
        for j in range(0, len(data_bytes), bytes_per_sample):
            sample = int.from_bytes(data_bytes[j:j+bytes_per_sample], byteorder='little', signed=True)
            audio_data[i] = sample
            i += 1
        
        # Increment the number of samples read
        samples_read += len(data_bytes) / bytes_per_sample
    
    # Close the serial port
    ser.close()
    
    # Path to the output file
    output_file = 'output.wav'
    
    # Write to .wav file
    wavfile.write(output_file, Fs, audio_data)
    
    print(f"Audio data saved to {output_file}")

  • iam using the sample code of Dmic in nrf5340 .
    attaching the code below.(currently trying to work on PCM sampling rate as 8khz ).

    1) is there any down sampling method / api . so that i can use PCM sampling as 2Khz. because if i increase the pcm sampling rate memory is a big constrain.

    2) how to build the binary file using the attached code and use the above python code generate the audio file . please refer the attached code and feel free to guide me in detail. 

    note: using windows machine.



    #include <zephyr/kernel.h>
    #include <zephyr/audio/dmic.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(dmic_sample);
    
    // #define MAX_SAMPLE_RATE  16000
    #define MAX_SAMPLE_RATE	 8000
    #define SAMPLE_BIT_WIDTH 16
    #define BYTES_PER_SAMPLE sizeof(int16_t)
    /* Milliseconds to wait for a block to be read. */
    #define READ_TIMEOUT	 3000 // 1000
    
    /* Size of a block for 100 ms of audio data. */
    #define BLOCK_SIZE(_sample_rate, _number_of_channels)                                              \
    	(BYTES_PER_SAMPLE * (_sample_rate / 10) * _number_of_channels)
    
    /* Driver will allocate blocks from this slab to receive audio data into them.
     * Application, after getting a given block from the driver and processing its
     * data, needs to free that block.
     */
    #define MAX_BLOCK_SIZE BLOCK_SIZE(MAX_SAMPLE_RATE, 2)
    #define BLOCK_COUNT    30 // 50 //4
    K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, 4);
    
    static int do_pdm_transfer(const struct device *dmic_dev, struct dmic_cfg *cfg, size_t block_count)
    {
    	int ret;
    
    	LOG_INF("PCM output rate: %u, channels: %u", cfg->streams[0].pcm_rate,
    		cfg->channel.req_num_chan);
    
    	ret = dmic_configure(dmic_dev, cfg);
    	if (ret < 0) {
    		LOG_ERR("Failed to configure the driver: %d", ret);
    		return ret;
    	}
    
    	ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START);
    	if (ret < 0) {
    		LOG_ERR("START trigger failed: %d", ret);
    		return ret;
    	}
    
    	for (int i = 0; i < block_count; ++i) {
    		void *buffer;
    		uint32_t size;
    		int ret;
    
    		ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT);
    		if (ret < 0) {
    			LOG_ERR("%d - read failed: %d", i, ret);
    			return ret;
    		}
    
    		// LOG_INF("%d - got buffer %p of %u bytes", i, buffer, size);
    
    		uint16_t *audio_data = (uint16_t *)buffer;
    		uint32_t num_samples = size / sizeof(uint16_t);
    
    
    
    		for (uint32_t j = 0; j < num_samples ; ++j)
    		{
    		 	uint32_t sample = audio_data[j];
    
    		 	printk("%04x ", audio_data[j]);
    
    		 }
    
    
    		k_mem_slab_free(&mem_slab, &buffer);
    	}
    
    	ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP);
    	if (ret < 0) {
    		LOG_ERR("STOP trigger failed: %d", ret);
    		return ret;
    	}
    
    	return ret;
    }
    
    int main(void)
    {
    	const struct device *const dmic_dev = DEVICE_DT_GET(DT_NODELABEL(dmic_dev));
    	int ret;
    
    	LOG_INF("DMIC sample");
    	
    	if (!device_is_ready(dmic_dev)) {
    		LOG_ERR("%s is not ready", dmic_dev->name);
    		return 0;
    	}
    
    	struct pcm_stream_cfg stream = {
    		.pcm_width = SAMPLE_BIT_WIDTH,
    		.mem_slab = &mem_slab,
    	};
    	struct dmic_cfg cfg = {
    		.io =
    			{
    				/* These fields can be used to limit the PDM clock
    				 * configurations that the driver is allowed to use
    				 * to those supported by the microphone.
    				 */
    				// .min_pdm_clk_freq = 120000,   //for 2khz
    				// .max_pdm_clk_freq = 160000,   // for 2khz
    				.min_pdm_clk_freq = 512000,      // for 8khz
    				.max_pdm_clk_freq = 640000,      // for 8khz
    				.min_pdm_clk_dc = 40,
    				.max_pdm_clk_dc = 60,
    			},
    		.streams = &stream,
    		.channel =
    			{
    				.req_num_streams = 1,
    			},
    	};
    
    	// cfg.channel.req_num_chan = 1;
    	// cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT);
    	// cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE;
    	// cfg.streams[0].block_size = BLOCK_SIZE(cfg.streams[0].pcm_rate,
    	// cfg.channel.req_num_chan);
    
    	// ret = do_pdm_transfer(dmic_dev, &cfg, 2 * BLOCK_COUNT);
    	// if (ret < 0) {
    	// 	return 0;
    	// }
    
    	cfg.channel.req_num_chan = 2;
    	cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT) |
    				      dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT);
    	cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE;
    	cfg.streams[0].block_size = BLOCK_SIZE(cfg.streams[0].pcm_rate, cfg.channel.req_num_chan);
    
    	ret = do_pdm_transfer(dmic_dev, &cfg, 2 * BLOCK_COUNT);
    	if (ret < 0) {
    		return 0;
    	}
    
    	LOG_INF("Exiting");
    	return 0;
    }
    

  • I'm not entirely certain about your intentions. Are you looking to utilize a sampling rate of 2 KHz or 8 KHz?

    Upon reviewing your code, I noticed you adjust the minimum and maximum clock frequency based on the sampling rate. However, this isn't necessary. The min and max sampling rates are determined by the microphone's specifications, so they remain constant regardless of your chosen sampling rate.

    If you're aiming to minimize memory usage without altering the sampling rate, consider adjusting the size of each sample block. Currently, with a block width of 100 ms, it seems you're consuming 6400 bytes of data. By reducing the block width to 50 ms, you can bring this down to 3200 bytes. Do note, however, that reducing the block width will lead to more frequent thread switches, potentially increasing the relative overhead and leaving less time for data processing in each cycle.

    Regarding the down sampling, are you unable to set the PCM rate to 2 KHz and this is why you want to down sample the data? In that case I cannot help you as I do not know how to do this or if there is an API for it, but surely there must be some code available online that can do this for you.

    If you want an example of how to get the audio data to your PC, refer to this example:
    https://docs.zephyrproject.org/latest/samples/shields/x_nucleo_iks02a1/microphone/README.html#x-nucleo-iks02a1-mic

    EDIT: Regarding this statement: "how to build the binary file using the attached code", are you referring to the python code?

Reply
  • I'm not entirely certain about your intentions. Are you looking to utilize a sampling rate of 2 KHz or 8 KHz?

    Upon reviewing your code, I noticed you adjust the minimum and maximum clock frequency based on the sampling rate. However, this isn't necessary. The min and max sampling rates are determined by the microphone's specifications, so they remain constant regardless of your chosen sampling rate.

    If you're aiming to minimize memory usage without altering the sampling rate, consider adjusting the size of each sample block. Currently, with a block width of 100 ms, it seems you're consuming 6400 bytes of data. By reducing the block width to 50 ms, you can bring this down to 3200 bytes. Do note, however, that reducing the block width will lead to more frequent thread switches, potentially increasing the relative overhead and leaving less time for data processing in each cycle.

    Regarding the down sampling, are you unable to set the PCM rate to 2 KHz and this is why you want to down sample the data? In that case I cannot help you as I do not know how to do this or if there is an API for it, but surely there must be some code available online that can do this for you.

    If you want an example of how to get the audio data to your PC, refer to this example:
    https://docs.zephyrproject.org/latest/samples/shields/x_nucleo_iks02a1/microphone/README.html#x-nucleo-iks02a1-mic

    EDIT: Regarding this statement: "how to build the binary file using the attached code", are you referring to the python code?

Children
  • 1) currently iam using the pcm sampling rate as 8khz (because 2khz is not supported by the mems microphone).

              1.1) aim:  Any way to manually down sample to 2khz after reading 8khz.

                     we may need to implement down conversion in our code. is it possible?
                     can you guide me on it, any sample code 
    available?


    2) have referred the link that you have provided 

    "If you want an example of how to get the audio data to your PC, refer to this example:"

    https://docs.zephyrproject.org/latest/samples/shields/x_nucleo_iks02a1/microphone/README.html#x-nucleo-iks02a1-mic 

    it does not explain how to convert ASCII PCM data to binary. can u guide me on it step by step.

    feel free to refer the attached code, suggest corrections if any.

    #include <zephyr/kernel.h>
    #include <zephyr/audio/dmic.h>
    #include <zephyr/logging/log.h>
    
    LOG_MODULE_REGISTER(dmic_sample);
    
    #define MAX_SAMPLE_RATE	 8000
    #define SAMPLE_BIT_WIDTH 16
    #define BYTES_PER_SAMPLE sizeof(int16_t)
    /* Milliseconds to wait for a block to be read. */
    #define READ_TIMEOUT	 3000 // 1000
    
    /* Size of a block for 100 ms of audio data. */
    #define BLOCK_SIZE(_sample_rate, _number_of_channels)                                              \
    	(BYTES_PER_SAMPLE * (_sample_rate / 10) * _number_of_channels)
    
    /* Driver will allocate blocks from this slab to receive audio data into them.
     * Application, after getting a given block from the driver and processing its
     * data, needs to free that block.
     */
    #define MAX_BLOCK_SIZE BLOCK_SIZE(MAX_SAMPLE_RATE, 2)
    #define BLOCK_COUNT    30 // 50 //4
    K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, 4);
    
    void *buffer[BLOCK_COUNT];
    uint32_t size = MAX_BLOCK_SIZE;
    
    
    static int do_pdm_transfer(const struct device *dmic_dev, struct dmic_cfg *cfg, size_t block_count)
    {
    	int ret;
    
    	// LOG_INF("PCM output rate: %u, channels: %u", cfg->streams[0].pcm_rate,cfg->channel.req_num_chan);
    
    	ret = dmic_configure(dmic_dev, cfg);
    	if (ret < 0) {
    		LOG_ERR("Failed to configure the driver: %d", ret);
    		return ret;
    	}
    
    	ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_START);
    	if (ret < 0) {
    		LOG_ERR("START trigger failed: %d", ret);
    		return ret;
    	}
    
    	for (int i = 0; i < block_count; ++i) {
    
    		int ret;
    
    		ret = dmic_read(dmic_dev, 0, &buffer, &size, READ_TIMEOUT);
    		if (ret < 0) {
    			LOG_ERR("%d - read failed: %d", i, ret);
    			return ret;
    		}
    
    		// LOG_INF("%d - got buffer %p of %u bytes", i, buffer, size);
    
    		// Print buffer in hexadecimal using LOG_HEXDUMP_INF()
    		// LOG_HEXDUMP_INF(buffer, size, "Buffer contents:");
    
    		// uint16_t *audio_data = (uint16_t *)buffer;
    		// uint32_t num_samples = size / sizeof(uint16_t);
           
    		// for (int j = 0; j < num_samples; ++j) {
    		// printk("0x%04x ", audio_data[j]);
    		// k_sleep(K_MSEC(1));
            // }
    
    		k_mem_slab_free(&mem_slab, &buffer);
    	}
    
    	// 	for (uint32_t ms = 0; ms < block_count; ms++) {
    	// 	ret = dmic_read(dmic_dev, 0, &buffer[ms], &size, READ_TIMEOUT);
    	// 	if (ret < 0) {
    	// 		printk("microphone audio read error\n");
    	// 		return 0;
    	// 	}
    	// }
    
    	ret = dmic_trigger(dmic_dev, DMIC_TRIGGER_STOP);
    	if (ret < 0) {
    		LOG_ERR("STOP trigger failed: %d", ret);
    		return ret;
    	}
    
    	printk("-- start\n");
    	int j;
    
    	for (int i = 0; i < BLOCK_COUNT; i++) {
    		uint16_t *pcm_out = buffer[i];
    
    		for (j = 0; j < size/2; j++) {
    			// printk("0x%04x,\n", pcm_out[j]);
    			printk("0x%04x", pcm_out[j]);
    			k_sleep(K_MSEC(1));
    		}
    	}
    	printk("-- end\n");
    	
    	return ret;
    }
    
    int main(void)
    {
    	const struct device *const dmic_dev = DEVICE_DT_GET(DT_NODELABEL(dmic_dev));
    	int ret;
    
    	// LOG_INF("DMIC sample");
    	
    	if (!device_is_ready(dmic_dev)) {
    		LOG_ERR("%s is not ready", dmic_dev->name);
    		return 0;
    	}
    
    	struct pcm_stream_cfg stream = {
    		.pcm_width = SAMPLE_BIT_WIDTH,
    		.mem_slab = &mem_slab,
    	};
    	struct dmic_cfg cfg = {
    		.io =
    			{
    				/* These fields can be used to limit the PDM clock
    				 * configurations that the driver is allowed to use
    				 * to those supported by the microphone.
    				 */
    				.min_pdm_clk_freq = 512000,
    				.max_pdm_clk_freq = 640000,
    				.min_pdm_clk_dc = 40,
    				.max_pdm_clk_dc = 60,
    			},
    		.streams = &stream,
    		.channel =
    			{
    				.req_num_streams = 1,
    			},
    	};
    
    	// cfg.channel.req_num_chan = 1;
    	// cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT);
    	// cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE;
    	// cfg.streams[0].block_size = BLOCK_SIZE(cfg.streams[0].pcm_rate,
    	// cfg.channel.req_num_chan);
    
    	// ret = do_pdm_transfer(dmic_dev, &cfg, 2 * BLOCK_COUNT);
    	// if (ret < 0) {
    	// 	return 0;
    	// }
    
    	cfg.channel.req_num_chan = 2;
    	cfg.channel.req_chan_map_lo = dmic_build_channel_map(0, 0, PDM_CHAN_LEFT) |dmic_build_channel_map(1, 0, PDM_CHAN_RIGHT);
    	cfg.streams[0].pcm_rate = MAX_SAMPLE_RATE;
    	cfg.streams[0].block_size = BLOCK_SIZE(cfg.streams[0].pcm_rate, cfg.channel.req_num_chan);
    
    	ret = do_pdm_transfer(dmic_dev, &cfg, 2 * BLOCK_COUNT);
    	if (ret < 0) {
    		return 0;
    	}
    
    	// LOG_INF("Exiting");
    	return 0;
    }


  • Unfortunately I cannot help you with the down scaling of the signal.

    Regarding "recording" the data into a .wav file. It does not really matter what format you send the data in, as long as you process it correctly on the receiving end. But outputting it as binary will make the parsing of the data on the receiving end easier. In the sample, you can see that you can configure it to output the data in either ASCII or binary format.

    This part of the code outputs the data in binary format:

    	unsigned char pcm_l, pcm_h;
    	int j;
    
    	for (i = 0; i < NUM_MS; i++) {
    		uint16_t *pcm_out = rx_block[i];
    
    		for (j = 0; j < rx_size/2; j++) {
    			pcm_l = (char)(pcm_out[j] & 0xFF);
    			pcm_h = (char)((pcm_out[j] >> 8) & 0xFF);
    
    			z_impl_k_str_out(&pcm_l, 1);
    			z_impl_k_str_out(&pcm_h, 1);
    		}
    	}

    They seem to be using z_impl_k_str_out, not sure if this will work I haven't used it myself. Otherwise I think you will have to use the uart API directly. You should be able to get a device pointer to the console (uart0 if using the default board) and use uart_poll_out instead of z_impl_k_str_out.

    	const struct device *uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
    	if (!device_is_ready(uart)) {
    	    printk("failed to retrieve uart device");
    	    return -1;
    	}
    	// ... 
    	
    	unsigned char pcm_l, pcm_h;
    	int j;
    
    	for (i = 0; i < NUM_MS; i++) {
    		uint16_t *pcm_out = rx_block[i];
    
    		for (j = 0; j < rx_size/2; j++) {
    			pcm_l = (char)(pcm_out[j] & 0xFF);
    			pcm_h = (char)((pcm_out[j] >> 8) & 0xFF);
    
                uart_poll_out(uart, pcm_l);
                uart_poll_out(uart, pcm_h)
    		}
    	}

Related