SPI CS0 Causing Freeze

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Greetings all,

 

First-time poster on this forum.  Please let me know if I should be posting elsewhere or if more information is required.

 

I've pored through the data sheet and all the app notes I can find, as well as any likely-looking Google hits for "atmel AVR32 SPI Freezes" and a couple of other terms, with much learning of details, but no real change in the hardware's behaviour.

 

I'm one of several software engineers working on a project for a medical device, using an Atmel AT32US3A0512.  Among other peripherals, the hardware team has given us two quadrature decoder chips (LSI's LS7366R) on SPI0.

 

The first of these chips is connected to CS1 and works quite well.

 

The second chip is connected to CS0, which is also NSS, and is giving us all sorts of "fun".  Basically, the SPI driver sends one byte, then times out.  By this, I mean that the driver is looking for the TDRE and TDXEMPTY signals and is not seeing them.  When the driver sees this enough times (currently set to 0x0FFFFF), it returns an error.  An SCK signal appears on the output for the first byte, but when I load the TDR for the second byte no SCK signal appears and the TDRE and TXEMPTY signals stay stubbornly low.  

 

One hypothesis I had was that the SPI hardware is switching to Slave mode when the CS0/NSS line goes low, even though I've set the MSTR bit (in several places, now).  However, this would seem not to be the case given the behaviour I've noted in the previous paragraph.

 

Any alternative hypotheses or advice would be more than welcome. I don't have enough hair left to tear any more out.  A very likely hypothesis is that I've missed something critical in the code and/or the documentation.

 

We have inherited a code base, including SPI and other drivers, from a previous project.  That in no way stops us from modifying it or correcting any issues we find.  However, it means there's a lot of "baggage" and proprietary information that I can't feasibly include.

 

Here are the relevant code fragments:

Setup:

            ISpi *pQuad2Spi = new SpiAt32uc3a(
                                            QUADRATURE2_SPI_ID,         // 0
                                            QUADRATURE2_SPI_CS,         // 0
                                            QUADRATURE2_SPI_DECODED,    // FALSE
                                            QUADRATURE2_SPI_BITS,       // 8
                                            QUADRATURE2_SPI_BAUD,       // 1000000
                                            clock_manager,
                                            QUADRATURE2_SPI_PIN_MAP,
                                            QUADRATURE2_SPI_MAP_LEN);
            vQuads[1] = new LS7366R(pQuad2Spi);

The pin map has CS0 mapped to AVR32_SPI0_NPCS_0_1_PIN (which is also PX30) and allocated to AVR32_SPI0_NPCS_0_1_FUNCTION.

 

SPI Constructor:

SpiAt32uc3a::SpiAt32uc3a(uint8_t id_in, uint8_t cs_in, bool decoded_cs_in, uint8_t bits, uint32_t baud, IClockMgr * clock_mgr, const IGpio::GpioMap_t * pin_maps, uint8_t len_pin_maps)
    : spi((id_in == 0)? &AVR32_SPI0 : &AVR32_SPI1), cs(cs_in), id(id_in), mCsaat(false)
{
    Init(decoded_cs_in, bits, baud, clock_mgr, pin_maps, len_pin_maps);
}


