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

Read continuous stream of data over SPI

Hello,

I want to read a status register in my SPI peripheral. Once I send my read request, the peripheral will send the status byte over and over, and I want to watch for a change. When it changes, I want to terminate the SPI transfer. 

What is the best way to do this? The complication here is that there isn't a fixed RX buffer length. The status byte might change in 5 bytes or 100 or 1000. It seems suboptimal to allocate memory for a really long transfer buffer just to accommodate, say, the case in which it takes 1000 bytes for the status byte to change, both because of the extra memory, and also because there's no way to know if it'll even be enough. I am using this function:

ret_code_t nrf_drv_spi_transfer(nrf_drv_spi_t const * const p_instance,
uint8_t const * p_tx_buffer,
uint8_t tx_buffer_length,
uint8_t * p_rx_buffer,
uint8_t rx_buffer_length)

Is there a way I can push the RX bytes into a queue, maybe, pop from the queue to read the bytes (which makes room for more bytes to be clocked in on SPI), then abort the spi transfer when I notice the changed byte? If so, is there a way to make sure the dequeueing rate is faster than the spi transfer rate? Or can I read from the transfer buffer even if it is not full?

As a workaround, I guess I could TX my read request over and over and get back a few status bytes each time (buffer length = n+1, TX length = 1, RX = n), but it seems like this would defeat the purpose of SPI. 

Note: I think, based on the below screenshot, that I am using SPI and not SPIM.

Thanks!

  • Hi,

    There are several ways to do this and I am not sure which is better. Using the SPI (and not SPIM) peripheral you will use the CPU for every byte, and you can inspect it. The driver is designed to handle this for you though, but you could for instance do a small hack and add your own callback function that is called from within the transfer_byte() function in nrfx_spi.c so that you can process each byte as it comes in, but still let the driver do it's work as normal.

  • Thank you Einar. One complication with using this method is that most of the time, I want to read a SPI buffer of known length, and only sometimes, I want to process each byte as it comes in in the manner described above.

    I suppose I could create a duplicate transfer_byte() function, as well as a duplicate of all the functions that depend on transfer_byte(), such as spi_xfer(), nrf_drv_spi_xfer(), and nrf_drv_spi_transfer(), and simply call the modified functions (e.g., "my_nrf_drv_spi_transfer") when I need to. But two issues:

    1. this still requires me to specify an RX buffer length, which I don't necessarily know in advance.

    2. the below function also is dependent on transfer_byte(), but I am not really sure under what circumstances it is called. It seems somewhat redundant with the end of spi_xfer(), except that finish_transfer() is more thorough. Why does the end of spi_xfer() only set the chip select back high and not do the rest of the things in finish_transfer()? If I choose to modify the functions, is there any downside to calling finish_transfer() after while (transfer_byte(p_spi, p_cb)); inside spi_xfer()? 

    static void irq_handler_spi(NRF_SPI_Type * p_spi, spi_control_block_t * p_cb)
    {
        ASSERT(p_cb->handler);
    
        nrf_spi_event_clear(p_spi, NRF_SPI_EVENT_READY);
        NRF_LOG_DEBUG("SPI: Event: NRF_SPI_EVENT_READY.");
    
        if (!transfer_byte(p_spi, p_cb))
        {
            finish_transfer(p_cb);
        }
    }

    Do you have recommendations for a way to approach the continuous stream read that do not encounter the issues above? Thank you!

  • Hi,

    nordev said:
    1. this still requires me to specify an RX buffer length, which I don't necessarily know in advance.

    Yes, that is the case. For the SPI peripheral that is not enforced by the HW (which just has double buffered Rx and Tx buffers), so you could write your own driver or modify the driver if that is a problem. Or perhaps simpler, just call nrfx_spi_abort() in the middle of a transfer if you find that you do not need to continue.

    nordev said:
    2. the below function also is dependent on transfer_byte(), but I am not really sure under what circumstances it is called.

    Where transfer_byte() is called from depends on if using the driver in blocking mode or interrupt driven. If blocking, it is called from spi_xfer(). If non-blocking (which is probably what you will use in most cases), it is called from irq_handler(). In either case it is called when a READY event has occurred, which according to the product specification happens in this situation: "The SPI master will generate a READY event every time a new byte is moved to the RXD register.".

    nordev said:
    Why does the end of spi_xfer() only set the chip select back high and not do the rest of the things in finish_transfer()?

    That is because it is a blocking function. So there is no callbck function to call etc.

    nordev said:
    If I choose to modify the functions, is there any downside to calling finish_transfer() after while (transfer_byte(p_spi, p_cb)); inside spi_xfer()? 

    I am not sure why you want to do that? I think you first need to decide to use blocking or non blocking and also understand the driver if you want to make changes to it (other than hooking into transfer_byte() just to read as I suggested, as that does not change the driver behaviour in any way).

  • Where transfer_byte() is called from depends on if using the driver in blocking mode or interrupt driven. If blocking, it is called from spi_xfer()

    I am using blocking mode, and my call stack does include spi_xfer()

    I decided to handle this by just polling for the status byte over and over and receiving 1B back each time, instead of modifying the SDK to read a continuous stream.

    But I seem to be hitting an infinite loop because this READY event you mention doesn't get generated sometimes:

    In either case it is called when a READY event has occurred, which according to the product specification happens in this situation: "The SPI master will generate a READY event every time a new byte is moved to the RXD register.".

    Here is my call stack. The highlighted line is where it is stuck. It just so happens here that a separate interrupt happened, so other code executed for a moment.

    My code is like this:

    uint8_t at_read_status_once(void)
    {
        uint8_t spi_tx_cmd[1] = {CMD_READ_STATUS_1};
        uint8_t spi_rx_buf[2];	// 1 B tx, 1 B rx
    
        nrf_drv_spi_transfer(&m_spi_at, spi_tx_cmd, sizeof(spi_tx_cmd), spi_rx_buf, sizeof(spi_rx_buf));
        return spi_rx_buf[1];	// 2nd byte is the rx response
    }

    And I call it like this:

    while(at_read_status_once() != 0x0);

    My purpose is to stay in the while loop and not move onto the next instruction until the status byte turns to 0x0. This works sometimes, but sometimes, this gets stuck in the while loop in the SDK here:

    Why is the READY event never getting generated?

  • As an update, I found out when specifically this "stuck in loop" problem seems to occur:

    I have an RTC that generates a COMPARE event every X seconds, upon which it calls my at_read_status_once() fn from above, which TX data to the peripheral. The "stuck in loop" problem happens if the SPI TX command happens while SPI RX is in progress from the same peripheral. The SPI TX and SPI RX were initiated with two separate nrf_drv_spi_transfer(). In theory, SPI is supposed to allow duplex communication, so the simultaneous RX and TX should not be a problem; the issue is probably that at_read_status_once() expects a RX response, but there is already a separate RX in progress. 1) Do you have suggestions for how to mitigate this problem?

    Somehow, UART interrupts are able to get through, as you can see from the screenshot above, but other interrupts don't. For example, because the bluetooth device is caught in this while loop, it isn't sending connection packets to the central, and the connection fails -- but no interrupt is generated to process the tasks inside ble_event_handler() case BLE_GAP_EVT_DISCONNECTED. 2) Why do you think some interrupts  are being generated, but others not? (note, RTC interrupt priority = 6. connection related events come from the softdevice, which i thought has the highest interrupt priority)

    3) Can you tell me the chain of events that causes the "READY" event to get generated?

Related