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.

TIVA launchpad: Need to toggle FSYNC on a per-word basis when SSI/SPI slave... so trying to get DMA to do it for me.



I have been working on a situation where the TIVA processor needs to be a slave on an SPI bus, but the master holds chip select down for multi-word transfers.   My solution is to use a GPIO for chip select and then wire another GPIO to the FSYNC line and toggle it.   If there's a mode that somehow escaped me that would enable me to do this without all this cruft, I'd love to hear about it.    What the software currently does (and it works) is the following:

1) push a data word into the TX buffer
2) pull FSYNCgpio low
3) do a blocking get on SSI
4) bring FSYNCgpio high
5) wash, rinse, repeat.

However, a coworker suggested that this could be done with the uDMA scatter-gather mode.  So far, I have yet to get the DMA scatter gather mode to toggle the GPIO.   So now the software does this:

1) push data word into TX buffer
2) pull the FSYNC line low
3) set up DMA to deal with the rest of the com cycle

And the DMA does this:

1) Bring FSYNC high (via gpio wired to FSYNC)
2) push data word into TX buffer
3) Bring FSYNC low
4) grab data word from RX buffer

At the end of each word, the DMA should do what it does and then interrupt the processor.  The processor should handle the buffers and set up the next word for the DMA to transfer.   I know this is quite unorthodox.  Earlier I had posted code in the forum post, but it got chewed up... so here's the code file cleaned up and presentable.

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_ssi.h"
#include "inc/hw_sysctl.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/ssi.h"
#include "driverlib/timer.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/udma.h"
#include "utils/uartstdio.h"

#include "bobcomms.h"
#include "settings.h"

#define NUM_SSI_DATA (NUM_ADCS+2)

uint32_t dataRx[NUM_SSI_DATA];
uint32_t dataTx[NUM_SSI_DATA];
uint32_t sendCnt;


#ifdef DO_DMA

uint32_t dmaTxBuffer;
uint32_t dmaRxBuffer;

#if defined(ewarm)
#pragma data_alignment=1024
tDMAControlTable dmaControlTable[64];
#elif defined(IS_CCS)
#pragma DATA_ALIGN(dmaControlTable, 1024)
tDMAControlTable dmaControlTable[64];
#else
tDMAControlTable dmaControlTable[64] __attribute__ ((aligned(1024)));
#endif


// cannot be stored in flash... therefore, should not be const.
unsigned short zero = 0;
unsigned short eight = 8;

#define BOBSPI_UDMA_CHANNEL UDMA_CH12_SSI2RX

#define FSYNC_GPIO_WRITE ((uint32_t*)0x40004020)
#define SSI_DATA_REG ((uint32_t *)(SPI_BASE + SSI_O_DR))




tDMAControlTable g_TaskTableSrc[] =
{
		// Task 1: Copy 0x08 to 0x40004020  to set FSYNC high
	uDMATaskStructEntry(
		1,                              UDMA_SIZE_32,
		UDMA_SRC_INC_NONE,              &eight,
		UDMA_DST_INC_NONE,              FSYNC_GPIO_WRITE,
		UDMA_ARB_1,                    UDMA_MODE_PER_SCATTER_GATHER
	),

		// Task 2: Copy TX word from buffer to the SPI tx fifo...
	uDMATaskStructEntry(
		1,                              UDMA_SIZE_32,
		UDMA_SRC_INC_NONE,				&dmaTxBuffer,
		UDMA_DST_INC_NONE,              SSI_DATA_REG,
		UDMA_ARB_1,                    UDMA_MODE_PER_SCATTER_GATHER
	),

                // Task 3: Copy 0x00 to 0x40004020  to set FSYNC low
	uDMATaskStructEntry(
		1,                              UDMA_SIZE_32,
		UDMA_SRC_INC_NONE,              &zero,
		UDMA_DST_INC_NONE,              FSYNC_GPIO_WRITE,
		UDMA_ARB_1,                    UDMA_MODE_PER_SCATTER_GATHER
	),

                // Task 4: Copy RX word from SPI rx fifo to buffer
	uDMATaskStructEntry(
		1,                              UDMA_SIZE_32,
		UDMA_SRC_INC_NONE,              SSI_DATA_REG,
		UDMA_DST_INC_NONE,              &dmaRxBuffer,
		UDMA_ARB_1,                    UDMA_MODE_BASIC  //UDMA_MODE_PER_SCATTER_GATHER  .. auto?
	),
};


