This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[cc3200] SPI DMA interrupts too often

Other Parts Discussed in Thread: CC3200

I am using GSPI in slave mode, with FIFO, serviced by DMA in ping-pong mode.  I am operating the RX side only.  Data buffers are stored OK, but the ISR is called way too often.  I expect the ISR to be called once upon the completion of each of the DMA jobs (primary and alternate), thus signifying that a new buffer has been transferred.  What I observe is that the interrupt is triggered on every FIFO request to the DMA.  That is, when my DMA transfer length is 1024, and UDMA_ARB_16, I get 64 interrupts before the transfer is actually complete.  Experimenting with different ARB size and corresponding AFL produces similar results: ISR is called every time the FIFO requests service. The interrupt status reported on all of these "false" interrupts is SPI_INT_DMARX.

Granted, I detect this condition in the ISR, like so:
    //
    // Read the interrupt status of the GSPI.
    //
    ulStatus = MAP_SPIIntStatus(GSPI_BASE, true);	// ask for masked interrupts


    if(ulStatus & ( SPI_INT_RX_OVRFLOW))
    {
    	overFlowCounter++;
    }

    //
    // Clear any pending status
    //
    MAP_SPIIntClear(GSPI_BASE, ulStatus);

    //
    // Check the DMA control table to see if the ping-pong "A" transfer is
    // complete.  The "A" transfer uses receive buffer "A", and the primary
    // control structure.
    //
    ulModeP = MAP_uDMAChannelModeGet(MYDMACHANNEL | UDMA_PRI_SELECT);

    //
    // If the primary control structure indicates stop, that means the "A"
    // receive buffer is done.  The uDMA controller should still be receiving
    // data into the "B" buffer.
    //
    if(ulModeP == UDMA_MODE_STOP)
    {
        //
        // Increment a counter to indicate data was received into buffer A.
        //
        g_ulRxBufACount++;

        //
        // Set up the next transfer for the "A" buffer, using the primary
        // control structure.  When the ongoing receive into the "B" buffer is
        // done, the uDMA controller will switch back to this one.
        //

        MAP_uDMAChannelTransferSet( MYDMACHANNEL | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
                (void *)(GSPI_BASE + MCSPI_O_RX0), g_usRxBufA,
                sizeof(g_usRxBufA)/sizeof(uint16_t) );

        if (spiAB == 1) syncErrorA++;
        spiAB = 1;	// buffer A is ready
        postEvent = 1;
    }

    //
    // Check the DMA control table to see if the ping-pong "B" transfer is
    // complete.  The "B" transfer uses receive buffer "B", and the alternate
    // control structure.
    //
    ulModeA = MAP_uDMAChannelModeGet(MYDMACHANNEL | UDMA_ALT_SELECT);

    //
    // If the alternate control structure indicates stop, that means the "B"
    // receive buffer is done.  The uDMA controller should still be receiving
    // data into the "A" buffer.
    //
    if(ulModeA == UDMA_MODE_STOP)
    {
        //
        // Increment a counter to indicate data was received into buffer A.
        //
        g_ulRxBufBCount++;

        //
        // Set up the next transfer for the "B" buffer, using the alternate
        // control structure.  When the ongoing receive into the "A" buffer is
        // done, the uDMA controller will switch back to this one.
        //

        MAP_uDMAChannelTransferSet( MYDMACHANNEL | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
                (void *)(GSPI_BASE + MCSPI_O_RX0), g_usRxBufB,
                sizeof(g_usRxBufB)/sizeof(uint16_t) );

        if (spiAB == 0) syncErrorB++;
        spiAB = 0;	// buffer B is ready
        postEvent = 1;

    }

    if ((ulModeA != UDMA_MODE_STOP) && (ulModeP != UDMA_MODE_STOP))
    	slaveIntFalse++;	// this was a false interrupt

I set up SPI and DMA in the standard fashion:

