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.

Use CC2640 as SPI master and customize the SPI communication protocol

Other Parts Discussed in Thread: CC2640, ADS1220

Hi,

I am trying to use the CC2640 as a SPI master, which interfaces to a 24-bit ADC. 

The ADC chip requires customized packet format to setup its registers and read out ADC data. 

I use the SimplePeripheral example as the baseline, and add the SPI driver according the notes here:

processors.wiki.ti.com/.../Adding_driver_cc2650

Then I did the following modifications to the NPI_TL driver coming along with the BLE_STACK V2.2.0:

1. set the macro of 'Board_SPI1_CSN' to 'IOID_25' in CC2650DK_7ID.h, so the there will be a 'CSN' signal generated on GPIO PIN #25

2. Modify the NPITLSPI_initializeTransport function in npi_tl_spi.c as follows:

void NPITLSPI_initializeTransport(Char *tRxBuf, Char *tTxBuf, npiCB_t npiCBack)
{
    SPI_Params spiParams;

    TransportRxBuf = tRxBuf;
    TransportTxBuf = tTxBuf;
    npiTransmitCB = npiCBack;

    // Configure SPI parameters
    SPI_Params_init(&spiParams);

    // Slave mode
    spiParams.mode = SPI_MASTER;
    spiParams.bitRate = SPI_SLAVE_BAUD_RATE;
    spiParams.frameFormat = SPI_POL0_PHA0; // 2016.Jun.30 : change to Mode (0,0) from Mode (1,1)
    spiParams.transferMode = SPI_MODE_CALLBACK;
    spiParams.transferCallbackFxn = NPITLSPI_CallBack;

    SPI_init(); // init first
    // Attempt to open SPI
    spiHandle = SPI_open(NPI_SPI_CONFIG, &spiParams);
    return;
}

 Basically, I change the spiParams to adapt to the interface of my 24-bit ADC chip. And more importantly, I called the SPI_init(); before doing SPI_open();

 Because I found if I don't init the SPI interface first, the interface can not be opened successfully.

3. The original TL SPI driver automatically adds a packet header to user data, in my case I have to disable this. So I modified the write function as follows:

uint16 NPITLSPI_writeTransport(uint16 len)
{
//    int16 i = 0;
    ICall_CSState key;

    key = ICall_enterCriticalSection();

    TransportTxBufLen = len;

    /*
     * 2016 July 1: the original code (commented out below) add
     * a packet header to the raw data.
     * This needs to be removed 
     *
     *
    TransportTxBufLen = len + SPI_TX_FIELD_LEN;

    // Shift TX message two bytes to give room for SPI header
    for( i = ( TransportTxBufLen - 1 ) ; i >= 0 ; i-- )
    {
      TransportTxBuf[i + SPI_TX_HDR_LEN] = TransportTxBuf[i];
    }

    // Add header (including a zero padding byte required by the SPI driver)
    TransportTxBuf[SPI_TX_ZERO_PAD_INDEX] = 0x00;
    TransportTxBuf[SPI_TX_SOF_INDEX] = SPI_SOF;
    TransportTxBuf[SPI_TX_LEN_INDEX] = len;

    // Calculate and append FCS at end of Frame
    TransportTxBuf[TransportTxBufLen - 1] =
        NPITLSPI_calcFCS(&TransportTxBuf[SPI_TX_LEN_INDEX],len + 1);
     *
     * End of comment
     *
     */
    // Clear DMA Rx buffer and clear extra Tx buffer bytes to ensure clean buffer
    //    for next RX/TX
    memset(TransportRxBuf, 0, NPI_TL_BUF_SIZE);
    memset(&TransportTxBuf[TransportTxBufLen], 0, NPI_TL_BUF_SIZE - TransportTxBufLen);

    // set up the SPI Transaction
    spiTransaction.count = TransportTxBufLen; //NPI_TL_BUF_SIZE;
    spiTransaction.txBuf = TransportTxBuf;
    spiTransaction.rxBuf = TransportRxBuf;

    // Check to see if transport is successful. If not, reset TxBufLen to allow
    // another write to be processed
    if( ! SPI_transfer(spiHandle, &spiTransaction) )
    {
      TransportTxBufLen = 0;
    }

    ICall_leaveCriticalSection(key);

    return TransportTxBufLen;
}

 So I commented out the code to add the packet header, and modified the length of the packet to be exactly the number of bytes in the transmit buffer instead of 270.

Then I modified the read back function as follows:

static void NPITLSPI_CallBack(SPI_Handle handle, SPI_Transaction  *objTransaction)
{
    uint8 i;
    uint16 readLen = 0;
    uint16 storeTxLen = TransportTxBufLen;

    // Check if a valid packet was found
    // SOF:

    /*
     * DO NOT Check packet format when communicate with MCP3901 chips
     * 2016 July 1
     *
    if ( TransportRxBuf[SPI_RX_SOF_INDEX] == SPI_SOF &&
            objTransaction->count)
    {
        // Length:
        readLen = TransportRxBuf[SPI_RX_LEN_INDEX];

        // FCS:
        if ( TransportRxBuf[readLen + SPI_RX_HDR_LEN] ==
                NPITLSPI_calcFCS(&TransportRxBuf[SPI_RX_LEN_INDEX],readLen + 1) )
        {
            // Message is valid. Shift bytes to remove header
            for( i = 0 ; i < readLen ; i++)
            {
                TransportRxBuf[i] = TransportRxBuf[i + SPI_RX_HDR_LEN];
            }
        }
        else
        {
            // Invalid FCS. Discard message
            readLen = 0;
        }
    }
    *
    *
    * End of Comment
	*/
     //All bytes in TxBuf must be sent by this point
    TransportTxBufLen = 0;
    RxActive = FALSE;

    readLen = objTransaction->count;
    SPI_Status stat = objTransaction->status;

    if ( npiTransmitCB )
    {
        npiTransmitCB(readLen,storeTxLen);
    }
}

 I commented out the code the check the packet format, and return the raw data directly to the user. 

4. I removed the definition of 'POWER_SAVING' so the NPI_FLOW_CTRL is disabled:

#ifndef NPI_FLOW_CTRL
#  ifdef POWER_SAVING
#    define NPI_FLOW_CTRL       1
#  else
#    define NPI_FLOW_CTRL       0
#  endif

 This is because I don't need the MRDY and SRDY signals in my case

That is basically what I have done up to now. In the application code, I make sure to wait until each transaction is completed before issuing next request.

The problem is when I tried to read from the ADC chip, it always reads zero. 

I have tested the ADC chip with Arduino, and everything works as expected. So I don't know what I have done wrong.