void SSIIntHandler(void)
{
        uint32_t status;
        uint32_t mode;

	static uint32_t sent = 0;

        status = SSIIntStatus(SPI_BASE, 1);
        SSIIntClear(SPI_BASE, status);

        // ok, in here we've got to set up the next transfer if there's text remaining to be shoveled.
        if(mode == UDMA_MODE_STOP)
        {
        	sent++;
            // the transfer completed... set up the next one iff there's data left... otherwise assert nDONE
            if(  sendCnt > sent  )
            {
            	dataRx[sent] = dmaRxBuffer;

            	dmaTxBuffer = dataTx[sent];

                    uDMAChannelScatterGatherSet(BOBSPI_UDMA_CHANNEL, 4, g_TaskTableSrc, true);
                    if(uDMAChannelIsEnabled(BOBSPI_UDMA_CHANNEL))
                    uDMAChannelEnable(BOBSPI_UDMA_CHANNEL);
            }
            else
            {
            	sent = 0;
		GPIOPinWrite(NDONE_PORT, NDONE_PIN, 0);
            }
        }
}

#endif  /* DO_DMA */




// Some very recognizable values.
const uint32_t junkData[] = {
	0xfafa,
	0xfcaf,
	0xf0f0,
	0xc63e,
};

uint32_t requestStatus = 0x00A3;


int main(void)
{
    volatile uint32_t ui32Loop;
    uint32_t junk;
    uint32_t mode;
    volatile uint32_t i, j, k;

	SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_20MHZ);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);

	SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);

	GPIOPinTypeGPIOInput(DRDY_PORT, DRDY_PIN);
	chiptype = TYPE_SLAVE;
 	mode = SSI_MODE_SLAVE;

	GPIOPinTypeGPIOOutput(FSYNC_PORT, FSYNC_PIN);
	GPIOPinWrite(FSYNC_PORT, FSYNC_PIN, FSYNC_PIN);

#ifdef DO_DMA
	SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); // Enable uDMA controller at system level
	uDMAEnable();

	uDMAControlBaseSet(&dmaControlTable[0]);

	uDMAChannelDisable(BOBSPI_UDMA_CHANNEL); // Ensure channel is disabled before modifying register

	uDMAChannelAssign(BOBSPI_UDMA_CHANNEL);

	uDMAChannelAttributeDisable(BOBSPI_UDMA_CHANNEL,
	        UDMA_ATTR_ALTSELECT |
	        UDMA_ATTR_USEBURST |
	        UDMA_ATTR_HIGH_PRIORITY |
	        UDMA_ATTR_REQMASK);

	uDMAChannelScatterGatherSet(BOBSPI_UDMA_CHANNEL, 4, g_TaskTableSrc, true);

	uDMAChannelEnable(BOBSPI_UDMA_CHANNEL);

	SSIDMAEnable(SPI_BASE, SSI_DMA_RX | SSI_DMA_TX);   // hmmm... maybe not TX?  I wonder if this simply sets up triggers...

	IntEnable(INT_SSI2);
	IntEnable(INT_UDMAERR);