#define	UDMA_ARB	UDMA_ARB_16		// number of transfers per DMA burst, which are 16 bits, so must be half of AFL
#define AFL	32		// almost full level for RX FIFO.  Must read this many bytes each INT

    //
    // Reset SPI
    //
    MAP_SPIReset(GSPI_BASE);

    //
    // Configure SPI interface
    //
    MAP_SPIConfigSetExpClk(GSPI_BASE,MAP_PRCMPeripheralClockGet(PRCM_GSPI),
                     SPI_IF_BIT_RATE,SPI_MODE_SLAVE,SPI_SUB_MODE_0,
                     (SPI_HW_CTRL_CS |
                     SPI_4PIN_MODE |
                     SPI_TURBO_OFF |
                     SPI_CS_ACTIVELOW |
                     SPI_WL_16));

    /*
     * Enable RX FIFO
     */
    MAP_SPIFIFOEnable(GSPI_BASE,SPI_RX_FIFO);
    MAP_SPIFIFOLevelSet(GSPI_BASE,1,AFL);

	UDMAInit();

	//
	// Activate uDMA for GSPI
	//
	MAP_uDMAChannelAssign(MYDMACHANNEL);

	/*
	 * Make this channel high priority
	 */
	MAP_uDMAChannelAttributeEnable(MYDMACHANNEL, UDMA_ATTR_HIGH_PRIORITY );


    UDMASetupTransfer(MYDMACHANNEL | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
            sizeof(g_usRxBufA)/sizeof(uint16_t), UDMA_SIZE_16, UDMA_ARB,
            (void *)(GSPI_BASE + MCSPI_O_RX0), UDMA_SRC_INC_NONE,
                                            g_usRxBufA, UDMA_DST_INC_16);

    UDMASetupTransfer(MYDMACHANNEL | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
            sizeof(g_usRxBufB)/sizeof(uint16_t),UDMA_SIZE_16, UDMA_ARB,
              (void *)(GSPI_BASE + MCSPI_O_RX0), UDMA_SRC_INC_NONE,
                                            g_usRxBufB, UDMA_DST_INC_16);


    MAP_SPIDmaEnable(GSPI_BASE, SPI_RX_DMA);

    //
    // Enable the GSPI peripheral interrupts. uDMA controller will cause an
    // interrupt on the GSPI interrupt signal when a uDMA transfer is complete.
    //
    if (0 > osi_InterruptRegister(INT_GSPI, SlaveIntHandler, 0x80))
    	Message("SPI Slave setup failed\n\r");

    else {

		//
		// Enable Interrupts
		//
		MAP_SPIIntEnable(GSPI_BASE, SPI_INT_DMARX | SPI_INT_RX_OVRFLOW);

		//
		// Enable SPI for communication
		//
		MAP_SPIEnable(GSPI_BASE);

While I have a work-around, you can see how the interrupts coming every UDMA_ARB_16 transfers seems to defeat the purpose of using the DMA in the first place.

Is there a way to configure the SPI/DMA to interrupt only when DMA jobs complete? 

  • Sorry -- post left without showing the code.....

    Granted, I detect this condition in the ISR, like so:
    
        //
        // Read the interrupt status of the GSPI.
        //
        ulStatus = MAP_SPIIntStatus(GSPI_BASE, true);	// ask for masked interrupts
    
    
        if(ulStatus & ( SPI_INT_RX_OVRFLOW))
        {
        	overFlowCounter++;
        }
    
        //
        // Clear any pending status
        //
        MAP_SPIIntClear(GSPI_BASE, ulStatus);
    
        //
        // Check the DMA control table to see if the ping-pong "A" transfer is
        // complete.  The "A" transfer uses receive buffer "A", and the primary
        // control structure.
        //
        ulModeP = MAP_uDMAChannelModeGet(MYDMACHANNEL | UDMA_PRI_SELECT);
    
        //
        // If the primary control structure indicates stop, that means the "A"
        // receive buffer is done.  The uDMA controller should still be receiving
        // data into the "B" buffer.
        //
        if(ulModeP == UDMA_MODE_STOP)
        {
            //
            // Increment a counter to indicate data was received into buffer A.
            //
            g_ulRxBufACount++;
    
            //
            // Set up the next transfer for the "A" buffer, using the primary
            // control structure.  When the ongoing receive into the "B" buffer is
            // done, the uDMA controller will switch back to this one.
            //
    
            MAP_uDMAChannelTransferSet( MYDMACHANNEL | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
                    (void *)(GSPI_BASE + MCSPI_O_RX0), g_usRxBufA,
                    sizeof(g_usRxBufA)/sizeof(uint16_t) );
    
            if (spiAB == 1) syncErrorA++;
            spiAB = 1;	// buffer A is ready
            postEvent = 1;
        }
    
        //
        // Check the DMA control table to see if the ping-pong "B" transfer is
        // complete.  The "B" transfer uses receive buffer "B", and the alternate
        // control structure.
        //
        ulModeA = MAP_uDMAChannelModeGet(MYDMACHANNEL | UDMA_ALT_SELECT);
    
        //
        // If the alternate control structure indicates stop, that means the "B"
        // receive buffer is done.  The uDMA controller should still be receiving
        // data into the "A" buffer.
        //
        if(ulModeA == UDMA_MODE_STOP)
        {
            //
            // Increment a counter to indicate data was received into buffer A.
            //
            g_ulRxBufBCount++;
    
            //
            // Set up the next transfer for the "B" buffer, using the alternate
            // control structure.  When the ongoing receive into the "A" buffer is
            // done, the uDMA controller will switch back to this one.
            //
    
            MAP_uDMAChannelTransferSet( MYDMACHANNEL | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
                    (void *)(GSPI_BASE + MCSPI_O_RX0), g_usRxBufB,
                    sizeof(g_usRxBufB)/sizeof(uint16_t) );
    
            if (spiAB == 0) syncErrorB++;
            spiAB = 0;	// buffer B is ready
            postEvent = 1;
    
        }
    
        if ((ulModeA != UDMA_MODE_STOP) && (ulModeP != UDMA_MODE_STOP))
        	slaveIntFalse++;	// this was a false interrupt
    
    I set up SPI and DMA in the standard fashion:
    
    #define	UDMA_ARB	UDMA_ARB_16		// number of transfers per DMA burst, which are 16 bits, so must be half of AFL
    #define AFL	32		// almost full level for RX FIFO.  Must read this many bytes each INT
    
        //
        // Reset SPI
        //
        MAP_SPIReset(GSPI_BASE);
    
        //
        // Configure SPI interface
        //
        MAP_SPIConfigSetExpClk(GSPI_BASE,MAP_PRCMPeripheralClockGet(PRCM_GSPI),
                         SPI_IF_BIT_RATE,SPI_MODE_SLAVE,SPI_SUB_MODE_0,
                         (SPI_HW_CTRL_CS |
                         SPI_4PIN_MODE |
                         SPI_TURBO_OFF |
                         SPI_CS_ACTIVELOW |
                         SPI_WL_16));
    
        /*
         * Enable RX FIFO
         */
        MAP_SPIFIFOEnable(GSPI_BASE,SPI_RX_FIFO);
        MAP_SPIFIFOLevelSet(GSPI_BASE,1,AFL);
    
    	UDMAInit();
    
    	//
    	// Activate uDMA for GSPI
    	//
    	MAP_uDMAChannelAssign(MYDMACHANNEL);
    
    	/*
    	 * Make this channel high priority
    	 */
    	MAP_uDMAChannelAttributeEnable(MYDMACHANNEL, UDMA_ATTR_HIGH_PRIORITY );
    
    
        UDMASetupTransfer(MYDMACHANNEL | UDMA_PRI_SELECT, UDMA_MODE_PINGPONG,
                sizeof(g_usRxBufA)/sizeof(uint16_t), UDMA_SIZE_16, UDMA_ARB,
                (void *)(GSPI_BASE + MCSPI_O_RX0), UDMA_SRC_INC_NONE,
                                                g_usRxBufA, UDMA_DST_INC_16);
    
        UDMASetupTransfer(MYDMACHANNEL | UDMA_ALT_SELECT, UDMA_MODE_PINGPONG,
                sizeof(g_usRxBufB)/sizeof(uint16_t),UDMA_SIZE_16, UDMA_ARB,
                  (void *)(GSPI_BASE + MCSPI_O_RX0), UDMA_SRC_INC_NONE,
                                                g_usRxBufB, UDMA_DST_INC_16);
    
    
        MAP_SPIDmaEnable(GSPI_BASE, SPI_RX_DMA);
    
        //
        // Enable the GSPI peripheral interrupts. uDMA controller will cause an
        // interrupt on the GSPI interrupt signal when a uDMA transfer is complete.
        //
        if (0 > osi_InterruptRegister(INT_GSPI, SlaveIntHandler, 0x80))
        	Message("SPI Slave setup failed\n\r");
    
        else {
    
    		//
    		// Enable Interrupts
    		//
    		MAP_SPIIntEnable(GSPI_BASE, SPI_INT_DMARX | SPI_INT_RX_OVRFLOW);
    
    		//
    		// Enable SPI for communication
    		//
    		MAP_SPIEnable(GSPI_BASE);
    

  • Hi Kemeron,


    We will look into this and get back to you.


    Regards,
    Aashish
  • Hi Kemeron,


    Please try once by setting ARB and AFL same nymber of bits means

    #define UDMA_ARB UDMA_ARB_32
    #define AFL 32


    Regards,
    Aashish
  • Thank you for your suggestion.  I tried your suggestion, and reconfirmed what I had found earlier:

    1. increasing (doubling) UDMA_ARB_16 to UDMA_ARB_32 reduces the number of spurious interrupts by a factor of two.  (I only get 32 interrupts per 1024-unit buffer, instead of 64).

    2. Data buffer is corrupted by repeating data -- obviously what is happening is that the DMA is attempting to move data after FIFO empties.  (Roughly 16 correct data -- 16-bit -- followed by roughly 16 repeats of the last datum.)

    When UDMA_ARB is set to half AFL (because my word length is 16 bits), then data always comes through flawlessly, but with 1024 / UDMA_ARB interrupts per DMA job.

    I have attached a simplified master SPI/DMA project that reveals the problem I'm describing.  Apparently, it also shows up for TX and master mode!  When you run this, the console prints out that 10 jobs have been completed (for which I expect 20 interrupts: 10 each for TX and RX), but that 2562 interrupts were counted!  This works out to one interrupt for each FIFO call, both TX and RX.  (AFL = 16, and UDMA_ARB_8, transfer size = 1024, for both RX and TX).  If a wire is jumpered between MOSI and MISO, RX buffers reliably receive TX content.

    kind regards,

    kemeron


    spi_demo.zip

  • Hi Kemeron,


    We can't find DMA configuration in attached project. Attached project is same as example that come with SDK. Can you please verify whether you attached attached right project or not?


    Regards,
    Aashish
  • So sorry!  Yes, wrong project.  Here is right one:

    spi_udma.zip

  • Hi Kemeron,


    We are able to reproduce the issue using your code. But we need some time to find root cause and solution.


    Regards,
    Aashish
  • Hi, Aashish,


    Any progress with this issue?

    Regards,

    kemeron

  • Hi, Aashish,

    We are getting close to making a product decision, but this issue is turning out to be a show stopper.  Do you have an idea whether a solution can be found short of silicon rev? Do you expect you might have a solution within a week or two, or is this a deeper problem?

    best regards,

    kemeron

  • Hi Kemeron,

    Apologies for delayed response.

    The behavior that you see here is due to multiple DMA channel assignment of GSPI DMA request line(s).

    For example GSPI TX can be mapped to

    1. Channel 31  - Mode 0 (Default)
    2. Channel 07  - Mode 1

    And now, if we configure to use channel 07 and leave Channel 31 to default, both the channels get the DMA request simultaneously. But since CH 31 is not active it will generate the interrupt immediately.

    There are two possible solutions

    1. Use CH 31
    2. Move CH 31 to some other mapping, other than mode 0

    Pls try either of the above solutions and let me know if that resolves your problem

    Thanks and Regards

    Praveen

  • Praveen,

    Many thanks -- you got it right on. Any of the permutations you suggest work right. So, if I want higher priority DMA on GSPI, I use channels 6/7, but I need to { MAP_uDMAChannelAssign(UDMA_CH30_SW);
    MAP_uDMAChannelAssign(UDMA_CH31_SW); } to tell channels 30/31 not to listen to GSPI.

    This removes the road block, and I think our product plans will proceed with cc3200.

    Kemeron