Hello all,
I'm using a SAM4S2A and communicating with two external devices via SPI using manual chip select (PS bit = 0). I'm building code in Keil MDK-ARM 5.27.1.0 and using FreeRTOS.
The SPI_MR setup looks like this:
SPI->SPI_MR = SPI_MR_MSTR /* Master mode */ | SPI_MR_MODFDIS /* Mode fault detection is disabled */ | SPI_MR_DLYBCS( SPI_COMMON_DLYBCS ); /* Common delay between chip select activations */
The SPI_CS registers for my two peripherals look like this:
SPI->SPI_CSR[0] = SPI_CSR_CPOL | SPI_CSR_NCPHA | SPI_CSR_BITS_8_BIT | SPI_CSR_SCBR ( SPI_CSR_0_SBCR ) | SPI_CSR_DLYBS ( SPI_CSR_0_DLYBS ) | SPI_CSR_DLYBCT ( SPI_CSR_0_DLYBCT );
SPI->SPI_CSR[3] = SPI_CSR_NCPHA | SPI_CSR_BITS_8_BIT | SPI_CSR_SCBR ( SPI_CSR_3_SBCR ) | SPI_CSR_DLYBS ( SPI_CSR_3_DLYBS ) | SPI_CSR_DLYBCT ( SPI_CSR_3_DLYBCT );
I'm enabling the PDC in my init code, for both transmitting and receiving:
PDC_SPI->PERIPH_PTCR = PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN;
This works great for transmitting. My code for transmitting a packet of 3 bytes looks like this:
static uint8_t spi_write_internal( uint8_t const * buf_p, uint16_t num_out_bytes ) { uint8_t ret_val = 1; /* Initialize the pointer to the write/read buffer. This is incremented by the PDC as the write happens. */ PDC_SPI->PERIPH_TPR = ( uint32_t )buf_p; /* Set the transfer count. This is decremented by the PDC as the write happens. The PDC should already be enabled for transmitting. This should kick off the transfer. */ PDC_SPI->PERIPH_TCR = num_out_bytes; /* Enable the completion interrupt; ENDRX indicates all data bits are shifted out - see the datasheet figure 33-9 p. 697. */ SPI->SPI_IER = SPI_IER_TXEMPTY; /* Wait for the semaphore */ BaseType_t sem_take_result = xSemaphoreTake( spi_sem_h, SPI_TIMEOUT ); if ( pdFALSE == sem_take_result ) { /* We didn't get the semaphore within the expected timeout period. It should have been given by our interrupt handler when the transfer was completed. Attempt to give the mutex without checking the error return and bail out. */ Runtime_Err_Report( RUNTIME_ERR_SPI_DRIVER_TAKE_SEM_FAILED ); ret_val = 0; } return ret_val; }
I'm setting the chip select before calling this function:
/* Set the peripheral select bits manually */ SPI->SPI_MR = ( ( SPI->SPI_MR & ~SPI_MR_PCS_Msk ) | SPI_MR_PCS( periph_select_bits ) );
I'm using an interrupt handler to catch the end of transfer and give the semaphore. Currently I'm using TXEMPTY but I've also done it with ENDRX and that works OK for transmitting, too.
This works great for transmitting, but I'm trying to use it for receiving. Specifically, I want to take the chip select low, clock out a command, and then continue to transmit zeroes while the chip select is still low to read several bytes of data before the chip select goes high.
The problem I'm having is that for all my receive transactions, the SPI peripheral only sends the first outgoing byte and then it seems to get into an overflow condition. I can see the peripheral take control of MISO but before any clocks go out, the transmission ends and I never get my completion interrupt.
I've tried messing with the CSAAT bits and that will result in the chip select staying low, but no more bytes. I've tried avoiding the OVRES condition by reading SPI_SR and RDR before setting the read counter. It doesn't seem to make a difference whether I disable and re-enable the PDC for each transaction, or not.
Here's my read routine (which fails as described) --
static uint8_t spi_read_internal( uint8_t * buf_p, uint16_t num_in_bytes ) { uint8_t ret_val = 1; /* Set the transmit data register to send zeroes when we read */ SPI->SPI_TDR = 0; /* Set up the pointer, which the PDC will increment */ PDC_SPI->PERIPH_RPR = ( uint32_t )buf_p; /* Set the read count */ PDC_SPI->PERIPH_RCR = num_in_bytes; /* Enable the completion interrupt; ENDRX indicates all data bits are shifted out - see the datasheet figure 33-9 p. 697. */ SPI->SPI_IER = SPI_IER_TXEMPTY; /* Wait for the semaphore */ BaseType_t sem_take_result = xSemaphoreTake( spi_sem_h, SPI_TIMEOUT ); if ( pdFALSE == sem_take_result ) { /* We didn't get the semaphore within the expected timeout period. It should have been given by our interrupt handler when the transfer was completed. */ Runtime_Err_Report( RUNTIME_ERR_SPI_DRIVER_TAKE_SEM_FAILED ); ret_val = 0; } return ret_val; }
It seems like opening up a single transaction and doing a write-then-read with the PDC should be a common and simple use case, especially since peripherals like the Atmel AT25M01 EEPROM chip expects this. Any suggestions?
Thanks,
Paul R. Potts