Is LPSPI Slave Read-Only Half-Duplex Possible on RT1176?

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Is LPSPI Slave Read-Only Half-Duplex Possible on RT1176?

Jump to solution
965 Views
mrd
Contributor III

I have application where need to receive data from an external A/D converter through LPSPI port in slave mode. The A/D converter will be driving LPSPI2's PCS0 low and clocking data into the RT1176 using SCK and SIN. The RT1176 SOUT would not be used. All GPIO pins are used so I cannot allocate a pin for SOUT. My hope is that I can use half-duplex where SIN is input and also output via CFGR1[PINCFG] but to avoid contention I don't want SIN to ever be in the output direction.

Can someone confirm that I can force SIN to always be an input in half-duplex mode by setting the TXMSK bit in the TCR?

I've read chapter 70 LPSPI section in the RT1170 reference manual several times. It took me a while to realize that TXMSK=1 keeps SIN in the input direction when using half-duplex mode. Please let me know if I have this wrong.

If there is an application note describing how LPSPI half-duplex works, please let me know that too.

Thank you!

0 Kudos
1 Solution
856 Views
mrd
Contributor III

Thank you to NXP support (Hui_Ma) for the response.   There is good information in your code excerpts, but my application is a little simpler in that I only need to receive from an A/D converter in LPSPI slave mode on the SIN and be able to use what would normally be the SOUT GPIO for another purpose (ACMP input in this case).  I found that this works fine.  Setting the TXMSK bit is all that is required so that SIN doesn't turn around to transmit.  In case it helps anyone else, the code excerpt for the setup is below.  I did use Peripheral configurator to setup the driver instance, but I found it necessary to redo most of the configuration as shown in the code excerpt below the picture.  I haven't try to sort out why Peripheral configuration didn't set things up...I may have left something out in the code, but I do know that LPSPI2_init() is being called by BOARD_InitPeripherals(), which is called from main() at start-up.  Having to do the lpspi2 setup in code wasn't a problem so I didn't look into this.

The only slightly annoying thing is that the Peripherals configurator warns that lpspi_sout is not routed so if anyone knows how to eliminate the warning I'd appreciate it.  Picture of my Peripheral setup for LPSPI2 is below.  Thank you.

Peripheral.png

 

// Setup to receive data in LPSPI slave mode from the ADC
// The ADC is the SPI master. RT1176 is the slave.
lpspi_slave_config_t slaveConfig;
LPSPI_SlaveGetDefaultConfig(&slaveConfig);
slaveConfig.whichPcs = ADC_DATA_LPSPI_SLAVE_PCS;

// Set the SPI clock to idle low with active high.
// SPI shift happens on rising edge of DCLK
// and data bit is latched on DCLK falling edge
slaveConfig.cpol = kLPSPI_ClockPolarityActiveHigh;
slaveConfig.cpha = kLPSPI_ClockPhaseSecondEdge;

// Setup for half-duplex. Only want to read in ADC data on SIN.
// I.e., ADC_DO is always an output. It is connected to the RT1176
// LPSPI SIN pin, which must always be an input. Never transmit
// to the ADC on its data LPSPI connection.
slaveConfig.pinCfg = kLPSPI_SdiInSdiOut; // We won't be outputting anything
slaveConfig.dataOutConfig = kLpspiDataOutTristate; // Tristate data output

// Initialize the LPSPI slave interface.
// Note that the PCS# (peripheral chip select) in slaveConfig.whichPcs
// is only used to configure the polarity. The PCS# selection
// must be done separately below.
LPSPI_SlaveInit(ADC_DATA_LPSPI_SLAVE_BASEADDR, &slaveConfig);

// Clear buffer for receiving ADC data over LPSPI in ISR.
for (unsigned i = 0; i < SLAVE_RX_DATA_MAX; i++) {
_slaveRxData[i] = 0;
}

// Set the Receiver FIFO water mark so LPSPI interrupts only when have full-packet of data.
// See comments at top of LPSPI2_IRQHandler() for more info.
#define ADC_DATA_RECEIVE_WATERMARK (16-1) // Interrupt when have 16 bytes. I.e., More than 15.
LPSPI_SetFifoWatermarks(ADC_DATA_LPSPI_SLAVE_BASEADDR, 0, ADC_DATA_RECEIVE_WATERMARK);

// Disable LPSPI "stall transfers" when xmit FIFO is empty to prevent transmit FIFO underrun error.
// Note this is probably N/A since running in Slave mode, but NXP's demo code for Slave
// receive does this so copying it for now.
LPSPI_Enable(ADC_DATA_LPSPI_SLAVE_BASEADDR, false);
ADC_DATA_LPSPI_SLAVE_BASEADDR->CFGR1 &= (~LPSPI_CFGR1_NOSTALL_MASK);
LPSPI_Enable(ADC_DATA_LPSPI_SLAVE_BASEADDR, true);

