This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TMS320F28379D: SPI Software CS

Part Number: TMS320F28379D

Hello. I'm developing a device based on the F28379D. It will have about 10 or more SPI slaves. I want to use software control of the CS. Unfortunately, there doesn't seem to be a standard way to do this. I'm using synchronous transfer using a FIFO and plan to also implement DMA + FIFO transfer in the future. The problem is that I haven't found an interrupt or register flag that would reliably indicate that the physical SPI transfer is complete and the CS output can be set high. You can also check out another ticket on this forum from ten years ago with the exact same problem, but on a different microcontroller . Unfortunately there is no working solution there. Here is the best solution I found. 

bool Spi::transmitSync(const uint8_t *pBuff, uint32_t len, uint32_t timeout)
{
    volatile uint16_t *pTxBuff = (volatile uint16_t *)(mPortBase + SPI_O_TXBUF);

    for(register uint32_t i = 0; i < len; i++)
    {
        while(SPI_getTxFIFOStatus(mPortBase) == SPI_FIFO_TXFULL) {}
        *pTxBuff = pBuff[i] << 8;
    }

    while (SPI_getTxFIFOStatus(mPortBase) != SPI_FIFO_TXEMPTY) {}

    SPI_resetRxFIFO(mPortBase);

    DELAY_SYSTEM(m8bitsDelay);

    return true;
}
 
But it doesn't work stably and in some scenarios the CS still triggers earlier than necessary. 
 
This is how I set up SPI.
 
void Spi::init(Port_t port, uint32_t bitRate, bool polarityHi, uint8_t front)
{
    switch(port){
        case kPortA:
            mPortBase = SPIA_BASE;
            initGpioPortA();
            initDmaPortA();
            break;

        case kPortB:
            mPortBase = SPIB_BASE;
            initGpioPortB();
            initDmaPortB();
            break;

        case kPortC:
            mPortBase = SPIC_BASE;
            initGpioPortC();
            initDmaPortC();
            break;

        default:
            ASSERT(false);
            return;
    }

    spPort[port] = this;

    SPI_disableModule(mPortBase);

    SPI_TransferProtocol protocol;
    if (polarityHi) {
        protocol = (front) ? SPI_PROT_POL1PHA1 : SPI_PROT_POL1PHA0;  // Mode 3/2
    } else {
        protocol = (front) ? SPI_PROT_POL0PHA1 : SPI_PROT_POL0PHA0;  // Mode 1/0
    }
   
    float rawDelay = 8.0 * (1.0 / ((float)bitRate / 1000000.0));
    m8bitsDelay = CALC_NUMBER_OF_CYCLES_US(rawDelay < 1.0 ? 1UL : rawDelay);

    SPI_setConfig(mPortBase, DEVICE_LSPCLK_FREQ, protocol,
                  SPI_MODE_CONTROLLER, bitRate, 8);

    SPI_disableLoopback(mPortBase);

    SPI_setEmulationMode(mPortBase, SPI_EMULATION_STOP_AFTER_TRANSMIT);

    SPI_enableFIFO(mPortBase);

    SPI_resetTxFIFO(mPortBase);    
    SPI_resetRxFIFO(mPortBase);

    SPI_clearInterruptStatus(mPortBase, SPI_INT_RXFF | SPI_INT_TXFF);
       
    SPI_setFIFOInterruptLevel(mPortBase,
        SPI_FIFO_TXEMPTY, (SPI_RxFIFOLevel)(mkFifoLevel));

    SPI_enableModule(mPortBase);
    SPI_enableTalk(mPortBase);
}
 
