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.

CC2652P: Two SPIs, one using driverlib (uDMA, SSI), one using TI-Drivers. As soon as the TI-Drivers SPI uses DMA, it stops working. Where is the problem?

Part Number: CC2652P
Other Parts Discussed in Thread: ADS131M06, , SYSCONFIG

Hello there!

We are working to simultaneously read out two SPI ADCs (2x ADS131M06) and write to a SPI flash device using the two SPI peripherals provided in the CC2652P microcontroller. Due to a fairly high data rate we need for the ADCs (16 kHz), the SPI of the two ADCs is configured as follows:

  • SPI0, uses driverlib (udma.h and ssi.h) and DMA channels 3 and 4
  • manual CS pin configuration (one is active high, one active low)
  • 16 kHz interrupt generated by zero-latency timer (HWI priority 0), bypasses RTOS 
  • writes into the FLASH_QUEUE

Then, a lower priority flash RTOS task will write a huge amount of data (4228 bytes) to the flash once the FLASH_QUEUE is filled. The flash SPI is configured as follows:

  • SPI1, uses sysconfig and TI Drivers (SPI.h) with DMA
  • manual CS pin configuration (pulled low and back high manually) 
  • writes content of FLASH_QUEUE to flash

The problem is that whenever the it uses DMA - automatically fulfilled as soon as transfer amount exceeds 10: transaction->count < hwAttrs->minDmaTransferSize in line 755 of SPICC26X2DMA.c - the flash task never returns at the semaphore pend at line 785 and gets preempted. What could be the reason for DMA not to work in the second SPI instance? I have attached the relevant lines of code below.

Any help would be greatly appreciated!

Flash.c

// Simplified code of FLASH.c

// HWI Interrupt generated by low latency driver through Hwi_post()
void _Flash_Data_Ready_Notifier(UArg arg1) {
    Semaphore_post(flash_ready_semaphore);
}

void Flash_Execute(UArg arg0, UArg arg1) {
    bool request_busy = false;
    while(1) {
        // Binary semaphore to wait for data ready
        Semaphore_pend(flash_ready_semaphore, BIOS_WAIT_FOREVER);
        
        // ... configure 4228 bytes to transfer
        Flash_SPI_Transaction.count = length;
        GPIO_write(CONFIG_SPI_FLASH_CS, 0);
        SPI_transfer(_Flash_SPI_Handle, &Flash_SPI_Transaction);
        GPIO_write(CONFIG_SPI_FLASH_CS, 1);
    }
}

ADC.c

GPTimerCC26XX_Handle gptHandle;
extern const GPTimerCC26XX_Config GPTimerCC26XX_config[];
void ADC_Timer_Zero_Latency_Callback(uintptr_t a0);
#endif

/* Lookup table definition for interfacing driverlib and register fields.
Used to simplify code and to easily look up register fields as several fields
are not symmetric across timer A and timer B registers (interrupts & dma)
*/
typedef struct GPTimerCC26XX_LUT
{
uint16_t map; /* Timer argument in driverlib (TIMER_A / TIMER_B) */
uint16_t shift; /* Bit shift for registers shared between GPT_A / GPT_B */
uint16_t offset; /* Byte offset for registers sequentially in memory map for GPT_A/GPT_B */
uint16_t interrupts[GPT_NUM_INTS]; /* Interrupt bitfields for GPTA/B. Order must match GPTimerCC26XX_Interrupt */
} const GPTimerCC26XX_LUT;

/* Lookup table definition for interfacing driverlib and register fields. */
static const GPTimerCC26XX_LUT GPT_LUT[GPT_PARTS_COUNT] =
{
{
.map = TIMER_A,
.shift = 0,
.offset = 0,
.interrupts ={ GPT_MIS_TATOMIS, GPT_MIS_CAMMIS, GPT_MIS_CAEMIS, GPT_MIS_TAMMIS },
},
{
.map = TIMER_B,
.shift = 8,
.offset = 4,
.interrupts ={ GPT_MIS_TBTOMIS, GPT_MIS_CBMMIS, GPT_MIS_CBEMIS, GPT_MIS_TBMMIS },
},
};

void ADC_Timer_Zero_Latency_Callback(uintptr_t a0) {
GPTimerCC26XX_Handle handle = (GPTimerCC26XX_Handle)gptHandle;
GPTimerCC26XX_HWAttrs const *hwAttrs = gptHandle->hwAttrs;
uint32_t timer = GPT_LUT[handle->timerPart].map;
uint32_t interrupts = HWREG(hwAttrs->baseAddr + GPT_O_MIS);
uint32_t interruptClr = timer & interrupts;
HWREG(hwAttrs->baseAddr + GPT_O_ICLR) = interruptClr;

ADS131_Read();
}


