Dear TI Support Team,
a software bug in SPICC26XXDMA.c breaks the "Master mode with multiple slaves using hardware chip select" use case scenario documented in the driver's Doxygen documentation due to broken implementation of the SPICC26XXDMA_CMD_SET_CSN_PIN command within the SPICC26XXDMA_control() API.
The issue is present in all TI Drivers component releases up to and including tidrivers_cc13xx_cc26xx_2_16_00_08. Tested distribution packages are tirtos_cc13xx_cc26xx_2_16_00_08 and tirtos_simplelink_2_13_00_06.
According to the driver documentation to change the SSI peripheral's chip select pin definition at run-time, while the SSI peripheral is open in master mode, the following example code could be used as reference:
// Init SPI and specify non-default parameters SPI_Params_init(¶ms); params.bitRate = 1000000; params.frameFormat = SPI_POL1_PHA1; params.mode = SPI_MASTER; // Configure the transaction transaction.count = sizeof(txBuf); transaction.txBuf = txBuf; transaction.rxBuf = NULL; // Open the SPI and perform transfer to the first slave handle = SPI_open(Board_SPI, ¶ms); SPI_transfer(handle, &transaction); // Then switch chip select pin and perform transfer to the second slave SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csnPin1); SPI_transfer(handle, &transaction);
In the above code the SPI_contol() API is used to change the active chip select pin definition between transactions.
According to the driver documentation hardware chip select is ought to be supported by both master and slave operation modes. Calling SPI_open() configures the default chip select pin to output (master mode) or input with pull-up (slave mode) within the SPICC26XXDMA_initIO() API:
/* Configure IOs */ /* Build local list of pins, allocate through PIN driver and map HW ports */ if (object->mode == SPI_SLAVE) { /* Configure IOs for slave mode */ ... spiPinTable[i++] = object->csnPin | PIN_INPUT_EN | PIN_PULLUP; } else { /* Configure IOs for master mode */ ... /* If CSN isn't SW controlled, drive it high until SPI module drives signal to avoid glitches */ if(object->csnPin != PIN_UNASSIGNED) { spiPinTable[i++] = object->csnPin | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; } }
As mentioned earlier according to the documentation the driver is supposed to support hardware chip select in master mode with multiple slaves on the same bus line as well. In this use case the driver documentation states that the active chip select pin can be reconfigured by calling SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csPin) before initiating the actual SPI transaction. But the implementation of SPICC26XXDMA_control() only supports slave mode hardware chip select configuration. Within the SPICC26XXDMA_CMD_SET_CSN_PIN command handler code snippet it is assumed that the chip select pin needs to be configured as input with pull-up enabled regardless of the SPI driver's operation mode (master or slave):
case SPICC26XXDMA_CMD_SET_CSN_PIN: /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */ pinConfig = PIN_INPUT_EN | PIN_PULLUP | (*(PIN_Id *) arg); /* Attempt to add the new pin */ if (PIN_add(object->pinHandle, pinConfig) == PIN_SUCCESS) { /* Configure pin mux */ PINCC26XX_setMux(object->pinHandle, *(PIN_Id *)arg, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS)); /* Remove old pin and revert to default setting specified in the board file */ PIN_remove(object->pinHandle, object->csnPin); /* Keep track of current CSN pin */ object->csnPin = *(PIN_Id *)arg; /* Set return value to indicate success */ ret = SPI_STATUS_SUCCESS; } break;
In the above code pinConfig is set up according to the slave mode SSI operation mode and there is no conditional configuration based on operation mode. There are two issues with the existing implementation. One is that master mode hardware chip select reconfiguration is not implemented and the other is that a valid master mode configuration gets completely broken by calling SPI_control(handle, SPICC26XXDMA_SET_CSN_PIN, &csPin) as it changes the chip select into input so the entire SSI driver gets broken as long as SPI_close() and SPI_open() are not called again.
To eliminate the issue the implementation of SPICC26XXDMA_control() needs to be extended to conditionally set up the pin based on SPI operation mode (master or slave):
case SPICC26XXDMA_SET_CSN_PIN: /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */ if (object->mode == SPI_SLAVE) { pinConfig = PIN_INPUT_EN | PIN_PULLUP | (*(PIN_Id *) arg); } else { pinConfig = PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED | (*(PIN_Id *) arg); }
Another recommendation would be not to cast and dereference arg , (*(PIN_Id *) arg), each time manually within SPICC26XXDMA_control() instead create a local variable to hold the result of the operation which the compiler can optimize out putting the value into a Cortex-M3 scratch register:
PIN_Config pinConfig; PIN_Id pinId; /* Get the pointer to the object and hwAttr */ hwAttrs = handle->hwAttrs; object = handle->object; pinId = PIN_ID((*(PIN_Id *) arg)); /* Initialize return value*/ int ret = SPI_STATUS_ERROR; /* Perform command */ switch(cmd) { ... case SPICC26XXDMA_SET_CSN_PIN: /* Configure CSN pin and remap PIN_ID to new CSN pin specified by arg */ if (object->mode == SPI_SLAVE) { pinConfig = PIN_INPUT_EN | PIN_PULLUP | pinId; } else { pinConfig = pinId | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_INPUT_DIS | PIN_DRVSTR_MED; } if (PIN_UNASSIGNED != pinId) { ... /* Configure pin mux */ PINCC26XX_setMux(object->pinHandle, pinId, (hwAttrs->baseAddr == SSI0_BASE ? IOC_PORT_MCU_SSI0_FSS : IOC_PORT_MCU_SSI1_FSS)); ... /* Keep track of current CSN pin */ object->csnPin = pinId;
Please fix the above described issue and if possible provide information on whether the bug can be fixed in the next TI-RTOS for CC13xx and CC26xx release and whether the fix would make it into the next CC26xx BLE-SDK release.
Thank you in advance for your kind support.
Best regards,
Tamas
_