Skip to content

Implemented asyncio SPI for rp2040 & rp2350#11040

Open
kamocat wants to merge 2 commits into
adafruit:mainfrom
kamocat:abusio_spi
Open

Implemented asyncio SPI for rp2040 & rp2350#11040
kamocat wants to merge 2 commits into
adafruit:mainfrom
kamocat:abusio_spi

Conversation

@kamocat

@kamocat kamocat commented Jun 1, 2026

Copy link
Copy Markdown

Implements asynchronous SPI on rp2040 and rp2350.
Created with the help of an LLM - please let me know if specific cleanup is needed.

This has been tested on a rp pico 2 W. The current tests are pretty basic:

  • async_spi.py verifies that SPI can send long transfers without blocking
  • spi_mode_test.py checks that the phase and polarity options work as expected

We have discussion of async busio over in #10856. What I am looking for here is discussion around

  • style and documentation
  • implementation performance and robustness
  • tests methods and gaps

@dhalbert dhalbert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! I have some minor comments and some higher-level comments.

Comment thread ports/raspberrypi/common-hal/abusio/SPI.c Outdated
Comment thread shared-bindings/abusio/examples/async_spi.py Outdated
Comment thread shared-bindings/abusio/examples/async_spi.py Outdated
Comment thread shared-bindings/abusio/examples/async_spi.py Outdated
Comment on lines +32 to +34
// Async completion flag — ports may override by defining these in
// mpconfigport.h before this header is included.
//

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In MicroPython, there is an asyncio.ThreadSafeFlag and also asyncio.event. Tjey are not in CPython asyncio, so I removed it from CircuitPython's asyncio. However, it would be worth looking at them to see if their semantics are similar to what you are doing here. They could be added as CircuitPython-specific classes if they would also be useful to someone writing CircuitPython async code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I came up with for a standard awaitable object from the native core of CP. There is similar discussion about how to do this in CPython here: https://discuss.python.org/t/adding-a-c-api-for-coroutines-awaitables/22786/16

The third-party library for doing it is here too: https://pyawaitable.zintensity.dev/

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! I'm really excited to bring more async to CP. I think it'll really make it easy and performant to do the multiple things.

I've got a restructuring suggestion to remove the data packing and unpacking. In short, make another function specific macro to handle it for you. My example only showed the one argument case, yours is the kwarg case.

common_hal_abusio_spi_write_cancel,
data);
}
static MP_DEFINE_CONST_FUN_OBJ_KW(abusio_spi_write_obj, 1, abusio_spi_write);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intention was to introduce corresponding ASYNC macros to match the SYNC ones from MP. This will remove the need to pack and unpack all of the args because you can use a slightly different awaitable for each one. (Or one that has all of the args at the end of a variable length struct.)

Suggested change
static MP_DEFINE_CONST_FUN_OBJ_KW(abusio_spi_write_obj, 1, abusio_spi_write);
static CIRCUITPY_DEFINE_ASYNC_FUN_OBJ_KW(abusio_spi_write_obj, 1, abusio_spi_write);

The macro will then assume there are corresponding abusio_spi_write (which calls start), common_hal_abusio_spi_write_cancel and common_hal_abusio_spi_write_end. There is a subtle behavior thing here because CPython validates arguments once the object is awaited, not before. That's why the awaitable is returned before validating args.

Comment thread shared-bindings/abusio/SPI.c Outdated
- Moved dual-task async example into docstring
- Removed unneeded examples and setup
- Revised naming
- Added awaitable spi.lock() method
- Moved unpack routings to shared-modules/abusio
- Enabled abusio for all rp2xxx builds
@kamocat

kamocat commented Jun 3, 2026

Copy link
Copy Markdown
Author

Thanks for the feedback!
I fixed the easy stuff and marked as resolved.
The awaitable constructors will take some more consideration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants