This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SPI Slave Comms using byte count (eg Rx buffer full) rather CSN (Chip Select) to indicate end of SPI transaction)

Hi

My SPI Master wraps SPI comms using a couple of additional logic lines nReadFromSlave (Master Output) and nSlaveReady (Master Input).

It treats an SPI transaction as a Write (of 8 bytes), followed by a read of the ACK/NAK response which is a single byte. The master takes the slave's CS line low at the start of the write, and high again once the read of the Ack/Nak response has been completed.

Nordic's default SPI-Slave behaviour in all the examples etc is that you ONLY get an event indicating SPI Write to Master complete when CS is Taken High. This wont work in my scenario - rather, I need an event when 8 bytes have been received (eg supplied buffer full), not when the state of CS changes.

How to achieve this. It looks from the Docs that this should be possible using the ENDRX register by configuring the following during SPIS setup:

NRF_SPIS1->INTENSET |= ((SPIS_INTENSET_ENDRX_Enabled << SPIS_INTENSET_ENDRX_Pos) | (SPIS_INTENSET_END_Enabled << SPIS_INTENSET_END_Pos) | (SPIS_INTENSET_ACQUIRED_Enabled << SPIS_INTENSET_ACQUIRED_Pos));

I would have thought that this should cause the SPIS to call the handler for all SPI conditions, but I note that in the upper levels, there is not even an enumerated type for the ENDRX event:

/** @brief SPI slave driver event types. */
typedef enum
{
NRFX_SPIS_BUFFERS_SET_DONE, //!< Memory buffer set event. Memory buffers have been set successfully to the SPI slave device, and SPI transaction can be done.
NRFX_SPIS_XFER_DONE, //!< SPI transaction event. SPI transaction has been completed.
NRFX_SPIS_EVT_TYPE_MAX //!< Enumeration upper bound.
} nrfx_spis_evt_type_t;

How do I manually attach the ENDRX event to a brand new callback in my code? How do I signal the SPIS CS line internally (eg eithout wiring an input to an output!) that the transaction is complete?

Am I just doing to have to write my own SPIS code using the HAL functions?

Nigel

