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.

TM4C1294NCPDT: SSI uDMA transfer

Part Number: TM4C1294NCPDT

Hi,

I am trying to transfer data from an external ADC into the TM4C using SPI. The ADC is the slave device and after each conversion sends  24 bytes. I have configured the SSI0 dma TX and RX. I transmit a set of 24 dummy bytes to receive the actual sample from the ADC. However, for some reason my interrupt function for the end of RX dma is invoked continuously. Also, I get a lot of FIFO overrun error and rx time out errors. Anyway, it looks like the dma transfer is behaving very erratically. So, in order to find the problem, I simplified my code. I have enabled the loopback mode and just send one set of 24 dummy bytes. In my main function, I just call

ssi0_Init();
ssi0_DmaTransferSetup();
ssi0_DmaTransferStart();

However, I observe the same behavior and no data is written in my receive buffer. So far, I have not found what is wrong with the code. I would appreciate your help (copied below).

Thanks,

Sina

extern uint32_t g_systemClockFrequency;  //120Mhz

#define SAMPLE_BUFFER_SIZE  24
uint8_t g_dummyByte = 8;
uint8_t g_rxSampleBuffer[SAMPLE_BUFFER_SIZE];

uint32_t g_rxBufCount = 0;
uint32_t g_nbOverrun = 0;
uint32_t g_nbTimeout = 0;

void ssi0_HwSetup(void);

void ssi0_Init(void)
{
  memset(g_rxSampleBuffer,0,SAMPLE_BUFFER_SIZE);
  
  g_rxBufCount = 0;
  g_nbOverrun = 0;
  g_nbTimeout = 0;

  ssi0_HwSetup();
  GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_4,0);
  GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_5,0);
}


void ssi0_HwSetup(void)
{
  SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
  GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_5);
  GPIOPinConfigure(GPIO_PA2_SSI0CLK);
  GPIOPinConfigure(GPIO_PA5_SSI0XDAT1);

  SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);

  //wait for the peripheral to be ready
  while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)){}

  SSIClockSourceSet(SSI0_BASE,SSI_CLOCK_SYSTEM);

  //ADC_SPI_CLOCK_FREQUENCY = 4Mhz  
  //ADC_DATA_FORMAT_BITS = 8
  SSIConfigSetExpClk(SSI0_BASE, g_systemClockFrequency, SSI_FRF_MOTO_MODE_2, SSI_MODE_MASTER, ADC_SPI_CLOCK_FREQUENCY, ADC_DATA_FORMAT_BITS);

  SSIEnable(SSI0_BASE);

  g_dummyByte = 0;
  uint32_t dummy = 0;
  while(SSIDataGetNonBlocking(SSI0_BASE, &dummy)){}
}

void ssi0_DmaTransferSetup(void)
{
    // Enable the uDMA interface for both TX and RX channels.
    SSIDMAEnable(SSI0_BASE, SSI_DMA_TX|SSI_DMA_RX);

    //DMA TX setup
    //Put the attributes in a known state for the uDMA SSI0TX channel.
    uDMAChannelAttributeDisable(UDMA_CHANNEL_SSI0TX,
                                      UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST |
                                      UDMA_ATTR_HIGH_PRIORITY |
                                      UDMA_ATTR_REQMASK);


    //no increment on source since we are sending the same dummy byte
    //no increment on destination since we are copying to the same register location
    uDMAChannelControlSet(UDMA_CHANNEL_SSI0TX | UDMA_PRI_SELECT,
                                UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE |
                                UDMA_ARB_8);



    //DMA RX setup
    // Put the attributes in a known state for the uDMA SSI0RX channel.  These
    // should already be disabled by default.
    uDMAChannelAttributeDisable(UDMA_CHANNEL_SSI0RX,
                                       UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST |
                                       UDMA_ATTR_HIGH_PRIORITY |
                                       UDMA_ATTR_REQMASK);


    //no increment on source since we are reading the same register
    //increment destination address one byte on each dma transfer since we are writing to reception buffer
    uDMAChannelControlSet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT,
                                 UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 |
                                 UDMA_ARB_8);

    //enable loopback for debugging
    HWREG(SSI0_BASE + SSI_O_CR1) |= SSI_CR1_LBM;

    //clear any pending interrupt
    SSIIntClear(SSI0_BASE, 0xFF);

    SSIIntEnable(SSI0_BASE, SSI_DMARX|SSI_RXOR|SSI_RXTO);

    //enable the interrupt in the NVIC
    IntEnable(INT_SSI0);
}



