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!

  • So a new transaction would not be able to cause problems for an old, but should just result in the second call failing with NRF_ERROR_BUSY.

    I don't know if this is actually the case, and I think the following concern still stands:

    So sure I might get an error code, but the damage will already have been done: the second transfer will have already disrupted the first transfer, which is what gets me stuck in the infinite while loop. 

    Because I changed my code in this function (code in a previous response):

    at_read_status_once()

    to do this: 

    while(nrf_drv_spi_transfer(&m_spi_at, spi_tx_cmd, sizeof(spi_tx_cmd), spi_rx_buf, sizeof(spi_rx_buf)) == NRF_ERROR_BUSY);

    so that if I got a NRF_ERROR_BUSY event it would just try again until it isn't busy. But I still get stuck in this while loop forever inside spi_xfer(): 

    Here is the full stack. I have highlighted the two calls to nrf_drv_spi_transfer(). 

    The first one is called from my main loop, and the second one is called from an RTC interrupt. My RTC is also a low interrupt priority, 6. 

    Note that NRF_ERROR_BUSY should be returned from nrf_drv_spi_xfer (second in the call stack above). But this does not happen -- "p_cb->transfer_in_progress" seems to be false, even though there IS a transfer in progress (first highlight on above stack):

    What do you think is happening? Is this a bug with SDK 14.2? Is it fixed in later versions? 

    -----

    As a separate matter, I had to refresh this webpage 5 times to get the "reply" button to show up at the bottom of your latest message (it showed up at the top underneath my original message but not elsewhere). Generally, every time I use the forum, I have to refresh multiple times in order for the reply button to show up. Perhaps you can relay this to your web development team. 

  • Hi Einar,

    As an update, I found out what the issue is:

    I hope this image is big enough for you to see, but basically:

    1. there is only one place in the driver where transfer_in_progress() is ever set to true

    2. it is guarded by the following conditional:

    if (p_cb->handler && !(flags & (NRF_DRV_SPI_FLAG_REPEATED_XFER |
                                            NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER)))
            {
    			p_cb->transfer_in_progress = true;
            }

    3. flags are 0, which is not a problem, but handler is ALSO 0, which is a problem. So the condition is never satisfied, and even though there is a transfer in progress, the transfer_in_progress boolean is always false. 

    Handler = 0 because I initialize the SPI driver with: 

    err_code = nrf_drv_spi_init(&m_spi_at, &config, NULL, NULL);

    Because I thought I don't need a handler if I am not using interrupts, since I am in blocking mode.

    My questions: 

    1) Did nordic intend this behavior? (basically, did I do something wrong, or is this a bug?)

    2) Do you see any issue with me removing p_cb->handler from the conditional above in (2.)? 

  • Hi,

    That is good spotted

    nordev said:
    1) Did nordic intend this behavior? (basically, did I do something wrong, or is this a bug?)

    I am not sure if this is intended or not. You could argue that when using blocking mode it is easy for you to ensure that there is no conflict. And you would only be able to get a problem here if calls are made from different interrupt priorities. If that happens and they can overlap in time, then this will not be enough. On the other hand, there is no atomic operations or critical sections for updating the transfer_in_progress flag in any case, so you would still risk issues if calling from different interrupt priorities and not ensuring that one transaction finish before you initiate the next.

    nordev said:
    2) Do you see any issue with me removing p_cb->handler from the conditional above in (2.)? 

    No, I do not immediately see any issues. But note my point above. There is nothing preventing transfer_in_progress from being checked first, then an interrup toccuring checking it again, and then writing it. So the driver is not thread safe and if you call the API from different interrupt priorities you must ensure that there are no collisions outside of the driver. Alternatively, ensure that you always call the driver API from the same interrupt priority, then this is no issue.

  • There is nothing preventing transfer_in_progress from being checked first, then an interrup toccuring checking it again, and then writing it.
    then it is theoretically possible that the second call with higher priority would interrupt the first after the transfer_in_progress was read but before it is written to. And then you would have problems

    so this could still be a potential problem.

    right now, the only conflicting things that might simultaneously call the SPI driver are my main loop (not interrupt based) and the RTC interrupt. Given that I don’t expect anything else to be able to interrupt my RTC interrupt, I should be safe, right? 

    eventually though, I will add another SPI transfer that will be triggered from a Current Time Service interrupt. I think this interrupt comes from the softdevice, which means there’s nothing I can do about matching interrupt priority, correct? So I have to make sure they don’t conflict in some other way. 

  • Hi,

    nordev said:
    right now, the only conflicting things that might simultaneously call the SPI driver are my main loop (not interrupt based) and the RTC interrupt. Given that I don’t expect anything else to be able to interrupt my RTC interrupt, I should be safe, right? 

    I am not sure about that. If you make calls to the driver from both your main loop and an RTC interrupt, that means you may have this problem (think of the main context as the lowest possible priority). Specifically, the main loop may start a SPI transaction and the transfer_in_progress flag may be read, but before it is written to a RTC interrupt occurs. Then the a new transaction might be started from the RTC interrupt.

    nordev said:
    I think this interrupt comes from the softdevice, which means there’s nothing I can do about matching interrupt priority, correct? So I have to make sure they don’t conflict in some other way. 

    Yes, that will have SoftDevice priority. You could move execution down to the main loop for all SPI transactions, perhaps. Either using some synchronization flags or using the app_scheduler or another mechanism. That is one way to avoid having to think about this. Alternatively, you can use a mutex as mentioned before, and handle this yourself outside the driver. Which is more appropriate depends on the specific application, but both are common approaches.

Related