void ADC_Init() {
ADS131_Init(...);

struct ADS131_Config_t ads131_config = {...};
ADS131_Setup(&ads131_config);

// Timer Interrupt of ADS131 ADC at 16 kHz using Hardware Timer 0
Timer_Params_init(&adc_timer_params);
adc_timer_params.period = 16 kHz
adc_timer_params.periodUnits = Timer_PERIOD_COUNTS;
adc_timer_params.timerMode = Timer_CONTINUOUS_CALLBACK;
adc_timer_params.timerCallback = ADC_Timer_Callback;
adc_timer_handle = Timer_open(CONFIG_TIMER_0, &adc_timer_params);

TimerCC26XX_HWAttrs *timerHwAttrs = (TimerCC26XX_HWAttrs *)adc_timer_handle->hwAttrs;
gptHandle = (GPTimerCC26XX_Handle)&GPTimerCC26XX_config[timerHwAttrs->gpTimerUnit];

GPTimerCC26XX_HWAttrs const *gptHwAttrs = gptHandle->hwAttrs;
GPTimerCC26XX_Object *gptObject = gptHandle->object;

/* Construct RTOS HWI */
HwiP_Struct *pHwi = &gptObject->hwi[gptHandle->timerPart];
HwiP_Params hp;
HwiP_Params_init(&hp);
hp.arg = (uintptr_t)gptHandle;
hp.enableInt = true;
hp.priority = 0;

GPTimerCC26XX_unregisterInterrupt(gptHandle);
HwiP_construct(pHwi, gptHwAttrs->intNum, ADC_Timer_Zero_Latency_Callback, &hp);

HwiP_enableInterrupt(gptHwAttrs->intNum);
uint32_t ui32Base = gptHwAttrs->baseAddr;
/* Enable interrupts in timer unit */
TimerIntEnable(ui32Base, TIMER_TIMA_TIMEOUT);
}
ADS131.c
/* The control table used by the uDMA controller, primary and alternate for 31 channels */
tDMAControlTable DMAControlTable[64] __attribute__ ((aligned (1024)));

void ADS131_Read() {
ADS131_Data_Ready_Callback(_ADS131_SPI_RX_Buffer_1, _ADS131_SPI_RX_Buffer_2);

// Perform ADC 0 (active low) readout by writing a zero frame. The ADC data will be in RX_Buffer[1] ... RX_Buffer[N - 2]
_ads131_active_adc = !_ads131_active_adc;
HWREGB(GPIO_BASE + GPIO_O_DOUT3_0 + IOID_8) = _ads131_active_adc;

if(!(uDMAChannelIsEnabled (UDMA0_BASE, UDMA_CHAN_SSI0_RX))) {
uDMAChannelTransferSet(
UDMA0_BASE,
UDMA_CHAN_SSI0_TX | UDMA_PRI_SELECT,
UDMA_MODE_BASIC,
_ads131_active_adc ? _ADS131_SPI_TX_Buffer_1 : _ADS131_SPI_TX_Buffer_2,
(void *)(SSI0_BASE + SSI_O_DR),
2 * ADS131_FRAME_NUM_WORDS * _ADS131_FRAME_NUM_BYTES_PER_WORD
);
uDMAChannelEnable(UDMA0_BASE, UDMA_CHAN_SSI0_TX);
}
if(!(uDMAChannelIsEnabled (UDMA0_BASE, UDMA_CHAN_SSI0_TX))) {
uDMAChannelTransferSet(
UDMA0_BASE,
UDMA_CHAN_SSI0_RX | UDMA_PRI_SELECT,
UDMA_MODE_BASIC,
(uint32_t *)(SSI0_BASE + SSI_O_DR),
_ads131_active_adc ? _ADS131_SPI_RX_Buffer_1 : _ADS131_SPI_RX_Buffer_2,
2 * ADS131_FRAME_NUM_WORDS * _ADS131_FRAME_NUM_BYTES_PER_WORD
);
uDMAChannelEnable(UDMA0_BASE, UDMA_CHAN_SSI0_RX);
}
return 0;
}

void ADS131_Setup(ADS131_Config_t *config) {
...
}

static inline void _ADS131_SPI_Transfer_1(uint16_t input_data) {
uint_fast8_t i;
_ADS131_SPI_TX_Buffer_1[0] = input_data;
for(i = 0; i < 2 * ADS131_FRAME_NUM_WORDS * _ADS131_FRAME_NUM_BYTES_PER_WORD; ++i) {
//SSIDataPut(SSI0_BASE, _ADS131_SPI_TX_Buffer_1[i]);
//SSIDataGet(SSI0_BASE, (uint32_t *)(_ADS131_SPI_RX_Buffer_1 + i));
SSIDataPut(SSI0_BASE, _ADS131_SPI_TX_Buffer_1[i]);
SSIDataGet(SSI0_BASE, (uint32_t *)(_ADS131_SPI_RX_Buffer_1 + i));
}

}

void SPIDMA_init()
{
unsigned int key;

/* Disable interrupts when transfer*/
key = HwiP_disable();

/* power up and enable clock for DMA. */
PRCMPeripheralRunEnable(PRCM_PERIPH_UDMA); // UDMA
PRCMPeripheralSleepEnable(PRCM_PERIPH_UDMA);
PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_UDMA);
PRCMLoadSet();
while(!PRCMLoadGet());

/* Set the base for the channel control table. */
uDMAControlBaseSet (UDMA0_BASE, &DMAControlTable);
/* Enable DMA. */
uDMAEnable(UDMA0_BASE);

