Atmel website | ARM Community | AVR freaks | Technical Support
Banner
Welcome to AT91SAM Community Forum
http://www.at91.com/samphpbb/

SAM7X512's SPI peripheral gets disabled on write to SPI_TDR
http://www.at91.com/samphpbb/viewtopic.php?f=15&t=19077
Page 1 of 1

Author:  mDor [ Thu Mar 18, 2010 10:51 pm ]
Post subject:  SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

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):
  1. 1. SPI_MR:
    • MSTR = 1
    • PS = 0
    • PCSDEC = 0
    • PCS = 0111
    • DLYBCS = 0
  2. 2. SPI_CSR[3]:
    • CPOL = 0
    • NCPHA = 1
    • CSAAT = 0
    • BITS = 0000
    • SCBR = 20
    • DLYBS = 0
    • DLYBCT = 0
  3. 3. 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:
Code:
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:
  1. 1. What's causing the SPI peripheral to become disabled on the write to SPI_TDR?
  2. 2. 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)
    Code:
    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);
    }
  3. 3. 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 use the PDC/DMA controller and prefer not using it.
  • * 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:
    Quote:
    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:
An example of initializing the SPI and performing a transfer of 5 bytes is highly appreciated and helpful.

Thank you.

Best regards,
Dor.

Author:  dfridley [ Fri Mar 19, 2010 12:35 am ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Hi Dor,

Here is initialization code and spi data writing code that uses the Variable mode of the SPI peripheral. I mangled the names to C style (this is from a CPP project) and you will not be able to drop this in and use it, but, I think you should be able compare it to what you are doing. This is on a system that has 3 SPI peripherals that we communicate to, each on its own CS line.

Hope it helps...

The standard disclaimer applies! :D

Code:
// NOTE: Code excerpts from a project. Will NOT compile directly but to be used as example.
// This code uses the SPI variable mode (i.e. SPI peripheral handles the CS lines)

// TYPEDEFS / CLASS DEFINITION =====================================================================

// Normally in a typedefs.h file but included here
typedef unsigned char       U8;
typedef unsigned int        U32;

// Normally in spi.h file but included here
typedef enum
{
    SPI_CS0,
    SPI_CS1,
    SPI_CS2,
    SPI_CS3
}eSpiCs;

// DEFINES and CONSTANTS ===========================================================================

// Normally in a typedefs.h file but included here
const U32 BIT0  = 0x00000001;
const U32 BIT1  = 0x00000002;
const U32 BIT2  = 0x00000004;
const U32 BIT3  = 0x00000008;
const U32 BIT4  = 0x00000010;
const U32 BIT5  = 0x00000020;
const U32 BIT6  = 0x00000040;
const U32 BIT7  = 0x00000080;
const U32 BIT8  = 0x00000100;
const U32 BIT9  = 0x00000200;
const U32 BIT10 = 0x00000400;
const U32 BIT11 = 0x00000800;
const U32 BIT12 = 0x00001000;
const U32 BIT13 = 0x00002000;
const U32 BIT14 = 0x00004000;
const U32 BIT15 = 0x00008000;
const U32 BIT16 = 0x00010000;
const U32 BIT17 = 0x00020000;
const U32 BIT18 = 0x00040000;
const U32 BIT19 = 0x00080000;
const U32 BIT20 = 0x00100000;
const U32 BIT21 = 0x00200000;
const U32 BIT22 = 0x00400000;
const U32 BIT23 = 0x00800000;
const U32 BIT24 = 0x01000000;
const U32 BIT25 = 0x02000000;
const U32 BIT26 = 0x04000000;
const U32 BIT27 = 0x08000000;
const U32 BIT28 = 0x10000000;
const U32 BIT29 = 0x20000000;
const U32 BIT30 = 0x40000000;
const U32 BIT31 = 0x80000000;

// .c file constants
static const U32 SPI_PORT_CS0_BIT    = BIT4;
static const U32 SPI_PORT_CS1_BIT    = BIT5;
static const U32 SPI_PORT_CS2_BIT    = BIT6;
static const U32 SPI_PORT_MISO_BIT   = BIT8;
static const U32 SPI_PORT_MOSI_BIT   = BIT9;
static const U32 SPI_PORT_CLK_BIT    = BIT10;
static const U32 SPI_PERIPH_ID       = BIT12;

// The chip select lines used when sending data.
// These values are loaded into the SPI Transmit Data Register (TDR) when sending data.
static const U32 SPI_TXRX_CS0 = BIT19 | BIT18 | BIT17        ;
static const U32 SPI_TXRX_CS1 = BIT19 | BIT18 |         BIT16;
static const U32 SPI_TXRX_CS2 = BIT19 |         BIT17 | BIT16;
static const U32 SPI_TXRX_CS3 =         BIT18 | BIT17 | BIT16;

// VARIABLES =======================================================================================


// PROTOTYPES ======================================================================================


// CODE ============================================================================================

/*------------------------------------------------------------------------------
  Description
    Initializes the spi hardware

  Remarks
    Uses the microprocessor's SPI1 port
------------------------------------------------------------------------------*/
void Spi_Init( void )
{
    // Bits used to configure the spi registers
    const U32 CSR_NCPHA = BIT1;
    const U32 CSR_CSAAT = BIT3;
    const U32 MR_MSTR = BIT0;
    const U32 MR_PS = BIT1;
    const U32 MR_MODFDIS = BIT4;
    const U32 CR_SPIEN = BIT0;
    const U32 CR_SWRST = BIT7;

    m_pRegs = (SPI_SFRs *)SPI1_BASE;

    // Assign SPI pins to the peripheral B functions
    gcPortA.PeriphBOn( SPI_PORT_CS0_BIT | SPI_PORT_CS1_BIT | SPI_PORT_CS2_BIT |
                       SPI_PORT_MISO_BIT | SPI_PORT_MOSI_BIT | SPI_PORT_CLK_BIT );

    // Perform SW reset
    m_pRegs->CR = CR_SWRST;

    gcPMC.EnablePeriphClock( SPI_PERIPH_ID );

    // Setup for the chips: Mode 0, 8 Data bits, baud rate MCK/15, CSAAT
    m_pRegs->CSR0 = CSR_NCPHA | CSR_CSAAT | (U32)(0xF<<8);
    m_pRegs->CSR1 = CSR_NCPHA | CSR_CSAAT | (U32)(0xF<<8);
    m_pRegs->CSR2 = CSR_NCPHA | CSR_CSAAT | (U32)(0xF<<8);

    // Set the configuration: Master, Disable Mode Fault Detection, Peripheral Select
    m_pRegs->MR = MR_MSTR | MR_PS | MR_MODFDIS;

    // Enable the spi
    m_pRegs->CR = CR_SPIEN;
}


/*--------------------------------------------------------------------------------------------------
  Description
    Sends a byte on the spi port and returns the byte received

  Inputs
    data_ - The data to send
    spiCs_ - The chip select to use
    assertCs_ - true: sets the cs line high after sending, false: leaves the cs line low

  Returns
    The byte read from the bus

  Remarks
    Assumes the spi is NOT set up to use fixed CS's. Sends the byte passed in on the bus and
    retrieves the byte returned.
--------------------------------------------------------------------------------------------------*/
U8 Spi_SendByte(U8 data_, eSpiCs spiCs_, bool assertCs_ )
{
    U8 returnVal;
    U32 txData;

    while( !(m_pRegs->SR & BIT1) )
    {
        ; // transfer compl. wait
    }

    txData = (U32)data_;

    // Load the CS line
    switch( spiCs_ )
    {
        case SPI_CS0:
            txData |= SPI_TXRX_CS0;
            break;

        case SPI_CS1:
            txData |= SPI_TXRX_CS1;
            break;

        case SPI_CS2:
            txData |= SPI_TXRX_CS2;
            break;

        case SPI_CS3:
            txData |= SPI_TXRX_CS3;
            break;

        default:
            break;
    }

    // Check if the CS line should be asserted after this transfer
    if( assertCs_ == true )
    {
        txData |= BIT24;
    }
    returnVal = (U8)m_pRegs->RDR;
    m_pRegs->TDR = txData;

    while( !(m_pRegs->SR & BIT0) )
    {
        ; // wait for char
    }
    returnVal = (U8)m_pRegs->RDR;

    return( returnVal );
}

/*--------------------------------------------------------------------------------------------------
  Description
    Example of sending data data over the spi bus

  Remarks
    Sends 5 bytes of random data over the spi bus using CS0.
    When the last byte is sent the CS line is pulled back high.
--------------------------------------------------------------------------------------------------*/
void SendDataExample( void )
{
    // Filling array with random data
    U8 data[] = {0xA5, 0x34, 0x12, 0x00, 0xFF};
   
    // Loop through the data until the last byte
    for( U8 index=0; index < (U8)(sizeof(data)-1); index++ )
    {
        // Ignore the return value, and make sure the CS line is low while
        // sending this packet
        (void)Spi_SendByte( data[index], SPI_CS0, false );
    }
    // After this last byte is sent pull the CS line high
    // Also don't care about the return value.
    (void)Spi_SendByte( data[index], SPI_CS0, true );
}

Author:  mDor [ Fri Mar 19, 2010 11:38 am ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Hi dfridley,

Thank you for the code! :) I'll compare that to the code that I've written when I'll be at work.

Few things noticed:
  1. * A SPI software reset is performed only once, which contradicts the errata that tells to perform a reset twice.
  2. * The Master mode is set after configuring the SPI_CSR register, which also contradicts the errata, that tells to set the Master mode before configuring the SPI_CSR register.
Just in case - Is that code developed for another SAM7 MCU? :)

