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.

CC2652R: SPI slave driver with minimal latency

Part Number: CC2652R

Hi,

I am trying to build spi slave driver that will receive and process incoming SPI frame (1 byte) and prepare new data to reply in the next SPI frame (when SPI master generate next clk).

For the current implementation I use a RTOS thread and spi in SPI_MODE_CALLBACK.  Enclosed you can find a source code.

I notice quite a delay in hwi and swi. Total time of reaction and processing is some 150us. But the reaction to hardware event is some multiple of 30us delayed.

I assume that it comes from RTOS.

So the question is quite broad...

* Is there any example of SPI receiving a direct ISR from DMA after transfer is done (and avoid delay of RTOS)?

* How to best minimize a reaction time on spi slave and combine it with another thread doing Radio communication.

Thanks, Ivan

  • // ============================================================================
    // Common includes
    // ============================================================================
    
    #include <stddef.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <string.h>
    
    /* Driver Header files */
    #include <ti/drivers/GPIO.h>
    #include <ti/drivers/SPI.h>
    
    /* POSIX Header files */
    #include <pthread.h>
    #include <semaphore.h>
    
    /* Driver configuration */
    #include "ti_drivers_config.h"
    
    #include <SPI_Parser.h>
    #include <RandomGenerator.h>
    
    // ============================================================================
    // Global constant
    // ============================================================================
    // ============================================================================
    // Global Function Prototypes
    // ============================================================================
    // ============================================================================
    // Global variables
    // ============================================================================
    // ============================================================================
    // Private types
    // ============================================================================
    // ============================================================================
    // Private constants
    // ============================================================================
    #define SPI_MSG_LENGTH  (64)
    
    // ============================================================================
    // Private variables
    // ============================================================================
    /* Semaphore to block slave until transfer is complete */
    static sem_t m_slaveSem;
    
    /* Status indicating whether or not SPI transfer succeeded. */
    static bool m_transferStatus;
    
    static uint8_t m_slaveRxBuffer[ SPI_MSG_LENGTH ] = { 0 };
    static uint8_t m_slaveTxBuffer[ SPI_MSG_LENGTH ] = { 0 };
    
    static uint8_t m_spiFrameIdx_in  = 0;
    static uint8_t m_spiFrameIdx_out = 0;
    
    static uint8_t m_cmd[ SPI_MSG_LENGTH ] = { 0 };
    
    static bool m_taskkActive = true;
    
    // ============================================================================
    // Private function prototypes
    // ============================================================================
    static void sendAndGetDataOverSPI( void );
    static void TaskComm_parseFrame( void );
    static void TaskComm_setCommandComplete( void );
    static void transferCompleteFxn( SPI_Handle handle, SPI_Transaction *transaction );
    static void restart_SPI_transfer( void );
    static void GPIO_SPI_CS_CallbackFxn( uint8_t index );
    
    // ----------------------------------------------------------------------------
    //  Slave SPI sends a message to master while simultaneously receiving a message from the master.
    void* TaskCommunication_run( void *arg0 )
    {
      // send and receive data over SPI
      sendAndGetDataOverSPI( );
    
      return ( NULL );
    }
    
    // ----------------------------------------------------------------------------
    static void sendAndGetDataOverSPI( void )
    {
      SPI_Handle slaveSpi;
      SPI_Params spiParams;
      SPI_Transaction transaction;
      bool transferOK;
    
      // After reset is performed, master will pull CS low and wait until MISO line goes high
      // that indicates that we are ready to receive messages
      // Here we set a MSB bit to high, which will drive MISO line to high immediately after CS assert
      memset( (void*) m_slaveTxBuffer, 0xFF, sizeof( m_slaveTxBuffer ) );
    
      /*
       * Create synchronization semaphore; this semaphore will block the slave
       * until a transfer is complete.  The slave is configured in callback mode
       * to allow us to configure the SPI transfer & then notify the master the
       * slave is ready.  However, we must still wait for the current transfer
       * to be complete before setting up the next.  Thus, we wait on m_slaveSem;
       * once the transfer is complete the callback function will unblock the
       * slave.
       */
      int32_t status = sem_init( &m_slaveSem, 0, 0 );
      if ( status != 0 )
      {
        // Display_printf(display, 0, 0, "Error creating m_slaveSem\n");
    
        while ( 1 );
      }
    
      // Enable monitoring logic for SPI CS
      GPIO_setCallback( CONFIG_GPIO_SPI_SLAVE_CS, GPIO_SPI_CS_CallbackFxn );
      GPIO_enableInt( CONFIG_GPIO_SPI_SLAVE_CS );
    
      /*
       * Open SPI as slave in callback mode; callback mode is used to allow us to
       * configure the transfer.
       */
      SPI_Params_init( &spiParams );
      spiParams.frameFormat = SPI_POL0_PHA0;
      spiParams.mode = SPI_SLAVE;
      spiParams.transferCallbackFxn = transferCompleteFxn;
      spiParams.transferMode = SPI_MODE_CALLBACK;
      spiParams.dataSize = 8;
      slaveSpi = SPI_open( CONFIG_SPI_SLAVE, &spiParams );
      if ( slaveSpi == NULL )
      {
        // Display_printf(display, 0, 0, "Error initializing slave SPI\n");
        while ( 1 );
      }
      else
      {
        // Display_printf(display, 0, 0, "Slave SPI initialized\n");
      }
    
      // Init
      SPI_Parser_init( );
    
      while ( m_taskkActive )
      {
        // Initialize slave SPI transaction structure
        transaction.count = 1;
        transaction.txBuf = (void*) &m_slaveTxBuffer[m_spiFrameIdx_in];
        transaction.rxBuf = (void*) &m_slaveRxBuffer[m_spiFrameIdx_in];
    
        // Setup next SPI transfer
        transferOK = SPI_transfer( slaveSpi, &transaction );
        if ( transferOK )
        {
          // restart if not first transfer
          if ( m_spiFrameIdx_in != m_spiFrameIdx_out )
          {
            restart_SPI_transfer( );
          }
    
          /* Wait until transfer has completed */
          sem_wait( &m_slaveSem );
    
          // New data available from SPI
          TaskComm_parseFrame( );
    
          // prepare new frame
          m_spiFrameIdx_in++;
          m_spiFrameIdx_in %= SPI_MSG_LENGTH;
    
          // set next data in Rx buffer (to be sent in next frame when new data are received)
          m_slaveTxBuffer[m_spiFrameIdx_in] = SPI_Parser_getNextFrame( );
    
          if ( m_transferStatus == false )
          {
            // Display_printf(display, 0, 0, "SPI transfer failed!");
          }
          else
          {
            // Display_printf(display, 0, 0, "Slave received: %s", m_slaveRxBuffer);
          }
        }
        else
        {
          // Display_printf(display, 0, 0, "Unsuccessful slave SPI transfer");
        }
      }
    
      SPI_close( slaveSpi );
    }
    
    // ----------------------------------------------------------------------------
    static void TaskComm_parseFrame( void )
    {
      // get data to parse
      uint8_t cmdLength = ( m_spiFrameIdx_in + 1 - m_spiFrameIdx_out + SPI_MSG_LENGTH ) % SPI_MSG_LENGTH;
    
      if ( m_spiFrameIdx_in >= m_spiFrameIdx_out )
      {
        memcpy( &m_cmd[0], &m_slaveRxBuffer[m_spiFrameIdx_out], cmdLength );
      }
      else
      {
        memcpy( &m_cmd[0], &m_slaveRxBuffer[m_spiFrameIdx_out], ( SPI_MSG_LENGTH - m_spiFrameIdx_out ) );
        memcpy( &m_cmd[SPI_MSG_LENGTH - m_spiFrameIdx_out], &m_slaveRxBuffer[0], ( m_spiFrameIdx_in + 1 ) );
      }
    
      // New byte available --> parse data
      (void) SPI_Parser_parse( m_cmd, cmdLength );
    }
    
    // ----------------------------------------------------------------------------
    static void TaskComm_setCommandComplete( void )
    {
      m_spiFrameIdx_out = m_spiFrameIdx_in;
    }
    
    // ----------------------------------------------------------------------------
    //  Callback function for SPI_transfer().
    static void transferCompleteFxn( SPI_Handle handle, SPI_Transaction *transaction )
    {
      if ( transaction->status != SPI_TRANSFER_COMPLETED )
      {
        m_transferStatus = false;
      }
      else
      {
        m_transferStatus = true;
      }
    
      sem_post( &m_slaveSem );
    }
    
    // ----------------------------------------------------------------------------
    static void restart_SPI_transfer( void )
    {
      GPIO_write( CONFIG_GPIO_SPI_SLAVE_SS_OUTPUT, 1 );
    
      // Add delay to ensure signal propagation
      CPUdelay( 60 );
    
      GPIO_write( CONFIG_GPIO_SPI_SLAVE_SS_OUTPUT, 0 );
    }
    
    // ----------------------------------------------------------------------------
    static void GPIO_SPI_CS_CallbackFxn( uint8_t index )
    {
      if ( index == CONFIG_GPIO_SPI_SLAVE_CS )
      {
        if ( GPIO_read( CONFIG_GPIO_SPI_SLAVE_CS ) == 0 )
        {
          // transfer CS to SPI peripheral
          GPIO_write( CONFIG_GPIO_SPI_SLAVE_SS_OUTPUT, 0 );
        }
        else if ( GPIO_read( CONFIG_GPIO_SPI_SLAVE_CS ) != 0 )
        {
          // transfer CS to SPI peripheral
          GPIO_write( CONFIG_GPIO_SPI_SLAVE_SS_OUTPUT, 1 );
    
          SPI_Parser_reset( );
          TaskComm_setCommandComplete( );
        }
      }
    }
    

    Description:

    * ISR on CS from SPI to handle 3 pin mode internally

    * SPI in callback mode running on RTOS thread

  • ch4 - CS from SPI master to signal start and stop of communication

    ch2 - workaround to handle 3 pin mode and reload SPI after every byte (CS is asserted when master signals start and also after every frame)

    ch1 - clk

    ch3 - MISO

    Here is the example of ~60us delay on HWI and SWI.

    Between ch4 and ch2 we can observe around 60us constant delay. Setting og ch 2 is done from the hwi. Ideally this latency shall we couple of us

    between ch1 and spikes on ch2 we can observe a time for the SPI callback to fire (also 60us). The spikes on CH2 are only to show start and end of processing time in the callback.

    The goal would be to minimize a latency on hwi and sw and with that reduce time needed to transfer 1byte over SPI.

    How can this be achieved?

  • Hi Ivan,

    It would appear that you have already done an adequate job of resourcing the spislave example and the SPI TI driver already uses the underlying SPICC26X2DMA.h layer.  Your master device could send multiple bytes consecutively which could then all be processed by the slave device without need for additional delays.  Otherwise you would need to forego the RTOS and use DriverLib directly which would be much more complicated to develop.

    Regards,
    Ryan

  • Hi Ryan,

    Thanks for the reply.

    I analysed your proposed and it seems to be the best option for what I am searching for.

    Are there some public available examples for the SPI slave with DriverLib?

    Thanks, Ivan

  • Hi,

    I am not aware of any.

    I'd recommend referring to the how the SPI TI Driver interacts with the driverlib.

    SPI TI Driver source files are located in <SDK>/source/ti/drivers/spi.

    Thanks,
    Toby