This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

I2S->EVENT_TXPTRUPD cannot be cleared in the nRF52840

Hello Nordic Semiconductor

I am developing a small application on the nRF52840 development kit where I need to transfer I2S signals in/out of the MCU. I have set up an I2S_IRQHandler() that gets triggered whenever EVENT_TXPTRUPD gets set. However, I cannot clear it again, which I am supposed to do in the interrupt handler. Basically, this turns my interrupt handler into an endless loop, never returning to the main loop after the event.

I believe this is a bug. I found this thread as well, which I believe is the same problem:

https://devzone.nordicsemi.com/f/nordic-q-a/65853/nrf5340-i2s-events_txptrupd-and-events_rxptrupd-cannot-be-cleared

So, I created a small project that can reproduce the problem. I used Ozone - The J-Link Debugger to debug the problem. However, I could not find the EVENTS_TXPTRUPD register inside the register viewer, so I watched a variable "tmp" instead.

I have included a source file that can reproduce the problem below, as well as some screenshots, where I go through a few breakpoints. Watch the variable "tmp" in the "Global Data" window in the upper right corner.

My SDK and toolchain versions are nRF5_SDK_17.1.0_ddde560 and gcc-arm-none-eabi-9-2020-q2-update respectively.

I am not sure which revision of the nRF52840 DK I am using, but there is a sticker saying "PCA10056, 2.0.2, 2021.50, 683128720". Please let me know if you need further information.

Thank you in advance :-)

#include "boards.h"

// Temporary variable to store register value
volatile uint32_t tmp;

// RX and TX buffers for I2S
volatile int16_t i2s_rx_buf[128];
volatile int16_t i2s_tx_buf[128];

int main(void)
{
    tmp = 0;

    // Set I2S in master mode and the clocks to ~ 48 kHz sample rate
    NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_Master;
    NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV21;
    NRF_I2S->CONFIG.RATIO = I2S_CONFIG_RATIO_RATIO_32X;

    
    // Set the format to 16-bit stereo
    NRF_I2S->CONFIG.CHANNELS = I2S_CONFIG_CHANNELS_CHANNELS_Stereo;
    NRF_I2S->CONFIG.FORMAT = I2S_CONFIG_FORMAT_FORMAT_I2S;
    NRF_I2S->CONFIG.ALIGN = I2S_CONFIG_ALIGN_ALIGN_Left;
    NRF_I2S->CONFIG.SWIDTH = I2S_CONFIG_SWIDTH_SWIDTH_16Bit;
    
    // Buffer setup
    NRF_I2S->RXTXD.MAXCNT = 64;
    NRF_I2S->RXD.PTR = (uint32_t)i2s_rx_buf;
    NRF_I2S->TXD.PTR = (uint32_t)i2s_tx_buf;
    
    // Pin setup
    NRF_I2S->PSEL.MCK = (1<<5) | 1;     // Connect MCK   to P1.01
    NRF_I2S->PSEL.SCK = (1<<5) | 2;     // Connect SCK   to P1.02
    NRF_I2S->PSEL.LRCK = (1<<5) | 3;    // Connect LRCK  to P1.03
    NRF_I2S->PSEL.SDIN = (1<<5) | 4;    // Connect SDIN  to P1.04
    NRF_I2S->PSEL.SDOUT = (1<<5) | 5;   // Connect SDOUT to P1.05
    
    // Configure interrupts
    NRF_I2S->INTEN = I2S_INTEN_TXPTRUPD_Enabled;
    NVIC_EnableIRQ(I2S_IRQn);
    
    // Enable and start
    NRF_I2S->CONFIG.RXEN = I2S_CONFIG_RXEN_RXEN_Enabled;
    NRF_I2S->CONFIG.TXEN = I2S_CONFIG_TXEN_TXEN_Enabled;
    NRF_I2S->CONFIG.MCKEN = I2S_CONFIG_MCKEN_MCKEN_Enabled;
    NRF_I2S->ENABLE = 1;
    NRF_I2S->TASKS_START = 1;
    
    // Main loop
    while (1);
}


void I2S_IRQHandler()
{
    // Clear the event (but store the value before and after for debugging)
    tmp = NRF_I2S->EVENTS_TXPTRUPD;     // Set breakpoint here
    NRF_I2S->EVENTS_TXPTRUPD = 0;
    tmp = NRF_I2S->EVENTS_TXPTRUPD;     // Set another break point here
    tmp;                                // and a third breakpoint here
}

