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!

  • Thank you. 

    Will you consider asking the development team to remove the p_cb->handler condition from the driver in a future SDK version?

  • EDIT: I realized it is rather unwieldy to add all of the things that might call SPI to the main loop, especially since there is stuff I haven't implemented yet that I will have to add in the future, and maybe there will be even more beyond that. I decided that the best solution would be to add SPI callback functions to a queue that then gets processed in the main loop, if this is possible. My problem is that all my callback functions will have different parameters . 1) Is there any way to define some sort of generic function prototype that I can make a queue for? 

    Alternatively, I discovered this note:

    which suggests that "interrupt pending" flags are set independently of interrupts being enabled or disabled. Which suggests that I can disable interrupts, then incoming interrupts will set "interrupt pending" flags but not execute, and then whatever has an interrupt pending flag will simply execute once I re-enable interrupts. This is what I was getting at here:

    what I would like is for RTC interrupts to be disabled while a transfer is in progress (i.e. while chip select is low) and re-enabled when the transfer completes (chip select goes high), such that any interrupt that tries to happen in the middle of a transfer instead happens immediately after the transfer completes.

    2) Do you know a way I can do this? 

    As a final alternative.... if I remove p_cb->handler and use the NRF_BUSY return code to try again if a transfer was in progress, it would be probably very rare for something to interrupt after the transfer starts but before the "transfer in progress" flag gets set, right? So I could accept this risk, and maybe would have to reboot the device once in a rare while, if it is sufficiently rare (like 1 in 100,000 attempted SPI transfers). 3) Or maybe do you have some suggestion for making this flag-setting atomic? 

    Thank you for your help!

  • Hi,

    nordev said:
    1) Is there any way to define some sort of generic function prototype that I can make a queue for? 

    I would suggest the app_scheduler here. That is designed for exactly this use case. It is essentially just a queue that you put stuff into when handling interrupts, and process in the main loop. You will queue a function pointer and a context data (so it can be anything, you decide - and as large as you want as long as you adjust the element size for it). There are no good examples in the SDK, but the usage is quite simple so you can refer to examples\nfc\writable_ndef_msg\main.c. Essentially you first initialize the queue so that it holds the largest elements you need. Then you queue stuff using app_sched_event_put() when handlign the interrupt, and process it in your main loop by calling app_sched_execute().

    nordev said:
    2) Do you know a way I can do this? 

    You can disable the interrupt any time and then re-enable it. If an interrupt is pending it will be processed. I do not know if you use the RTC driver, but if you do interrupts are enabled when you init it. You can do this directly using NVIC_EnableIRQ(RTC1_IRQn) and NVIC_DisableIRQ(RTC1_IRQn) - adjust if using other RTC instance.

    If you ask me option 1 is by far the best. That is a straightforward and sensible way of handling this and similar issues, and is how you would typically solve this in a complex application.

  • Thanks so much for your help. 

    Just to sanity check, but I was thinking about it again, and if the transfer_in_progress flag were set properly, it seems like I wouldn't actually run into the problem you describe here? 

    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.

    because if the main loop starts an SPI transaction, but doesn't get to setting the transfer_in_progress flag before it gets interrupted, won't the interrupt return and let the main loop keep going only after it is done with its SPI transfer? And if the SPI transfer that was initiated by the main loop didn't even get to sending the transfer_in_progress flag, that means it hasn't actually started sending anything yet, so there wouldn't be two conflicting uses of the same resources? So the flow would be, main loop calls the SPI driver --> gets interrupted before setting flag --> second transfer gets started --> second transfer completes and returns to main loop SPI driver call --> first transfer starts and completes. Unless I am misunderstanding how the interrupts work, please let me know. 

    I was getting the issue only because the flag wasn't being set, so the first SPI transfer was actually in the middle of sending data when the interrupt happened. 

  • Hi,

    Sorry, I always think in terms of non-blocking calls so I forgot again that you were using blocking. As long as you use blocking API calls that is correct, it will not be a problem. In most real products where you do not want to waste CPU time and power you will use non-blocking calls, but you are right that it should be OK to ignore this issue in your specific use case.

Related