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.

TM4C1294XL SSI DMA PingPong DMA_TX complete interrupt called endless

Other Parts Discussed in Thread: TLC5971, ENERGIA, TM4C1294NCPDT

hi, i have a problem with the TM4C1294 microcontroller when using DMA with the SSI peripheral and interrupts.

For a LED driver project (using many TLC5971) i need a constant stream of data (overall ~20k Bytes of data, every 30ms), so I decided to use the SSI with the DMA in pingpong mode, because i need more then 1024 elements to be transfered to the SSI and I need nearly zero gap (max. 8 SSI clock cycles gap).
After the first transfer of the 1024 elements of the PRI channel I want to set the next data to be sent for the PRI channel (while the ALT channel is transferring the data to the SSI) in the SSI DMA TX complete interrupt handler.

My problem is now with the SSI interrupt handler, which is called after the first 1024 Bytes from the PRI channel. I cannot clear the interrupt flag correctly, therefore the ISR is called in an endless loop.

I enabled the SSI_DMATX interrupt (SSIIntEnable(SSI0_BASE,SSI_DMATX)) to be able to set the next data bytes to be sent to the SSI for the ALT or PRI channel of the DMA controller.

At the beginning of the SSI interrupt handler i clear the interrupt flag (SSIIntClear(SSI0_BASE,SSIIntStatus(SSI0_BASE, true)), i also tested it with SSIIntClear(SSI0_BASE,SSI_DMATX)) but i verified that it is not being cleared.

If i set the interrupt only to SSI_TXFF and/or SSI_TXEOT it works as expected, but I think SSI_DMATX is better to use for getting nearly zero gap.

I tested more hints for clearing the interrupt flag to prevent the endless loop:
In the ISR of the SSI I did the following (which I think was suggested in the datasheet of the uC at page 1240):
HWREG(SSI0_BASE + 0x0024)&=~SSI_DMACTL_TXDMAE;//clear txdmae in the ssidmactl
HWREG(SSI0_BASE + 0x20)|=(1<<5);//setting DMATXIC in SSIICR (clears interrupt)
This also didn't work. I also tested this:
SSIDMADisable(SSI0_BASE,SSI_DMA_TX);
SSIIntClear(SSI0_BASE,SSIIntStatus(SSI0_BASE, true));
SSIDMAEnable(SSI0_BASE,SSI_DMA_TX);
But after enabling the DMA in the SSI peripheral the interrupt flag is set, so I got also no luck.

 

Here is my initialization code snippet:

SysCtlGPIOAHBEnable(SYSCTL_PERIPH_GPIOA);
SysCtlGPIOAHBEnable(SYSCTL_PERIPH_SSI0);
SysCtlGPIOAHBEnable(SYSCTL_PERIPH_UDMA);
SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);

//Enable uDMA
   uDMAEnable();

   //Set uDMA control table
   uDMAControlBaseSet(pui8ControlTable);

   SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
   SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);  
 
    GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    GPIOPinConfigure(GPIO_PA4_SSI0XDAT0);///TX
    GPIOPinConfigure(GPIO_PA5_SSI0XDAT1);//RX

    GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 |
                   GPIO_PIN_5);

    SSIClockSourceSet(SSI0_BASE,SSI_CLOCK_SYSTEM);

   SSIDisable(SSI0_BASE);

SSIConfigSetExpClk(SSI0_BASE, 120000000, SSI_FRF_MOTO_MODE_0,
           SSI_MODE_MASTER, 10000000, 8);

SSIEnable(SSI0_BASE);

   SSIDMAEnable(SSI0_BASE, SSI_DMA_TX);

uDMAChannelAttributeDisable(UDMA_CH11_SSI0TX,
        UDMA_ATTR_USEBURST |
        UDMA_ATTR_REQMASK);

   uDMAChannelAttributeEnable(UDMA_CH11_SSI0TX, UDMA_ATTR_HIGH_PRIORITY);
 
  uDMAChannelAssign(UDMA_CH11_SSI0TX);

   uDMAChannelControlSet(UDMA_CH11_SSI0TX | UDMA_PRI_SELECT,
        UDMA_SIZE_8   |
        UDMA_SRC_INC_8   |
        UDMA_DST_INC_NONE |
        UDMA_ARB_4);

   uDMAChannelTransferSet(UDMA_CH11_SSI0TX | UDMA_PRI_SELECT,
                           UDMA_MODE_PINGPONG,
        (void *) &data_ssi0[data_ssi0_actual_frame][0],
        (void *)(SSI0_BASE + SSI_O_DR),
        1024);

        
  uDMAChannelControlSet(UDMA_CH11_SSI0TX | UDMA_ALT_SELECT,
        UDMA_SIZE_8   |
        UDMA_SRC_INC_8   |
        UDMA_DST_INC_NONE |
        UDMA_ARB_4);

   uDMAChannelTransferSet(UDMA_CH11_SSI0TX | UDMA_ALT_SELECT,
                           UDMA_MODE_PINGPONG,
        (void *) &data_ssi0[data_ssi0_actual_frame][1024],
        (void *)(SSI0_BASE + SSI_O_DR),
        1024);

