nRFX based PDM to PCM implementation (I2S example)

Hello,

I am currently interfacing my nRF52840 DK with i2S based MP34DT05 sensor. I am using nrfx i2s driver's for interfacing with sensor. I am using master clk of 2MHz, LCLK 16KHZ. 

My sensor output PDM data. In my project I am recording audio samples on left channel, so I am currently getting 2*16bit of left data per 32bits as shown in nrf docs. I have got 16bit recorded samples in buffer, but to how to convert this pdm data in pcm format. 

Normally we need to divide the PDM data with a decimation factor, but in I2S we already are dividing the bit sampling rate with ratio value. So is the output from the I2S itself is decimated PCM ?  

Or can anyone give a example of how to convert the PDM to PCM data. Any sort of help is dually appreciated. 

  • Hello,

    instead of the NRFX I2S driver, it sounds like you should use the NRFX PDM driver: https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.0.2/group__nrf__pdm.html

    The PDM driver interfaces with the PDM hardware on nRF52840: https://infocenter.nordicsemi.com/topic/ps_nrf52840/pdm.html?cp=4_0_0_5_14

    The PDM hardware has the decimation filtering built into hardware, and outputs PCM samples. Two decimation factors are supported, 64 and 80. For example, if you want a 16 kHz PCM rate, you should choose a PDM clock rate of 1.28 MHz, and a decimation factor of 80.

    Best regards,

    Audun

  • Hello,

    first off, I would recommend printing out the entire pdm_buf array and look at the contents. The microphone has a ~10 ms startup time (see below), so it is likely that the first sample in the first buffer will always be the same value.

    Second, data on the DIN pin is normally in the same frequency range as the clock signal, but this will vary according to the acoustic input to the sensor.

    If you want to validate the DIN signal you see in the logic analyzer, you can try the attached python script. The script parses and filters an exported .csv from Saleae Logic analyzer software. Just make sure the exported csv has DIN as the first exported value. For example:

    Time [s],Channel 5,Channel 7
    -0.000051150,0,0
    0.000000000,1,0
    0.000000030,1,1
    0.000999980,0,1
    0.001000018,0,0
    0.001999976,1,0
    0.002000010,1,1
    0.002999954,0,1
    0.002999998,0,0

    Here, channel 5 is DIN, and Channel 7 is CLK.

    Python script syntax, using 16 kHz as target PCM rate:

    $ python pdm_parse.py my_exported_saleae_capture.csv 16000

  • Thanks for the Logic analyzer traces!

    The logic analyzer trace from the sensor directly looks to be more in range of the expected frequency, but it's hard to say if it's good or not just by looking at this snippet. Did you try running the python script on this measurement?

    It's possible that the issue is electrical. What does your test setup look like? Are you powering the sensor from one of the nRF52840-DK headers, or is there a separate supply? Are there other components connected to the CLK or DIN lines?

    I would recommend connecting the CLK signal to the sensor, but keep the DIN line to nRF52840 disconnected. This way you can measure the data output on the sensor again, without getting potential interference from the nRF52840-DK. For example if the nRF52840 is configured with a pull resistor on P1.05, this could influence the signal level.

  • L/R pin connection Information

    When i see the nRF52840 PDM docs (Fig 2), it shows i have to connect L/R pin to VDD for left channel, but my sensor say opposite. 

    I wouldn't worry so much about the L/R selection or clock edge details at this stage. In my experience when only one microphone is connected to the PDM peripheral, the data signal will in effect end up in both channels as no other sensor is driving the data line for the other channel. Regardless, you can configure the nrfx pdm driver to sample stereo and sort out the exact channel details later.

    Your hardware setup sounds sensible. The MP34DT05 supply voltage falls within VDD on the nRF52840-DK. I think VDD on nRF52840-DK defaults to 1.8V, but I'm not 100% sure.

    Okay will also check that and will also use python script on the same. Any others suggestions or guidance is also appreciated.

    In terms of electrical setup, wire length could be an issue.

    In the logic analyzer trace made at the sensor, I also noticed that the two signals seems very similar. It's hard to tell from just this snippet, but could there be a short between the clock and data lines?

    This screenshot below was taken a while ago, but shows the kind of pattern I would expect to see on the PDM clock and data lines:

  • Can it be a problem with my sensor ? It is outputting at PDM CLK rate but false data.  

    It's possible. If you have another sensor at hand, it would be worth checking.

    I re-read your description the setup. Am I understanding correctly that you don't have this USB connected?

    Have you measured the voltage on the sensor VDD?

  • Sorry, I have been thinking about this one for a bit, and I don't have any good ideas. I checked if it could be a VDD supply issue regarding nRF_USB, but I don't think that is a problem. VDD on the headers should be able to supply external circuitry regardless of which USB connection you have used.

    I think it would be worthwhile to test another sensor, in case this particular unit is faulty.

    To summarize: You've verified that VDD and CLK is within the sensor specification (albeit in the lower end for the clock, but that should not matter), but still the output does not look good. You've also verified that there is no external circuitry messing with the output signal, by disconnecting the data line. I think the next step would be to try another sensor unit.

  • Hello,

    there are a couple of problems with the script and data:
    1) wavfile.writeframes() needs raw int16_t byte data, not a string. Your read the text file as a string, but to put it in writeframes each of the values in the comma-separated string needs to be extracted and formatted into a continuous array of formatted bytes.

    2) The first parameter (number of channels) in wavfile.setparams is 2, but the data seems to be single channel? How is the PDM peripheral configured in the code?

    3) I would recommend printing your buffer as int16_t (signed short), as this is the PCM format generated by the PDM peripheral. When I plot the data it looks somewhat good, but there also seems to be some formatting error going on in the printouts:

  • This seems to work better. Tested using python3.

    import sys
    import wave
    import re
    import struct
    
    with open('0317.test.txt', 'r') as pcmfile:
        # Convert comma-separated hex strings into list of integers
        pcm_list = list(map(lambda x: int(x, 16), re.findall(r'([\w\d]+)', pcmfile.read())))
    
    # Convert list of integers into little endian-encoded byte array
    pcmdata = bytes()
        
    for sample in pcm_list:
        # Original sample is signed 16-bit. Format as uint16, and decode as int16.
        tmpdata = struct.pack('<H', sample)
        tmpdata = struct.unpack('<h', tmpdata)[0]
        pcmdata += struct.pack('<h', tmpdata)
        
    with wave.open('0317.test.wav', 'wb') as wavfile:
        wavfile.setparams((1, 2, 16000, 0, 'NONE', 'NONE'))
        wavfile.writeframes(pcmdata)

  • The UART baud rate might be an issue here. The default is 115200 baud, while your audio is 16-bit * 16 kHz = 256000. To test if this is the problem, you can try setting the board baud rate to 1000000 instead of 115200. The proper way to do this is to set up an overlay file in your sample, but as a quick test you can also update this config: https://github.com/nrfconnect/sdk-zephyr/blob/master/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts#L146

    I also recommend checking out the USBD audio sample: https://github.com/nrfconnect/sdk-zephyr/tree/master/samples/subsys/usb/audio/headset This sample makes the nRF52840-DK appear as an audio device when connected to a USB host. With this you could copy the audio from the PDM directly to USB, and listen to the audio in real time on your computer without any additional scripts.

  • If you find any bug in above code kindly mention or have any another tricks than please help because i can't recognize voice patterns just on the basis of 2-3 sec audio.

    I think it would make things easier if you use some data structures to help organize the PCM buffers. Specifically, I recommend using a memory slab for allocating PCM buffers, and a queue/FIFO to enqueue PCM buffers that have been filled.

    Let's say your algorithm runs on audio block sizes of 100 milliseconds, at 16 kHz sampling rate, this means each audio block is 3200 bytes. You would then define a memory slab of size X * 3200. X can be any number that fits within total memory. Note that it is probably best to define a struct for your audio data, which contains the int16_t audio buffer, plus the fifo_reserved data field, plus whatever else might be useful.

    In the PDM event hander, when a new buffer is requested, you try to allocate a new buffer from the slab (k_mem_slab_alloc). If this fails, you can log the error.

    In the PDM event handler, when a freshly filled buffer is released, you put the pointer to this buffer in the FIFO (k_fifo_put).

    Your algorithm can run as soon as there is one or more items in the FIFO (k_fifo_get).

    Once the algorithm has finished processing one audio buffer, it will release it back to the memory slab (k_mem_slab_free).

    This way your algorithm will always process the audio in order, and it is easy to see if you have a bottleneck somewhere.

    If you have multiple consumers of your audio data (e.g. local algorithm plus UART printout), this can be solved by adding a reference counter to the audio buffer struct.