// Flush FIFO , clear status , disable all LPSPIx interrupts.
LPSPI_FlushFifo(ADC_DATA_LPSPI_SLAVE_BASEADDR, true, true);
LPSPI_ClearStatusFlags(ADC_DATA_LPSPI_SLAVE_BASEADDR, kLPSPI_AllStatusFlag);
LPSPI_DisableInterrupts(ADC_DATA_LPSPI_SLAVE_BASEADDR, kLPSPI_AllInterruptEnable);

// Select the PCS# that the ADC will be driving low (with its ODR output) when data is ready.
LPSPI_SelectTransferPCS(ADC_DATA_LPSPI_SLAVE_BASEADDR, ADC_DATA_LPSPI_SLAVE_PCS);

// Setup for half-duplex so can use the GPIO that would normally be the SDO output
// for another purpose. Only receive from the ADC. Need to disable the transmit direction.
ADC_DATA_LPSPI_SLAVE_BASEADDR->TCR |= LPSPI_TCR_TXMSK_MASK;

// Enable the NVIC (Nested-Vector-Interrupt-Controller) for the LPSPI peripheral.
EnableIRQ(ADC_DATA_LPSPI_SLAVE_IRQN);

// TCR is also shared the FIFO , so wait for TCR written before enabling the interrupt.
while (LPSPI_GetTxFifoCount(ADC_DATA_LPSPI_SLAVE_BASEADDR) != 0) {
;
}

// Enable the Frame Complete and Receive Interrupts. 
LPSPI_EnableInterrupts(ADC_DATA_LPSPI_SLAVE_BASEADDR, (kLPSPI_FrameCompleteInterruptEnable | kLPSPI_RxInterruptEnable));

 

View solution in original post

0 Kudos
2 Replies
857 Views
mrd
Contributor III

Thank you to NXP support (Hui_Ma) for the response.   There is good information in your code excerpts, but my application is a little simpler in that I only need to receive from an A/D converter in LPSPI slave mode on the SIN and be able to use what would normally be the SOUT GPIO for another purpose (ACMP input in this case).  I found that this works fine.  Setting the TXMSK bit is all that is required so that SIN doesn't turn around to transmit.  In case it helps anyone else, the code excerpt for the setup is below.  I did use Peripheral configurator to setup the driver instance, but I found it necessary to redo most of the configuration as shown in the code excerpt below the picture.  I haven't try to sort out why Peripheral configuration didn't set things up...I may have left something out in the code, but I do know that LPSPI2_init() is being called by BOARD_InitPeripherals(), which is called from main() at start-up.  Having to do the lpspi2 setup in code wasn't a problem so I didn't look into this.

The only slightly annoying thing is that the Peripherals configurator warns that lpspi_sout is not routed so if anyone knows how to eliminate the warning I'd appreciate it.  Picture of my Peripheral setup for LPSPI2 is below.  Thank you.

Peripheral.png

 

// Setup to receive data in LPSPI slave mode from the ADC
// The ADC is the SPI master. RT1176 is the slave.
lpspi_slave_config_t slaveConfig;
LPSPI_SlaveGetDefaultConfig(&slaveConfig);
slaveConfig.whichPcs = ADC_DATA_LPSPI_SLAVE_PCS;

// Set the SPI clock to idle low with active high.
// SPI shift happens on rising edge of DCLK
// and data bit is latched on DCLK falling edge
slaveConfig.cpol = kLPSPI_ClockPolarityActiveHigh;
slaveConfig.cpha = kLPSPI_ClockPhaseSecondEdge;

// Setup for half-duplex. Only want to read in ADC data on SIN.
// I.e., ADC_DO is always an output. It is connected to the RT1176
// LPSPI SIN pin, which must always be an input. Never transmit
// to the ADC on its data LPSPI connection.
slaveConfig.pinCfg = kLPSPI_SdiInSdiOut; // We won't be outputting anything
slaveConfig.dataOutConfig = kLpspiDataOutTristate; // Tristate data output

// Initialize the LPSPI slave interface.
// Note that the PCS# (peripheral chip select) in slaveConfig.whichPcs
// is only used to configure the polarity. The PCS# selection
// must be done separately below.
LPSPI_SlaveInit(ADC_DATA_LPSPI_SLAVE_BASEADDR, &slaveConfig);

// Clear buffer for receiving ADC data over LPSPI in ISR.
for (unsigned i = 0; i < SLAVE_RX_DATA_MAX; i++) {
_slaveRxData[i] = 0;
}

// Set the Receiver FIFO water mark so LPSPI interrupts only when have full-packet of data.
// See comments at top of LPSPI2_IRQHandler() for more info.
#define ADC_DATA_RECEIVE_WATERMARK (16-1) // Interrupt when have 16 bytes. I.e., More than 15.
LPSPI_SetFifoWatermarks(ADC_DATA_LPSPI_SLAVE_BASEADDR, 0, ADC_DATA_RECEIVE_WATERMARK);