ISpi::spiError_t SpiAt32uc3a::Init(bool decoded_cs_in, uint8_t bits, uint32_t baud, IClockMgr * clock_mgr, const IGpio::GpioMap_t * pin_maps, uint8_t len_pin_maps)
{
    if ((id != 0) && (id != 1)) return ISpi::SPI_ERROR;
    if (cs >= ((decoded_cs_in) ? 15 : 4)) return ISpi::SPI_ERROR;
    if ((bits > 16) || (bits < 8)) return ISpi::SPI_ERROR;

    // Set local private references
    spi = (id == 0)? &AVR32_SPI0 : &AVR32_SPI1;
    decoded_cs[id] = decoded_cs_in;

    volatile avr32_spi_csr0_t * aCSR = &(spi->CSR0);
    uint8_t idx = (decoded_cs[id])? (cs >> 2) : cs;
    CSR = &aCSR[idx];

    // Set NCS[0] high before configuring, as otherwise it will put the chip into slave mode
    // NOTE: THIS WAS ADDED TODAY TO VAINLY TRY TO DEBUG THE CODE
    if (!configured[id])
    {
        // Reset the controller
        spi->CR.swrst = 1;

        const IGpio::GpioMap_t *maps = pin_maps;
        if (maps == NULL)
        {
            maps = (id == 0) ? default_pin_map_spi0 : default_pin_map_spi1;
        }
        cs0_pin[id] = new GpioAt32uc3a(1, &maps[2], 1, true);
    }
    cs0_pin[id]->SetOutput(1);
	
    //Set hardware features
    spi->MR.mstr = 1;           // Master mode by default, unless set otherwise NOTE: ALSO ADDED TODAY
    spi->MR.ps = 1;             // Set Peripheral Select to "Variable Peripheral Select" such that the active cs is declared during txrx
    spi->MR.dlybcs = 16;        // Delay between chip selects.
    CSR->csnaat = 0;            // CS line only rises after last transfer is flagged, not in between each transaction
    CSR->csaat = 0;             // CS line rises after last transfer is flagged, if does not stay active
    CSR->dlybct = 16;           // Delay before deasserting cs line (originally set to 48 in the inherited code)
    CSR->dlybs = 100;           // Delay from asserting Chip Select to starting the message

    // Set PCSDEC: Chip Select Decode
    spi->MR.pcsdec = decoded_cs[id];

    configured[id] = true;

    SetBits(bits);

    // Configure gpio peripheral functions
    if (pin_maps == NULL)
    {
        if (id == 0)
        {
            if (ConfigurePeripherals(default_pin_map_spi0, len_default_pin_map_spi0) != ISpi::SPI_NO_ERROR) return ISpi::SPI_ERROR;
        }
        else
        {
            if (ConfigurePeripherals(default_pin_map_spi1, len_default_pin_map_spi1) != ISpi::SPI_NO_ERROR) return ISpi::SPI_ERROR;
        }
    }
    else
    {
        if (ConfigurePeripherals(pin_maps, len_pin_maps) != ISpi::SPI_NO_ERROR) return ISpi::SPI_ERROR;
    }

    if (SetSpiMaster(true) != ISpi::SPI_NO_ERROR) return ISpi::SPI_ERROR;

    if ((int8_t)SetBaud(baud, clock_mgr) < 0) return ISpi::SPI_ERROR;

    if (Enable() != ISpi::SPI_NO_ERROR) return ISpi::SPI_ERROR;

	// Only required for the 32-bit transfers used for the stepper drivers.
    if (decoded_cs[id])
    {
        // For a decoded CS, this signal is output directly to a decoder
	    cs_bits = (cs << AVR32_SPI_TDR_PCS_OFFSET) & AVR32_SPI_TDR_PCS_MASK;
    }
    else
    {
        // For non-decoded CS bits, re-map as per the data sheet
        // Note that only 4 entries are allowed in this case
        const uint32_t csBitMapping[4] =
        {
            0x00000000,
            0x00010000,
            0x00030000,
            0x00070000
        };
        cs_bits = csBitMapping[cs];
    }   

    return ISpi::SPI_NO_ERROR;
}   // Init

Quadrature Decoder constructor:

LS7366R::LS7366R(ISpi * pSpi, uint8_t dataBytes) :
    m_bytesPerTransfer(dataBytes)
{
    Assert(dataBytes <= 4);   // Data is not allowed to be more than 4 bytes long
    iAssert(dataBytes > 0);    // Data is also not allowed to be 0 bytes long
    Init(pSpi);
}


