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 ADS131void 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);}