IntEnable(INT_SSI0);

SSIIntEnable(SSI0_BASE,SSI_DMATX);

 uDMAChannelAssign(UDMA_CH11_SSI0TX);
 uDMAChannelEnable(UDMA_CH11_SSI0TX);

 

Here is my interrupt handler code snippet:

SSIIntClear(SSI0_BASE,SSIIntStatus(SSI0_BASE, true));

if(uDMAChannelModeGet(UDMA_CH11_SSI0TX | UDMA_PRI_SELECT) == UDMA_MODE_STOP) //set next data to be sent to the SSI
 {  
uDMAChannelTransferSet(UDMA_CH11_SSI0TX | UDMA_PRI_SELECT,
              UDMA_MODE_PINGPONG,
             (void *) &data_ssi0[data_ssi0_actual_frame][data_ssi0_dma_counter],
             (void *)(SSI0_BASE + SSI_O_DR),
              1024);

}

if(uDMAChannelModeGet(UDMA_CH11_SSI0TX | UDMA_ALT_SELECT) == UDMA_MODE_STOP)
 {
uDMAChannelTransferSet(UDMA_CH11_SSI0TX | UDMA_ALT_SELECT,
              UDMA_MODE_PINGPONG,
             (void *) &data_ssi0[data_ssi0_actual_frame][data_ssi0_dma_counter],
             (void *)(SSI0_BASE + SSI_O_DR),
              1024);
}

PS:

The uDMA table is aligned.

I'm using the connected launchpad TM4C1294XL with 120MHZ CPU clock (from PLL).

I use Keil MDK ARM V5 for compiling and debugging.

