Skip to content

QSPI bus driver and Waveshare ESP32-S3 Touch AMOLED 2.41" board support#10844

Open
ppsx wants to merge 22 commits intoadafruit:mainfrom
ppsx:rm690b0
Open

QSPI bus driver and Waveshare ESP32-S3 Touch AMOLED 2.41" board support#10844
ppsx wants to merge 22 commits intoadafruit:mainfrom
ppsx:rm690b0

Conversation

@ppsx
Copy link

@ppsx ppsx commented Feb 23, 2026

Add qspibus display bus and Waveshare ESP32-S3 Touch AMOLED 2.41" board support

Summary

  • Add qspibus module — a QSPI (quad-SPI) display bus transport for high-speed AMOLED panels. Follows the standard CircuitPython 3-layer pattern: shared-bindings/qspibus, shared-module/qspibus, ports/espressif/common-hal/qspibus.
  • Add board definition for Waveshare ESP32-S3 Touch AMOLED 2.41" (ESP32-S3, 16MB flash, 8MB PSRAM, RM690B0-based AMOLED display).
  • Display is initialized in board_init() and available as board.DISPLAY at boot (splash screen + REPL terminal work out of the box). The RM690B0 init sequence is embedded in board.c; a dedicated RM690B0 driver module will follow in a separate PR.
  • Optimize BusDisplay refresh for QSPI: larger stack buffers (configurable via CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE), fill_area/DMA overlap for ~20% throughput improvement.
  • Add displayio_display_bus_send_region_commands() — a no-transaction variant of the region command helper, enabling single-transaction QSPI refresh (set_region + RAMWR + pixels in one CS assertion).

Hardware

  • MCU: ESP32-S3 (240 MHz dual-core)
  • Flash: 16MB QSPI (QIO, 80MHz)
  • PSRAM: 8MB Octal (OPI, 80MHz)
  • Display: 600×450 AMOLED, RM690B0 controller, QSPI interface at 40MHz
  • Touch: FT6336U capacitive (I2C, user-initialized)
  • USB: 0x303A:0x8278 (Waveshare)

Copilot AI review requested due to automatic review settings February 23, 2026 23:09
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a new QSPI (Quad-SPI) bus driver for CircuitPython, specifically to support the Waveshare ESP32-S3 Touch AMOLED 2.41" display which uses the RM690B0 controller. The implementation follows CircuitPython's established display bus architecture patterns.

Changes:

  • Adds a new qspibus module providing QSPI bus protocol support analogous to fourwire for standard SPI
  • Implements ESP32-S3 specific QSPI bus driver using ESP-IDF's LCD panel IO API with DMA and ISR support
  • Adds board definition for Waveshare ESP32-S3 Touch AMOLED 2.41" with comprehensive pin mappings
  • Optimizes display buffer sizes for QSPI panels to reduce command/window overhead

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
shared-bindings/qspibus/__init__.c Python module definition for qspibus
shared-bindings/qspibus/__init__.h Module header with imports
shared-bindings/qspibus/QSPIBus.c Python bindings for QSPIBus class with send(), write_command(), write_data() methods
shared-bindings/qspibus/QSPIBus.h QSPIBus class interface declaration
ports/espressif/common-hal/qspibus/QSPIBus.c ESP32-S3 QSPI implementation using ESP-IDF LCD panel IO with DMA buffers and ISR callbacks
ports/espressif/common-hal/qspibus/QSPIBus.h ESP32-S3 QSPI bus structure definition
ports/espressif/common-hal/qspibus/__init__.c Empty implementation file (placeholder)
ports/espressif/common-hal/qspibus/__init__.h Empty header file (placeholder)
shared-module/displayio/bus_core.c Integrates QSPI bus into displayio bus abstraction
shared-module/displayio/__init__.h Adds QSPI bus to primary display bus union
shared-module/displayio/__init__.c Adds QSPI bus cleanup to display release path
shared-module/busdisplay/BusDisplay.c QSPI-specific buffer optimization for efficient refresh
py/circuitpy_mpconfig.mk Build flag for QSPI bus support
py/circuitpy_mpconfig.h QSPI display buffer size configuration
py/circuitpy_defns.mk Build pattern matching for qspibus module
ports/espressif/Makefile Links esp_lcd component when QSPIBUS enabled
ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.h Board configuration with pin definitions and buffer size
ports/espressif/boards/waveshare_esp32_s3_amoled_241/mpconfigboard.mk Board build configuration enabling QSPIBUS
ports/espressif/boards/waveshare_esp32_s3_amoled_241/pins.c Comprehensive pin mapping with functional names
ports/espressif/boards/waveshare_esp32_s3_amoled_241/board.c Empty board initialization
ports/espressif/boards/waveshare_esp32_s3_amoled_241/sdkconfig ESP-IDF SDK configuration for PSRAM and peripherals
locale/circuitpython.pot Translation strings for QSPI error messages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

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

This is definitely getting closer! Please prevent additional error messages from being added. You likely can reuse existing ones. Please also iterate with copilot on your own fork before making a PR.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

locale/circuitpython.pot:922

  • The error message "Data buffer is null" is added to the locale file but is never used in the codebase. Either remove this unused error message from the locale file or add the corresponding validation check in the code where it's needed.
