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.

Using two SPI simultaneously in CC2640

Other Parts Discussed in Thread: CC2640, CC2650

Hello, dear colleagues!

I'm trying to use TWO SPI simultaneously in CC2640: one for read/write external spi - FLASH, and other one is for shifting data to the shift register. The oscilloscope observation shows that  SPI-transfer when started simultaneously on both channels actually is not simultaneously and occurring in time - shared manner.

Is it possible to achieve truly simultaneously transmitting/receiving via SPI ? Both SPI use two independent DMA-channels.

Here is the my board file definition:

/* SPI objects */

SPICC26XX_Object spiCC26XXDMAObjects[CC2650_SPICOUNT];

/* SPI configuration structure, describing which pins are to be used */
const SPICC26XX_HWAttrs spiCC26XXDMAHWAttrs[CC2650_SPICOUNT] = {

.baseAddr = SSI0_BASE,
.intNum = INT_SSI0,
.defaultTxBufValue = 0,
.powerMngrId = PERIPH_SSI0,
.rxChannelBitMask = 1<<UDMA_CHAN_SSI0_RX,
.txChannelBitMask = 1<<UDMA_CHAN_SSI0_TX,
.mosiPin = Board_DSP_MOSI,
.misoPin = Board_DSP_MISO,
.clkPin = Board_DSP_CLK,
.csnPin = PIN_UNASSIGNED 
},

.baseAddr = SSI1_BASE,
.intNum = INT_SSI1,
.defaultTxBufValue = 0,
.powerMngrId = PERIPH_SSI1,
.rxChannelBitMask = 1<<UDMA_CHAN_SSI1_RX,
.txChannelBitMask = 1<<UDMA_CHAN_SSI1_TX,
.mosiPin = Board_FLASH_MOSI,
.misoPin = Board_FLASH_MISO,
.clkPin = Board_FLASH_CLK,
.csnPin = PIN_UNASSIGNED 
},
};