#endif  /* Do DMA */

	GPIOPinTypeGPIOOutput(NDONE_PORT, NDONE_PIN);
	GPIOPinWrite(NDONE_PORT, NDONE_PIN, NDONE_PIN);
	GPIOPinTypeGPIOInput(NCS_PORT, NCS_PIN);

	GPIOPinConfigure(GPIO_PB4_SSI2CLK);
	GPIOPinConfigure(GPIO_PB5_SSI2FSS);
	GPIOPinConfigure(GPIO_PB6_SSI2RX);
	GPIOPinConfigure(GPIO_PB7_SSI2TX);

	GPIOPinTypeSSI(GPIO_PORTB_BASE, GPIO_PIN_7 | GPIO_PIN_6  | GPIO_PIN_4 | GPIO_PIN_5 );
	SSIConfigSetExpClk(SPI_BASE, SysCtlClockGet(), SPI_MOTO_MODE, mode, SPI_SPEED, SPI_WORD_SIZE);

	SSIEnable(SPI_BASE);

	while(SSIDataGetNonBlocking(SPI_BASE, &junk));

	// help reset the bus yanno.
	GPIOPinWrite(NDONE_PORT, NDONE_PIN, 0);
	while(GPIOPinRead(DRDY_PORT, DRDY_PIN) == 0);
	GPIOPinWrite(NDONE_PORT, NDONE_PIN, NDONE_PIN);

	IntMasterEnable();

    while(1)
    {
        requestStatus = 0x5AC7;   // recognizable junk

    		/// \TODO do whatever... this is your time to do it.  The com cycle hasn't started yet.  Otherwise, sleep in this loop.
        	///       The ADC would be converting here.
    	while(GPIOPinRead(DRDY_PORT, DRDY_PIN))
    	{
    	}

    	sendCnt = 0;

    	// send status or response.  This is set up already by the previous iteration
    	SSIDataPut(SPI_BASE, requestStatus);


	// Loop is trimmed for brevity...  select what data needs to be sent.
    	for(i = 0; i < NUM_ADCS; i++)
    	{
		dataTx[sendCnt] = junkData[i];
		sendCnt++;
    	}

    	while(GPIOPinRead(NCS_PORT, NCS_PIN))
    	{
    		/// \TODO do whatever... this is your time to do it.  My com cycle hasn't started yet.  Otherwise, sleep in this loop.
    	}

        //  Com cycle start
        // "Talk" for a bit.
        //

    	while(SSIDataGetNonBlocking(SPI_BASE, &junk));

//    	GPIOPinWrite(FSYNC_PORT, FSYNC_PIN, 0);
    	FSYNC_BB = 0;

#ifdef DO_DMA
        uDMAChannelEnable(BOBSPI_UDMA_CHANNEL);
    	while(GPIOPinRead(NDONE_PORT, NDONE_PIN));
#else

    	SSIDataGet(SPI_BASE, &dataRx[0]);

//    	GPIOPinWrite(FSYNC_PORT, FSYNC_PIN, FSYNC_PIN);
    	FSYNC_BB = 8;

    	j = 1;
    	// send data from each ADC that's enabled.
    	for(i = 0; i < sendCnt; i++)
    	{
    			SSIDataPut(SPI_BASE, dataTx[i]);
//    			GPIOPinWrite(FSYNC_PORT, FSYNC_PIN, 0);
    	    	FSYNC_BB = 0;

    			SSIDataGet(SPI_BASE, &dataRx[j]);
    			j++;
//    			GPIOPinWrite(FSYNC_PORT, FSYNC_PIN, FSYNC_PIN);
    	    	FSYNC_BB = 8;
     	}
    	GPIOPinWrite(NDONE_PORT, NDONE_PIN, 0);

#endif  /* not doing DMA */

    	while(GPIOPinRead(DRDY_PORT, DRDY_PIN) == 0)
    	{
    		/// \todo wait for DRDY to clear...  This may happen quickly, it may not.  Should remain 'on it' so com cycle completes normally.
    		// I guess clock down
    	}

	GPIOPinWrite(NDONE_PORT, NDONE_PIN, NDONE_PIN);
	// This is a good place to handle the data that was transacted.   At top of loop, we sleep.
    }
}

Thank you,

Rob Stoddard