Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

I2S issue - last sample in memory always replaced with 0x0001

I'm using the nRF52 DK and attempting to use I2S to send data to a DAC. I'm using the nRF5 SDK, version 17.0.2. The last sample in the data I'm sending is always replaced with 0x0001. I've set up a simple example where the audio data I'm sending is only 4 16-bit samples, but I've seen the same thing in all my tests.

Here's the data I'm trying to send:

static int16_t audio_data[4] = {
    0,
    10,
    0,
    -10
};

And here's my I2S initalization:

// Enable transmission
NRF_I2S->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_ENABLE << I2S_CONFIG_TXEN_TXEN_Pos;

// Enable MCK generator, set MCK to 1 MHz
NRF_I2S->CONFIG.MCKEN = I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos;
NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV32  << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos;

// Set MCK/LRCK ratio to 64
NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_64X << I2S_CONFIG_RATIO_RATIO_Pos;

// Master mode, 16-bit, left-aligned
NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos;
NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16BIT << I2S_CONFIG_SWIDTH_SWIDTH_Pos;
NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_LEFT << I2S_CONFIG_ALIGN_ALIGN_Pos;

// I2S format
NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S << I2S_CONFIG_FORMAT_FORMAT_Pos;

// Left channel only
//NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_LEFT << I2S_CONFIG_CHANNELS_CHANNELS_Pos;
NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_STEREO << I2S_CONFIG_CHANNELS_CHANNELS_Pos;

// Configure pins
NRF_I2S->PSEL.MCK = PIN_MCK << I2S_PSEL_MCK_PIN_Pos;
NRF_I2S->PSEL.SCK = PIN_SCK << I2S_PSEL_SCK_PIN_Pos;
NRF_I2S->PSEL.LRCK = PIN_LRCK << I2S_PSEL_LRCK_PIN_Pos;
NRF_I2S->PSEL.SDOUT = PIN_SDOUT << I2S_PSEL_SDOUT_PIN_Pos;

NRF_I2S->ENABLE = 1;

NRF_I2S->TXD.PTR = (uint32_t)&audio_data[0];
NRF_I2S->RXTXD.MAXCNT = sizeof(audio_data) / sizeof(uint32_t);
NRF_LOG_INFO("TXDPTR: %ul", NRF_I2S->TXD.PTR);

NRF_I2S->TASKS_START = 1;

When I run the code and look at the location of NRF_I2S->TXD.PTR in memory, I see the 4 samples as expected (0x0000, 0x000A, 0x0000, 0xF6FF):

But when the data is actually being transmitted, the last sample (-10 or 0xF6FF in this case) is always replaced with 1 (see logic analyzer capture).