Can any body give me a hint on what to examine?

  • Using scope, I see all the four GPIO pins signal are generated correctly. Now I am confused, why do I keep getting zero from the Rx buffer?

  • Hi Zefu,

    The NPI protocol (that the TL layer is based on) requires MRDY/SRDY to be used with SPI regardless of POWER_SAVINGS settings.
    Additionally, the length field is required by the protocol as well.

    Given your use case, I recommend starting with a TI-RTOS SPI driver example and integrate that into your BLE application.
  • Hi Sean,

    Thanks for the reply.
    I can understand the NPI protocol by reading through the code in npi_tl.* npi_tl_spi.*.
    The modifications I have done to the code, as shown in the first post, is basically building a customized layer upon the TI-RTOS SPI driver.

    Now what I don't quite understand is that, although I can see the signals on the SPI bus working correctly as expected. That is the CC2640 outputs correct waveform to the PINs of SPI_MOSI, SPI_CLK and SPI_CSN, and receives correct response from the slave on the pin SPI_MISO. I am sure they are correct because I have compared the waveform with my arduino design, which has been demonstrated to work properly.

    However, when my code read data from the Rx Buffer, I still gets all zeros.
    I didn't touch the uDMA part of the SPI driver, so I have no clue right now what is wrong.

    Zefu
  • Zefu,

    NPI is designed to use MRDY/SRDY at all times for SPI. Unless the protocol is modified, not using these could cause data loss.

    When you say the RxBuf has all zeroes do you mean TransportRxBuf within npi_tl_spi layer or the rx buffer within the SPI driver?
  • Hi Sean,

    In the code, it seems that if you set NPI_FLOW_CTRL = 0, all MRDY/SRDY related code will be removed.

    I read the npiRxBuf inside the npi_tl.c, which is passed as a pointer to the npi_tl_spi.c and then to the SPI driver.

    I have tracked all the way down to the uDMA driver, which setups pointer to this npiRxBuf in its Rx channel.

    By reading the code, I believed the uDMA channels (both Tx and Rx) are setup properly.

    And one strong evidence I got is that the HWI function of the uDMA will call my application callback function only when the Rx channel has completed the transfer and set the interrupt bit in the SSI interrupt register, as indicated by the last line of the following code, found in the swifxn of the SPICC26XXDMA.c.

      /*
             * Determine if the TX DMA channel has completed... In SPI slave mode
             * this interrupt may occur immediately (without the RX DMA channel).
             *
             * All transfers will set up both TX and RX DMA channels and both will finish.
             * Even if the transaction->rxBuf == NULL, it will setup a dummy RX transfer to
             * a scratch memory location which is then discarded.
             */
            if (UDMACC26XX_channelDone(object->udmaHandle, hwAttrs->txChannelBitMask)) {
                /* Disable SPI TX DMA and clear DMA done interrupt. */
                SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_TX);
                UDMACC26XX_clearInterrupt(object->udmaHandle, hwAttrs->txChannelBitMask);
            }
    
            /*
             * Determine if the RX DMA channel has completed... In slave mode this interrupt
             * occurrence depends on when the SPI master starts sending data.
             */
            if (UDMACC26XX_channelDone(object->udmaHandle, hwAttrs->rxChannelBitMask)) {
                /* Disable SPI RX DMA and clear DMA done interrupt. */
                SSIDMADisable(hwAttrs->baseAddr, SSI_DMA_RX);
                UDMACC26XX_clearInterrupt(object->udmaHandle, hwAttrs->rxChannelBitMask);
    
                /* Post SWI to handle remaining clean up and invocation of callback */
                Swi_post(Swi_handle(&(object->swi)));
            }

    When I debugged the program, the callback function is executed every time I write to the SPI bus.

    Now I suspect there is something wrong with my own code. I will keep on debugging

  • Zefu,

    You are correct about NPI_FLOW_CTRL = 0, but as I mentioned, for SPI, the protocol is designed to always use MRDY and SRDY.

    See the following excerpt from the wiki page:

    Q: When is PM needed?
    A: Power Management(PM) is needed whenever the CC26xx device is allowed to sleep (when POWER_SAVING is defined in the application). It is also required whenever the SPI protocol is used, this due to inherent limitations of SPI.

  • Hi Sean,

    I have figured it out. The design is now working.
    So the problem in my case is that I have to operate the CSN signal of the SPI bus manually instead of let the SPI driver take control. This is done by setting the macro of Board_SPI1_CSN to PIN_UNASSIGNED ( which is the default value). Then define my own macro for the CSN pin and manually set it to zero or one for each transaction.

    Another mistake I have made is that although the ADC chip I am using claim it can operate on a digital voltage range of 2.7-5.5V. It actually failed to operated correctly on 3.3V voltage. That is why even though I got my arduino to work perfectly but my CC2640 could not.

    Finally, I got it work by modifying the NPI_TL code as described on this post. But you are right about the MRDY and SRDY things. I can have a cleaner code without using the NPI_TL layer.
    You mentioned that Power Management is needed whenever SPI protocol is used. How will that impact my code? Will the SPI protocol stop working at some point when I disable POWER_SAVING feature?

    thanks

    Zefu
  • Hi Zefu

    I'm going to design almost the same thing as you mentioned above. I'm going to connect a 24-bits ADC (ADS1220) through SPI, a 12-bits DAC(LTC2631) through I2C and maybe a UART connection to a computer. May you please share your experiences about pin connections and above mentioned solutions you found? I'm really confused if I can connect those peripheral pins to any GPIO on MCU or not!! Even if I can know about your ADC IC selection would be excellent.
    if you would like, please contact me by amir_sh_elec(attt)yahoo(dottt)com

    Cheers
    Amir