// Disable LPSPI "stall transfers" when xmit FIFO is empty to prevent transmit FIFO underrun error.
// Note this is probably N/A since running in Slave mode, but NXP's demo code for Slave
// receive does this so copying it for now.
LPSPI_Enable(ADC_DATA_LPSPI_SLAVE_BASEADDR, false);
ADC_DATA_LPSPI_SLAVE_BASEADDR->CFGR1 &= (~LPSPI_CFGR1_NOSTALL_MASK);
LPSPI_Enable(ADC_DATA_LPSPI_SLAVE_BASEADDR, true);

// Flush FIFO , clear status , disable all LPSPIx interrupts.
LPSPI_FlushFifo(ADC_DATA_LPSPI_SLAVE_BASEADDR, true, true);
LPSPI_ClearStatusFlags(ADC_DATA_LPSPI_SLAVE_BASEADDR, kLPSPI_AllStatusFlag);
LPSPI_DisableInterrupts(ADC_DATA_LPSPI_SLAVE_BASEADDR, kLPSPI_AllInterruptEnable);

// Select the PCS# that the ADC will be driving low (with its ODR output) when data is ready.
LPSPI_SelectTransferPCS(ADC_DATA_LPSPI_SLAVE_BASEADDR, ADC_DATA_LPSPI_SLAVE_PCS);

// Setup for half-duplex so can use the GPIO that would normally be the SDO output
// for another purpose. Only receive from the ADC. Need to disable the transmit direction.
ADC_DATA_LPSPI_SLAVE_BASEADDR->TCR |= LPSPI_TCR_TXMSK_MASK;

// Enable the NVIC (Nested-Vector-Interrupt-Controller) for the LPSPI peripheral.
EnableIRQ(ADC_DATA_LPSPI_SLAVE_IRQN);

// TCR is also shared the FIFO , so wait for TCR written before enabling the interrupt.
while (LPSPI_GetTxFifoCount(ADC_DATA_LPSPI_SLAVE_BASEADDR) != 0) {
;
}

// Enable the Frame Complete and Receive Interrupts. 
LPSPI_EnableInterrupts(ADC_DATA_LPSPI_SLAVE_BASEADDR, (kLPSPI_FrameCompleteInterruptEnable | kLPSPI_RxInterruptEnable));

 

0 Kudos
927 Views
Hui_Ma
NXP TechSupport
NXP TechSupport

Hi,

Yes, my colleague had a test with LPSPI SIN to do data receive with Half duplex 1-bit receive.

Please refer below test code for your reference:

1. Use polling b2b example

2. add these to LPSPI_MasterTransferNonBlocking() in fsl_lpspi.c

    /* If there is not rxData , can mask the receive data (receive data is not stored in receive FIFO).
* For master transfer , we'd better not masked the transmit data in TCR since the transfer flow is hard to
* controlled by software.*/
if (handle->rxData == NULL)
{
isRxMask = true;
handle->rxRemainingByteCount = 0;
}
if (handle->txData == NULL)
{
isTxMask = true;
handle->txRemainingByteCount = 0U;
}

3. change the master from blocking to nonblocking. (reference slave example)

/*Start master transfer, transfer data to slave.*/
masterXfer.txData = masterTxData;
masterXfer.rxData = NULL;
masterXfer.dataSize = TRANSFER_SIZE;
masterXfer.configFlags =
EXAMPLE_LPSPI_MASTER_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous | kLPSPI_SlaveByteSwap;

LPSPI_MasterTransferNonBlocking(EXAMPLE_LPSPI_MASTER_BASEADDR, &g_s_handle, &masterXfer);

while (!isTransferCompleted)
{
}

/* Set master transfer ready to receive data */
isTransferCompleted = false;
volatile uint32_t sr = LPSPI1->SR;
PRINTF("%x\n", sr);

/* Start master transfer, receive data from slave */
masterXfer.txData = NULL;
masterXfer.rxData = masterRxData;
masterXfer.dataSize = TRANSFER_SIZE;
masterXfer.configFlags =
EXAMPLE_LPSPI_MASTER_PCS_FOR_TRANSFER | kLPSPI_MasterPcsContinuous | kLPSPI_SlaveByteSwap;
LPSPI_MasterTransferNonBlocking(EXAMPLE_LPSPI_MASTER_BASEADDR, &g_s_handle, &masterXfer);

LPSPI1->TCR |= LPSPI_TCR_TXMSK_MASK;

while (!isTransferCompleted)
{
}

4. Add the following under MasterUserCallBack and SlaveUserCallBack, and must be before the PRINTF.

#define LPSPI_TCR_RXTXMSK (LPSPI_TCR_RXMSK_MASK | LPSPI_TCR_TXMSK_MASK)

void LPSPI_MasterUserCallback(LPSPI_Type *base, lpspi_master_handle_t *handle, status_t status, void *userData)
{
LPSPI1->TCR &= ~LPSPI_TCR_RXTXMSK;
LPSPI1->CFGR1 &= ~LPSPI_CFGR1_OUTCFG_MASK;

 

The requirements for half-duplex are - OUTCFG (set by LPSPI_Master/SlaveTransferNonBlocking when it detects PINCFG is 1 or 2), PINCFG, and RxMSK/TxMSK.