/* SPI configuration structure */
const SPI_Config SPI_config[] = {
/* CC2650_SPI0 */
{&SPICC26XXDMA_fxnTable, &spiCC26XXDMAObjects[0], &spiCC26XXDMAHWAttrs[0]},
{&SPICC26XXDMA_fxnTable, &spiCC26XXDMAObjects[1], &spiCC26XXDMAHWAttrs[1]},
{NULL, NULL, NULL},
};

  • Hello Stanislav,

    It is possible to use both simultaneously but there is currently a bug in the driver wrt power management. If one transaction finishes at the same time as the other one is ongoing, the device might enter Standby mode causing the ongoing transaction to stop.
    This will be fixed in the TI RTOS version coming out next week.

    Regards,
    Svend
  • Hello Svend,

    I want just to get more accurate information.

    Is it possible for DMA (two independent channels)  to get information from two SPI-buffers (SPI0 and SPI1) and to put it into memory (in two different memory location) simultaneously?

    I can't get this mode.

  • Yes this should be fully possible, they are using two different DMA channels.
  • Dear Svend!

    Could you give me an code example (or link to it), that showing correct implementation two independent DMA-channels for copy data from SPI-buffers to memory?

  • You set up the second one just like you set up the first one except you run SPI_open with a different argument.

    Please see the SPI DMA driver documentation. (tirtos_installation_path\docs\docs_overview.html -> TI-RTOS Driver Runtime APIs (doxygen) -> SPIDMACC26XX.h

  • I've made changes in my project to add new version of TI RTOS (2.15.00.17 spi bug should be fixed?).

    Unfortunately, the two SPIs are still not working.

    In following code all works fine only if one of SPI is disabled. With both of them four zero bytes included in first AUX spi reading data in each transfer.

    What is wrong in my code?

    board.h
    
    	#define Board_FLASH_MISO                     IOID_2
    	#define Board_FLASH_MOSI                     IOID_10
    	#define Board_FLASH_CLK                      IOID_12
    	#define Board_Flash_CSN                     IOID_3
    	#define Board_CS_MOTOR                      IOID_11
    
    	#define Board_DSP_MISO                     IOID_13
    	#define Board_DSP_MOSI                     IOID_5
    	#define Board_DSP_CLK                      IOID_8
    
    #define Board_SPI0                  CC2650_DSP_SPI
    #define Board_SPI1                  CC2650_FLASH_SPI	
    
    typedef enum CC2650_SPIName {
        CC2650_DSP_SPI = 0,
        CC2650_FLASH_SPI,
        CC2650_SPICOUNT
    } CC2650_SPIName;
    
    
    typedef enum CC2650_UdmaName {
        CC2650_UDMA0 = 0,
        CC2650_UDMACOUNT
    } CC2650_UdmaName;
    
    
    
    board.c
    
    /* Include drivers */
    #include <ti/drivers/dma/UDMACC26XX.h>
    
    /* UDMA objects */
    UDMACC26XX_Object udmaObjects[CC2650_UDMACOUNT];
    
    /* UDMA configuration structure */
    const UDMACC26XX_HWAttrs udmaHWAttrs[CC2650_UDMACOUNT] = {
        {
            .baseAddr    = UDMA0_BASE,
            .powerMngrId = PowerCC26XX_PERIPH_UDMA,
            .intNum      = INT_DMA_ERR,
            .intPriority = ~0
        }
    };
    
    /* UDMA configuration structure */
    const UDMACC26XX_Config UDMACC26XX_config[] = {
        {
             .object  = &udmaObjects[0],
             .hwAttrs = &udmaHWAttrs[0]
        },
        {NULL, NULL}
    };
    
    #include <ti/drivers/spi/SPICC26XXDMA.h>
    
    /* SPI objects */
    SPICC26XXDMA_Object spiCC26XXDMAObjects[CC2650_SPICOUNT];
    
    /* SPI configuration structure, describing which pins are to be used */
    const SPICC26XXDMA_HWAttrsV1 spiCC26XXDMAHWAttrs[CC2650_SPICOUNT] = {
        {
            .baseAddr           = SSI0_BASE,
            .intNum             = INT_SSI0_COMB,
            .intPriority        = ~0,
            .swiPriority        = 0,
            .powerMngrId        = PowerCC26XX_PERIPH_SSI0,
            .defaultTxBufValue  = 0,
            .rxChannelBitMask   = 1<<UDMA_CHAN_SSI0_RX,
            .txChannelBitMask   = 1<<UDMA_CHAN_SSI0_TX,
            .mosiPin 						= Board_DSP_MOSI,
            .misoPin 						= Board_DSP_MISO,
            .clkPin 						= Board_DSP_CLK,
            .csnPin 						= PIN_UNASSIGNED
    		},
    #if BOARD_VER > 1
        {
            .baseAddr           = SSI1_BASE,
            .intNum             = INT_SSI1_COMB,
            .intPriority        = ~0,
            .swiPriority        = 0,
            .powerMngrId        = PowerCC26XX_PERIPH_SSI1,
            .defaultTxBufValue  = 0,
            .rxChannelBitMask   = 1<<UDMA_CHAN_SSI1_RX,
            .txChannelBitMask   = 1<<UDMA_CHAN_SSI1_TX,
            .mosiPin 						= Board_FLASH_MOSI,
            .misoPin 						= Board_FLASH_MISO,
            .clkPin 						= Board_FLASH_CLK,
            .csnPin 						= PIN_UNASSIGNED 
        }
    #endif
    };
    
    /* SPI configuration structure */
    const SPI_Config SPI_config[] = {
        {
             .fxnTablePtr = &SPICC26XXDMA_fxnTable,
             .object      = &spiCC26XXDMAObjects[0],
             .hwAttrs     = &spiCC26XXDMAHWAttrs[0]
        },
        {
             .fxnTablePtr = &SPICC26XXDMA_fxnTable,
             .object      = &spiCC26XXDMAObjects[1],
             .hwAttrs     = &spiCC26XXDMAHWAttrs[1]
        },
        {NULL, NULL, NULL}
    };
    
    
    maintask.c
    
    static SPI_Handle DspSpiHandle, AuxSpiHandle;
    static SPI_Params DspSpiParams, AuxSpiParams;
    static SPI_Transaction DspSpiTransaction, AuxSpiTransaction;
    
    static void MainTaskFxn(UArg a0, UArg a1) // main task function in new app task
    {
    	// other code
    	// .....................
    	SPI_init();
    	// once SPI for outgoing data
    	SPI_Params_init(&DspSpiParams);
    	DspSpiParams.bitRate = SPI_BAUDRATE; // 16 Mhz
    	DspSpiParams.transferMode = SPI_MODE_CALLBACK;
    	DspSpiParams.transferCallbackFxn = &display_spi_callback;
    	DspSpiHandle = SPI_open(CC2650_DSP_SPI, &DspSpiParams);
    	// and onother one - for M25P16 flash reading and external shift register
    	SPI_Params_init(&AuxSpiParams);
    	AuxSpiParams.bitRate = SPI_BAUDRATE;
    	AuxSpiParams.transferMode = SPI_MODE_CALLBACK;
    	AuxSpiParams.transferCallbackFxn = &aux_spi_callback;
    	AuxSpiHandle = SPI_open(CC2650_FLASH_SPI, &AuxSpiParams);
    	
    	AuxSpiTransaction.count = 16;
    	AuxSpiTransaction.txBuf = RGB_Array;
    	AuxSpiTransaction.rxBuf = 0;
    	SPI_transfer(AuxSpiHandle, &AuxSpiTransaction);
    	//................
    	 // start GTP3 timer for PWM and accurate time interval measuring
    	 // when sound is on, the GTP3B interrupt calls StartMusicBuf() for loading of next sond chunck from ext. flash
    	SoundInit();
    	// other code
    	
    	// main task cycle
    }
    
    static void aux_spi_callback(SPI_Handle handle, SPI_Transaction *transaction) // SWI context
    {
    	switch (FlashSpiStep)
    	{
    	case 1:
    		FlashSpiStep++;
    		AuxSpiTransaction.rxBuf = (void*)SoundBuffPtr;
    		AuxSpiTransaction.txBuf = 0;
    		AuxSpiTransaction.count = SOUND_BUFF_LENGTH; // 128 bytes
    		SPI_transfer(AuxSpiHandle, &AuxSpiTransaction);
    		break;
    	case 2:
    		FLASH_CS_OFF();
    		FlashSpiStep = 0;
    		SoundBuffPtr = 0;
    		break;
    	case 3:
    		// trig latch
    		GPIOPinWrite(Board_CS_MOTOR, 1);
    		FlashSpiStep = 0;
    		GPIOPinWrite(Board_CS_MOTOR, 0);
    		if (!CurStepCombination) DCDC36Off(PWR_MOTOR);
    	}
    }
    
    void StartMusicBuf(void) // SPI out 4 bytes - flash reading address
    {
    	while (AuxSpiTransaction.status == SPI_TRANSFER_STARTED);
    	
    //	SPI_transferCancel(AuxSpiHandle);
    	FlashSpiStep = 1;
    	uint32_t addr = FlashAddr;
    	static uint8_t read_req[4];
    	uint8_t* d = (void*)&addr;
    	read_req[0] = 3; //cm_READ
    	read_req[1] = *(d+2);
    	read_req[2] = *(d+1);
    	read_req[3] = *(d);
    
    	AuxSpiTransaction.count = 4;
    	AuxSpiTransaction.txBuf = read_req;
    	AuxSpiTransaction.rxBuf = 0;
    	FLASH_CS_ON();
    	SPI_transfer(AuxSpiHandle, &AuxSpiTransaction);
    	
    	FlashAddr += SOUND_BUFF_LENGTH;
    	FlashAddr &= 0x001fffff;
    }
    
    void MotorEIOWrite(void) // called from TI TROS clock function
    {
    	while (AuxSpiTransaction.status == SPI_TRANSFER_STARTED);
    
    	FlashSpiStep = 3;
    	AuxSpiTransaction.count = 1;
    	AuxSpiTransaction.txBuf = &CurStepCombination;
    	AuxSpiTransaction.rxBuf = 0;
    	SPI_transfer(AuxSpiHandle, &AuxSpiTransaction);
    }
    
    static void display_spi_callback (SPI_Handle handle, SPI_Transaction *transaction)
    {
    	switch (spi_step)
    	{
    	case 1:
    		GPIOPinWrite(Board_MSEL, 0); // strobe MSEL, calib data has been transfered
    		// now prepare main data
    		display_send_pack((void*)&RGB_Array[line], NULL, RGB_DRIVERS_COUNT*RGB_BYTES_PER_LINE+1);
    		spi_step++;
    		break;
    	case 2:
    		// the main data has been sent
    		display_send_pack(Text_Array[line], NULL, 5); // start sending last text data
    		spi_step = 0;
    		break;
    	}
    }
    
    static bool display_send_pack(uint8_t* pSrc, uint8_t* pDest, uint16_t length)
    {
       while (DspSpiTransaction.status != SPI_TRANSFER_COMPLETED);
      // if spi is ready to transmit
      // spi config
      DspSpiTransaction.arg = NULL;
      DspSpiTransaction.rxBuf = pDest;
      DspSpiTransaction.count = length;
      DspSpiTransaction.txBuf = pSrc;
      // send pack
      return (SPI_transfer(DspSpiHandle, &DspSpiTransaction));
    }
    
    void StartRGBFlood(void)
    {
      // send first pack of data, residuals will be sent from spi callback
      GPIOPinWrite(Board_MSEL, 1);
      display_send_pack((void*)&calibration_Array[line], NULL, 24*3+6); // the last 6 is dummy bytes
      spi_step = 1;
    }
    
    static void display_timer_spi(UArg arg) // TI RTOS clock function // interval is 10 ms, full SPI transfer time is about 600 us
    {
    // if respective clock is turned off (so this func. is not invoked) all works fine!
    	GPIOPinWrite(Board_LAT, 1);
    	line = (++line) & 0x07;
    	GPIOPinWrite(Board_LAT, 0);
    	StartRGBFlood();
    	GPIOPinWrite(Board_EN, 0);	// enable output section
    }
    
    
    sound.c
    
    void SoundInit(void)
    {
    	// power on the timer first
    	Power_setDependency(PowerCC26XX_PERIPH_GPT3);
    	TimerStallControl(GPT_BASE, TIMER_BOTH, true);
    
    	// p. 1086
    	//1
    	HWREG(GPT_BASE + GPT_O_CTL) &= ~(GPT_CTL_TAEN|GPT_CTL_TBEN);
    	//2
    	HWREG(GPT_BASE + GPT_O_CFG) = GPT_CFG_CFG_16BIT_TIMER;
    	//3 // add: p1091: TCACT = 6h = Set CCP output pin immediately and clear on Time-Out
    	HWREG(GPT_BASE + GPT_O_TAMR) = GPT_TAMR_TAMR_PERIODIC + GPT_TAMR_TAMRSU_TOUPDATE + GPT_TAMR_TCACT_CLRSET_ON_TO + GPT_TAMR_TAAMS_PWM;
    	HWREG(GPT_BASE + GPT_O_TBMR) |= GPT_TBMR_TBMR_PERIODIC;
    	//4
    	HWREG(GPT_BASE + GPT_O_CTL) |= GPT_CTL_TAPWML_INVERTED; // pwm control
    	//5
    	//HWREG(GPT_BASE + GPT_O_TBPR) = 0x10; // prescaler
    	//6 no interrupts
    	//7
    	HWREG(GPT_BASE + GPT_O_TAILR) = PWM_DAC_PERIOD;
    	HWREG(GPT_BASE + GPT_O_TBILR) = DAC_SAMPLE_RATE;
    	//8
    	HWREG(GPT_BASE + GPT_O_TAMATCHR) = 0x50; // PWM value
    	// reg int on timer3b overflow
    	HWREG(GPT_BASE + GPT_O_IMR) |= TIMER_TIMB_TIMEOUT;
    	
    	 // start and stop timer to avoid high-pin state during timer off
    	HWREG(GPT_BASE + GPT_O_TAMATCHR) = 1;
    	HWREG(GPT_BASE + GPT_O_CTL) |= GPT_CTL_TAEN;
    	HWREG(GPT_BASE + GPT_O_CTL) &= ~(GPT_CTL_TAEN|GPT_CTL_TBEN);
    	
    	PIN_State  pinState;
    	PIN_Config pinCfg[] = { Board_AUDIO | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX, PIN_TERMINATE };
    	PIN_Handle PinAudio = PIN_open(&pinState, pinCfg);
    		// pp.873 tech ref man
    	PIN_Status p = PINCC26XX_setMux(PinAudio, PIN_ID(Board_AUDIO), 29);
    	
    	IntRegister(INT_GPT3B, &SoundIRQ); // 22050 Hz PWM
    	IntEnable(INT_GPT3B);
    }
    
    void SoundIRQ(void) // hwi or swi context // calls StartMusicBuf() - aux spi transfer
    {
    	TimerIntClear(GPT_BASE, TIMER_TIMB_TIMEOUT);
    
    
    	SetDACLevel(*(int16_t*)CurSoundBuffPtr);
    
    	if (CurSoundBuffPtr == SoundBuff[1])
    	{
    		SoundBuffPtr = SoundBuff[0]; // initiate starting of download of next chunk of sound
    		StartMusicBuf();
    	}
    	else if (CurSoundBuffPtr == &SoundBuff[0][0]+sizeof(SoundBuff)-2)
    	{
    		SoundBuffPtr = SoundBuff[1];
    		StartMusicBuf();
    		CurSoundBuffPtr = SoundBuff[0];
    		return;
    	}
    	CurSoundBuffPtr += 2;
    }

  • The problem is solved with just reducing SPI baud rate downto 4 MHz.
  • The problem rises again: most data transactions on both SPI channels are successfull but after short time of running with rate of transfer if about 2400 transfers per second on both channels the program falls in ExceptionHandler() with registers stamp:

    Expression Value
    e 0x20002FA8
    _r4 0x00000018
    _r5 0x00000001
    _r6 0xFFFFFFFF
    _r7 0xFFFFFFFF
    _r8 0xFFFFFFFF
    _r9 0xFFFFFFFF
    _r10 0xFFFFFFFF
    _r11 0xFFFFFFFF
    _r0 0x1001C941
    _r1 0x00000000
    _r2 0x00000001
    _r3 0x0000000A
    _r12 0x0000000A
    _lr 0x20000E78
    _pc 0xFFD80000
    _xpsr 0x00006F05
    execLr 0xFFFFFFFD
    CFSR 0x00040000


    After reset from IAR PC starts from main() and failed in 

    SPICC26XXDMA.c\SPICC26XXDMA_open() line 700

    Hwi_construct(&(object->hwi), (int) hwAttrs->intNum, SPICC26XXDMA_hwiFxn, &paramsUnion.hwiParams, NULL);

    with registers stamp:

    Expression Value
    e 0x20004360
    _r4 0x200030B8
    _r5 0x00006F8C
    _r6 0x20003030
    _r7 0x00006F54
    _r8 0x00000002
    _r9 0xFFFFFFFF
    _r10 0xFFFFFFFF
    _r11 0xFFFFFFFF
    _r0 0xC230E0F2
    _r1 0x00000010
    _r2 0x200001A4
    _r3 0x2000304C
    _r12 0x0000000C
    _lr 0x00002F49
    _pc 0x00002CEC
    _xpsr 0x41000017
    execLr 0xFFFFFFF1
    CFSR 0x00008200

    It occurs every Reset in IAR and Program starts in normal flow only if it will be donwloaded again.

    What does it mean? Any suggestions?

  • The problem with fails solved. I used ti-rtos clock to start spi1 and hardware timer to start spi2. Problem was with wrong timer configuration. I've used direct configuration timer registries and interrupts. Now I've changed it to Hwi_construct and both spis run successful.