Skip to content

displayio: send one area buffer while composing the next - #11468

Open
lynt-smitka wants to merge 2 commits into
adafruit:mainfrom
MakerClassCZ:display-bus-async-core
Open

lynt-smitka wants to merge 2 commits into
adafruit:mainfrom
MakerClassCZ:display-bus-async-core

Conversation

@lynt-smitka

Copy link
Copy Markdown

BusDisplay on a FourWire bus now composes the next area buffer while the previous one is sent over SPI with DMA.

  • busio: internal common_hal_busio_spi_write_async() and common_hal_busio_spi_wait(), enabled by CIRCUITPY_BUSIO_SPI_ASYNC. Implemented on raspberrypi, samd51/same5x and espressif. No new Python API.
  • displayio: the display bus gets an optional send_async. FourWire provides both when the SPI port has the async write. Without a DC pin, with a chip select toggled per byte, and on the other buses nothing changes.
  • Background tasks run between area buffers only while the bus is released, as before, so another device on the same SPI bus is not locked out during a refresh.

Full-screen repaint of a TileGrid with an 8-bit Bitmap, main -> this PR:

board rotation 0 rotation 90
PicoPad, RP2040 (320x240) 76.7 -> 57.2 ms 198.4 -> 178.4 ms
PyBadge, SAMD51 (160x128) 26.5 -> 15.5 ms 58.4 -> 47.4 ms
Feather ESP32-S3 TFT (240x135) 29.9 -> 19.6 ms 49.3 -> 38.3 ms

Flash: PicoPad +624 B, Fruit Jam +576 B, Feather ESP32-S3 +528 B, PyBadge +320 B.

With an async bus the second area buffer is on the stack during a refresh. A full-screen refresh at the Python recursion limit ran without a crash on both boards. On RP2 the display's SPI bus keeps one DMA channel claimed until it is deinitialized.

picogame will use this in a follow-up PR and drop its own port DMA backends.

common_hal_busio_spi_write_async() starts a DMA write and returns;
common_hal_busio_spi_wait() waits for it without running background
tasks, since the caller still holds the bus. Ports that have them set
CIRCUITPY_BUSIO_SPI_ASYNC. There is no Python API.

raspberrypi: TX-only DMA. The channel is claimed on first use and kept
until deinit. Writes shorter than 32 bytes or from flash/PSRAM stay
synchronous.

atmel-samd: samd51/same5x only, through shared_dma_transfer_start().

espressif: up to two queued ESP-IDF transactions (8 KB). Writes of four
bytes or less, longer writes and word sizes other than 8 bits stay
synchronous.
A display bus can now have send_async next to send, and flush waits
for it. FourWire provides both when the SPI port has an async write.
BusDisplay then uses two area buffers: it composes the next one while
the previous one is sent. Background tasks still run only while the bus
is released, so another device on the same SPI bus is not locked out.

Full-screen repaint: PicoPad (RP2040) 76.7 -> 57.2 ms, PyBadge (SAMD51)
26.5 -> 15.5 ms, Feather ESP32-S3 TFT 29.9 -> 19.6 ms. Ports without the
async write build the same code as before.
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.

1 participant