NRF5340 SPIM turnaround time

Hello,

I am trying to optimize performance of a series of SPIM transactions using the 5340. Each transaction is 3 bytes long: CSN goes low, clock out 3 bytes, CSN goes high. Only the 3 received bytes matter here, the transmitted bytes are ignored. I want to minimize the turnaround time between transactions so that I can maximize the number of transactions per second.

I implemented a test using the 5340 DK and the NRFx driver, with SPIM 4 set up at 16 MHz with hardware CSN control. The SPIM END/START short is set, so as soon as one transaction completes the next begins. This should minimize turnaround time, since each transaction is started via DPPI and no CPU involvement is needed (in fact, for this test the CPU is put to sleep after kicking off the first SPI transaction). This seems to work as expected, transferring data from the sensor continuously.

However, I am seeing that there is a significant delay between the completion of one SPI transaction and the start of the next - approximately 900 ns. I am wondering what this turnaround delay is attributable to and whether there's anything I can do to reduce or eliminate it.

To get a better idea of what the SPIM peripheral is doing, I connected the SPI STARTED event to one GPIOTE task (toggle) and SPIM END event to another GPIOTE task (also toggle). Then, scoped these lines plus CSN and SCLK. An example capture is below. You can see there is an ~800 ns delay between CSN going high and the SPIM END event, which is larger than I expected. It's not pictured here, but I also measured the timing of the SPIM ENDRX event, which is 125 ns before the SPIM END event.

I found in the 5340 product spec that "for low values of CSNDUR, the system turnaround time will dominate the actual time between transactions". But, no mention of what the system turnaround time is or whether it can be influenced. In my case CSNDUR is set to 2, so it is only ~4% of the observed delay. I thought maybe the DMA is transferring the final byte into RAM during this time, but would have expected that to run much more quickly than ~800 ns.

My questions are:

1. Is there any more info on what the "system turnaround time" is, or where it comes from?

2. What causes the 125 ns delay between SPIM ENDRX and SPIM END? Is there any way to remove this portion of the overall delay? Note that END is not delayed to the Tx event, since 0 bytes are transmitted (I confirmed ENDTX occurs at the beginning of the transfer at the same time as the STARTED event).

3. Is there any other way to reduce the turnaround time between transactions?

4. I noticed from the scope capture that the SPIM STARTED event appears to occur after the CSN line goes low (~30 ns to be specific). I would have thought  Is this expected? I'm not sure if this is an artifact of the DPPI signal propagation or if the CSN line going low actually precedes the STARTED event.

