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

Glitch in analog audio output when using I2S module

Hello.

I'm using the NRF52832 with I2S module connected to TLV320DAC from TI. I've configured the DAC and I2S module successfully. I'm getting audio output (simple sine wave read from a table) but there is an impulse glitch in the audio signal. The glitch appears to occur at the end of every I2S buffer send. When I set the I2S TX buffer size to 250 samples, I see a glitch in the audio at the corresponding time interval. For example, 250 buffer size at 41666 sample rate, causes a glitch in the audio every 0.006 seconds. When I increase the I2S buffer size, the glitch period increases correspondingly.

I've checked my sine table interpolation algorithm. It looks good. I believe the problem is with the NRF52832 and not the DAC. The I2S buffer size is the only variable which seems to affect the audio glitch. Oh, I also tried using I2S slave mode where I program the DAC to send the I2S clocks to the NRF chip. That did not change the glitch though.

I recorded the audio output in Audacity. Take a look at the waveform and frequency spectrum...

\

  • Okay, looks like I was have been deceived. The scratch buffer seems to have only masked the issue. After some seemingly unrelated code changes the original symptoms returned. This time I managed to suppress them simply by adding an extra word to the start of the buffer, and then passing a pointer to index 1 instead! It's as if the DMA is sucking data from *before* the buffer pointer passed to it.

  • Not sure if I have any idea here, you could try to read the I2S chapter in the product specification for any suggestion also:
    https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/i2s.html#concept_z2v_24y_vr

    You may also look into placing the i2s buffers into a specific RAM section not used by other peripherals:
    https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/memory.html#memory

    Kenneth

  • Thanks Kenneth. Yes I've already studied those documents more than I was hoping to have to.

    I've even gone so far as to review the driver code. As far as I can tell it is following the instructions as given in the product specification.

    Nonetheless, I can now reliably demonstrate that the DMA is sucking data from before the buffer pointer passed to it. Here's what I found:

    If I pass a pointer to a buffer containing N words of 32 bits, what comes out the SDOUT pin is the data in the buffer, starting one half-word before the start of the buffer, and ending one half-word before the end of the buffer. The LRCLK pin also behaves this way, going low one half word too early (the net effect being that it is inverted).

    In other words, if my buffer contains 2 words, with bytes [0][1][2][3][4][5][6][7], and I supply the buffer to each call to nrfx_i2s_next_buffers_set, the SDOUT pin will show [X][X][1][0][3][2][5][4][X][X][1][0][3][2][5][4], where [X][X] is the contents of memory before the start of the buffer. If you compensate for fact that it appears the DMA is expecting little-endian half-words (which makes sense), then the digital stream lines up exactly with the explanation that it starts one half-word before the start of the buffer and ends one half-word before the end of the buffer.

  • Oh boy. I discovered why. The buffer I was passing was not word aligned. Variables aren't necessary so. The I2S driver checks and enforces word alignment, but the code that does so gets compiled to a nop! This is the critical line of code:

    NRFX_ASSERT((p_initial_buffers->p_tx_buffer == NULL) ||
                    (nrfx_is_in_ram(p_initial_buffers->p_tx_buffer) &&
                     nrfx_is_word_aligned(p_initial_buffers->p_tx_buffer)));



    Alas, it turns out NRFX_ASSERT expands to nothing, so the misalignment goes unnoticed. Assumedly the DMA then takes no notice of the lower two bits in the pointer, resulting in a start address that is half a word early.

    I've now declared my buffer with the suffix __attribute__((aligned(4))) and all is rosy.

    Pretty tough going here...

  • Well spotted, you are likely compiling your code with "Release", not "Debug", then the code will not do these checks, ref:

    #if (defined(DEBUG_NRF) || defined(DEBUG_NRF_USER))
    #define NRF_ASSERT_PRESENT 1
    #else
    #define NRF_ASSERT_PRESENT 0
    #endif
Related