RFC: Designing an API for adding non-blocking IO functionality to peripherals. #13081
Replies: 6 comments 7 replies
-
This is something we'd love to add into CircuitPython as well. We've already added a bit to the rp2pio module such as I'm not a huge fan of Is it a requirement that the API be non-blocking but not force asyncio use? |
Beta Was this translation helpful? Give feedback.
-
Compare with machine.i2s which has synchronous and asynchronous API's. |
Beta Was this translation helpful? Give feedback.
-
Would this allow async writes to flash cards and other external peripherals? |
Beta Was this translation helpful? Give feedback.
-
I also wanted non-blocking IO in MicroPython so created a fork and added it. You can check out the fork here. There are lots of readmes explaining how the non-blocking IO works. I implemented non-blocking IO for sockets, UART, and PIO, since it is more interesting for protocols with asynchronous reads. However I could very easily add it for I2C and SPI as well. |
Beta Was this translation helpful? Give feedback.
-
Speaking to a few people, I think the socket API would be worth copying, basically add a set blocking() python method to switch operating mode of peripheral classes like At a C level the appropriate |
Beta Was this translation helpful? Give feedback.
-
To add a couple more points, nonblocking support for ADC and DAC objects has been mentioned above. In these cases I/O needs to take place at a tightly controlled, user specified rate. Nonblocking would enable applications to process continuous streams of data, which is currently difficult. The API would need a way to specify the sample rate. The second point is a limitation of the current A use case for nonblocking SPI is the transfer of a large pre-allocated buffer (e.g. the |
Beta Was this translation helpful? Give feedback.
-
There are a number of use cases where non-blocking IO would be highly advantageous.
An example I've been discussing a lot recently is display drivers for something like LVGL working with SPI or I2C devices where it's desirable to write a large buffer out the peripheral in the background while other user and/or rendering code is running.
Background peripheral IO will also be a core requirement to eventually add asyncio support for hardware (along with something like #6125 to signal user code / async loop for input signals)
What I'd like to discuss here is the API that could hopefully be common across a range of peripherals / modules to enable this to be built in a standard way.
Non blocking write / master-initiated read
Options could include:
• Separate function: eg
write()
is unmodified,send()
or similar is added.• Arguments added to readinto / write: eg.
write(data, blocking=False)
•
ioctl
: new control endpoint to set modes and query progress.I personally think
ioctl
is the best path forward, it's least disruptive the the existing api, the most flexible and also has the potential to align some of these peripherals with the stream protocol.Both SPI and I2C are out a type where both write and read operations are initiated by the MCU (in the default host / controller / master mode) so you don't have to worry about data reception at any time like UART does, it's a lot more structured.
These peripherals could have an
.ioctl()
api added with functions to:Then write/readinto functions extended such that in non-blocking mode they return immediately, but hold a reference to the buffer provided. The transfer to/from buffer can be handled in background by IRQ/dma - indeed the dma is already used for (blocking) spi transfers on stm already.
I expect the write/readinto functions would immediately return the "expected length" of the buffers that's been successfully queued to process, or return 0 if it's already busy.
Added bonus - the ioctl should make these classes essentially stream compatible, so existing asyncio.Stream wrapper could be used with very little extra work?
The
read()
function can also likely be made to work for stream compatibility but it would maybe need to allocate the required size buffer itself and return that immediately, to be filled in the background. The asyncio.Stream interface uses it differently though, it waits on data being available before calling the read() function, so perhaps there would need to be a way to "request" ie start the transfer, then use select / asyncio to wait for the transfer to finish before usingread()
to return the data buffer.I think this same ioctl interface should be workable for other uses like display drivers, they can enable non-blocking mode then trigger screen updates as needed, with the framebuffer being transferred to screen somewhat transparently in the background..
I wonder if this could possibly be used with external SPI flash too, push some of a write operation into background... Many write operations are probably immediately followed by a (page) read so likely not help much, but perhaps some optimisations could be made.
Some other peripherals like ADC, DAC could likely follow the same pattern. Others like Pin's can have input signals at any time, these any others that have interrupt style inputs likely need a different pattern.
How does this all sound, any other / counter ideas?
Beta Was this translation helpful? Give feedback.
All reactions