bool LS7366R::Init( ISpi * pSpi )
{
    bool retValue = true;
    Assert(pSpi != 0, false);			// C++ does not like comparisons between member pointers and NULL

    m_pSpi    = pSpi;

    m_pSpi->SetSpiMaster(true);

    m_pSpi->SetBits(8);
    m_pSpi->SetCsBehavior(false, false, 8);

    m_pSpi->SetClkPolarity(false);
    m_pSpi->SetClkPhase(true);
    
    m_pSpi->Enable();

    DUMMY  // Dummy send to set the clock low
    
    // Set up the LS7366R
    union 
    {
        uint8_t byte;
        MDR0_t  mdr;
    } mdr0;
    mdr0.byte = 0;
    mdr0.mdr.quadratureMode = LS7366R::MDR0_QUAD_MODE_X1;
    this->Request(WR_MDR0, mdr0.byte);
    uint8_t regValue = this->Request(RD_MDR0);
    if (regValue != mdr0.byte)
    {
        retValue = false;
    }
    
    union
    {
        uint8_t byte;
        MDR1_t  mdr;
    } mdr1;
    mdr1.byte = 0;
    // Set the number of bytes per transfer
    mdr1.mdr.counterBytes = (m_bytesPerTransfer == 1) ? LS7366R::MDR1_BYTES_3 :
                                                        ((m_bytesPerTransfer == 2) ? LS7366R::MDR1_BYTES_2 :
                                                                                     ((m_bytesPerTransfer == 3) ? LS7366R::MDR1_BYTES_3 : 
                                                                                                                  LS7366R::MDR1_BYTES_4));
    this->Request(WR_MDR1, mdr1.byte);
    regValue = this->Request(RD_MDR1);
    if (regValue != mdr1.byte)
    {
        retValue = false;
    }

    // Clear the counter
    if (!this->ClearCounter())
    {
        retValue = false;
    }
    return retValue;
}   // Constructor

The "DUMMY" macro was sending a NOP byte because the decoder is fussy about the polarity of its signals, but as part of my debugging I have set the statement to an empty string.  This did not change anything.

 

The timeout failure happens at each of the "Request" statements.  The "Request" function is as follows:

uint32_t LS7366R::Request( uint8_t command, uint32_t data )
{
    uint32_t rx = 0;
    
    // Work out how many bytes this request has
    uint8_t  bytes = gBytesPerTransfer[command >> XFER_BYTE_INDEX_SHIFT];
    if (bytes > 0)
    {
        if (bytes == 1)
        {
            // Single-byte command
            rx = ls7366r_tx_rx(command);
        }
        else if ((bytes == 2) || (m_bytesPerTransfer == 1))
        {
            uint8_t  data8  =  (uint8_t)(data & 0x000000FF);
            rx = ls7366r_tx_rx(command, data8);
        }
        else if (m_bytesPerTransfer == 2)
        {
            uint16_t data16 = (uint16_t)(data & 0x0000FFFF);
            rx = ls7366r_tx_rx(command, data16);
        }
        else if (m_bytesPerTransfer == 3)
        {
            // This case needs to be handled slightly differently because of the unusual number of bits
            uint32_t data24 = (data & 0x00FFFFFF) | (command << 24);    // Shift command into the top bits
            TxRx((uint8_t *)(&data24), (uint8_t *)(&rx), 4);
            rx = rx >> 8;   // Shift the returned data down into the bottom 3 bytes
        }
        else
        {
            rx = ls7366r_tx_rx(command, data);
        }
    }
    
    return rx;
}   // Request

For most of the statements in the initialisation, the "Request" statement goes through the "((bytes == 2).." case, calling the overloaded function ls7366r_tx_rx as follows:

uint8_t LS7366R::ls7366r_tx_rx(uint8_t cmd, uint8_t out, bool *success)
{
    union instruct8_t
    {
        struct indata8_t
        {
            uint8_t     throwaway;  // A throw-away byte
            uint8_t     response;   // The actual response
        } instruct;
        uint8_t bytes[sizeof(struct indata8_t)];
    } indata;
    indata.instruct.throwaway = 0;
    indata.instruct.response = 0;
    
    // Transfer the data to a packed bit field
    struct 
    {
        uint8_t     cmd;
        uint8_t     out;
    } outdata;
    
    outdata.cmd = cmd;
    outdata.out = out;
    
    ISpi::spiError_t err = m_pSpi->TxRx(&outdata.cmd, &indata.instruct.throwaway, sizeof(outdata), true);
    if (success != NULL)
    {
        *success = (err == ISpi::SPI_NO_ERROR) ? true : false;
    }

    return indata.instruct.response;
}   // ls7366r_tx_rx(8-bit)

The TxRx function concerned is as follows:

ISpi::spiError_t SpiAt32uc3a::TxRx(const uint8_t * tx, uint8_t * rx, uint32_t len, bool blocking)
{
    ISpi::spiError_t status = ISpi::SPI_NO_ERROR;

    if(!configured[id])
    {
        status = ISpi::SPI_NOT_CONFIGURED;
    }
    else if (!IsEnabled())
    {
        status = ISpi::SPI_ERROR;
    }
    else if (CSR->bits != bits_8)
    {
        status = ISpi::SPI_ERROR;
    }
    else
    {
        // If busy give some time to clear, before failing.
        uint64_t timeout = txrxTimeout;
        while (IsBusy() && timeout--)
        {
            // Do nothing
        }
        if (timeout == 0) return ISpi::SPI_BUSY;

        // Stop the Chip Select line from rising after each block of bits
        CSR->csaat = 1;

        // set cs
        uint8_t set_cs = (~(1 << cs)) & ((1 << AVR32_SPI_TDR_PCS_SIZE) - 1);
        uint32_t tdr;

        for (uint32_t i = 0; i < len; i++)
        {
            // set data
            tdr = (tx != NULL)? tx[i] : 0;

            if (decoded_cs[id])
            {
                tdr |= (cs << AVR32_SPI_TDR_PCS_OFFSET) & AVR32_SPI_TDR_PCS_MASK;
            }
            else
            {
                tdr |= (set_cs << AVR32_SPI_TDR_PCS_OFFSET) & AVR32_SPI_TDR_PCS_MASK;
            }

            // set lastxfer bit;
            tdr |= (i == (len-1))? (1 << AVR32_SPI_TDR_LASTXFER_OFFSET) : 0;

            // Send the data
            spi->tdr = tdr;

            // Only wait for completion if blocking is true;
            if ((blocking) || (i < (len-1)))
            {
                timeout = txrxTimeout;
                while (IsBusy() && timeout--)
                {
                    // Do nothing
                }
                if (timeout == 0) return ISpi::SPI_ERROR;

                // If wrong cs, data error occurred
                if (spi->RDR.pcs != set_cs) return ISpi::SPI_ERROR;

                // Grab received data.
                if (rx != NULL)
                {
                    rx[i] = spi->RDR.rd & 0xFF;
                }
            }
            
            // On the last transfer, restore the Chip Select behaviour
            if (i >= (len - 1))
            {
                CSR->csaat = (mCsaat) ? 1 : 0;
            }
        }
    }

    // Overrun error status
    if (spi->SR.ovres != 0)
    {
        status = ISpi::SPI_ERROR;
    }
    
    // Mode fault error status
    if (spi->SR.modf != 0)
    {
        status = ISpi::SPI_ERROR;
    }
    return status;
}   // TxRx (variable length)

The fiddling with CSAAT was required to get multi-byte transfers performing satisfactorily without the CS line rising between transfers.

 

Finally, the "problem" function is "IsBusy":

bool SpiAt32uc3a::IsBusy()
{
    if(!configured[id]) return ISpi::SPI_NOT_CONFIGURED;

    // If spi is disabled, return false;
    if (!IsEnabled()) return false;

    return ((spi->SR.txempty == 0) || (spi->SR.tdre == 0));
}