Thanks for your help!

  • Just wanted to say I'm still interested in any answers to these questions if anyone knows. I haven't made any progress on this issue since I posted.

  • Dear sheindel-llt,

    I'm also an nRF5340 user experiencing issues with improving SPI4 communication speed.

    However, in my case, I'm seeing a huge delay of about 12us between the CLK and CS signals. (I've already posted about this issue, but it hasn't been resolved yet.)

     NRF5340DK High frequency SPIM communication failure / spi_transceive() delay problem 



    I was wondering if you might be able to help me with this.

    You mentioned that you set the SPIM END/START short, and I'd like to ask how you implemented that. (In my case, the main code runs in a while loop, which seems to make the code very slow.)


    Best regards,

    gwan0624

  • I would guess your delay is due to manually setting the CS line in the code. It's probably just the time between the CPU executing the spi_tranceive() and gpio_pin_set_dt().

    To eliminate this you can tell the SPIM to control the CS line directly (I believe there's some restrictions on which SPI peripherals support this). Look into how the nrfx driver takes the "use_hw_ss" field in one of the parameters to nrfx_spim_init() - I'm not sure if/how this would be done with Zephyr.

    Then, you can set up DPPI to trigger a SPIM START task when a SPIM END event occurs. There is a function nrf_spim_shorts_enable() that handles this for you in the nrf driver, but it's just setting the right bits in the SHORTS register.

  • Thank you.

    I've changed the code to use the Device Tree for control instead of GPIO, but the results haven't improved significantly.

    It seems that manually controlling events using DPPI, rather than the SPI driver, is the correct approach.

    However, I'm not familiar with writing code using DPPI. I also use LLM to write the basic code, but it doesn't works:

    #include <zephyr/kernel.h>
    #include <stdio.h>
    
    #include <zephyr/sys/printk.h>
    #include <zephyr/device.h>
    
    #include <zephyr/drivers/uart.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    
    #include <nrfx_spim.h>
    
    
    struct spi_dt_spec spispec = SPI_DT_SPEC_GET(DT_NODELABEL(bme280), SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0);
    
    static const nrfx_spim_t spim_inst = NRFX_SPIM_INSTANCE(4);
    static uint8_t rx_buf0[2];
    static uint8_t rx_buf1[2];
    static uint8_t tx_cmd_buf[2];
    K_SEM_DEFINE(sem_data_ready, 0, 1);
    static volatile uint8_t *latest_rx_data_ptr;
    
    
    void spim_event_handler(nrfx_spim_evt_t const *p_event, void *p_context)
    {
        if (p_event->type == NRFX_SPIM_EVENT_DONE) {
            // 1. main() loop can process the completed buffer
            latest_rx_data_ptr = p_event->xfer_desc.p_rx_buffer;
            k_sem_give(&sem_data_ready);
    
            // 2. Immediately start the next transfer (IRQ-based Ping-Pong).
            // The fix is to declare and initialize the struct in one step.
            if (latest_rx_data_ptr == rx_buf0) {
                nrfx_spim_xfer_desc_t next_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf1, sizeof(rx_buf1));
                nrfx_spim_xfer(&spim_inst, &next_xfer_desc, 0);
            } else {
                nrfx_spim_xfer_desc_t next_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf0, sizeof(rx_buf0));
                nrfx_spim_xfer(&spim_inst, &next_xfer_desc, 0);
            }
            // Note: The call to nrfx_spim_xfer is also moved inside the if/else blocks
            // to respect the scope of next_xfer_desc. We can no longer check its return
            // value easily here, but in an IRQ handler, this is often acceptable.
        }
    }
    
    static int rhd_nrfx_spi_transfer_blocking(uint16_t tx_command, uint8_t *rx_buffer)
    {
        uint8_t tx_b[2];
        tx_b[0] = (uint8_t)(tx_command >> 8);
        tx_b[1] = (uint8_t)(tx_command & 0xFF);
        nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_TRX(tx_b, sizeof(tx_b), rx_buffer, 2);
        nrfx_err_t result = nrfx_spim_xfer(&spim_inst, &xfer, 0);
        return (result == NRFX_SUCCESS) ? 0 : -EIO;
    }
    
    
    
    
    int main(void)
    {
        int err;
        uint8_t rx_main_buffer[2];
        
        printk("nRF5340 SPIM high-speed example started.\n");
        
        
        nrfx_spim_config_t spim_config = {
            .sck_pin      = 47, // P1.15
            .mosi_pin     = 46, // P1.14
            .miso_pin     = 45, // P1.13
            .ss_pin       = 44, // P1.12
            .ss_active_high = false,
            .irq_priority = NRFX_SPIM_DEFAULT_CONFIG_IRQ_PRIORITY,
            .orc          = 0xFF,
            .frequency    = 16000000UL,
            .mode         = NRF_SPIM_MODE_0,
            .bit_order    = NRF_SPIM_BIT_ORDER_MSB_FIRST,
            .use_hw_ss    = true, 
            .ss_duration  = 2, 
       
        };
        
        
        err = nrfx_spim_init(&spim_inst, &spim_config, spim_event_handler, NULL);
        //for blocking mode 
        
        uint16_t read_cmd = RHD_CMD_READ(0);
        tx_cmd_buf[0] = (uint8_t)(read_cmd >> 8);
        tx_cmd_buf[1] = (uint8_t)(read_cmd & 0xFF);
    
        nrf_spim_shorts_enable(spim_inst.p_reg, NRF_SPIM_SHORT_END_START_MASK);
        
        nrfx_spim_xfer_desc_t first_xfer_desc = NRFX_SPIM_XFER_TRX(tx_cmd_buf, sizeof(tx_cmd_buf), rx_buf0, sizeof(rx_buf0));
    
        err = nrfx_spim_xfer(&spim_inst, &first_xfer_desc, 0);
        
        
        
            while (1) {
    
            k_sem_take(&sem_data_ready, K_FOREVER);
    
        }
        
        return 0;
    }
    
    
    
    




    Could you please recommend any reference materials or websites for me?

    Best regards,

    gwan0624

  • I'm not really able to help further dive into details, but you should start with the DPPI documentation in the product spec.

Related