Parents
  • Thank you, Håkon :-)

    The main culprit was the incorrect setting of INTEN. I have now done as you suggested:

    NRF_I2S->INTENSET = I2S_INTEN_TXPTRUPD_Enabled << I2S_INTEN_TXPTRUPD_Pos;

    It solved the problem, and the TXPTRUPD event now occurs with correct timing, synchronized to the actual data transfer. Stupid mistake, but thank you very much for pointing this out!

    I have implemented the errata workarounds inside an i2s_stop() function like this:

    void i2s_stop()
    {
        // Erratta 55 workaround (part 1)
        volatile uint32_t tmp = NRF_I2S->INTEN;
        NRF_I2S->INTEN = 0;
        
        NRF_I2S->TASKS_STOP = 1;
        
        // Errata 194 workaround
        *((volatile uint32_t *)0x40025038) = 1;
        *((volatile uint32_t *)0x4002503C) = 1;
        while (NRF_I2S->EVENTS_STOPPED == 0);
        NRF_I2S->EVENTS_STOPPED = 0;
        (void)NRF_I2S->EVENTS_STOPPED;
        
        // Errata 55 workaround (part 2)
        NRF_I2S->EVENTS_RXPTRUPD = 0;
        NRF_I2S->EVENTS_TXPTRUPD = 0;
        NRF_I2S->EVENTS_STOPPED = 0;
        NRF_I2S->INTEN = tmp;
    }

    However, I noticed that an event seems to be generated immediately whenever I2S is started as well, before any data transmission have actually occurred. Therefore, I have made an i2s_start() function as well:

    void i2s_start()
    {
        // Prevent EVENTS_RXPTRUPD and EVENTS_TXPTRUPD from occuring when starting
        // I2S transmission: INTEN is saved, set to zero, and then restored after
        // I2S is started.
        volatile uint32_t tmp = NRF_I2S->INTEN;
        NRF_I2S->INTEN = 0;
        
        NRF_I2S->TASKS_START = 1;
        
        NRF_I2S->EVENTS_RXPTRUPD = 0;
        NRF_I2S->EVENTS_TXPTRUPD = 0;
        NRF_I2S->EVENTS_STOPPED = 0;
        
        NRF_I2S->INTEN = tmp;
    }

    My I2S_IRQHandler() now looks like this (I take care of the two other events and I removed the while-loop)

    void I2S_IRQHandler()
    {
        if(NRF_I2S->EVENTS_TXPTRUPD != 0)
        {
            NRF_I2S->EVENTS_TXPTRUPD = 0;
            (void)NRF_I2S->EVENTS_TXPTRUPD;
        }
        
        if(NRF_I2S->EVENTS_RXPTRUPD != 0)
        {
            NRF_I2S->EVENTS_RXPTRUPD = 0;
            (void)NRF_I2S->EVENTS_RXPTRUPD;
        }
        
        if(NRF_I2S->EVENTS_STOPPED != 0)
        {
            NRF_I2S->EVENTS_STOPPED = 0;
            (void)NRF_I2S->EVENTS_STOPPED;
        }
        
        // For easier debugging with scope, stop I2S after a number of interrupts
        count++;
        if (count > 1)
            i2s_stop();
        
        // Transmit one byte through the UART
        uart_transmit(buf, 1);
    }

    This time, I stop the I2S transmission after just two events. A scope screenshot reveals that it now finally works!

    I2S finally working

    If you have comments or further suggestions for my i2s_start() and i2s_stop() functions, please let me know. Otherwise, thank you very much, Håkon :-)

Reply
  • Thank you, Håkon :-)

    The main culprit was the incorrect setting of INTEN. I have now done as you suggested:

    NRF_I2S->INTENSET = I2S_INTEN_TXPTRUPD_Enabled << I2S_INTEN_TXPTRUPD_Pos;

    It solved the problem, and the TXPTRUPD event now occurs with correct timing, synchronized to the actual data transfer. Stupid mistake, but thank you very much for pointing this out!

    I have implemented the errata workarounds inside an i2s_stop() function like this:

    void i2s_stop()
    {
        // Erratta 55 workaround (part 1)
        volatile uint32_t tmp = NRF_I2S->INTEN;
        NRF_I2S->INTEN = 0;
        
        NRF_I2S->TASKS_STOP = 1;
        
        // Errata 194 workaround
        *((volatile uint32_t *)0x40025038) = 1;
        *((volatile uint32_t *)0x4002503C) = 1;
        while (NRF_I2S->EVENTS_STOPPED == 0);
        NRF_I2S->EVENTS_STOPPED = 0;
        (void)NRF_I2S->EVENTS_STOPPED;
        
        // Errata 55 workaround (part 2)
        NRF_I2S->EVENTS_RXPTRUPD = 0;
        NRF_I2S->EVENTS_TXPTRUPD = 0;
        NRF_I2S->EVENTS_STOPPED = 0;
        NRF_I2S->INTEN = tmp;
    }

    However, I noticed that an event seems to be generated immediately whenever I2S is started as well, before any data transmission have actually occurred. Therefore, I have made an i2s_start() function as well:

    void i2s_start()
    {
        // Prevent EVENTS_RXPTRUPD and EVENTS_TXPTRUPD from occuring when starting
        // I2S transmission: INTEN is saved, set to zero, and then restored after
        // I2S is started.
        volatile uint32_t tmp = NRF_I2S->INTEN;
        NRF_I2S->INTEN = 0;
        
        NRF_I2S->TASKS_START = 1;
        
        NRF_I2S->EVENTS_RXPTRUPD = 0;
        NRF_I2S->EVENTS_TXPTRUPD = 0;
        NRF_I2S->EVENTS_STOPPED = 0;
        
        NRF_I2S->INTEN = tmp;
    }

    My I2S_IRQHandler() now looks like this (I take care of the two other events and I removed the while-loop)

    void I2S_IRQHandler()
    {
        if(NRF_I2S->EVENTS_TXPTRUPD != 0)
        {
            NRF_I2S->EVENTS_TXPTRUPD = 0;
            (void)NRF_I2S->EVENTS_TXPTRUPD;
        }
        
        if(NRF_I2S->EVENTS_RXPTRUPD != 0)
        {
            NRF_I2S->EVENTS_RXPTRUPD = 0;
            (void)NRF_I2S->EVENTS_RXPTRUPD;
        }
        
        if(NRF_I2S->EVENTS_STOPPED != 0)
        {
            NRF_I2S->EVENTS_STOPPED = 0;
            (void)NRF_I2S->EVENTS_STOPPED;
        }
        
        // For easier debugging with scope, stop I2S after a number of interrupts
        count++;
        if (count > 1)
            i2s_stop();
        
        // Transmit one byte through the UART
        uart_transmit(buf, 1);
    }

    This time, I stop the I2S transmission after just two events. A scope screenshot reveals that it now finally works!

    I2S finally working

    If you have comments or further suggestions for my i2s_start() and i2s_stop() functions, please let me know. Otherwise, thank you very much, Håkon :-)

Children
No Data
Related