Please tell me if there is a solution to my problem.
  • Hi Deric,

    Let me look into this and get back to you.

    Best Regards,

    Delaney

  • Hi Deric,

    Are you trying to send a variable number of bytes to each slave device or is this value fixed? Also, which FIFO level are you using for the TX FIFO? 

    Best Regards,

    Delaney

  • Hi Delaney.
    I'm not sending a fixed number of bytes. The number of bytes ranges from 1 to 1024.
    I set the FIFO levels to 8 (mkFifoLevel = 8).

  • SPI_setFIFOInterruptLevel(mPortBase,
            SPI_FIFO_TXEMPTY, (SPI_RxFIFOLevel)(mkFifoLevel));
  • Hi Deric,

    I see, thank you for clarifying. 

    I believe the issue with your current code is that waiting until the TX FIFO is empty doesn't necessarily mean that all data is shifted out of the SPIDAT register yet - the very last character could still be in the process of shifting out when the TX FIFO goes empty. Do you have an idea of what data will be sent by each of the slave devices? For example, if it is always nonzero, I'm thinking you could instead wait until the SPIDAT register = zero before having the CS go high. 

    Best Regards,

    Delaney

  • Hi Delaney. 

    This method will not always work because the data sent to the slave may end with zero.

    Perhaps it makes sense to check the received byte flag after checking for an empty FIFO? Since during a transaction, a byte is considered received only after the last SCK clock cycle. I haven't tried this yet and don't know if such a flag exists.

  • Hi Deric,

    I see, let me look through and see if there is any flag for this. I will also consult another SPI expert to see if they have any other ideas.

    Best Regards,

    Delaney

  • Hi Delaney.
    Are there any changes?

  • Hi Deric,

    Apologies for the delay. Are you operating in high-speed mode? Could you also send your waveforms for me to take a look at how it operates with the current method?

    Best Regards,

    Delaney

  • Hi Delaney.
    No, at the moment I'm working in the normal 10 MHz mode.

  • This is a fragment of the EEPROM driver library's operation. As you can see in the second screenshot, only in the last transfer (P4) did the CS rise after 700 ns, which is a good time. In all other cases, the time between the end of the clock cycle and the rise of CS is several times longer.

  • Here's the receive and transmit implementation I'm currently using. CS deactivates as soon as I exit these functions. 

    bool Spi::transmitSync(const uint8_t *pBuff, uint32_t len)
    {
        volatile uint16_t *pTxBuff = (volatile uint16_t *)(mPortBase + SPI_O_TXBUF);
    
        for(register uint32_t i = 0; i < len; i++)
        {
            while(SPI_getTxFIFOStatus(mPortBase) == SPI_FIFO_TXFULL) {}
            *pTxBuff = pBuff[i] << 8;
        }
    
        while (SPI_getTxFIFOStatus(mPortBase) != SPI_FIFO_TXEMPTY) {}
    
        // SPI_resetRxFIFO(mPortBase);
    
        DELAY_SYSTEM(m8bitsDelay);
    
        return true;
    }
    
    bool Spi::receiveSync(uint8_t *pBuff, uint32_t len)
    {
        //
        // Reset the TX / RX FIFO buffers to default state
        //
        SPI_resetRxFIFO(mPortBase);
        SPI_resetTxFIFO(mPortBase);
    
        uint32_t remainingWords = len;
    
        volatile uint16_t *pTxBuff = (volatile uint16_t *)(mPortBase + SPI_O_TXBUF);
        volatile uint16_t *pRxBuff = (volatile uint16_t *)(mPortBase + SPI_O_RXBUF);
    
        for(register uint32_t i = 0; i < len; i += 16) {
    
            uint32_t length = remainingWords > 16? 16 : remainingWords;
            remainingWords -= length;
    
            for(uint32_t j = 0; j < length; j++) {
                while(SPI_getTxFIFOStatus(mPortBase) == SPI_FIFO_TXFULL) {}
                *pTxBuff = 0;
            }
    
            for(uint32_t j = 0; j < length; j++) {
                while(SPI_getRxFIFOStatus(mPortBase) == SPI_FIFO_RXEMPTY) {}
                pBuff[i + j] = *pRxBuff;
            }
    
        }
    
        DELAY_SYSTEM(m8bitsDelay);
    
        return true;
    }

  • Hi Deric,

    Since you are operating in FIFO mode, I believe the best way to do this is to poll the SPISTS.BUFFULL_FLAG flag directly after the last byte is written into SPITXBUF. Then once the flag goes low, wait x amount of time before pulling CS high again. This amount of time x should be equal to how long it takes to shift out a full byte given your baud rate settings and should be the same every time - however I would test this to make sure. You will just need to make sure that all of this process is not interrupted, otherwise it will throw everything off. The execution needs to be deterministic. 

    Also, for reference, linked here is the section of the datasheet with the SPI peripheral timing specs. I would take a look at this to try to match your CS timing to that of module CS. You will also have to verify with all the slave devices that the CLK and CS timing complies with their specs as well. 

    Best Regards,

    Delaney