Parents
  • Hi

    Got this working - the issue is that Nordic don't provide a means to propagate the ENDRX signal back to the upper layers. Perhaps Nordic can implement these changes in a future SDK?

    If you search for EVENTS_ENDRX you'll find it present in nrf52.h for UARTE, SPIM, SPIS and NFCT.

    Apart from UARTE, it is not propigated upwards through the SDK Layers, some SDK-hacking fixes this (NB Don't hack the SDK - shift the code into your project level!)

    SDK15_2_0_9412b96\nRF5_SDK\modules\nrfx\hal\nrf_spis.h

    /**
     * @brief SPIS events.
     */
    typedef enum
    {
        /*lint -save -e30*/
        NRF_SPIS_EVENT_END      = offsetof(NRF_SPIS_Type, EVENTS_END),     ///< Granted transaction completed.
        NRF_SPIS_EVENT_ACQUIRED = offsetof(NRF_SPIS_Type, EVENTS_ACQUIRED), ///< Semaphore acquired.
    	NRF_SPIS_EVENT_ENDRX = offsetof(NRF_SPIS_Type,EVENTS_ENDRX)   ///< ENDRX completed
        /*lint -restore*/
    } nrf_spis_event_t;
    
    /**
     * @brief SPIS interrupts.
     */
    typedef enum
    {
        NRF_SPIS_INT_END_MASK      = SPIS_INTENSET_END_Msk,     ///< Interrupt on END event.
        NRF_SPIS_INT_ACQUIRED_MASK = SPIS_INTENSET_ACQUIRED_Msk, ///< Interrupt on ACQUIRED event.
    		NRF_SPIS_INT_ENDRX_MASK = SPIS_INTENSET_ENDRX_Msk    ///< Interrupt on ENDRX event
    	
    } nrf_spis_int_mask_t;

    SDK15_2_0_9412b96\nRF5_SDK\modules\nrfx\drivers\include\nrfx_spis.h

    /** @brief SPI slave driver event types. */
    typedef enum
    {
        NRFX_SPIS_BUFFERS_SET_DONE, //!< Memory buffer set event. Memory buffers have been set successfully to the SPI slave device, and SPI transaction can be done.
        NRFX_SPIS_XFER_DONE,        //!< SPI transaction event. SPI transaction has been completed.
    		NRFX_SPIS_ENDRX_DONE,   //!< SPI Transation event. SPI transaction completed - expected no of bytes recieved via ENDRX event
        NRFX_SPIS_EVT_TYPE_MAX      //!< Enumeration upper bound.
    } nrfx_spis_evt_type_t;

    SDK15_2_0_9412b96\nRF5_SDK\modules\nrfx\drivers\src\nrfx_spis.c

    //Needs sorting!
    #define EVT_TO_STR(event)                                           \
        (event == NRF_SPIS_EVENT_ACQUIRED ? "NRF_SPIS_EVENT_ACQUIRED" : \
        (event == NRF_SPIS_EVENT_END      ? "NRF_SPIS_EVENT_END"      : \
                                         "UNKNOWN ERROR - prob ENDRX"))
                                         
    
    /**@brief States of the SPI transaction state machine. */
    typedef enum
    {
        SPIS_STATE_INIT,                                 /**< Initialization state. In this state the module waits for a call to @ref spi_slave_buffers_set. */
        SPIS_BUFFER_RESOURCE_REQUESTED,                  /**< State where the configuration of the memory buffers, which are to be used in SPI transaction, has started. */
        SPIS_BUFFER_RESOURCE_CONFIGURED,                 /**< State where the configuration of the memory buffers, which are to be used in SPI transaction, has completed. */
        SPIS_XFER_COMPLETED,
        SPIS_ENDRX_COMPLETED
    	/**< State where SPI transaction has been completed. */
    } nrfx_spis_state_t;
    
    //--------------------------
    
    // Clear possible pending events.
        nrf_spis_event_clear(p_spis, NRF_SPIS_EVENT_END);
    	nrf_spis_event_clear(p_spis, NRF_SPIS_EVENT_ENDRX);
        nrf_spis_event_clear(p_spis, NRF_SPIS_EVENT_ACQUIRED);
    
    //---------------------------
    
    // Enable IRQ.
        nrf_spis_int_enable(p_spis, NRF_SPIS_INT_ACQUIRED_MASK |
                                    NRF_SPIS_INT_END_MASK | NRF_SPIS_INT_ENDRX_MASK);
                     
        //-----------------------------
                     
                                    
    	case SPIS_ENDRX_COMPLETED:  //todo nmw remove SDK hack
    
    						event.evt_type  = NRFX_SPIS_ENDRX_DONE;
                event.rx_amount = nrf_spis_rx_amount_get(p_spis);
                event.tx_amount = nrf_spis_tx_amount_get(p_spis);
                NRFX_LOG_INFO("Transfer rx_len:%d.", event.rx_amount);
                NRFX_LOG_DEBUG("Rx data:");
                NRFX_LOG_HEXDUMP_DEBUG((uint8_t const *)p_cb->rx_buffer,
                                       event.rx_amount * sizeof(p_cb->rx_buffer[0]));
                NRFX_ASSERT(p_cb->handler != NULL);
                p_cb->handler(&event, p_cb->p_context);
                break;
                
        //------------------------------
        
        switch (p_cb->spi_state)
        {
            case SPIS_STATE_INIT:
            case SPIS_XFER_COMPLETED:
    	    case SPIS_ENDRX_COMPLETED:    
            case SPIS_BUFFER_RESOURCE_CONFIGURED:
                p_cb->tx_buffer      = p_tx_buffer;
                p_cb->rx_buffer      = p_rx_buffer;
                p_cb->tx_buffer_size = tx_buffer_length;
                p_cb->rx_buffer_size = rx_buffer_length;
                err_code             = NRFX_SUCCESS;
    
                spis_state_change(p_instance->p_reg, p_cb, SPIS_BUFFER_RESOURCE_REQUESTED);
                break;
    
            case SPIS_BUFFER_RESOURCE_REQUESTED:
                err_code = NRFX_ERROR_INVALID_STATE;
                break;
    
            default:
                // @note: execution of this code path would imply internal error in the design.
                err_code = NRFX_ERROR_INTERNAL;
                break;
        }
        
        //----------------------------------
        
        //duplicate block above for ENDRX event
        
        // Check for SPI transaction complete event (via ENDRX).
        if (nrf_spis_event_check(p_spis, NRF_SPIS_EVENT_ENDRX))
        {
            nrf_spis_event_clear(p_spis, NRF_SPIS_EVENT_ENDRX);
            NRFX_LOG_DEBUG("SPIS: Event: %s.", EVT_TO_STR(NRF_SPIS_EVENT_ENDRX));
    
            switch (p_cb->spi_state)
            {
                case SPIS_BUFFER_RESOURCE_CONFIGURED:
                    spis_state_change(p_spis, p_cb, SPIS_ENDRX_COMPLETED);
                    break;
    
                default:
                    // No implementation required.
                    break;
            }
        }
        
        
        

    SDK15_2_0_9412b96\nRF5_SDK\integration\nrfx\legacy\nrf_drv_spis.h

    #define NRF_DRV_SPIS_BUFFERS_SET_DONE       NRFX_SPIS_BUFFERS_SET_DONE
    /** @brief Macro for forwarding the new implementation. */
    #define NRF_DRV_SPIS_XFER_DONE              NRFX_SPIS_XFER_DONE
    /** @brief Macro for forwarding the new implementation. */
    #define NRF_DRV_SPIS_ENDRX_DONE              NRFX_SPIS_ENDRX_DONE

    And then in your own SPI Event Handler callback...

    void spis_read_event_handler(nrf_drv_spis_event_t event)
    {
    		if (event.evt_type == NRF_DRV_SPIS_ENDRX_DONE)
    		{
    			if (event.rx_amount > 0)
    			{
    					NRF_LOG_INFO("ENDRX --> Rx Byte Count: %d",event.rx_amount);
    					spis_xfer_rx_done = true;
    					return;
    			}
    		}
    	
    		if (event.evt_type == NRF_DRV_SPIS_XFER_DONE)
            {
    			//Detect rx/tx - we should not get both at the same time
    			if (event.rx_amount > 0 && event.tx_amount > 0)
    			{
    				NRF_LOG_INFO("Error - simultanious Rx and Tx | Rx Count: %d | Tx Count: %d",event.rx_amount,event.tx_amount);
    				return;
    			}
    			
    			if (event.rx_amount > 0)
    			{
    				NRF_LOG_INFO("Rx Byte Count: %d",event.rx_amount);
    				spis_xfer_rx_done = true;
    					return;
    			}
    					
    			//If we get here it was as a result of having completed an SPI Tx (for the ACK/NAK
    			NRF_LOG_INFO("Tx Byte Count: %d",event.tx_amount);
    				
    			if (event.tx_amount > 0)
    			{
    				NRF_LOG_INFO("Tx Byte Count: %d",event.tx_amount);
    				spis_xfer_tx_done = true;
    				return;
    			}
        }
    }

  • Hi

    To add to this, you'll get funny results if the interrupts for both NRF_SPIS_INT_END_MASK  and NRF_SPIS_INT_ENDRX_MASK are enabled via the following in the API code above.

    // Enable IRQ.
    nrf_spis_int_enable(p_spis, NRF_SPIS_INT_ACQUIRED_MASK |
    NRF_SPIS_INT_END_MASK | NRF_SPIS_INT_ENDRX_MASK);

    Where both are enabled, Callbacks to your SPIS Handler wont happen properly - in my experience, behaviour is a bit random if they are both enabled. I suspect that the enumerated types that these signal through the layers eg:

    NRF_DRV_SPIS_XFER_DONE

    NRFX_SPIS_XFER_DONE

    SPIS_XFER_COMPLETED

    NRF_SPIS_EVENT_END

    would need to become bitmasks at each code layer so that the callback gets all the reasons for end of transaction delivered at the same time?

    I will just mod nrf_drv_spis_init so that the accepted interrupt sources can be passed in. With NRF_SPIS_INT_END_MASK  disabled, ENDRX will work alone, and the driving CS line can be tied high permanently, the SPIS config does not accept NRF_SPIS_PIN_NOT_CONNECTED which is a pain because the SPIM config does via NRF_DRV_SPI_PIN_NOT_USED.

    Nigel

  • Appears I was premature on thinking this was 'solved' although the data Rx is signalled complete  via ENDRX, it is not possible to start a new transaction (eg to respond to the master with ACK/NAK) until CS has been de-asserted and then re-asserted (which causes an END event). Basically, the SPIS pheriperal hogs the semaphore until CS toggle is seen. I believe that this is hardwired in the MCU.

    Actively requesting the semaphore from my own code (after the initial ENDRX has been seen) using the code below does not work.

    nrf_spis_semstat_t semstat = nrf_spis_semaphore_status_get (spis.p_reg);
    NRF_LOG_INFO("Semstat B4 NRF_SPIS_TASK_ACQUIRE: %d",semstat);
    nrf_spis_task_trigger(spis.p_reg, NRF_SPIS_TASK_ACQUIRE);
    while (1)
    {
                    semstat = nrf_spis_semaphore_status_get (spis.p_reg);
                    NRF_LOG_INFO("Semstat In While(1) Loop: %d",semstat);
                    if (semstat == NRF_SPIS_SEMSTAT_CPU)
                                    break;
                                                                    
                    nrf_delay_ms(1000);
    }
                                    
    NRF_LOG_INFO("Semstat On While Loop Exit: %d",semstat);
    
    typedef enum
    {
        NRF_SPIS_SEMSTAT_FREE       = 0, ///< Semaphore is free.
        NRF_SPIS_SEMSTAT_CPU        = 1, ///< Semaphore is assigned to the CPU.
        NRF_SPIS_SEMSTAT_SPIS       = 2, ///< Semaphore is assigned to the SPI slave.
        NRF_SPIS_SEMSTAT_CPUPENDING = 3  ///< Semaphore is assigned to the SPI, but a handover to the CPU is pending.
    } nrf_spis_semstat_t;
    
    

    The log output from the above is below. The semaphore request is registered (state changes to 3), but CPU does not get handed the semaphore until I manually trigger a CS toggle on the master:

    <info> app: Started Proteus-Murata Main Application
    <info> app: Entered WaitForNextSpiMessage
    <info> app: ENDRX --> Rx Byte Count: 8
    <info> app: ENDRX --> Tx Byte Count: 0
    <info> app: Rx Transfer Done - preparing ACK
    <info> app: Semstat B4 NRF_SPIS_TASK_ACQUIRE: 2       The semstat content before entering the while loop SPIS owns semaphore
    <info> app: Semstat In While(1) Loop: 3                                   Semstat content in the while loop – this is repeated forever  status:     NRF_SPIS_SEMSTAT_CPUPENDING
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 1                                  Here, I manually de-assert CS on the master, this instantly hands the Semaphore to the CPU
    <info> app: Semstat On While Loop Exit: 1                            
    <info> SPIS: Rx Max Cnt: 8 | Tx Max Cnt: 1
    

    Going to have to find another way - eg re-write the SPIS driver. NB: this will need to be done on the registers, not using the SPIS HAL as the HAL functions appear to use the hardware SPIS semaphore as well. I will need to implement my own guard around EASYDMA - which appears to be the only mechanism to get data to/from SPIS.

    Nigel

Reply
  • Appears I was premature on thinking this was 'solved' although the data Rx is signalled complete  via ENDRX, it is not possible to start a new transaction (eg to respond to the master with ACK/NAK) until CS has been de-asserted and then re-asserted (which causes an END event). Basically, the SPIS pheriperal hogs the semaphore until CS toggle is seen. I believe that this is hardwired in the MCU.

    Actively requesting the semaphore from my own code (after the initial ENDRX has been seen) using the code below does not work.

    nrf_spis_semstat_t semstat = nrf_spis_semaphore_status_get (spis.p_reg);
    NRF_LOG_INFO("Semstat B4 NRF_SPIS_TASK_ACQUIRE: %d",semstat);
    nrf_spis_task_trigger(spis.p_reg, NRF_SPIS_TASK_ACQUIRE);
    while (1)
    {
                    semstat = nrf_spis_semaphore_status_get (spis.p_reg);
                    NRF_LOG_INFO("Semstat In While(1) Loop: %d",semstat);
                    if (semstat == NRF_SPIS_SEMSTAT_CPU)
                                    break;
                                                                    
                    nrf_delay_ms(1000);
    }
                                    
    NRF_LOG_INFO("Semstat On While Loop Exit: %d",semstat);
    
    typedef enum
    {
        NRF_SPIS_SEMSTAT_FREE       = 0, ///< Semaphore is free.
        NRF_SPIS_SEMSTAT_CPU        = 1, ///< Semaphore is assigned to the CPU.
        NRF_SPIS_SEMSTAT_SPIS       = 2, ///< Semaphore is assigned to the SPI slave.
        NRF_SPIS_SEMSTAT_CPUPENDING = 3  ///< Semaphore is assigned to the SPI, but a handover to the CPU is pending.
    } nrf_spis_semstat_t;
    
    

    The log output from the above is below. The semaphore request is registered (state changes to 3), but CPU does not get handed the semaphore until I manually trigger a CS toggle on the master:

    <info> app: Started Proteus-Murata Main Application
    <info> app: Entered WaitForNextSpiMessage
    <info> app: ENDRX --> Rx Byte Count: 8
    <info> app: ENDRX --> Tx Byte Count: 0
    <info> app: Rx Transfer Done - preparing ACK
    <info> app: Semstat B4 NRF_SPIS_TASK_ACQUIRE: 2       The semstat content before entering the while loop SPIS owns semaphore
    <info> app: Semstat In While(1) Loop: 3                                   Semstat content in the while loop – this is repeated forever  status:     NRF_SPIS_SEMSTAT_CPUPENDING
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 3
    <info> app: Semstat In While(1) Loop: 1                                  Here, I manually de-assert CS on the master, this instantly hands the Semaphore to the CPU
    <info> app: Semstat On While Loop Exit: 1                            
    <info> SPIS: Rx Max Cnt: 8 | Tx Max Cnt: 1
    

    Going to have to find another way - eg re-write the SPIS driver. NB: this will need to be done on the registers, not using the SPIS HAL as the HAL functions appear to use the hardware SPIS semaphore as well. I will need to implement my own guard around EASYDMA - which appears to be the only mechanism to get data to/from SPIS.

    Nigel

Children
No Data
Related