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

\

Parents
  • 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
  • Okay thank you. It's a little off-topic, but I have been looking for some guidance for performing a debug build but have not been able to locate any. I cannot find any documentation on DEBUG_NRF. There is no reference to a "Debug" build in the NRF Connect SDK docs, including those associated with running the debugger in SES.

    The closest I've come to instructions on performing a debug build is the notes of a few users, such as Jimmy Wong, who list "CONFIG_LOG", "CONFIG_LOG_DEFAULT_LEVEL" and "CONFIG_DEBUG_OPTIMIZATIONS" as the relevant options.

    Are you aware of any documentation on doing a debug build when working with a project based on the NRF Connect SDK?

  • Oh dear. Not only was it a mission to effectively define DEBUG_NRF with the nRF Connect SDK, after spending far too long trying to figure out why it wasn't working, I finally discover that DEBUG_NRF isn't even part of the SDK! It does not appear anywhere, and is only found in projects with a nRF5x library. This being a nRF9x project does not use it at all!

    So I finally discover that the way to do it now is to use CONFIG_ASSERT - only to find that my bare bones application will no longer run thanks to a mysterious assertion fail in a part of the SDK I've never looked at:

    ASSERTION FAIL [((arch_is_in_isr() == 0) || ((timeout).ticks == (((k_timeout_t) {})).ticks))] @ WEST_TOPDIR/zephyr/kernel/sem.c:140

    The fail is deep within disk_access_init --> sdhc_spi_detect --> spi_nrfx_transceive, but I can't afford to spend any more time unpicking the incomplete nRF SDK. Will have to make do and move on.

Reply
  • Oh dear. Not only was it a mission to effectively define DEBUG_NRF with the nRF Connect SDK, after spending far too long trying to figure out why it wasn't working, I finally discover that DEBUG_NRF isn't even part of the SDK! It does not appear anywhere, and is only found in projects with a nRF5x library. This being a nRF9x project does not use it at all!

    So I finally discover that the way to do it now is to use CONFIG_ASSERT - only to find that my bare bones application will no longer run thanks to a mysterious assertion fail in a part of the SDK I've never looked at:

    ASSERTION FAIL [((arch_is_in_isr() == 0) || ((timeout).ticks == (((k_timeout_t) {})).ticks))] @ WEST_TOPDIR/zephyr/kernel/sem.c:140

    The fail is deep within disk_access_init --> sdhc_spi_detect --> spi_nrfx_transceive, but I can't afford to spend any more time unpicking the incomplete nRF SDK. Will have to make do and move on.

Children
  • Aaargh! These issues are three layers deep!

    So while the word-alignment issue I've raised is still valid, it turns out cory123's suggestion from a month ago about having to double buffer the data is also true.

    After re-reading the docs, I see that the driver only double buffers the pointer! So yes, the user also has to double buffer the data, because the driver requests more data before it has finished reading from that previously provided.

  • Typically you can choose between Release and Debug in SES by:

    The difference between the two is found by opening the project file in text editor, e.g.:

      <configuration Name="Release"
        c_preprocessor_definitions="NDEBUG"
        link_time_optimization="No"    gcc_optimization_level="Optimize For Size" />
      <configuration Name="Debug"
        c_preprocessor_definitions="DEBUG; DEBUG_NRF"
        gcc_optimization_level="None"/>

  • Ah! Good to know thank you. I can confirm that those configurations certainly don't appear if you follow the nRF Connect SDK installation instructions, at least as of a month ago.

    I feel I'm probably not missing out on anything anyway, since "DEBUG_NRF" does not appear in the SDK anywhere:

    github.com/search

    As far as I can tell, that define is obsolete. I think the current way to do it is CONFIG_DEBUG and friends but I'm working blind so I'm not sure.

  • Sorry, I forgot you were using NCS, my screenshot were for nRF5 SDK. In your case I guess you can add the following to prj.conf:

    # Debugging configuration
    CONFIG_THREAD_NAME=y
    CONFIG_THREAD_ANALYZER=y
    CONFIG_THREAD_ANALYZER_AUTO=y
    CONFIG_THREAD_ANALYZER_RUN_UNLOCKED=y
    CONFIG_THREAD_ANALYZER_USE_PRINTK=y
    CONFIG_CONSOLE=y
    CONFIG_UART_CONSOLE=y
    CONFIG_SERIAL=y
    CONFIG_PRINTK=y
    CONFIG_USE_SEGGER_RTT=n
    
    # add asserts
    CONFIG_ASSERT=y
    CONFIG_ASSERT_VERBOSE=y
    CONFIG_ASSERT_NO_COND_INFO=n
    CONFIG_ASSERT_NO_MSG_INFO=n
    CONFIG_RESET_ON_FATAL_ERROR=n
    CONFIG_THREAD_NAME=y
    CONFIG_ARM_MPU=y

  • Thanks for that Kenneth. Yes, I sort of barged my way into this thread not realising how isolated the nRF5 world was from the NCS world, so I'm sorry for the confusion.

    I appreciate that list of CONFIGs. Haven't been burnt so bad stepping into the unknown so many times in this project so far, I'm going to tread very carefully. Every CONFIG seems to have unintended consequences that soak up precious hours. So to be honest, unless a compelling reason comes up, or some really good documentation, I'm going to leave sleeping dogs lie! Great to have them to refer back to though.

Related