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

Any ideas on how to decrease SPI overhead time?

With the maximum SPI speed of 8 Mbps, I was expecting to be able to send 2 bytes in just a little over 2 us, but it takes a lot longer. It appears that the mandatory event wait time between bytes (to avoid overwriting the first one) is about 4 us and, if I include the time it takes to toggle the slave device CE line on and off, the total transmission time is about 10 us. This gives me a throughput of only about 1.6 Mbps.

Is there any way to speed that up? I'm even using some "INLINE" instructions (see below):

NRF_GPIO->OUTCLR = CE_BIT_POS;

dac_spi_base_address->TXD = (uint32_t)(tx_data[0]);

while (dac_spi_base_address->EVENTS_READY == 0U) { } dac_spi_base_address->EVENTS_READY = 0U; rx_data[0] = (uint8_t)dac_spi_base_address->RXD;

dac_spi_base_address->TXD = (uint32_t)(tx_data[1]);

while ((dac_spi_base_address->EVENTS_READY == 0U) /&& (counter < DAC_TIMEOUT_COUNTER)/) { } dac_spi_base_address->EVENTS_READY = 0U; rx_data[1] = (uint8_t)dac_spi_base_address->RXD;

NRF_GPIO->OUTSET = CE_BIT_POS;

Thank you for any ideas!

Gil

  • I think I may not need the first while() because the TXD is double-buffered, right? Is the read of the RXD register mandatory? (I don't care about the received data).

    Thanks!

    Gil

  • I was able to decrease the time from 10 us to 3 us by:

    1. Doing some assignments ahead of time: register uint8_t rxData; register uint16_t * ptr = &(Data[0]); // 2 bytes register uint32_t* RxReg = (uint32_t*)&(dac_spi_base_address->RXD); register uint32_t* TxReg = (uint32_t*)&(dac_spi_base_address->TXD);

    2. Removing the RXD read between the bytes (the one after the second byte is still needed)

    3. Removing the while(event) between the bytes, and replacing the one after the second byte with a NOP delay sequence function.

    Final code:

    // Enable slave (slave select active low)

    NRF_GPIO->OUTCLR = SYNC_BIT_POS;

    // Write upper byte

    *TxReg = ((*ptr) >> 8);

    // Read Rx register (this one is not needed) // rxData = *RxReg;

    // Write lower byte

    *TxReg = ((uint8_t)(*ptr));

    // Read Rx register

    rxData = *RxReg;

    // Just a bunch of NOPs to wait for the byte transmittal

    dac_delay_100s_of_ns();

    // Disable slave (slave select active low)

    NRF_GPIO->OUTSET = SYNC_BIT_POS;

  • In the case of interrupt driven SPI, most of the overhead comes from the dispatch latency through the softdevice. To increase the speed of my driver instead of handling one byte in/out per interrupt I just say:

    while (hw->EVENTS_READY)
    {
        hw->EVENT_READY = 0;
        <send and receive>
    }
    

    This sped up my SPI code considerably with the side effect of staying in the interrupt handler for the entire transaction.

    It seems that even though the SPI interface is double buffered, it doesn't seem like the interrupt is triggered when the first byte is clocked in/out. In the single byte case I see a delay of ~82 cycles from the falling edge of the clock to the rising edge of a gpio when I enter my handler. In the double byte case I see the same 82 cycle delay, but it happens on the falling edge of the 16th SPI clock not the 8th clock as I would have expected.

    As a result of handling multiple bytes per interrupt I saw one annoying side effect. The nrf51 seems to be dispatching the SPI interrupt even when EVENTS_READY is 0. When I handle just one EVENTS_READY per interrupt I never see a EVENTS_READY==0 on entry to the interrupt. It looks like the internal interrupt bit pending is latched when the event happens. So handling multiple bytes per interrupt is only useful if your are doing fairly long transactions.

Related