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