/* DMA settings for SPI RX */
// Put the attributes in a known state for the uDMA SSI0RX channel. Disable the attr by default
uDMAChannelAttributeDisable(UDMA0_BASE, UDMA_CHAN_SSI0_RX, \
UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | \
UDMA_ATTR_HIGH_PRIORITY | \
UDMA_ATTR_REQMASK);
uDMAChannelAttributeEnable(UDMA0_BASE, UDMA_CHAN_SSI0_RX, UDMA_ATTR_USEBURST);

// Configure the control parameters for the primary & alternate control structure for SSI0RX

/* source address - no increment
* destination address - increment is 16-bit
* arbitration size - 4 // fixed at 4
* transfer data size - 16 bit
*/
uDMAChannelControlSet ( UDMA0_BASE, \
UDMA_CHAN_SSI0_RX | UDMA_PRI_SELECT, \
UDMA_SIZE_32 | UDMA_SRC_INC_NONE | UDMA_DST_INC_32 | \
UDMA_ARB_4 | UDMA_NEXT_USEBURST);

/* DMA settings for SPI TX */
// Put the attributes in a known state for the uDMA SSI0RX TX channel. Disable the attr by default
uDMAChannelAttributeDisable(UDMA0_BASE,
UDMA_CHAN_SSI0_TX, \
UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | \
UDMA_ATTR_HIGH_PRIORITY | \
UDMA_ATTR_REQMASK);

uDMAChannelAttributeEnable(UDMA0_BASE, UDMA_CHAN_SSI0_TX, UDMA_ATTR_USEBURST);

// Configure the control parameters for the primary & alternate control structure for SSI0TX

/* source address - no increment
* destination address - no increment
* arbitration size - 4 // fixed at 4
* transfer data size - 16 bit
*/
uDMAChannelControlSet ( UDMA0_BASE, \
UDMA_CHAN_SSI0_TX | UDMA_PRI_SELECT, \
UDMA_SIZE_32 | UDMA_SRC_INC_32 | UDMA_DST_INC_NONE| \
UDMA_ARB_4 | UDMA_NEXT_USEBURST);

/* Re-enable interrupts */
HwiP_restore(key);

/* enable the spi module for dma */
SSIDisable(SSI0_BASE); //disable operation of the SSI to make sure the CR1:SSE is cleared
SSIDMAEnable(SSI0_BASE, SSI_DMA_TX | SSI_DMA_RX);
}

// Public accessing features of ADS131
void ADS131_Init(uint8_t spi_instance) {
// ******************************************************************
// Hardware initialization
// ******************************************************************
unsigned int key;

/* Disable interrupts when transfer */
key = HwiP_disable();

/* power up and enable clock for SPI. */
PRCMPeripheralRunEnable(PRCM_PERIPH_SSI0); // SSI0
PRCMPeripheralSleepEnable(PRCM_PERIPH_SSI0);
PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_SSI0);
PRCMLoadSet();
while(!PRCMLoadGet());

HwiP_restore(key);

/* Configure IOs for SSI0: Base, RX, TX, CS, CLK */
IOCPinTypeSsiMaster(SSI0_BASE, IOID_11, IOID_10, IOID_UNUSED, IOID_12);

/* Configure SSI0 */
//disable operation of the SSI to make sure the CR1:SSE is cleared
SSIDisable(SSI0_BASE);

SSIConfigSetExpClk(SSI0_BASE, \
SysCtrlClockGet(), \
SSI_FRF_MOTO_MODE_1, \
SSI_MODE_MASTER, \
8000000, \
8);

SSIIntDisable(SSI0_BASE, SSI_TXFF | SSI_RXFF | SSI_RXTO | SSI_RXOR);
/* Configure DMA driver */
SPIDMA_init();

// enable the SPI module
SSIEnable(SSI0_BASE);

GPIO_setConfig(CONFIG_SPI_ADC_CS, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_HIGH);
GPIO_write(CONFIG_SPI_ADC_CS, 1);
}

  • Hi,

    We do not recommend to use driverlib functions, this could cause some conflicts when accessing hardware resources.

    In you case, I would verify if some issues could be caused by some conflicts in the DMA accesses. For example, make sure two different uDMA instances are used for the two DMA used.

    Best regards,

  • Dear Clement,

    thank you for your help. I have resolved the issue, which was caused by a conflicting DMA Control Table. During initialization of the TI Drivers 

    UDMACC26XX_initHw(UDMACC26XX_Handle handle)

    is called, which will set the DMA Control Table to 

    uDMAControlBaseSet(hwAttrs->baseAddr, (void *) UDMACC26XX_CONFIG_BASE);

    However my own driverlib DMA initialization eventually sets the DMA Control Table as well:

    • tDMAControlTable DMAControlTable[64] __attribute__ ((aligned (1024)));
    • uDMAControlBaseSet  (UDMA0_BASE, &DMAControlTable);

    by removing the initialization in my own driver and keeping the TI Drivers initialization, both DMAs and SPIs work!

    Cheers.