What I am seeing here is that SR has a value of 0x010000 for channel 0, instead of the expected 010202.  That is, after loading the TDR for the second byte of a transfer on CS0 only.

 

Thanks for your attention.

 

Regards,

 

Geoff (Stage Leveller)

--
Geoff Field
Professional Geek, Amateur Stage Levelling Gauge

Last Edited: Thu. Jan 19, 2017 - 11:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The processor type is not " Atmel AT32US3A0512".  Instead, it is " Atmel AT32UC3A0512".

--
Geoff Field
Professional Geek, Amateur Stage Levelling Gauge

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

UPDATE: I commented out the fiddling with CSAAT to find that the multi-byte transfers started happening.

 

The problem now is that the Chip Select line rises between the individual bytes of the transfer.  That's why it was put in there in the first place.

 

Swings and roundabouts...

--
Geoff Field
Professional Geek, Amateur Stage Levelling Gauge

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My first thought was that SetBaud(,) or SetBits() were scrambling something in the CSRn registers.


What puts the correct 'csr' into the pointer CSR before TxRx() is called ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Solved my own issue.

However, there's a new one now (actually, a reprise of an old one) - the CS line rises between bytes in the multi-byte transfers.  For this quadrature decoder, that is enough to stop it working.

CSAAT is set to FALSE and the LASTXFER bit only gets set on the last byte, but the line is rising anyway.  I'm not convinced the last byte is actually getting sent, either.

--
Geoff Field
Professional Geek, Amateur Stage Levelling Gauge

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Mikech,

 

The CSR member pointer is set up in the Init() routine called from the constructor:

    volatile avr32_spi_csr0_t * aCSR = &(spi->CSR0);
    
    uint8_t idx = (decoded_cs[id])? (cs >> 2) : cs;
    CSR = &aCSR[idx];

Note that this doesn't follow our current naming convention: It should be called something like m_pCsr given that it's a member of the class and a pointer.  It's inherited code and I didn't want to mess with it too much.  The member pointer is of type avr32_spi_csr0_t, taken from the AVR toolchain headers (specifically spi_1991.h).

 

SetBaud() is as follows:

ISpi::spiError_t SpiAt32uc3a::SetBaud(uint32_t baud, IClockMgr * clock_mgr)
{
    if(!configured[id]) return ISpi::SPI_NOT_CONFIGURED;

    ISpi::spiError_t status = ISpi::SPI_NO_ERROR;

    uint16_t scbr = clock_mgr->GetCPUSpeed()/(baud);

    status = ((clock_mgr->GetCPUSpeed() % baud) == 0)? ISpi::SPI_NO_ERROR : ISpi::SPI_INACCURATE;    // check if baud is possible, or just close
    if (scbr > 255)
    {
        scbr = 255;
        status = ISpi::SPI_OUT_OF_RANGE;
    }
    else if (scbr <= 1)    // Don't use SCBR = 1 (errata 41.1.3)
    {
        scbr = 2;
        status = ISpi::SPI_OUT_OF_RANGE;
    }

    //Set baud rate for current channel
    CSR->scbr = scbr;

    return status;
}   // SetBaud

SetBits() is this:

ISpi::spiError_t SpiAt32uc3a::SetBits(uint8_t bits)
{
    ISpi::spiError_t status = ISpi::SPI_NO_ERROR;
    if (!configured[id])
    {
        status =  ISpi::SPI_NOT_CONFIGURED;
    }
    else if ((bits > 16) || (bits < 8))
    {
        status = ISpi::SPI_ERROR;
    }

    if (status == ISpi::SPI_NO_ERROR)
    {
        CSR->bits = (bits-8);    // Number of bits per transfer
    }

    return status;
}

 

--
Geoff Field
Professional Geek, Amateur Stage Levelling Gauge