In general to everyone:
I'd love to see additional code examples that closely resembles to my situation: fixed CS lines, CS#3 is in PIO mode, etc.

Author:  dfridley [ Fri Mar 19, 2010 1:55 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Hi Dor,

This is for AT91SAM7X512.

Take note how we handle the write.

Spi_Write()
1) Wait until the TX register is empty (make sure there isn't data waiting to go out)
2) Perform dummy read of RX register (makes sure Bit0 is clear)
3) Send the data to the TX register
4) Wait until a byte is RX'd (if we recieved a byte the TX is complete)
5) Read the RX register and return the value

I believe this is different than the way you write bytes.

Quote:
Code:
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.
}


Try Changing this to:

Code:
// snip...

SetCS3();   // NPCS3 == PIOA15
while(i-- < dataSize) {
   mySPI_Write(data[i]);
   dummyRead = AT91C_BASE_SPI0->SPI_RDR;
}
ClearCS3();

/**********************************/
void mySPI_Write(unsigned char data) {
   volatile unsigned int dummy;

   while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TXEMPTY) == 0);
   dummy = AT91C_BASE_SPI0->SPI_RDR;
   AT91C_BASE_SPI0->SPI_TDR = data;
   while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TDRE) == 0);
}


Or you could condense by returning the recieved byte from the write.
Then you could recieve data simply by "rxData = mySPI_Write(0)"