#: ports/espressif/common-hal/qspibus/QSPIBus.c
msgid "Data buffer is null"
msgstr ""

ports/espressif/common-hal/qspibus/QSPIBus.c:359

  • The ternary operator CIRCUITPY_LCD_POWER_ON_LEVEL ? 1 : 0 is redundant since CIRCUITPY_LCD_POWER_ON_LEVEL is already defined as either 0 or 1 (see lines 32-34). Simplify this to just CIRCUITPY_LCD_POWER_ON_LEVEL to make the code clearer and avoid potential confusion.
        gpio_set_level((gpio_num_t)self->power_pin, CIRCUITPY_LCD_POWER_ON_LEVEL ? 1 : 0);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ppsx ppsx changed the title QSPI bus driver and …RM690B0 display driver for Waveshare ESP32-S3 Touch AMOLED 2.41" QSPI bus driver and Waveshare ESP32-S3 Touch AMOLED 2.41" board support Feb 24, 2026
@ppsx ppsx requested a review from Copilot February 24, 2026 23:56
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ppsx
Copy link
Author

ppsx commented Feb 25, 2026

Thank you @tannewt for your feedback. I've implemented requested changes and did a couple of sessions with Copilot. Now the code needs some more professional view than me or Copilot :)

@tannewt
Copy link
Member

tannewt commented Feb 25, 2026

Please make sure the automated tests also pass.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

ports/espressif/common-hal/qspibus/QSPIBus.c:467

  • Inconsistent indentation: this line uses 3 spaces instead of 4 spaces for indentation. All other if statements in this function use 4 spaces.
   if (!self->has_pending_command) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

py/circuitpy_mpconfig.h:407

  • The comment says this value can be overridden “for larger buffers”, but shared-module/busdisplay/BusDisplay.c enforces a hard maximum of 2048 words via _Static_assert. Either document the effective maximum here as well, or make the maximum configurable/port-specific so the documentation matches actual build constraints.
// QSPI display buffer size in uint32_t words for _refresh_area() VLA.
// Allocated on stack; boards should verify sufficient stack headroom.
// Default 512 words = 2KB. Override per-board for larger buffers.
#ifndef CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE
#define CIRCUITPY_QSPI_DISPLAY_AREA_BUFFER_SIZE (512)
#endif

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ppsx ppsx requested a review from Copilot February 26, 2026 17:02
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ppsx
Copy link
Author

ppsx commented Feb 26, 2026

Please make sure the automated tests also pass.

Hi @tannewt. I did all I could however some tests are failing because of zephyr which I obviously haven't touched.
I did some more sessions with Copilot.
Additionally I've introduced a proper board.DISPLAY implementation which should be more useful than external rm690b0 module.
Is there anything else I can do?

ppsx and others added 22 commits February 28, 2026 15:41
- Reuse existing CircuitPython error messages to reduce translation burden:
  "QSPI command/color timeout" -> "Operation timed out",
  "QSPI send[color] failed: %d" / "SPI bus init failed: %d" /
  "Panel IO init failed: %d" -> "%q failure: %d",
  "Failed to allocate DMA buffers" / "QSPI DMA buffers unavailable" ->
  "Could not allocate DMA capable buffer",
  "Data buffer is null" -> "Buffer too small".
  Net removal of 8 unique translatable strings with 0 new ones.

- Regenerate locale/circuitpython.pot to remove stale entries.

- Add _Static_assert in BusDisplay.c to guard the QSPI stack-allocated
  refresh buffer size (max 2048 uint32_t words = 8KB).

- Add comment clarifying that inflight_transfers bookkeeping is
  task-context only (ISR only signals semaphore), so no atomics needed.

- Fix SPDX file header format across all new qspibus files to match
  the CircuitPython project convention.
- Add reset() method binding (common_hal implementation already existed)
  with "No %q pin" error matching FourWire/ParallelBus pattern.
- Remove deinit(), __enter__(), __exit__() from Python API; display bus
  lifecycle is managed through displayio.release_displays().
- Remove two unreachable data == NULL guards: Python binding validates
  buffers via mp_get_buffer_raise(), and displayio always passes valid
  pointers.
- Align begin_transaction() with bus_free() by delegating to it, so
  transfer_in_progress and has_pending_command are checked before
  entering a transaction. This prevents silently discarding a staged
  write_command() or starting a transaction during active DMA.
- Regenerate locale/circuitpython.pot to remove stale "Data buffer is
  null" entry left over from the previous round of error message reuse.
#1)

* Initial plan

* Fix stale and missing locale entries for qspibus

Co-authored-by: ppsx <7107135+ppsx@users.noreply.github.com>

* qspibus: three low-cost cleanups consistent with CircuitPython conventions

Co-authored-by: ppsx <7107135+ppsx@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ppsx <7107135+ppsx@users.noreply.github.com>
Co-authored-by: ppsx <ppsx@users.noreply.github.com>
Refactor display bus region-update command emission into a shared helper with
optional transaction management. Keep the existing public path unchanged, and
add a QSPIBus-only entrypoint that can send region commands without nesting
begin/end transaction calls.

Also remove the TileGrid 16bpp+Palette fast path so fill_area uses the generic
path, and fix indentation in QSPIBus write_data.
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.

4 participants