Any suggestions are welcome.

 

  • Do you have any way of knowing if the interrupt your handler is even being executed? i had a similar problem because i use Energia in wich i always have to register the interrupts handlers

    Try using 

    void SSIIntRegister( uint32_t ui32Base, void (*pfnHandler)(void));

    Also, i have a similar problem with the DMA transfer counter limited to 1024, but i found sometigh. I have yet to implement it, i'm thinking of trying it this week, but it should work. In basic mode you can ignore the counter. My idea is to do that and use a Timer to count and with a match interrupt, disable the DMA chanel, probably will have to be in software call.  You can see that basic suports that in the datasheet of tm4c1294ncpdt:

    BASIC mode can be programmed to ignore when XFERSIZE reaches 0x000 and continue copying
    on request until the channel is stopped manually. If the NXTUSEBURST bit in the uDMA Channel
    Control Word (DMACHCTL) register is set while in BASIC mode and the XFERSIZE reaches 0x000
    and is not written back, transfers continue until the request is deasserted by the peripheral.

  • Thank you for your suggestions.

    Luis Afonso said:
    Do you have any way of knowing if the interrupt your handler is even being executed? i had a similar problem because i use Energia in wich i always have to register the interrupts handlers

    I register the interrupt handler at the beginning of the uC startup. In the example projects for the Keil MDK-ARM IDE there is a startup file, which defines the interrupt handlers. This file is called startup_rvmdk.s and i set the appropriate line to: DCD     SSI0_ISR           ; SSI0 Rx and Tx. Moreover I defined the interrupt handler to be in another file (SSI0_ISR is the interrupt handler name): EXTERN SSI0_ISR. I verified that this settings are right.

    I also tried setting the interrupt handler using the library function, but there was no difference if i set it using the startup file or registering it later with the function.

     

    Luis Afonso said:
    My idea is to do that and use a Timer to count and with a match interrupt, disable the DMA chanel, probably will have to be in software call.

    This looks a nice idea, but i need to stop exactly after a defined number of bytes. I'm not sure if this timing using the timers is accurate enough, because I run the SSI at full speed (=10MHz).

    If your method works well it will be very nice if you post some hints and/or code (if you implemented it).

     

  • My method will need to work with a windows of 600nS. I belive the DMA can channel data to it's own registers. If that is possible then you can use a timer match trigger to send change the DMA register to stop. I will try it and come back here.

    Also, since it's probably just the DMA done that probably interrupts, try to just clear that flag, without the SSIIntStatus(SSI0_BASE, true), how do you know this is returning the right value? Also could you check, with turning on a GPIO pin inside the interrupt handler, if the interrupt handler is actualy being called?

    Another thing, i always see using a variable to check the interrupt status, istead of direcly clearing it, (this is for a GPIO interruot), maybe also try it?

    uint32_t ui32IntStatus;

    ui32IntStatus = GPIOIntStatus(GPIO_PORTF_BASE,true);

  • Luis Afonso said:
    Also, since it's probably just the DMA done that probably interrupts, try to just clear that flag, without the SSIIntStatus(SSI0_BASE, true)

    I did this also, I cleared 0x20, which is the SSI_DMATX (DMA TX complete) interrupt. (I also try it with checking if exactly that interrupt is being set and also simply with clearing exactly this interrupt)

    Luis Afonso said:
    how do you know this is returning the right value?

    I printed out debug messages in the interrupt handler via UART0, which I connect to using a terminal. I verified that it's the 0x20 interrupt which is always returning (also after clearing with SSIIntClear(SSI0_BASE,SSIIntStatus(SSI0_BASE, true)), I tested also with SSIIntStatus(SSI0_BASE, false), but no luck).

    Luis Afonso said:
    Also could you check, with turning on a GPIO pin inside the interrupt handler, if the interrupt handler is actualy being called?

    I printed out debug messages using UART, so I can tell you that it is exactly the SSI_DMATX complete interrupt (always, also after clearing it).

    Luis Afonso said:
    Another thing, i always see using a variable to check the interrupt status, istead of direcly clearing it, (this is for a GPIO interruot), maybe also try it?

    I also did it this way but that changed nothing (status = SSIIntStatus(SSI0_BASE, true);
     SSIIntClear(SSI0_BASE, status);//...and so on). I printed out debug messages and verified that this does not change anything.

    I also tried clearing the interrupt at the top of the ISR and at the bottom, because of the ARM cache or something (stall cycles, etc.) it can happen that the command for clearing the interrupt is executed after returning or jumping to the function again. Since I have several commands in my ISR and I try clearing at first in the function I believe that it has nothing to do with the architecture.

    I think it has more to do with the DMA controller, which prevents me of clearing the interrupt. For my understanding the uDMA controller asserts the interrupt at the SSI interrupt line, so it can be that some thing needs to be first disabled to clear the interrupt. But I also try the suggestion of the microcontroller on pgae 1240, with no luck. I try disabling the DMA line to the SSI and then clearing it and then enabling the DMA line again, but the interrupt is set after that (after disabling the DMA line to the SSI the interrupt is unset).

    I believe it is something real strange behaviour with some settings and/or some sequence of commands.

  • There's sometigh i have in a code provided by Amit, before any DMA setup:

      SysCtlPeripheralDisable(SYSCTL_PERIPH_UDMA);
      SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA);
      SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    
      SysCtlDelay(10);

    Since i don't see any uDMAChannelAttributeEnable to set it to burst, it maybe has sometigh to do that the DMA trigger is always called when the TX fifo is called.

    Could you provide the full code to do some testing to see if i find the problem?

  • Christian,

    You are not the only one that is having this issue. I cant clear the SSI_DMATX interrupt bit.

    I have been struggling with the SAME problem for the past week and no success!!! 

    Khaled.

  • I noticed that if you enable the channel within your ISR after setting the "transfer channel", the TX/RX DMA interrupts function properly. A bit different than what you are doing but might be a hint to a solution. 

    void
    SSI0IntHandler(void)
    {
    	uint32_t ui32Status;
    	uint32_t ui32Mode;
    
    	ui32Status = ROM_SSIIntStatus(SSI0_BASE, 1);
    
    	ROM_SSIIntClear(SSI0_BASE, ui32Status);
    
    	ui32Mode = ROM_uDMAChannelModeGet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT);
    
    	if(ui32Mode == UDMA_MODE_STOP)
    	{
    		g_ui32SSIRxCount++;
    
    		ROM_uDMAChannelTransferSet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT,
    								   UDMA_MODE_BASIC,
    								   (void *)(SSI0_BASE + SSI_O_DR),
    								   g_ui8SSIRxBuf, sizeof(g_ui8SSIRxBuf));
    
    		ROM_uDMAChannelEnable(UDMA_CHANNEL_SSI0RX);
    	}
    
    	if(!ROM_uDMAChannelIsEnabled(UDMA_CHANNEL_SSI0TX))
    	{
    		g_ui32SSITxCount++;
    
    		ROM_uDMAChannelTransferSet(UDMA_CHANNEL_SSI0TX | UDMA_PRI_SELECT,
    								   UDMA_MODE_BASIC, g_ui8SSITxBuf,
    								   (void *)(SSI0_BASE + SSI_O_DR),
    								   sizeof(g_ui8SSITxBuf));
    
    		ROM_uDMAChannelEnable(UDMA_CHANNEL_SSI0TX);
    	}
    }

  • Hi,Calidaman

    You are using basic mode and not ping pong mode.

    The DMA after it's done and generates the DMA done interrupt just stops. You always need to setup the transfer and re-enable the chanel.

    Only in ping-pong mode that is not needed

  • Christian, could you try to clear the interrupt like this:

    SSIIntClear(SSI0_BASE,SSI_DMATX);

    Also, i'm not sure it makes a diference, i haven't checked but, i normaly see the uDMAChannelAssign(UDMA_CH11_SSI0TX);  before setting up the transfer and such.

    Are you sure you aren't getting a Fault ISR?

  • I have the same problem; I cannot clear the interrupt.

    In my case, I'm using SSI Rx (therefore SSIDMAEnable(SSI0_BASE, SSI_DMA_RX);, etc.

    Has this problem been resolved?

    To give you some more background, my SSI DMA Rx code worked perfectly on the LM3S9D96. When I ported it (and made some adjustments) to the TM4C1294, I started getting problems. I've been on this problem full time for over a week. It took me a while to realise that it was an interrupt that was continually being reentered. To make things worse, sometimes i can get it to work. However, I then add a line of code in a totally unrelated areas and suddenly it stops working (interrupt cannot be cleared). It has the hallmarks of two process sharing the same memory location.

    Was this problem ever solved? I think we need someone from TI to look into this and report.

  • Hello Vito,

    The SSI TX DMA Interrupt issue has been understood and a planned errata is in place (to be published after reviewing). But for the SSI RX DMA to be continuously asserted would mean that the RX Control structure scheduled number of transfers are over but there is still data in the SSI RX FIFO, which must be cleared by the CPU or re-init of the uDMA control structure.

    Regards
    Amit
  • My mistake. When defining the uDMAControlTable that is passed to uDMAControlBaseSet(uDMAControlTable), I moved it from global scope to my SPI/uDMA initialisation routine.

    As a consequence uDMAControlTable was an automatic variable and the memory space that was allocated for it was now free for other use. It was a matter of time before the uDMAControlTable became corrupted and then it was not possible to clear the interrupt bit (or something else that gave this impression).

    So, in summary, when defining the parameter to pass to the uDMAControlBaseSet, make sure it’s global (or static within the initialisation routine?).

  • Thanks Amit. I didn't notice your reply until after my next post, since it was on the next page. Your responsiveness is great!
  • A question of perhaps a general nature.

    Is it acceptable to place the definition "unsigned char uDMAControlTable[1024] __attribute__ ((aligned(1024)))" in any SSI/DMA initialization routine as a static variable. Is it technically possible? What would be considered best programming practice (global definition or static variable where it used)?

    Would welcome any discussion on practice/technique.

  • Hello Vito,

    The CPU must know that the section of code is for SRAM and it must take care to avoid. Either you reserve the location using cmd or define in a global section, it would be OK.

    Regards
    Amit
  • Hi Amit,

    I'm not familiar with cmd. What is it? Is a static variable in the "SPIInit" function acceptable?

    Regards,
    Vito
  • Hello Vita

    The cmd is the linker command file which can be used to reserve a location

    Regards
    Amit
  • Is a static variable in the "SPIInit" function acceptable?
  • Hello Vito

    Depends on how you use the variable and what optmization is being done on the variable. This is something that you would need to test.

    Regards
    Amit