Code:
// snip...

SetCS3();   // NPCS3 == PIOA15
while(i-- < dataSize) {
   (void)mySPI_Write(data[i]);
}
ClearCS3();

/**********************************/
unsigned char mySPI_Write(unsigned char data) {
   unsigned char dummy;

   // Make sure txer is empty
   while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TXEMPTY) == 0);

   // clear the rx flag by performing a dummy read
   dummy = AT91C_BASE_SPI0->SPI_RDR;

   // send the data
   AT91C_BASE_SPI0->SPI_TDR = data;

   // wait for reception
   while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TDRE) == 0);
   
   // return the recieved data
   dummy = AT91C_BASE_SPI0->SPI_RDR;
   return dummy;
}

Author:  mDor [ Fri Mar 19, 2010 3:54 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Hi dfridley,
dfridley wrote:
Take note how we handle the write.

Spi_Write()
1) Wait until the TX register is empty (make sure there isn't data waiting to go out)
2) Perform dummy read of RX register (makes sure Bit0 is clear)
3) Send the data to the TX register
4) Wait until a byte is RX'd (if we recieved a byte the TX is complete)
5) Read the RX register and return the value

I believe this is different than the way you write bytes.

Indeed!

The first code that you posted is different than the last one: in the first code you wait for RDRF bit to be set, and in the last code you wait for TDRE.