I see the same thing when I used different sized arrays for the audio data, as well as when I use a single channel instead of stereo. Some help would be much appreciated.

  • Small correction, anytime I mentioned the -10 value in hex it should say 0xFFF6, not 0xF6FF. I don't see an option to edit the original description.

  • Hi,

     I believe what you are seeing here might be a limitation in hardware. To avoid this, try doing zero-padding on your data. In the I2S example in the nRF5-SDK, we have e.g. this "m_zero_samples_to_ignore" variable, and this comment:

            // Normally a couple of initial samples sent by the I2S peripheral
            // will have zero values, because it starts to output the clock
            // before the actual data is fetched by EasyDMA. As we are dealing
            // with streaming the initial zero samples can be simply ignored.

    PS:  I would recommend using our drivers in the SDK. Have a look at the I2S example in the nRF5-SDK on how to use it.

  • Only the first left and right samples are transmitted, as only (4*2)/4=2 32-bit transactions are called for; maybe this would help:

    // Stereo 16-bit consumed 32-bits per transaction, 2 transactions, align on 32-bit boundary
    static int16_t audio_data[] = {
        0,      // Left  0
       10,      // Right 0
        0,      // Left  1
      -10       // Right 1
    };
    #define TRANSACTION_COUNT (sizeof(audio_data) / sizeof(audio_data[0]))
    STATIC_ASSERT ( TRANSACTION_COUNT == 2, "TRANSACTION_COUNT differs from 2");
    
    // Stereo 16-bit consumed 32-bits per transaction, 2 transactions, align on 32-bit boundary
    static int32_t audio_data[2] = {
        (int32_t) ((uint16_t)  10 << 16)  /* Right 0 */ || (int32_t) 0  /* Left 0 */,
        (int32_t) ((uint16_t) -10 << 16)  /* Right 1 */ || (int32_t) 0  /* Left 1 */,
    };
    #define TRANSACTION_COUNT (sizeof(audio_data) / sizeof(audio_data[0]))
    STATIC_ASSERT ( TRANSACTION_COUNT == 2, "TRANSACTION_COUNT differs from 2");
    
    // Left Mono 16-bit consumed 32-bits per transaction, 2 transactions, align on 32-bit boundary
    static int16_t audio_data[4] = {
        0,      // Left  0
       10,      // Left  1
        0,      // Left  2
      -10       // Left  3
    };
    #define TRANSACTION_COUNT (sizeof(audio_data) / sizeof(audio_data[0]))
    STATIC_ASSERT ( TRANSACTION_COUNT == 2, "TRANSACTION_COUNT differs from 2");
    
    // Left Mono 16-bit consumed 32-bits per transaction, 2 transactions, align on 32-bit boundary
    static int32_t audio_data[2] = {
        (int32_t) ((uint16_t)  10 << 16)  /* Left 1 */ || (int32_t) 0  /* Left 0 */,
        (int32_t) ((uint16_t) -10 << 16)  /* Left 3 */ || (int32_t) 0  /* Left 2 */,
    };
    #define TRANSACTION_COUNT (sizeof(audio_data) / sizeof(audio_data[0]))
    STATIC_ASSERT ( TRANSACTION_COUNT == 2, "TRANSACTION_COUNT differs from 2");
    
      // Number of 32-bit transactions from any of above definitions
      NRF_I2S->TXD.PTR = (uint32_t)audio_data;
      NRF_I2S->RXTXD.MAXCNT = sizeof(audio_data) / sizeof(audio_data[0]);
    

    Perhaps try sending many more 32-bit samples and see if only the last sample differs from expectation

  • Thanks for the response, but I'm not sure the issue is related to the transaction length. I've done several tests with more samples, it's always the last 16-bit sample that's swapped with 1. The number of transactions has always been what I expect (the number of samples / 2).

    Even in this example, the first 3 samples are transmitted and the last sample is replaced with 1. I am seeing 2 transactions, the first transaction transmits 0 and 10 (as expected) and the second transaction transmits 0 (expected) and 1 (instead of -10), and then it repeats. You can see this in the waveform image. I see the same thing in every test I've done, the last sample is always replaced with 1.

    For example, with the following data:

    static int16_t audio_data[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100};

    I see 5 transactions per cycle, and the transmitted samples in stereo mode are:

    L: 10; R: 20
    L: 30; R: 40
    L: 50; R: 60
    L: 70; R: 80
    L: 90; R: 1 (should be 100)
    (cycle repeats)
    L: 10; R: 20
    ...

    I see the same problem when using mono with only the left channel, the last sample is still replaced with 1:

    L: 10; R: 10
    L: 20; R: 20
    L: 30; R: 30
    L: 40; R: 40
    L: 50; R: 50
    L: 60; R: 60
    L: 70; R: 70
    L: 80; R: 80
    L: 90; R: 90
    L: 1; R: 1
    (cycle repeats)
    L: 10; R: 10
    ...

  • Thanks for the response.

    What I'm doing here is transmitting this data on a loop, it's meant to be a tone that's repeated, not just a one-shot transfer. The I2S module does this automatically (i.e. after the last sample it cycles back to the first one). So it's meant to send a periodic signal as follows:

    0, 10, 0, -10, 0, 10, 0, -10, 0, 10, 0, -10, ...

    And what I'm actually seeing is:

    0, 10, 0, 1, 0, 10, 0, 1, 0, 10, 0, 1, ...

    The actual tone I'm sending has more than 4 samples, but I'm seeing the same thing, the last sample is always replaced with 1.

    I'm not clear on how zero padding would solve this issue. I wouldn't want extra zeros in the actual data, and having padding between periods of the waveform would mess with the timing given that this is sound data used to drive an audio DAC.

Related