void ssi0_DmaTransferStart(void)
{
    uDMAChannelTransferSet(UDMA_CHANNEL_SSI0TX | UDMA_PRI_SELECT,
                                  UDMA_MODE_BASIC,
                                  &g_dummyByte,
                                  (void *)(SSI0_BASE + SSI_O_DR),
                                  SAMPLE_BUFFER_SIZE);

    //final code: get new buffer here
    uDMAChannelTransferSet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT,
                                 UDMA_MODE_BASIC,
                                 (void *)(SSI0_BASE + SSI_O_DR),
                                 g_rxSampleBuffer, SAMPLE_BUFFER_SIZE);

    uDMAChannelEnable(UDMA_CHANNEL_SSI0TX|UDMA_CHANNEL_SSI0RX);
}

void ssi0_IntHandler(void)
{
    uint32_t ui32Status;

    // Read the interrupt status
    ui32Status = SSIIntStatus(SSI0_BASE, 1);

    if(ui32Status & SSI_RXOR)
    {
        ++g_nbOverrun;
    }

    if(ui32Status & SSI_RXTO)
    {
       ++g_nbTimeout;
    }

    // Clear any pending status, even though there should be none since no SSI
    // interrupts were enabled.
    SSIIntClear(SSI0_BASE, ui32Status);

    // If the dma channel is disabled, that means the transfer is complete
    if(!uDMAChannelIsEnabled(UDMA_CHANNEL_SSI0RX))
    {
        g_rxBufCount++;
    }
}

  • Hi Sina,

    I don't see you configure the GPIO pin for SSI RX function. You only had the below two lines. You need PA4 for the SSI0DATA0. Please take a look at the spi_master.c example.

    GPIOPinConfigure(GPIO_PA2_SSI0CLK);
    GPIOPinConfigure(GPIO_PA5_SSI0XDAT1);
  • Hi Charles,

    Unless there is an error, according to table 17-1 of the chip user guide (SPMS433B), SSI0RX is PA5.

    regards,
    Sina
  • Hi Sina,
    You are right. I got them mixed up. I will suggest you isolate the problem with the uDMA first removed and see if you can get the SSI loopback working without using the uDMA. If the loopback works then continue with the external ADC and confirm you can receive 24 bytes from your external ADC without uDMA. If it works, then it means your SSI setup and the hardware hookup to the external ADC is all proper. You will then add the uDMA to improve the performance.
  • Hi Charles,

    Thanks for your suggestion, I will try it. Do you have a working sample code with this chip with SSI communication using dma for tx and rx?

    regards,
    Sina
  • Hi Sina,
    The DMA example we have is using UART, not SSI. But you can reference it. It is the udma_demo in TivaWare package.
  • Hi Charles,

    I have already seen this code. However, it would be better to have a working example using SSI and dma to make sure that there is no underlying issue in Tivaware or in the chip.

    regards,
    Sina
  • Hi Charles,

    I tried SSI loopback without DMA and it worked. However, the code with DMA and loopback is still showing the same behavior as before. I don't think it's necessary to test with the external ADC since the loopback with DMA is not working.

    regards,
    Sina
  • Hi Sina,
    You might find this post e2e.ti.com/.../367620 helpful which has example code for SSI with DMA.
  • Hi Charles,

    I found what was wrong in my code (posted above):
    1 - uDMAChannelEnable(UDMA_CHANNEL_SSI0TX|UDMA_CHANNEL_SSI0RX) is wrong. uDMAChannelEnable must be called once for each channel (i.e uDMAChannelEnable(UDMA_CHANNEL_SSI0TX) and uDMAChannelEnable(UDMA_CHANNEL_SSI0RX). Curiously, when I checked the uDMA DMAENASET register, the first call to uDMAChannelEnable sets the associated bit, however the second call clears the register. I was surprised with this behavior. However, this does not seem to affect the DMA engine behavior. Any thoughts on this?

    2 - If only a one shot DMA is needed, to properly clear the RX and/or TX interrupt in the ssi0_IntHandler function, the associated SSIDMADisable function must be called before calling SSIIntClear, otherwise the interrupt keeps firing.

    Sina
  • Hi Sina,

      You meant both channel enable bits are cleared after the second call? I will suggest you do single stepping to see what value is being written after the second call. Please note that the the associated bits are automatically cleared after the channels are transferred. How are you clearing the interrupt status in the ISR? After you call the SSIIntClear can you call SSIIntStatus to make sure the flags are truly cleared. There is a write buffer in the Cortex-M processor, it may take several clock cycles before the interrupt source is actually cleared. Therefore, it is recommended that the interrupt source be cleared early in the interrupt handler (as opposed to the very last action) to avoid returning from the interrupt handler before the interrupt source is actually cleared.

  • Hi Charles,

    Please try my code below. Set a breakpoint at   uDMAChannelEnable(UDMA_CHANNEL_SSI0RX) in InitSPITransfer function and check the DMAENASET  register. When I execute this sample code DMAENASET is cleared after the call to uDMAChannelEnable(UDMA_CHANNEL_SSI0TX).

    Regards,

    Sina

    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "inc/hw_uart.h"
    #include "inc/hw_ssi.h"
    #include "driverlib/gpio.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/systick.h"
    #include "driverlib/udma.h"
    #include "driverlib/ssi.h"
    
    
    //*****************************************************************************
    
    //****************************************************************************
    //
    // System clock rate in Hz.
    //
    //****************************************************************************
    uint32_t g_ui32SysClock;
    
    //*****************************************************************************
    //
    // The number of SysTick ticks per second used for the SysTick interrupt.
    //
    //*****************************************************************************
    #define SYSTICKS_PER_SECOND     100
    
    
    
    //*****************************************************************************
    //
    // The SSI0 Buffers
    //
    //*****************************************************************************
    #define SSI_BUFFER_SIZE         256
    static uint8_t g_ui8SSITxBuf[SSI_BUFFER_SIZE];
    static uint8_t g_ui8SSIRxBuf[SSI_BUFFER_SIZE];
    
    //*****************************************************************************
    //
    // The count of uDMA errors.  This value is incremented by the uDMA error
    // handler.
    //
    //*****************************************************************************
    static uint32_t g_ui32uDMAErrCount = 0;
    
    
    //*****************************************************************************
    //
    // The count of SSI0 buffers filled
    //
    //*****************************************************************************
    static uint32_t g_ui32SSIRxCount = 0;
    static uint32_t g_ui32SSITxCount = 0;
    static uint32_t g_nbInterrupts = 0;
    
    //*****************************************************************************
    //
    // The control table used by the uDMA controller.  This table must be aligned
    // to a 1024 byte boundary.
    //
    //*****************************************************************************
    #if defined(ewarm)
    #pragma data_alignment=1024
    uint8_t pui8ControlTable[1024];
    #elif defined(ccs)
    #pragma DATA_ALIGN(pui8ControlTable, 1024)
    uint8_t pui8ControlTable[1024];
    #else
    uint8_t pui8ControlTable[1024] __attribute__ ((aligned(1024)));
    #endif
    
    //*****************************************************************************
    //
    // The error routine that is called if the driver library encounters an error.
    //
    //*****************************************************************************
    #ifdef DEBUG
    void
    __error__(char *pcFilename, uint32_t ui32Line)
    {
        while(1);
    }
    #endif
    
    //*****************************************************************************
    //
    // The interrupt handler for the SysTick timer.  This handler will increment a
    // seconds counter whenever the appropriate number of ticks has occurred.  It
    // will also call the CPU usage tick function to find the CPU usage percent.
    //
    //*****************************************************************************
    void SysTickHandler(void)
    {
    
    }
    
    //*****************************************************************************
    //
    // The interrupt handler for uDMA errors.  This interrupt will occur if the
    // uDMA encounters a bus error while trying to perform a transfer.  This
    // handler just increments a counter if an error occurs.
    //
    //*****************************************************************************
    void uDMAErrorHandler(void)
    {
        uint32_t ui32Status;
    
        //
        // Check for uDMA error bit
        //
        ui32Status = uDMAErrorStatusGet();
    
        //
        // If there is a uDMA error, then clear the error and increment
        // the error counter.
        //
        if(ui32Status)
        {
            uDMAErrorStatusClear();
            g_ui32uDMAErrCount++;
        }
    }
    
    
    
    
    
    void ConfigureSSI0(void)
    {
        uint32_t trashBin[1] = {0};
    
        //
        // Enable the SSI0 Peripheral.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    
        //
        // Configure GPIOA_3 as the SSI Chip Select
        //
        GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, GPIO_PIN_3);
        GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_3, GPIO_PIN_3);
    
        //
        // Configure GPIO Pins for SSI0 mode.
        //
        GPIOPinConfigure(GPIO_PA2_SSI0CLK);
        GPIOPinConfigure(GPIO_PA5_SSI0XDAT1);
        GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_5 | GPIO_PIN_2);
    
        SSIConfigSetExpClk(SSI0_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_2, SSI_MODE_MASTER, 4000000, 8);
    
        SSIEnable(SSI0_BASE);
    
        /* Clear SSI0 RX Buffer */
        while (SSIDataGetNonBlocking(SSI0_BASE, &trashBin[0])) {}
    
        //enable loopback for debugging
        HWREG(SSI0_BASE + SSI_O_CR1) |= SSI_CR1_LBM;
    }
    
    void InitSPITransfer(void)
    {
        // Enable the uDMA interface for both TX and RX channels.
        //
        SSIDMAEnable(SSI0_BASE, SSI_DMA_RX | SSI_DMA_TX);
    
        //****************************************************************************
        //uDMA SSI0 TX
        //****************************************************************************
    
        //
        // Put the attributes in a known state for the uDMA SSI0TX channel.  These
        // should already be disabled by default.
        //
        uDMAChannelAttributeDisable(UDMA_CHANNEL_SSI0TX,
                                        UDMA_ATTR_ALTSELECT |
                                        UDMA_ATTR_HIGH_PRIORITY |
                                        UDMA_ATTR_REQMASK);
    
        //
        // Set the USEBURST attribute for the uDMA SSI0TX channel.  This will
        // force the controller to always use a burst when transferring data from
        // the TX buffer to the SSI0.  This is somewhat more effecient bus usage
        // than the default which allows single or burst transfers.
        //
        //uDMAChannelAttributeEnable(UDMA_CHANNEL_SSI0TX, UDMA_ATTR_USEBURST);
    
        //
        // Configure the control parameters for the SSI0 TX.
        //
        uDMAChannelControlSet(UDMA_CHANNEL_SSI0TX | UDMA_PRI_SELECT,
                                  UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE |
                                  UDMA_ARB_8);
    
    
    
    
        //****************************************************************************
        //uDMA SSI0 RX
        //****************************************************************************
    
        //
        // Put the attributes in a known state for the uDMA SSI0RX channel.  These
        // should already be disabled by default.
        //
        uDMAChannelAttributeDisable(UDMA_CHANNEL_SSI0RX,
                                        UDMA_ATTR_USEBURST | UDMA_ATTR_ALTSELECT |
                                        (UDMA_ATTR_HIGH_PRIORITY |
                                        UDMA_ATTR_REQMASK));
    
        uDMAChannelAssign(UDMA_CHANNEL_SSI0RX);
        //
        // Configure the control parameters for the primary control structure for
        // the SSIORX channel.
        //
        uDMAChannelControlSet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT,
                                  UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 |
                                  UDMA_ARB_8);
    
    
         // Set up the transfer parameters for the uDMA SSI0 TX channel.  This will
         // configure the transfer source and destination and the transfer size.
         // Basic mode is used because the peripheral is making the uDMA transfer
         // request.  The source is the TX buffer and the destination is theUART0
         // data register.
         //
         uDMAChannelTransferSet(UDMA_CHANNEL_SSI0TX | UDMA_PRI_SELECT,
                                                        UDMA_MODE_BASIC,
                                                        g_ui8SSITxBuf,
                                                        (void *)(SSI0_BASE + SSI_O_DR),
                                                        sizeof(g_ui8SSITxBuf));
    
        //
        // Set up the transfer parameters for the SSI0RX Channel
        //
        uDMAChannelTransferSet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT,
                                   UDMA_MODE_BASIC,
                                   (void *)(SSI0_BASE + SSI_O_DR),
                                   g_ui8SSIRxBuf,
                                   sizeof(g_ui8SSIRxBuf));
    
    
    
        //
        // Now both the uDMA SSI0 TX and RX channels are primed to start a
        // transfer.  As soon as the channels are enabled, the peripheral will
        // issue a transfer request and the data transfers will begin.
        //
    
        uDMAChannelEnable(UDMA_CHANNEL_SSI0RX);
        uDMAChannelEnable(UDMA_CHANNEL_SSI0TX);
    
        //
        // Enable the SSI0 DMA TX/RX interrupts.
        //
        //SSIIntEnable(SSI0_BASE, SSI_DMATX | SSI_DMARX);
        SSIIntEnable(SSI0_BASE, SSI_DMARX);
    
        //
        // Enable the SSI0 peripheral interrupts.
        //
        IntEnable(INT_SSI0);
    
    }
    
    //*****************************************************************************
    //
    // SSI0 Interrupt Handler
    //
    //*****************************************************************************
    void SSI0IntHandler(void)
    {
        uint32_t ui32Status;
        uint32_t ui32Mode;
    
        ++g_nbInterrupts;
    
        ui32Status = SSIIntStatus(SSI0_BASE, 1);
    
        SSIIntClear(SSI0_BASE, ui32Status);
    
        ui32Mode = uDMAChannelModeGet(UDMA_CHANNEL_SSI0RX | UDMA_PRI_SELECT);
    
        if(ui32Mode == UDMA_MODE_STOP)
        {
            g_ui32SSIRxCount++;
    
            if((ui32Status & SSI_MIS_DMARXMIS) == SSI_MIS_DMARXMIS )
              SSIDMADisable(SSI0_BASE,SSI_DMA_RX);
        }
    
        if(!uDMAChannelIsEnabled(UDMA_CHANNEL_SSI0TX))
        {
            g_ui32SSITxCount++;
    
            if((ui32Status & SSI_MIS_DMATXMIS) == SSI_MIS_DMATXMIS )
              SSIDMADisable(SSI0_BASE,SSI_DMA_TX);
        }
    }
    
    int main(void)
    {
        // Set the clocking to run directly from the crystal at 120MHz.
        //
        g_ui32SysClock = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                                 SYSCTL_OSC_MAIN |
                                                 SYSCTL_USE_PLL |
                                                 SYSCTL_CFG_VCO_480), 120000000);
    
    
        //
        // Enable the uDMA controller at the system level.  Enable it to continue
        // to run while the processor is in sleep.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    
        //
        // Enable the uDMA controller error interrupt.  This interrupt will occur
        // if there is a bus error during a transfer.
        //
        IntEnable(INT_UDMAERR);
    
        //
        // Enable the uDMA controller.
        //
        uDMAEnable();
    
        //
        // Point at the control table to use for channel control structures.
        //
        uDMAControlBaseSet(pui8ControlTable);
    
         // Initialize SSI0.
         //
         ConfigureSSI0();
    
         uint_fast16_t ui16Idx;
    
         //
         // Fill the TX buffer with a simple data pattern.
         //
         for(ui16Idx = 0; ui16Idx < SSI_BUFFER_SIZE; ui16Idx++)
         {
             g_ui8SSITxBuf[ui16Idx] = 58;
         }
    
         memset(&g_ui8SSIRxBuf,0,SSI_BUFFER_SIZE);
         g_nbInterrupts = 0;
    
        //
        // Initialize the uDMA SPI transfers.
        //
        InitSPITransfer();
    
        while(1)
        {
    
        }
    
    }

  • Hi Sina,
    Sorry for the delay response. I'm currently out of office for two weeks. I'm unable to run your code. I only have a iPAD with me. Can you single step through the uDMAChannelEnable API and see how the DMAENASET is behaving while you step through the API?