AT91SAM7X512's SPI peripheral gets disabled on write to SPI_TDR
- by Dor
My AT91SAM7X512's SPI peripheral gets disabled on the X time (X varies) that I write to SPI_TDR.
As a result, the processor hangs on the while loop that checks the TDRE flag in SPI_SR. This while loop is located in the function SPI_Write() that belongs to the software package/library provided by ATMEL.
The problem occurs arbitrarily - sometimes everything works OK and sometimes it fails on repeated attempts (attemp = downloading the same binary to the MCU and running the program).
Configurations are (defined in the order of writing):
SPI_MR:
MSTR = 1
PS = 0
PCSDEC = 0
PCS = 0111
DLYBCS = 0
SPI_CSR[3]:
CPOL = 0
NCPHA = 1
CSAAT = 0
BITS = 0000
SCBR = 20
DLYBS = 0
DLYBCT = 0
SPI_CR:
SPIEN = 1
After setting the configurations, the code verifies that the SPI is enabled, by checking the SPIENS flag.
I perform a transmission of bytes as follows:
const short int dataSize = 5;
// Filling array with random data
unsigned char data[dataSize] = {0xA5, 0x34, 0x12, 0x00, 0xFF};
short int i = 0;
volatile unsigned short dummyRead;
SetCS3(); // NPCS3 == PIOA15
while(i-- < dataSize) {
mySPI_Write(data[i]);
while((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TXEMPTY) == 0);
dummyRead = SPI_Read(); // SPI_Read() from Atmel's library
}
ClearCS3();
/**********************************/
void mySPI_Write(unsigned char data) {
while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TXEMPTY) == 0);
AT91C_BASE_SPI0->SPI_TDR = data;
while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TDRE) == 0); // <-- This is where
// the processor hangs, because that the SPI peripheral is disabled
// (SPIENS equals 0), which makes TDRE equal to 0 forever.
}
Questions:
What's causing the SPI peripheral to become disabled on the write to SPI_TDR?
Should I un-comment the line in SPI_Write() that reads the SPI_RDR register?
Means, the 4th line in the following code: (The 4th line is originally marked as a comment)
void SPI_Write(AT91S_SPI *spi, unsigned int npcs, unsigned short data)
{
// Discard contents of RDR register
//volatile unsigned int discard = spi->SPI_RDR;
/* Send data */
while ((spi->SPI_SR & AT91C_SPI_TXEMPTY) == 0);
spi->SPI_TDR = data | SPI_PCS(npcs);
while ((spi->SPI_SR & AT91C_SPI_TDRE) == 0);
}
Is there something wrong with the code above that transmits 5 bytes of data?
Please note:
The NPCS line num. 3 is a GPIO line (means, in PIO mode), and is not controlled by the SPI controller.
I'm controlling this line by myself in the code, by de/asserting the ChipSelect#3 (NPCS3) pin when needed.
The reason that I'm doing so is because that problems occurred while trying to let the SPI controller to control this pin.
I didn't reset the SPI peripheral twice, because that the errata tells to reset it twice only if I perform a reset - which I don't do. Quoting the errata:
If a software reset (SWRST in the SPI Control Register) is performed, the SPI may not work
properly (the clock is enabled before the chip select.)
Problem Fix/Workaround
The SPI Control Register field, SWRST (Software Reset) needs to be written twice to be cor-
rectly set.
I noticed that sometimes, if I put a delay before the write to the SPI_TDR register (in SPI_Write()), then the code works perfectly and the communications succeeds.
Useful links:
AT91SAM7X Series Preliminary.pdf
ATMEL software package/library
spi.c from Atmel's library
spi.h from Atmel's library
An example of initializing the SPI and performing a transfer of 5 bytes is highly appreciated and helpful.