The first code:
Code:
U8 Spi_SendByte(U8 data_, eSpiCs spiCs_, bool assertCs_ )
{
    /* ... code omitted ... */
    while( !(m_pRegs->SR & BIT0) )
    {
        ; // wait for char
    }
    returnVal = (U8)m_pRegs->RDR;

    return( returnVal );
}


The last code:
Code:
unsigned char mySPI_Write(unsigned char data) {
   /* ... code omitted ... */
   // wait for reception
   while ((AT91C_BASE_SPI0->SPI_SR & AT91C_SPI_TDRE) == 0);
   
   // return the recieved data
   dummy = AT91C_BASE_SPI0->SPI_RDR;
   return dummy;
}


Does it make a big difference? or both are correct? :)

Author:  dfridley [ Fri Mar 19, 2010 4:05 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Yep sorry about that.

Darn cut paste error... :oops:

The first is correct. Second is Wrong!

Author:  mDor [ Fri Mar 19, 2010 4:15 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

dfridley wrote:
Yep sorry about that.

Darn cut paste error... :oops:

The first is correct. Second is Wrong!

OK, thank you very much! :D

Author:  dfridley [ Fri Mar 19, 2010 4:46 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Quote:
OK, thank you very much!


No problem. I hope it solves your issue.

BTW: if you use this to read data I would call this passing 0xFF to the write instead of my example of "rxData = mySPI_Write(0)". This way the data line stays high and you are just toggling the clock line.

ex.

Code:
// Of couse you would have to set and clear the chip selects
// and possibly send some command to the device you are working
// with before the collection example.

unsigned char buffer[20];
unsigned char index;

// Other code that sets up the device to send data...

for( index=0; index < sizeof(buffer); index++ )
{
    buffer[index] = mySPI_Write( 0xFF );
}

Author:  mDor [ Sun Mar 21, 2010 9:17 pm ]
Post subject:  Re: SAM7X512's SPI peripheral gets disabled on write to SPI_TDR

Hi dfridley,

Well, I went today to work to try the code that you posted, so before writing any code - I read your code again to fully understand what is does. Then, I noticed the following line that sets SPI_MR:
Code:
m_pRegs->MR = MR_MSTR | MR_PS | MR_MODFDIS;

The MR_MODFDIS field (mode fault detection) stood out. I remember that I didn't modified that field in my configurations. Add to that what is written in the data sheet and the event of a "SPI disable" is possible:
Quote:
When a mode fault is detected, the MODF bit in the SPI_SR is set until the SPI_SR is read and
the SPI is automatically disabled until re-enabled by writing the SPIEN bit in the SPI_CR (Con-
trol Register) at 1.
By default, the Mode Fault detection circuitry is enabled. The user can disable Mode Fault
detection by setting the MODFDIS bit in the SPI Mode Register (SPI_MR).


After disabling the mode fault detection feature, I didn't encounter any problems.
I hope that this is really the reason and that no more problems will occur in the future. I appreciate your kind help! :)

Page 1 of 1 All times are UTC + 1 hour [ DST ]
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/