From 4c4a1888b09e02ca08f0d4b76f9b2aac3db3a627 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 17 Oct 2025 12:36:18 -0700 Subject: [PATCH 1/4] Add mipidsi module for MIPI DSI displays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds support for MIPI DSI (Display Serial Interface) displays via a new mipidsi module. This enables high-speed serial communication with DSI displays using differential signaling. The module provides: - Bus class: Manages DSI bus with 1-4 data lanes - Display class: Handles display configuration and framebuffer Module is: - Disabled by default globally - Enabled only for ESP32-P4 (has hardware MIPI-DSI support) 🤖 Initial code generated with [Claude Code](https://claude.com/claude-code) and then hand improved. Co-Authored-By: Claude --- locale/circuitpython.pot | 17 +- ports/espressif/Makefile | 17 +- .../mpconfigboard.h | 7 +- .../espressif_esp32p4_function_ev/pins.c | 93 +++--- ports/espressif/boards/m5stack_tab5/board.c | 9 + .../boards/m5stack_tab5/mpconfigboard.h | 28 ++ .../boards/m5stack_tab5/mpconfigboard.mk | 14 + ports/espressif/boards/m5stack_tab5/pins.c | 128 ++++++++ ports/espressif/boards/m5stack_tab5/sdkconfig | 0 .../common-hal/microcontroller/Pin.c | 15 +- ports/espressif/common-hal/mipidsi/Bus.c | 52 ++++ ports/espressif/common-hal/mipidsi/Bus.h | 17 ++ ports/espressif/common-hal/mipidsi/Display.c | 254 +++++++++++++++ ports/espressif/common-hal/mipidsi/Display.h | 37 +++ ports/espressif/common-hal/mipidsi/__init__.c | 7 + ports/espressif/common-hal/mipidsi/__init__.h | 7 + ports/espressif/mpconfigport.mk | 3 + ports/espressif/supervisor/port.c | 2 +- py/circuitpy_defns.mk | 6 + py/circuitpy_mpconfig.mk | 3 + shared-bindings/mipidsi/Bus.c | 96 ++++++ shared-bindings/mipidsi/Bus.h | 15 + shared-bindings/mipidsi/Display.c | 289 ++++++++++++++++++ shared-bindings/mipidsi/Display.h | 44 +++ shared-bindings/mipidsi/__init__.c | 30 ++ shared-bindings/mipidsi/__init__.h | 7 + shared-module/displayio/__init__.h | 6 + 27 files changed, 1153 insertions(+), 50 deletions(-) create mode 100644 ports/espressif/boards/m5stack_tab5/board.c create mode 100644 ports/espressif/boards/m5stack_tab5/mpconfigboard.h create mode 100644 ports/espressif/boards/m5stack_tab5/mpconfigboard.mk create mode 100644 ports/espressif/boards/m5stack_tab5/pins.c create mode 100644 ports/espressif/boards/m5stack_tab5/sdkconfig create mode 100644 ports/espressif/common-hal/mipidsi/Bus.c create mode 100644 ports/espressif/common-hal/mipidsi/Bus.h create mode 100644 ports/espressif/common-hal/mipidsi/Display.c create mode 100644 ports/espressif/common-hal/mipidsi/Display.h create mode 100644 ports/espressif/common-hal/mipidsi/__init__.c create mode 100644 ports/espressif/common-hal/mipidsi/__init__.h create mode 100644 shared-bindings/mipidsi/Bus.c create mode 100644 shared-bindings/mipidsi/Bus.h create mode 100644 shared-bindings/mipidsi/Display.c create mode 100644 shared-bindings/mipidsi/Display.h create mode 100644 shared-bindings/mipidsi/__init__.c create mode 100644 shared-bindings/mipidsi/__init__.h diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 1e167c30353dd..35fc94513a547 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -159,10 +159,6 @@ msgstr "" msgid "%q length must be >= %d" msgstr "" -#: py/runtime.c -msgid "%q moved from %q to %q" -msgstr "" - #: py/argcheck.c msgid "%q must be %d" msgstr "" @@ -838,6 +834,10 @@ msgstr "" msgid "Clock unit in use" msgstr "" +#: ports/espressif/common-hal/mipidsi/Display.c +msgid "Color depth must be 16 or 24" +msgstr "" + #: shared-bindings/_bleio/Connection.c msgid "" "Connection has been disconnected and can no longer be used. Create a new " @@ -932,6 +932,7 @@ msgstr "" #: shared-bindings/busdisplay/BusDisplay.c #: shared-bindings/epaperdisplay/EPaperDisplay.c #: shared-bindings/framebufferio/FramebufferDisplay.c +#: shared-bindings/mipidsi/Display.c msgid "Display rotation must be in 90 degree increments" msgstr "" @@ -1276,7 +1277,8 @@ msgstr "" #: ports/raspberrypi/bindings/rp2pio/StateMachine.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2040.c py/argcheck.c #: shared-bindings/digitalio/DigitalInOut.c -#: shared-bindings/epaperdisplay/EPaperDisplay.c shared-bindings/pwmio/PWMOut.c +#: shared-bindings/epaperdisplay/EPaperDisplay.c +#: shared-bindings/mipidsi/Display.c shared-bindings/pwmio/PWMOut.c #: shared-bindings/supervisor/__init__.c #: shared-module/aurora_epaper/aurora_framebuffer.c #: shared-module/lvfontio/OnDiskFont.c @@ -2266,6 +2268,7 @@ msgid "Unable to allocate to the heap." msgstr "" #: ports/espressif/common-hal/busio/I2C.c +#: ports/espressif/common-hal/busio/SPI.c msgid "Unable to create lock" msgstr "" @@ -3174,10 +3177,6 @@ msgstr "" msgid "file write is not available" msgstr "" -#: shared-bindings/storage/__init__.c -msgid "filesystem must provide mount method" -msgstr "" - #: extmod/ulab/code/numpy/vector.c msgid "first argument must be a callable" msgstr "" diff --git a/ports/espressif/Makefile b/ports/espressif/Makefile index b6ddc27e4084c..8302726aad7a4 100644 --- a/ports/espressif/Makefile +++ b/ports/espressif/Makefile @@ -76,6 +76,7 @@ INC += \ -isystem esp-idf/components/esp_driver_uart/include \ -isystem esp-idf/components/esp_event/include \ -isystem esp-idf/components/esp_hw_support/dma/include \ + -isystem esp-idf/components/esp_hw_support/ldo/include \ -isystem esp-idf/components/esp_hw_support/include \ -isystem esp-idf/components/esp_hw_support/include/soc \ -isystem esp-idf/components/esp_hw_support/port/$(IDF_TARGET)/private_include \ @@ -171,7 +172,8 @@ REGISTRATION_FUNCTIONS = \ -u esp_libc_init_funcs \ -u include_esp_phy_override \ -u vfs_include_syscalls_impl \ - -u esp_vfs_include_nullfs_register + -u esp_vfs_include_nullfs_register \ + -u usb_serial_jtag_vfs_include_dev_init #Debugging/Optimization @@ -329,7 +331,8 @@ LDFLAGS += \ CHIP_COMPONENTS = \ - esp_driver_tsens + esp_driver_tsens \ + esp_driver_usb_serial_jtag else ifeq ($(IDF_TARGET),esp32h2) LDFLAGS += \ @@ -467,6 +470,13 @@ CFLAGS += \ -isystem esp-idf/components/esp_lcd/rgb/include endif +ifneq ($(CIRCUITPY_MIPIDSI),0) +CFLAGS += \ + -isystem esp-idf/components/esp_lcd/include \ + -isystem esp-idf/components/esp_lcd/interface \ + -isystem esp-idf/components/esp_lcd/dsi/include +endif + ifneq ($(CIRCUITPY_ESPCAMERA),0) SRC_CAMERA := \ $(wildcard common-hal/espcamera/*.c) \ @@ -757,6 +767,9 @@ endif ifneq ($(CIRCUITPY_DOTCLOCKFRAMEBUFFER),0) ESP_IDF_COMPONENTS_LINK += esp_lcd endif +ifneq ($(CIRCUITPY_MIPIDSI),0) + ESP_IDF_COMPONENTS_LINK += esp_lcd +endif ifneq ($(CIRCUITPY_PARALLELDISPLAYBUS),0) ESP_IDF_COMPONENTS_LINK += esp_lcd endif diff --git a/ports/espressif/boards/espressif_esp32p4_function_ev/mpconfigboard.h b/ports/espressif/boards/espressif_esp32p4_function_ev/mpconfigboard.h index 646fb62e5623e..1bbf3a250fcd2 100644 --- a/ports/espressif/boards/espressif_esp32p4_function_ev/mpconfigboard.h +++ b/ports/espressif/boards/espressif_esp32p4_function_ev/mpconfigboard.h @@ -1,6 +1,6 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2019 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT @@ -16,5 +16,10 @@ #define DEFAULT_UART_BUS_RX (&pin_GPIO38) #define DEFAULT_UART_BUS_TX (&pin_GPIO37) +#define DEFAULT_I2C_BUS_SCL (&pin_GPIO8) +#define DEFAULT_I2C_BUS_SDA (&pin_GPIO7) + // Use the second USB device (numbered 0 and 1) #define CIRCUITPY_USB_DEVICE_INSTANCE 1 + +#define CIRCUITPY_USB_HOST_INSTANCE 0 diff --git a/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c b/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c index 3bb64f434d02f..7a3026f914dd9 100644 --- a/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c +++ b/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c @@ -1,6 +1,6 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT @@ -9,47 +9,72 @@ static const mp_rom_map_elem_t board_module_globals_table[] = { CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS - { MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) }, - { MP_ROM_QSTR(MP_QSTR_IO1), MP_ROM_PTR(&pin_GPIO1) }, - { MP_ROM_QSTR(MP_QSTR_IO2), MP_ROM_PTR(&pin_GPIO2) }, - { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, - { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, - { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, - { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, + // Header Block J1 + { MP_ROM_QSTR(MP_QSTR_I2C_SDA), MP_ROM_PTR(&pin_GPIO7) }, { MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_I2C_SCL), MP_ROM_PTR(&pin_GPIO8) }, { MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_GPIO8) }, - { MP_ROM_QSTR(MP_QSTR_IO9), MP_ROM_PTR(&pin_GPIO9) }, - { MP_ROM_QSTR(MP_QSTR_IO10), MP_ROM_PTR(&pin_GPIO10) }, - { MP_ROM_QSTR(MP_QSTR_IO11), MP_ROM_PTR(&pin_GPIO11) }, - { MP_ROM_QSTR(MP_QSTR_IO12), MP_ROM_PTR(&pin_GPIO12) }, - { MP_ROM_QSTR(MP_QSTR_IO13), MP_ROM_PTR(&pin_GPIO13) }, - { MP_ROM_QSTR(MP_QSTR_IO14), MP_ROM_PTR(&pin_GPIO14) }, - { MP_ROM_QSTR(MP_QSTR_IO15), MP_ROM_PTR(&pin_GPIO15) }, - { MP_ROM_QSTR(MP_QSTR_IO16), MP_ROM_PTR(&pin_GPIO16) }, - { MP_ROM_QSTR(MP_QSTR_IO17), MP_ROM_PTR(&pin_GPIO17) }, - { MP_ROM_QSTR(MP_QSTR_IO18), MP_ROM_PTR(&pin_GPIO18) }, - { MP_ROM_QSTR(MP_QSTR_IO19), MP_ROM_PTR(&pin_GPIO19) }, - { MP_ROM_QSTR(MP_QSTR_IO20), MP_ROM_PTR(&pin_GPIO20) }, - { MP_ROM_QSTR(MP_QSTR_IO21), MP_ROM_PTR(&pin_GPIO21) }, - { MP_ROM_QSTR(MP_QSTR_IO35), MP_ROM_PTR(&pin_GPIO35) }, - { MP_ROM_QSTR(MP_QSTR_IO36), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_IO23), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO37) }, { MP_ROM_QSTR(MP_QSTR_IO37), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO38) }, { MP_ROM_QSTR(MP_QSTR_IO38), MP_ROM_PTR(&pin_GPIO38) }, - { MP_ROM_QSTR(MP_QSTR_IO39), MP_ROM_PTR(&pin_GPIO39) }, - { MP_ROM_QSTR(MP_QSTR_IO40), MP_ROM_PTR(&pin_GPIO40) }, - { MP_ROM_QSTR(MP_QSTR_IO41), MP_ROM_PTR(&pin_GPIO41) }, - { MP_ROM_QSTR(MP_QSTR_IO42), MP_ROM_PTR(&pin_GPIO42) }, - { MP_ROM_QSTR(MP_QSTR_IO43), MP_ROM_PTR(&pin_GPIO43) }, - { MP_ROM_QSTR(MP_QSTR_IO44), MP_ROM_PTR(&pin_GPIO44) }, - { MP_ROM_QSTR(MP_QSTR_IO45), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_IO21), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_IO22), MP_ROM_PTR(&pin_GPIO22) }, + { MP_ROM_QSTR(MP_QSTR_IO20), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_C6_WAKEUP), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_IO2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_IO36), MP_ROM_PTR(&pin_GPIO36) }, + + { MP_ROM_QSTR(MP_QSTR_IO32), MP_ROM_PTR(&pin_GPIO32) }, + { MP_ROM_QSTR(MP_QSTR_IO24), MP_ROM_PTR(&pin_GPIO24) }, + { MP_ROM_QSTR(MP_QSTR_IO25), MP_ROM_PTR(&pin_GPIO25) }, + + { MP_ROM_QSTR(MP_QSTR_IO33), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_IO26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_C6_EN), MP_ROM_PTR(&pin_GPIO54) }, + { MP_ROM_QSTR(MP_QSTR_IO54), MP_ROM_PTR(&pin_GPIO54) }, + { MP_ROM_QSTR(MP_QSTR_IO48), MP_ROM_PTR(&pin_GPIO48) }, + + { MP_ROM_QSTR(MP_QSTR_PA_CTRL), MP_ROM_PTR(&pin_GPIO53) }, + { MP_ROM_QSTR(MP_QSTR_IO53), MP_ROM_PTR(&pin_GPIO53) }, { MP_ROM_QSTR(MP_QSTR_IO46), MP_ROM_PTR(&pin_GPIO46) }, { MP_ROM_QSTR(MP_QSTR_IO47), MP_ROM_PTR(&pin_GPIO47) }, - { MP_ROM_QSTR(MP_QSTR_IO48), MP_ROM_PTR(&pin_GPIO48) }, - { MP_ROM_QSTR(MP_QSTR_NEOPIXEL), MP_ROM_PTR(&pin_GPIO48) }, + { MP_ROM_QSTR(MP_QSTR_IO27), MP_ROM_PTR(&pin_GPIO27) }, + + // I2S + { MP_ROM_QSTR(MP_QSTR_I2S_DSDIN), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_I2S_LRCK), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_I2S_ASDOUT), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_I2S_SCLK), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_I2S_MCLK), MP_ROM_PTR(&pin_GPIO13) }, + + // Ethernet + { MP_ROM_QSTR(MP_QSTR_RMII_RXDV), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_RMII_RXD0), MP_ROM_PTR(&pin_GPIO29) }, + { MP_ROM_QSTR(MP_QSTR_RMII_RXD1), MP_ROM_PTR(&pin_GPIO30) }, + { MP_ROM_QSTR(MP_QSTR_MDC), MP_ROM_PTR(&pin_GPIO31) }, + { MP_ROM_QSTR(MP_QSTR_RMII_TXD0), MP_ROM_PTR(&pin_GPIO34) }, + { MP_ROM_QSTR(MP_QSTR_RMII_TXD1), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_RMII_TXEN), MP_ROM_PTR(&pin_GPIO49) }, + { MP_ROM_QSTR(MP_QSTR_RMII_CLK), MP_ROM_PTR(&pin_GPIO50) }, + { MP_ROM_QSTR(MP_QSTR_PHY_RSTN), MP_ROM_PTR(&pin_GPIO51) }, + { MP_ROM_QSTR(MP_QSTR_MDIO), MP_ROM_PTR(&pin_GPIO52) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + // SD Card + { MP_ROM_QSTR(MP_QSTR_SD_DATA0), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA1), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA2), MP_ROM_PTR(&pin_GPIO41) }, + { MP_ROM_QSTR(MP_QSTR_SD_DATA3), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_SD_CLK), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_SD_CMD), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_SD_PWRN), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, }; MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/m5stack_tab5/board.c b/ports/espressif/boards/m5stack_tab5/board.c new file mode 100644 index 0000000000000..a3a9eec047145 --- /dev/null +++ b/ports/espressif/boards/m5stack_tab5/board.c @@ -0,0 +1,9 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "supervisor/board.h" + +// Use the MP_WEAK supervisor/shared/board.c versions of routines not defined here. diff --git a/ports/espressif/boards/m5stack_tab5/mpconfigboard.h b/ports/espressif/boards/m5stack_tab5/mpconfigboard.h new file mode 100644 index 0000000000000..f85f1fe24b8ab --- /dev/null +++ b/ports/espressif/boards/m5stack_tab5/mpconfigboard.h @@ -0,0 +1,28 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2019 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +// Micropython setup + +#define MICROPY_HW_BOARD_NAME "M5Stack Tab5" +#define MICROPY_HW_MCU_NAME "ESP32P4" + +// I2C bus for touch, IMU, RTC, power monitor, and GPIO expanders +#define CIRCUITPY_BOARD_I2C (1) +#define CIRCUITPY_BOARD_I2C_PIN {{.scl = &pin_GPIO32, .sda = &pin_GPIO31}} + +// UART +#define DEFAULT_UART_BUS_RX (&pin_GPIO38) +#define DEFAULT_UART_BUS_TX (&pin_GPIO37) + +// SPI on M5-Bus +#define DEFAULT_SPI_BUS_SCK (&pin_GPIO5) +#define DEFAULT_SPI_BUS_MOSI (&pin_GPIO18) +#define DEFAULT_SPI_BUS_MISO (&pin_GPIO19) + +// Use the second USB device (numbered 0 and 1) +#define CIRCUITPY_USB_DEVICE_INSTANCE 1 diff --git a/ports/espressif/boards/m5stack_tab5/mpconfigboard.mk b/ports/espressif/boards/m5stack_tab5/mpconfigboard.mk new file mode 100644 index 0000000000000..4871a259e7a48 --- /dev/null +++ b/ports/espressif/boards/m5stack_tab5/mpconfigboard.mk @@ -0,0 +1,14 @@ +USB_VID = 0x303A +USB_PID = 0x832B +USB_PRODUCT = "M5Stack Tab5" +USB_MANUFACTURER = "M5Stack" + +IDF_TARGET = esp32p4 + +CIRCUITPY_ESP_FLASH_SIZE = 16MB +CIRCUITPY_ESP_FLASH_MODE = qio +CIRCUITPY_ESP_FLASH_FREQ = 80m + +CIRCUITPY_ESP_PSRAM_SIZE = 32MB +CIRCUITPY_ESP_PSRAM_MODE = hpi +CIRCUITPY_ESP_PSRAM_FREQ = 200m diff --git a/ports/espressif/boards/m5stack_tab5/pins.c b/ports/espressif/boards/m5stack_tab5/pins.c new file mode 100644 index 0000000000000..67012cc670df2 --- /dev/null +++ b/ports/espressif/boards/m5stack_tab5/pins.c @@ -0,0 +1,128 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2020 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/board/__init__.h" + +static const mp_rom_map_elem_t board_module_globals_table[] = { + CIRCUITPYTHON_BOARD_DICT_STANDARD_ITEMS + + // M5-Bus and general GPIO + { MP_ROM_QSTR(MP_QSTR_G2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_G3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_G4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_G6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_G7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_G16), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_G17), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_G35), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_G45), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_G47), MP_ROM_PTR(&pin_GPIO47) }, + { MP_ROM_QSTR(MP_QSTR_G48), MP_ROM_PTR(&pin_GPIO48) }, + { MP_ROM_QSTR(MP_QSTR_G51), MP_ROM_PTR(&pin_GPIO51) }, + { MP_ROM_QSTR(MP_QSTR_G52), MP_ROM_PTR(&pin_GPIO52) }, + + // SPI (on M5-Bus) + { MP_ROM_QSTR(MP_QSTR_G5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_SCK), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_G18), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_MOSI), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_G19), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_MISO), MP_ROM_PTR(&pin_GPIO19) }, + + // UART (on M5-Bus) + { MP_ROM_QSTR(MP_QSTR_G37), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_TXD0), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_G38), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_RXD0), MP_ROM_PTR(&pin_GPIO38) }, + + // I2C (Touch, IMU, RTC, Power Monitor, GPIO Expanders) + { MP_ROM_QSTR(MP_QSTR_G31), MP_ROM_PTR(&pin_GPIO31) }, + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_PTR(&pin_GPIO31) }, + { MP_ROM_QSTR(MP_QSTR_G32), MP_ROM_PTR(&pin_GPIO32) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_PTR(&pin_GPIO32) }, + + // Touch Panel + { MP_ROM_QSTR(MP_QSTR_G23), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_TP_INT), MP_ROM_PTR(&pin_GPIO23) }, + + // LCD + { MP_ROM_QSTR(MP_QSTR_G22), MP_ROM_PTR(&pin_GPIO22) }, + { MP_ROM_QSTR(MP_QSTR_LCD_BL), MP_ROM_PTR(&pin_GPIO22) }, + + // Audio ES8388/ES7210 + { MP_ROM_QSTR(MP_QSTR_G26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_I2S_DSDIN), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_G27), MP_ROM_PTR(&pin_GPIO27) }, + { MP_ROM_QSTR(MP_QSTR_I2S_SCLK), MP_ROM_PTR(&pin_GPIO27) }, + { MP_ROM_QSTR(MP_QSTR_G28), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_I2S_ASDOUT), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_G29), MP_ROM_PTR(&pin_GPIO29) }, + { MP_ROM_QSTR(MP_QSTR_I2S_LRCK), MP_ROM_PTR(&pin_GPIO29) }, + { MP_ROM_QSTR(MP_QSTR_G30), MP_ROM_PTR(&pin_GPIO30) }, + { MP_ROM_QSTR(MP_QSTR_I2S_MCLK), MP_ROM_PTR(&pin_GPIO30) }, + + // Camera + { MP_ROM_QSTR(MP_QSTR_G36), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_CAM_MCLK), MP_ROM_PTR(&pin_GPIO36) }, + + // microSD Card (SDIO mode) + { MP_ROM_QSTR(MP_QSTR_G39), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_SD_DAT0), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_SD_MISO), MP_ROM_PTR(&pin_GPIO39) }, + { MP_ROM_QSTR(MP_QSTR_G40), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_SD_DAT1), MP_ROM_PTR(&pin_GPIO40) }, + { MP_ROM_QSTR(MP_QSTR_G41), MP_ROM_PTR(&pin_GPIO41) }, + { MP_ROM_QSTR(MP_QSTR_SD_DAT2), MP_ROM_PTR(&pin_GPIO41) }, + { MP_ROM_QSTR(MP_QSTR_G42), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_SD_DAT3), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_SD_CS), MP_ROM_PTR(&pin_GPIO42) }, + { MP_ROM_QSTR(MP_QSTR_G43), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_SD_CLK), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_SD_SCK), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_G44), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_SD_CMD), MP_ROM_PTR(&pin_GPIO44) }, + { MP_ROM_QSTR(MP_QSTR_SD_MOSI), MP_ROM_PTR(&pin_GPIO44) }, + + // RS485 + { MP_ROM_QSTR(MP_QSTR_G20), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_RS485_TX), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_G21), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_RS485_RX), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_G34), MP_ROM_PTR(&pin_GPIO34) }, + { MP_ROM_QSTR(MP_QSTR_RS485_DIR), MP_ROM_PTR(&pin_GPIO34) }, + + // HY2.0-4P PORT.A + { MP_ROM_QSTR(MP_QSTR_G53), MP_ROM_PTR(&pin_GPIO53) }, + { MP_ROM_QSTR(MP_QSTR_PORTA_Y), MP_ROM_PTR(&pin_GPIO53) }, + { MP_ROM_QSTR(MP_QSTR_G54), MP_ROM_PTR(&pin_GPIO54) }, + { MP_ROM_QSTR(MP_QSTR_PORTA_W), MP_ROM_PTR(&pin_GPIO54) }, + + // ESP32-C6 SDIO interface + { MP_ROM_QSTR(MP_QSTR_G8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_D3), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_G9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_D2), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_G10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_D1), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_G11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_D0), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_G12), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_CK), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_G13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_C6_SDIO2_CMD), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_G14), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_C6_IO2), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_G15), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_C6_RESET), MP_ROM_PTR(&pin_GPIO15) }, + + // I2C and SPI objects + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&board_i2c_obj) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&board_spi_obj) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }, +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/espressif/boards/m5stack_tab5/sdkconfig b/ports/espressif/boards/m5stack_tab5/sdkconfig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ports/espressif/common-hal/microcontroller/Pin.c b/ports/espressif/common-hal/microcontroller/Pin.c index 922cde763e363..3ccd8dcbb693b 100644 --- a/ports/espressif/common-hal/microcontroller/Pin.c +++ b/ports/espressif/common-hal/microcontroller/Pin.c @@ -77,6 +77,15 @@ static uint64_t _in_use_pin_mask; #define GPIO_SEL_47 ((uint64_t)PIN_BIT(47)) /*!< Pin 47 selected */ #define GPIO_SEL_48 ((uint64_t)PIN_BIT(48)) /*!< Pin 48 selected */ #endif +#if SOC_GPIO_PIN_COUNT > 49 +#define GPIO_SEL_49 ((uint64_t)PIN_BIT(49)) /*!< Pin 49 selected */ +#define GPIO_SEL_50 ((uint64_t)PIN_BIT(50)) /*!< Pin 50 selected */ +#define GPIO_SEL_51 ((uint64_t)PIN_BIT(51)) /*!< Pin 51 selected */ +#define GPIO_SEL_52 ((uint64_t)PIN_BIT(52)) /*!< Pin 52 selected */ +#define GPIO_SEL_53 ((uint64_t)PIN_BIT(53)) /*!< Pin 53 selected */ +#define GPIO_SEL_54 ((uint64_t)PIN_BIT(54)) /*!< Pin 54 selected */ +#define GPIO_SEL_55 ((uint64_t)PIN_BIT(55)) /*!< Pin 55 selected */ +#endif // Bit mask of all pins that should never EVER be reset. // Typically these are SPI flash and PSRAM control pins, and communication pins. @@ -202,10 +211,10 @@ static const uint64_t pin_mask_reset_forbidden = GPIO_SEL_32 | GPIO_SEL_33 | GPIO_SEL_34 | - #if CIRCUITPY_ESP_USB_SERIAL_JTAG + #if CIRCUITPY_ESP_USB_SERIAL_JTAG || (defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) && CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) // Never ever reset serial/JTAG communication pins. - GPIO_SEL_50 | // USB D- - GPIO_SEL_51 | // USB D+ + GPIO_SEL_24 | // USB D- + GPIO_SEL_25 | // USB D+ #endif #if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) && CONFIG_ESP_CONSOLE_UART_DEFAULT && CONFIG_ESP_CONSOLE_UART_NUM == 0 // Never reset debug UART/console pins. diff --git a/ports/espressif/common-hal/mipidsi/Bus.c b/ports/espressif/common-hal/mipidsi/Bus.c new file mode 100644 index 0000000000000..7f9dc01b773ec --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/Bus.c @@ -0,0 +1,52 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/mipidsi/Bus.h" +#include "bindings/espidf/__init__.h" +#include "py/runtime.h" +#include + +void common_hal_mipidsi_bus_construct(mipidsi_bus_obj_t *self, mp_uint_t frequency, uint8_t num_lanes) { + self->frequency = frequency; + self->num_data_lanes = num_lanes; + self->bus_handle = NULL; + + esp_ldo_channel_handle_t ldo_mipi_phy = NULL; + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = 3, + .voltage_mv = 2500, + }; + ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); + + // Create the DSI bus + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = num_lanes, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = frequency / 1000000, // Convert Hz to MHz + }; + + CHECK_ESP_RESULT(esp_lcd_new_dsi_bus(&bus_config, &self->bus_handle)); +} + +void common_hal_mipidsi_bus_deinit(mipidsi_bus_obj_t *self) { + if (common_hal_mipidsi_bus_deinited(self)) { + return; + } + + // Delete the DSI bus + if (self->bus_handle != NULL) { + esp_lcd_del_dsi_bus(self->bus_handle); + self->bus_handle = NULL; + } + + self->frequency = 0; + self->num_data_lanes = 0; +} + +bool common_hal_mipidsi_bus_deinited(mipidsi_bus_obj_t *self) { + return self->bus_handle == NULL; +} diff --git a/ports/espressif/common-hal/mipidsi/Bus.h b/ports/espressif/common-hal/mipidsi/Bus.h new file mode 100644 index 0000000000000..0ffd333e52dd8 --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/Bus.h @@ -0,0 +1,17 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include + +typedef struct { + mp_obj_base_t base; + mp_uint_t frequency; + esp_lcd_dsi_bus_handle_t bus_handle; + uint8_t num_data_lanes; +} mipidsi_bus_obj_t; diff --git a/ports/espressif/common-hal/mipidsi/Display.c b/ports/espressif/common-hal/mipidsi/Display.c new file mode 100644 index 0000000000000..eff06f02bf7d4 --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/Display.c @@ -0,0 +1,254 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/mipidsi/Display.h" +#include "shared-bindings/mipidsi/Bus.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/time/__init__.h" +#include "bindings/espidf/__init__.h" +#include +#include +#include "py/runtime.h" + +// Cache write-back function (should be from rom/cache.h but it's not always available) +extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size); + +void common_hal_mipidsi_display_construct(mipidsi_display_obj_t *self, + mipidsi_bus_obj_t *bus, + const uint8_t *init_sequence, + size_t init_sequence_len, + mp_uint_t virtual_channel, + mp_uint_t width, + mp_uint_t height, + mp_int_t rotation, + mp_uint_t color_depth, + const mcu_pin_obj_t *backlight_pin, + mp_float_t brightness, + mp_uint_t native_frames_per_second, + bool backlight_on_high, + mp_uint_t hsync_pulse_width, + mp_uint_t hsync_back_porch, + mp_uint_t hsync_front_porch, + mp_uint_t vsync_pulse_width, + mp_uint_t vsync_back_porch, + mp_uint_t vsync_front_porch, + mp_uint_t pixel_clock_frequency) { + self->bus = bus; + self->virtual_channel = virtual_channel; + self->width = width; + self->height = height; + self->rotation = rotation; + self->color_depth = color_depth; + self->native_frames_per_second = native_frames_per_second; + self->backlight_on_high = backlight_on_high; + self->framebuffer = NULL; + self->dbi_io_handle = NULL; + self->dpi_panel_handle = NULL; + + // Create the DBI interface for sending commands + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = virtual_channel, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + + CHECK_ESP_RESULT(esp_lcd_new_panel_io_dbi(bus->bus_handle, &dbi_config, &self->dbi_io_handle)); + + // Determine the pixel format based on color depth + lcd_color_format_t color_format; + if (color_depth == 16) { + color_format = LCD_COLOR_FMT_RGB565; + } else if (color_depth == 24) { + color_format = LCD_COLOR_FMT_RGB888; + } else { + common_hal_mipidsi_display_deinit(self); + mp_raise_ValueError(MP_ERROR_TEXT("Color depth must be 16 or 24")); + } + + // Create the DPI panel for sending pixel data + esp_lcd_dpi_panel_config_t dpi_config = { + .virtual_channel = virtual_channel, + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = pixel_clock_frequency / 1000000, + .in_color_format = color_format, + .num_fbs = 1, + .video_timing = { + .h_size = width, + .v_size = height, + .hsync_pulse_width = hsync_pulse_width, + .hsync_back_porch = hsync_back_porch, + .hsync_front_porch = hsync_front_porch, + .vsync_pulse_width = vsync_pulse_width, + .vsync_back_porch = vsync_back_porch, + .vsync_front_porch = vsync_front_porch, + }, + .flags = { + .use_dma2d = false, + .disable_lp = false, + }, + }; + + esp_err_t ret = esp_lcd_new_panel_dpi(bus->bus_handle, &dpi_config, &self->dpi_panel_handle); + if (ret != ESP_OK) { + common_hal_mipidsi_display_deinit(self); + CHECK_ESP_RESULT(ret); + } + + // Get the framebuffer allocated by the driver + void *fb = NULL; + ret = esp_lcd_dpi_panel_get_frame_buffer(self->dpi_panel_handle, 1, &fb); + if (ret != ESP_OK || fb == NULL) { + common_hal_mipidsi_display_deinit(self); + CHECK_ESP_RESULT(ret); + } + + self->framebuffer = (uint8_t *)fb; + self->framebuffer_size = width * height * (color_depth / 8); + + // Send initialization sequence (format matches busdisplay) + #define DELAY 0x80 + uint32_t i = 0; + while (i < init_sequence_len) { + const uint8_t *cmd = init_sequence + i; + uint8_t data_size = *(cmd + 1); + bool delay = (data_size & DELAY) != 0; + data_size &= ~DELAY; + const uint8_t *data = cmd + 2; + esp_lcd_panel_io_tx_param(self->dbi_io_handle, cmd[0], data, data_size); + + uint16_t delay_length_ms = 0; + if (delay) { + data_size++; + delay_length_ms = *(cmd + 1 + data_size); + if (delay_length_ms == 255) { + delay_length_ms = 500; + } + } + common_hal_time_delay_ms(delay_length_ms); + i += 2 + data_size; + } + + // Initialize the panel after sending init commands + ret = esp_lcd_panel_init(self->dpi_panel_handle); + if (ret != ESP_OK) { + common_hal_mipidsi_display_deinit(self); + CHECK_ESP_RESULT(ret); + } + + // Setup backlight PWM if pin is provided + self->backlight_inout.base.type = &mp_type_NoneType; + if (backlight_pin != NULL && common_hal_mcu_pin_is_free(backlight_pin)) { + #if (CIRCUITPY_PWMIO) + pwmout_result_t result = common_hal_pwmio_pwmout_construct(&self->backlight_pwm, backlight_pin, 0, 50000, false); + if (result != PWMOUT_OK) { + self->backlight_inout.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->backlight_inout, backlight_pin); + common_hal_never_reset_pin(backlight_pin); + } else { + self->backlight_pwm.base.type = &pwmio_pwmout_type; + common_hal_pwmio_pwmout_never_reset(&self->backlight_pwm); + } + #else + self->backlight_inout.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->backlight_inout, backlight_pin); + common_hal_never_reset_pin(backlight_pin); + #endif + + // Set initial brightness + #if (CIRCUITPY_PWMIO) + if (self->backlight_pwm.base.type == &pwmio_pwmout_type) { + common_hal_pwmio_pwmout_set_duty_cycle(&self->backlight_pwm, (uint16_t)(brightness * 0xFFFF)); + } else + #endif + if (self->backlight_inout.base.type == &digitalio_digitalinout_type) { + bool on = brightness > 0; + if (!backlight_on_high) { + on = !on; + } + common_hal_digitalio_digitalinout_set_value(&self->backlight_inout, on); + } + } +} + +void common_hal_mipidsi_display_deinit(mipidsi_display_obj_t *self) { + if (common_hal_mipidsi_display_deinited(self)) { + return; + } + + // Cleanup backlight + #if (CIRCUITPY_PWMIO) + if (self->backlight_pwm.base.type == &pwmio_pwmout_type) { + common_hal_pwmio_pwmout_deinit(&self->backlight_pwm); + } else + #endif + if (self->backlight_inout.base.type == &digitalio_digitalinout_type) { + common_hal_digitalio_digitalinout_deinit(&self->backlight_inout); + } + + // Delete the DPI panel + if (self->dpi_panel_handle != NULL) { + esp_lcd_panel_del(self->dpi_panel_handle); + self->dpi_panel_handle = NULL; + } + + // Delete the DBI interface + if (self->dbi_io_handle != NULL) { + esp_lcd_panel_io_del(self->dbi_io_handle); + self->dbi_io_handle = NULL; + } + + self->bus = NULL; + self->framebuffer = NULL; +} + +bool common_hal_mipidsi_display_deinited(mipidsi_display_obj_t *self) { + return self->dpi_panel_handle == NULL; +} + +void common_hal_mipidsi_display_refresh(mipidsi_display_obj_t *self) { + // Write back the cache to ensure framebuffer changes are visible + Cache_WriteBack_Addr((uint32_t)(self->framebuffer), self->framebuffer_size); + + // The DPI panel will automatically refresh from the framebuffer + // No explicit refresh call is needed as the DSI hardware continuously + // sends data from the framebuffer to the display +} + +int common_hal_mipidsi_display_get_width(mipidsi_display_obj_t *self) { + return self->width; +} + +int common_hal_mipidsi_display_get_height(mipidsi_display_obj_t *self) { + return self->height; +} + +int common_hal_mipidsi_display_get_row_stride(mipidsi_display_obj_t *self) { + return self->width * (self->color_depth / 8); +} + +int common_hal_mipidsi_display_get_color_depth(mipidsi_display_obj_t *self) { + return self->color_depth; +} + +int common_hal_mipidsi_display_get_native_frames_per_second(mipidsi_display_obj_t *self) { + return self->native_frames_per_second; +} + +bool common_hal_mipidsi_display_get_grayscale(mipidsi_display_obj_t *self) { + return false; +} + +mp_int_t common_hal_mipidsi_display_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) { + mipidsi_display_obj_t *self = (mipidsi_display_obj_t *)self_in; + + bufinfo->buf = self->framebuffer; + bufinfo->len = self->framebuffer_size; + bufinfo->typecode = 'B'; + + return 0; +} diff --git a/ports/espressif/common-hal/mipidsi/Display.h b/ports/espressif/common-hal/mipidsi/Display.h new file mode 100644 index 0000000000000..61e6cb73f6d75 --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/Display.h @@ -0,0 +1,37 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include "shared-bindings/mipidsi/Bus.h" +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/digitalio/DigitalInOut.h" +#include "common-hal/pwmio/PWMOut.h" +#include +#include + +typedef struct { + mp_obj_base_t base; + mipidsi_bus_obj_t *bus; + mp_uint_t virtual_channel; + mp_uint_t width; + mp_uint_t height; + mp_int_t rotation; + mp_uint_t color_depth; + mp_uint_t native_frames_per_second; + uint8_t *framebuffer; + esp_lcd_panel_io_handle_t dbi_io_handle; + esp_lcd_panel_handle_t dpi_panel_handle; + size_t framebuffer_size; + union { + digitalio_digitalinout_obj_t backlight_inout; + #if CIRCUITPY_PWMIO + pwmio_pwmout_obj_t backlight_pwm; + #endif + }; + bool backlight_on_high; +} mipidsi_display_obj_t; diff --git a/ports/espressif/common-hal/mipidsi/__init__.c b/ports/espressif/common-hal/mipidsi/__init__.c new file mode 100644 index 0000000000000..173f2146e87ac --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/__init__.c @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "shared-bindings/mipidsi/__init__.h" diff --git a/ports/espressif/common-hal/mipidsi/__init__.h b/ports/espressif/common-hal/mipidsi/__init__.h new file mode 100644 index 0000000000000..972a7c082fd7b --- /dev/null +++ b/ports/espressif/common-hal/mipidsi/__init__.h @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once diff --git a/ports/espressif/mpconfigport.mk b/ports/espressif/mpconfigport.mk index f133e86c6e068..c3585c7e39984 100644 --- a/ports/espressif/mpconfigport.mk +++ b/ports/espressif/mpconfigport.mk @@ -312,6 +312,9 @@ CIRCUITPY_PARALLELDISPLAYBUS = 0 # Library doesn't support P4 yet it seems CIRCUITPY_ESPCAMERA = 0 +# P4 has MIPI-DSI +CIRCUITPY_MIPIDSI = 1 + #### esp32s2 ########################################################## else ifeq ($(IDF_TARGET),esp32s2) # Modules diff --git a/ports/espressif/supervisor/port.c b/ports/espressif/supervisor/port.c index c629eb52932e7..192a91717169a 100644 --- a/ports/espressif/supervisor/port.c +++ b/ports/espressif/supervisor/port.c @@ -244,7 +244,7 @@ safe_mode_t port_init(void) { #endif // Send the ROM output out of the UART. This includes early logs. - #if DEBUG + #if DEBUG && (defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) && CONFIG_ESP_CONSOLE_UART_DEFAULT) esp_rom_install_uart_printf(); #endif diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index f4ac2350ffbf1..56c836cb3a490 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -294,6 +294,9 @@ endif ifeq ($(CIRCUITPY_MICROCONTROLLER),1) SRC_PATTERNS += microcontroller/% endif +ifeq ($(CIRCUITPY_MIPIDSI),1) +SRC_PATTERNS += mipidsi/% +endif ifeq ($(CIRCUITPY_MSGPACK),1) SRC_PATTERNS += msgpack/% endif @@ -531,6 +534,9 @@ SRC_COMMON_HAL_ALL = \ mdns/__init__.c \ mdns/Server.c \ mdns/RemoteService.c \ + mipidsi/Bus.c \ + mipidsi/Display.c \ + mipidsi/__init__.c \ neopixel_write/__init__.c \ nvm/ByteArray.c \ nvm/__init__.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 09ef4361427da..9bbe5691dfb3b 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -405,6 +405,9 @@ CFLAGS += -DCIRCUITPY_MEMORYMONITOR=$(CIRCUITPY_MEMORYMONITOR) CIRCUITPY_MICROCONTROLLER ?= 1 CFLAGS += -DCIRCUITPY_MICROCONTROLLER=$(CIRCUITPY_MICROCONTROLLER) +CIRCUITPY_MIPIDSI ?= 0 +CFLAGS += -DCIRCUITPY_MIPIDSI=$(CIRCUITPY_MIPIDSI) + CIRCUITPY_MSGPACK ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_MSGPACK=$(CIRCUITPY_MSGPACK) diff --git a/shared-bindings/mipidsi/Bus.c b/shared-bindings/mipidsi/Bus.c new file mode 100644 index 0000000000000..2f2ce046fe06c --- /dev/null +++ b/shared-bindings/mipidsi/Bus.c @@ -0,0 +1,96 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" + +#include "shared-bindings/mipidsi/Bus.h" +#include "shared-bindings/util.h" +#include "shared/runtime/context_manager_helpers.h" + +//| class Bus: +//| def __init__( +//| self, +//| *, +//| frequency: int = 500_000_000, +//| num_lanes: int = 2, +//| ) -> None: +//| """Create a MIPI DSI Bus object. +//| +//| This creates a DSI bus interface. The specific pins used are determined by the board. +//| DSI supports 1-4 data lanes. +//| +//| :param int frequency: the high speed clock frequency in Hz (default 500 MHz) +//| :param int num_lanes: the number of data lanes to use (default 2, range 1-4) +//| """ +//| + +static mp_obj_t mipidsi_bus_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_frequency, ARG_num_lanes }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_frequency, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 500000000} }, + { MP_QSTR_num_lanes, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mipidsi_bus_obj_t *self = mp_obj_malloc(mipidsi_bus_obj_t, &mipidsi_bus_type); + + mp_uint_t frequency = (mp_uint_t)mp_arg_validate_int_min(args[ARG_frequency].u_int, 1, MP_QSTR_frequency); + uint8_t num_lanes = (uint8_t)mp_arg_validate_int_range(args[ARG_num_lanes].u_int, 1, 4, MP_QSTR_num_lanes); + + common_hal_mipidsi_bus_construct(self, frequency, num_lanes); + + return MP_OBJ_FROM_PTR(self); +} + +//| def deinit(self) -> None: +//| """Free the resources (pins, timers, etc.) associated with this +//| `mipidsi.Bus` instance. After deinitialization, no further operations +//| may be performed.""" +//| ... +//| +static mp_obj_t mipidsi_bus_deinit(mp_obj_t self_in) { + mipidsi_bus_obj_t *self = (mipidsi_bus_obj_t *)self_in; + common_hal_mipidsi_bus_deinit(self); + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_bus_deinit_obj, mipidsi_bus_deinit); + +//| def __enter__(self) -> Bus: +//| """No-op used by Context Managers.""" +//| ... +//| +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| +static mp_obj_t mipidsi_bus_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_mipidsi_bus_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mipidsi_bus___exit___obj, 4, 4, mipidsi_bus_obj___exit__); + +static const mp_rom_map_elem_t mipidsi_bus_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mipidsi_bus_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mipidsi_bus___exit___obj) }, +}; +static MP_DEFINE_CONST_DICT(mipidsi_bus_locals_dict, mipidsi_bus_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + mipidsi_bus_type, + MP_QSTR_Bus, + MP_TYPE_FLAG_NONE, + make_new, mipidsi_bus_make_new, + locals_dict, &mipidsi_bus_locals_dict + ); diff --git a/shared-bindings/mipidsi/Bus.h b/shared-bindings/mipidsi/Bus.h new file mode 100644 index 0000000000000..f2fd13f7fc165 --- /dev/null +++ b/shared-bindings/mipidsi/Bus.h @@ -0,0 +1,15 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/mipidsi/Bus.h" + +extern const mp_obj_type_t mipidsi_bus_type; + +void common_hal_mipidsi_bus_construct(mipidsi_bus_obj_t *self, mp_uint_t frequency, uint8_t num_lanes); +void common_hal_mipidsi_bus_deinit(mipidsi_bus_obj_t *self); +bool common_hal_mipidsi_bus_deinited(mipidsi_bus_obj_t *self); diff --git a/shared-bindings/mipidsi/Display.c b/shared-bindings/mipidsi/Display.c new file mode 100644 index 0000000000000..a553c7ca8fd55 --- /dev/null +++ b/shared-bindings/mipidsi/Display.c @@ -0,0 +1,289 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/objtype.h" + +#include "shared-bindings/mipidsi/Display.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/util.h" +#include "shared-module/displayio/__init__.h" +#include "shared-module/framebufferio/FramebufferDisplay.h" + +//| class Display: +//| def __init__( +//| self, +//| bus: Bus, +//| init_sequence: ReadableBuffer, +//| *, +//| width: int, +//| height: int, +//| hsync_pulse_width: int, +//| hsync_back_porch: int, +//| hsync_front_porch: int, +//| vsync_pulse_width: int, +//| vsync_back_porch: int, +//| vsync_front_porch: int, +//| pixel_clock_frequency: int, +//| virtual_channel: int = 0, +//| rotation: int = 0, +//| color_depth: int = 16, +//| backlight_pin: Optional[microcontroller.Pin] = None, +//| brightness: float = 1.0, +//| native_frames_per_second: int = 60, +//| backlight_on_high: bool = True, +//| ) -> None: +//| """Create a MIPI DSI Display object connected to the given bus. +//| +//| This allocates a framebuffer and configures the DSI display to use the +//| specified virtual channel for communication. +//| +//| The framebuffer pixel format varies depending on color_depth: +//| +//| * 16 - Each two bytes is a pixel in RGB565 format. +//| * 24 - Each three bytes is a pixel in RGB888 format. +//| +//| A Display is often used in conjunction with a +//| `framebufferio.FramebufferDisplay`. +//| +//| :param Bus bus: the DSI bus to use +//| :param ~circuitpython_typing.ReadableBuffer init_sequence: Byte-packed initialization sequence for the display +//| :param int width: the width of the framebuffer in pixels +//| :param int height: the height of the framebuffer in pixels +//| :param int hsync_pulse_width: horizontal sync pulse width in pixel clocks +//| :param int hsync_back_porch: horizontal back porch in pixel clocks +//| :param int hsync_front_porch: horizontal front porch in pixel clocks +//| :param int vsync_pulse_width: vertical sync pulse width in lines +//| :param int vsync_back_porch: vertical back porch in lines +//| :param int vsync_front_porch: vertical front porch in lines +//| :param int pixel_clock_frequency: pixel clock frequency in Hz +//| :param int virtual_channel: the DSI virtual channel (0-3) +//| :param int rotation: the rotation of the display in degrees clockwise (0, 90, 180, 270) +//| :param int color_depth: the color depth of the framebuffer in bits (16 or 24) +//| :param microcontroller.Pin backlight_pin: Pin connected to the display's backlight +//| :param float brightness: Initial display brightness (0.0 to 1.0) +//| :param int native_frames_per_second: Number of display refreshes per second +//| :param bool backlight_on_high: If True, pulling the backlight pin high turns the backlight on +//| """ +//| + +static mp_obj_t mipidsi_display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_bus, ARG_init_sequence, ARG_width, ARG_height, ARG_hsync_pulse_width, ARG_hsync_back_porch, + ARG_hsync_front_porch, ARG_vsync_pulse_width, ARG_vsync_back_porch, ARG_vsync_front_porch, + ARG_pixel_clock_frequency, ARG_virtual_channel, ARG_rotation, + ARG_color_depth, ARG_backlight_pin, ARG_brightness, ARG_native_frames_per_second, + ARG_backlight_on_high }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bus, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_init_sequence, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_height, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_hsync_pulse_width, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_hsync_back_porch, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_hsync_front_porch, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_vsync_pulse_width, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_vsync_back_porch, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_vsync_front_porch, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_pixel_clock_frequency, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_virtual_channel, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_rotation, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_color_depth, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 16} }, + { MP_QSTR_backlight_pin, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none} }, + { MP_QSTR_brightness, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NEW_SMALL_INT(1)} }, + { MP_QSTR_native_frames_per_second, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 60} }, + { MP_QSTR_backlight_on_high, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mipidsi_display_obj_t *self = &allocate_display_bus_or_raise()->mipidsi; + self->base.type = &mipidsi_display_type; + + mipidsi_bus_obj_t *bus = mp_arg_validate_type(args[ARG_bus].u_obj, &mipidsi_bus_type, MP_QSTR_bus); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_init_sequence].u_obj, &bufinfo, MP_BUFFER_READ); + + const mcu_pin_obj_t *backlight_pin = + validate_obj_is_free_pin_or_none(args[ARG_backlight_pin].u_obj, MP_QSTR_backlight_pin); + + mp_float_t brightness = mp_obj_get_float(args[ARG_brightness].u_obj); + + mp_int_t rotation = args[ARG_rotation].u_int; + if (rotation % 90 != 0) { + mp_raise_ValueError(MP_ERROR_TEXT("Display rotation must be in 90 degree increments")); + } + + mp_uint_t virtual_channel = (mp_uint_t)mp_arg_validate_int_range(args[ARG_virtual_channel].u_int, 0, 3, MP_QSTR_virtual_channel); + mp_uint_t width = (mp_uint_t)mp_arg_validate_int_min(args[ARG_width].u_int, 0, MP_QSTR_width); + mp_uint_t height = (mp_uint_t)mp_arg_validate_int_min(args[ARG_height].u_int, 0, MP_QSTR_height); + mp_uint_t color_depth = args[ARG_color_depth].u_int; + + if (color_depth != 8 && color_depth != 16 && color_depth != 24) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_color_depth); + } + + common_hal_mipidsi_display_construct(self, bus, bufinfo.buf, bufinfo.len, virtual_channel, width, height, + rotation, color_depth, MP_OBJ_TO_PTR(backlight_pin), brightness, + args[ARG_native_frames_per_second].u_int, + args[ARG_backlight_on_high].u_bool, + args[ARG_hsync_pulse_width].u_int, + args[ARG_hsync_back_porch].u_int, + args[ARG_hsync_front_porch].u_int, + args[ARG_vsync_pulse_width].u_int, + args[ARG_vsync_back_porch].u_int, + args[ARG_vsync_front_porch].u_int, + args[ARG_pixel_clock_frequency].u_int); + + return MP_OBJ_FROM_PTR(self); +} + +// Helper to ensure we have the native super class instead of a subclass. +static mipidsi_display_obj_t *native_display(mp_obj_t display_obj) { + mp_obj_t native_display = mp_obj_cast_to_native_base(display_obj, &mipidsi_display_type); + mp_obj_assert_native_inited(native_display); + return MP_OBJ_TO_PTR(native_display); +} + +//| def deinit(self) -> None: +//| """Free the resources (pins, timers, etc.) associated with this +//| `mipidsi.Display` instance. After deinitialization, no further operations +//| may be performed.""" +//| ... +//| +static mp_obj_t mipidsi_display_deinit(mp_obj_t self_in) { + mipidsi_display_obj_t *self = native_display(self_in); + common_hal_mipidsi_display_deinit(self); + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_display_deinit_obj, mipidsi_display_deinit); + +static void check_for_deinit(mipidsi_display_obj_t *self) { + if (common_hal_mipidsi_display_deinited(self)) { + raise_deinited_error(); + } +} + +//| width: int +//| """The width of the framebuffer, in pixels.""" +static mp_obj_t mipidsi_display_get_width(mp_obj_t self_in) { + mipidsi_display_obj_t *self = native_display(self_in); + check_for_deinit(self); + return MP_OBJ_NEW_SMALL_INT(common_hal_mipidsi_display_get_width(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_display_get_width_obj, mipidsi_display_get_width); +MP_PROPERTY_GETTER(mipidsi_display_width_obj, + (mp_obj_t)&mipidsi_display_get_width_obj); + +//| height: int +//| """The height of the framebuffer, in pixels.""" +//| +//| +static mp_obj_t mipidsi_display_get_height(mp_obj_t self_in) { + mipidsi_display_obj_t *self = native_display(self_in); + check_for_deinit(self); + return MP_OBJ_NEW_SMALL_INT(common_hal_mipidsi_display_get_height(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_display_get_height_obj, mipidsi_display_get_height); + +MP_PROPERTY_GETTER(mipidsi_display_height_obj, + (mp_obj_t)&mipidsi_display_get_height_obj); + +//| color_depth: int +//| """The color depth of the framebuffer.""" +static mp_obj_t mipidsi_display_get_color_depth(mp_obj_t self_in) { + mipidsi_display_obj_t *self = native_display(self_in); + check_for_deinit(self); + return MP_OBJ_NEW_SMALL_INT(common_hal_mipidsi_display_get_color_depth(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_display_get_color_depth_obj, mipidsi_display_get_color_depth); +MP_PROPERTY_GETTER(mipidsi_display_color_depth_obj, + (mp_obj_t)&mipidsi_display_get_color_depth_obj); + + +static const mp_rom_map_elem_t mipidsi_display_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mipidsi_display_deinit_obj) }, + + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&mipidsi_display_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&mipidsi_display_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_color_depth), MP_ROM_PTR(&mipidsi_display_color_depth_obj) }, +}; +static MP_DEFINE_CONST_DICT(mipidsi_display_locals_dict, mipidsi_display_locals_dict_table); + +static void mipidsi_display_get_bufinfo(mp_obj_t self_in, mp_buffer_info_t *bufinfo) { + common_hal_mipidsi_display_get_buffer(self_in, bufinfo, 0); +} + +// These versions exist so that the prototype matches the protocol, +// avoiding a type cast that can hide errors +static void mipidsi_display_swapbuffers(mp_obj_t self_in, uint8_t *dirty_row_bitmap) { + (void)dirty_row_bitmap; + common_hal_mipidsi_display_refresh(self_in); +} + +static void mipidsi_display_deinit_proto(mp_obj_t self_in) { + common_hal_mipidsi_display_deinit(self_in); +} + +static int mipidsi_display_get_width_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_width(self_in); +} + +static int mipidsi_display_get_height_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_height(self_in); +} + +static int mipidsi_display_get_color_depth_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_color_depth(self_in); +} + +static bool mipidsi_display_get_grayscale_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_grayscale(self_in); +} + +static int mipidsi_display_get_bytes_per_cell_proto(mp_obj_t self_in) { + return 1; +} + +static int mipidsi_display_get_native_frames_per_second_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_native_frames_per_second(self_in); +} + +static bool mipidsi_display_get_pixels_in_byte_share_row_proto(mp_obj_t self_in) { + return true; +} + +static int mipidsi_display_get_row_stride_proto(mp_obj_t self_in) { + return common_hal_mipidsi_display_get_row_stride(self_in); +} + +static const framebuffer_p_t mipidsi_display_proto = { + MP_PROTO_IMPLEMENT(MP_QSTR_protocol_framebuffer) + .get_bufinfo = mipidsi_display_get_bufinfo, + .get_width = mipidsi_display_get_width_proto, + .get_height = mipidsi_display_get_height_proto, + .get_color_depth = mipidsi_display_get_color_depth_proto, + .get_grayscale = mipidsi_display_get_grayscale_proto, + .get_row_stride = mipidsi_display_get_row_stride_proto, + .get_bytes_per_cell = mipidsi_display_get_bytes_per_cell_proto, + .get_native_frames_per_second = mipidsi_display_get_native_frames_per_second_proto, + .get_pixels_in_byte_share_row = mipidsi_display_get_pixels_in_byte_share_row_proto, + .swapbuffers = mipidsi_display_swapbuffers, + .deinit = mipidsi_display_deinit_proto, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + mipidsi_display_type, + MP_QSTR_Display, + MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + locals_dict, &mipidsi_display_locals_dict, + make_new, mipidsi_display_make_new, + buffer, common_hal_mipidsi_display_get_buffer, + protocol, &mipidsi_display_proto + ); diff --git a/shared-bindings/mipidsi/Display.h b/shared-bindings/mipidsi/Display.h new file mode 100644 index 0000000000000..1cec788f16cd9 --- /dev/null +++ b/shared-bindings/mipidsi/Display.h @@ -0,0 +1,44 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "common-hal/mipidsi/Display.h" +#include "shared-bindings/mipidsi/Bus.h" +#include "common-hal/microcontroller/Pin.h" + +extern const mp_obj_type_t mipidsi_display_type; + +void common_hal_mipidsi_display_construct(mipidsi_display_obj_t *self, + mipidsi_bus_obj_t *bus, + const uint8_t *init_sequence, + size_t init_sequence_len, + mp_uint_t virtual_channel, + mp_uint_t width, + mp_uint_t height, + mp_int_t rotation, + mp_uint_t color_depth, + const mcu_pin_obj_t *backlight_pin, + mp_float_t brightness, + mp_uint_t native_frames_per_second, + bool backlight_on_high, + mp_uint_t hsync_pulse_width, + mp_uint_t hsync_back_porch, + mp_uint_t hsync_front_porch, + mp_uint_t vsync_pulse_width, + mp_uint_t vsync_back_porch, + mp_uint_t vsync_front_porch, + mp_uint_t pixel_clock_frequency); +void common_hal_mipidsi_display_deinit(mipidsi_display_obj_t *self); +bool common_hal_mipidsi_display_deinited(mipidsi_display_obj_t *self); +void common_hal_mipidsi_display_refresh(mipidsi_display_obj_t *self); +int common_hal_mipidsi_display_get_width(mipidsi_display_obj_t *self); +int common_hal_mipidsi_display_get_height(mipidsi_display_obj_t *self); +int common_hal_mipidsi_display_get_row_stride(mipidsi_display_obj_t *self); +int common_hal_mipidsi_display_get_color_depth(mipidsi_display_obj_t *self); +int common_hal_mipidsi_display_get_native_frames_per_second(mipidsi_display_obj_t *self); +bool common_hal_mipidsi_display_get_grayscale(mipidsi_display_obj_t *self); +mp_int_t common_hal_mipidsi_display_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); diff --git a/shared-bindings/mipidsi/__init__.c b/shared-bindings/mipidsi/__init__.c new file mode 100644 index 0000000000000..0b6b856592918 --- /dev/null +++ b/shared-bindings/mipidsi/__init__.c @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/runtime.h" + +#include "shared-bindings/mipidsi/Bus.h" +#include "shared-bindings/mipidsi/Display.h" + +//| """Low-level routines for interacting with MIPI DSI""" + +static const mp_rom_map_elem_t mipidsi_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mipidsi) }, + { MP_ROM_QSTR(MP_QSTR_Bus), MP_ROM_PTR(&mipidsi_bus_type) }, + { MP_ROM_QSTR(MP_QSTR_Display), MP_ROM_PTR(&mipidsi_display_type) }, +}; + +static MP_DEFINE_CONST_DICT(mipidsi_module_globals, mipidsi_module_globals_table); + +const mp_obj_module_t mipidsi_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mipidsi_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_mipidsi, mipidsi_module); diff --git a/shared-bindings/mipidsi/__init__.h b/shared-bindings/mipidsi/__init__.h new file mode 100644 index 0000000000000..972a7c082fd7b --- /dev/null +++ b/shared-bindings/mipidsi/__init__.h @@ -0,0 +1,7 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once diff --git a/shared-module/displayio/__init__.h b/shared-module/displayio/__init__.h index 29b8c64c97240..f2f4691b313a7 100644 --- a/shared-module/displayio/__init__.h +++ b/shared-module/displayio/__init__.h @@ -38,6 +38,9 @@ #if CIRCUITPY_DOTCLOCKFRAMEBUFFER #include "common-hal/dotclockframebuffer/DotClockFramebuffer.h" #endif +#if CIRCUITPY_MIPIDSI +#include "shared-bindings/mipidsi/Display.h" +#endif // Port unique frame buffers. #if CIRCUITPY_VIDEOCORE #include "bindings/videocore/Framebuffer.h" @@ -81,6 +84,9 @@ typedef struct { #if CIRCUITPY_AURORA_EPAPER aurora_epaper_framebuffer_obj_t aurora_epaper; #endif + #if CIRCUITPY_MIPIDSI + mipidsi_display_obj_t mipidsi; + #endif }; } primary_display_bus_t; From f469e93d53b6b6bc5c16a3b0291d996c0be4a4dc Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 3 Nov 2025 11:27:07 -0800 Subject: [PATCH 2/4] Fix Zephyr build --- ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml | 1 + .../zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml | 1 + ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml | 1 + 7 files changed, 7 insertions(+) diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 222a325e346b6..35966d6f9e2c6 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index 41631c3d6c02c..e3b6cc2248f2e 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index ea009aa36e1ca..328105d715059 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index a96d1bad1d2a6..2bc6e3e03a249 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index e4946decb190b..2a2e16fd16afe 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 7519d8817687b..5042eb972b143 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index e029020982d20..cfe48a90ceea4 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -66,6 +66,7 @@ mdns = false memorymap = false memorymonitor = false microcontroller = true +mipidsi = false msgpack = false neopixel_write = false nvm = false From d2dcfecb89420de5ad1495b7d5fa6d7bb5417f23 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Tue, 4 Nov 2025 14:01:24 -0500 Subject: [PATCH 3/4] espressif_esp32p4_function_ev/pins.c: add whitespace to clearly show pin aliases --- .../boards/espressif_esp32p4_function_ev/pins.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c b/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c index 7a3026f914dd9..165ce711b736a 100644 --- a/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c +++ b/ports/espressif/boards/espressif_esp32p4_function_ev/pins.c @@ -12,18 +12,25 @@ static const mp_rom_map_elem_t board_module_globals_table[] = { // Header Block J1 { MP_ROM_QSTR(MP_QSTR_I2C_SDA), MP_ROM_PTR(&pin_GPIO7) }, { MP_ROM_QSTR(MP_QSTR_IO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_I2C_SCL), MP_ROM_PTR(&pin_GPIO8) }, { MP_ROM_QSTR(MP_QSTR_IO8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_IO23), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO37) }, { MP_ROM_QSTR(MP_QSTR_IO37), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO38) }, { MP_ROM_QSTR(MP_QSTR_IO38), MP_ROM_PTR(&pin_GPIO38) }, + { MP_ROM_QSTR(MP_QSTR_IO21), MP_ROM_PTR(&pin_GPIO21) }, { MP_ROM_QSTR(MP_QSTR_IO22), MP_ROM_PTR(&pin_GPIO22) }, { MP_ROM_QSTR(MP_QSTR_IO20), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_C6_WAKEUP), MP_ROM_PTR(&pin_GPIO6) }, { MP_ROM_QSTR(MP_QSTR_IO6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, { MP_ROM_QSTR(MP_QSTR_IO3), MP_ROM_PTR(&pin_GPIO3) }, @@ -36,12 +43,15 @@ static const mp_rom_map_elem_t board_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_IO33), MP_ROM_PTR(&pin_GPIO33) }, { MP_ROM_QSTR(MP_QSTR_IO26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_C6_EN), MP_ROM_PTR(&pin_GPIO54) }, { MP_ROM_QSTR(MP_QSTR_IO54), MP_ROM_PTR(&pin_GPIO54) }, + { MP_ROM_QSTR(MP_QSTR_IO48), MP_ROM_PTR(&pin_GPIO48) }, { MP_ROM_QSTR(MP_QSTR_PA_CTRL), MP_ROM_PTR(&pin_GPIO53) }, { MP_ROM_QSTR(MP_QSTR_IO53), MP_ROM_PTR(&pin_GPIO53) }, + { MP_ROM_QSTR(MP_QSTR_IO46), MP_ROM_PTR(&pin_GPIO46) }, { MP_ROM_QSTR(MP_QSTR_IO47), MP_ROM_PTR(&pin_GPIO47) }, { MP_ROM_QSTR(MP_QSTR_IO27), MP_ROM_PTR(&pin_GPIO27) }, From 292d209fae5750df85537f2f4c33936b2ea8c5a2 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 4 Nov 2025 16:29:30 -0800 Subject: [PATCH 4/4] Better reasoned allocations --- ports/espressif/common-hal/mipidsi/Bus.c | 17 +++++++++++ ports/espressif/common-hal/mipidsi/Bus.h | 4 +++ ports/espressif/common-hal/mipidsi/Display.c | 4 ++- shared-bindings/mipidsi/Bus.c | 30 ++++++-------------- shared-bindings/mipidsi/Display.c | 2 +- shared-module/displayio/__init__.c | 17 ++++++++++- 6 files changed, 50 insertions(+), 24 deletions(-) diff --git a/ports/espressif/common-hal/mipidsi/Bus.c b/ports/espressif/common-hal/mipidsi/Bus.c index 7f9dc01b773ec..dbb559ea30f22 100644 --- a/ports/espressif/common-hal/mipidsi/Bus.c +++ b/ports/espressif/common-hal/mipidsi/Bus.c @@ -14,6 +14,10 @@ void common_hal_mipidsi_bus_construct(mipidsi_bus_obj_t *self, mp_uint_t frequen self->num_data_lanes = num_lanes; self->bus_handle = NULL; + if (self->use_count > 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q in use"), MP_QSTR_mipidsi); + } + esp_ldo_channel_handle_t ldo_mipi_phy = NULL; esp_ldo_channel_config_t ldo_mipi_phy_config = { .chan_id = 3, @@ -33,6 +37,9 @@ void common_hal_mipidsi_bus_construct(mipidsi_bus_obj_t *self, mp_uint_t frequen } void common_hal_mipidsi_bus_deinit(mipidsi_bus_obj_t *self) { + if (self->use_count > 0) { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q in use"), MP_QSTR_Bus); + } if (common_hal_mipidsi_bus_deinited(self)) { return; } @@ -50,3 +57,13 @@ void common_hal_mipidsi_bus_deinit(mipidsi_bus_obj_t *self) { bool common_hal_mipidsi_bus_deinited(mipidsi_bus_obj_t *self) { return self->bus_handle == NULL; } + +void mipidsi_bus_increment_use_count(mipidsi_bus_obj_t *self) { + self->use_count++; +} +void mipidsi_bus_decrement_use_count(mipidsi_bus_obj_t *self) { + self->use_count--; + if (self->use_count == 0) { + common_hal_mipidsi_bus_deinit(self); + } +} diff --git a/ports/espressif/common-hal/mipidsi/Bus.h b/ports/espressif/common-hal/mipidsi/Bus.h index 0ffd333e52dd8..b6257fd7223dd 100644 --- a/ports/espressif/common-hal/mipidsi/Bus.h +++ b/ports/espressif/common-hal/mipidsi/Bus.h @@ -14,4 +14,8 @@ typedef struct { mp_uint_t frequency; esp_lcd_dsi_bus_handle_t bus_handle; uint8_t num_data_lanes; + uint8_t use_count; // Up to 4 displays } mipidsi_bus_obj_t; + +void mipidsi_bus_increment_use_count(mipidsi_bus_obj_t *self); +void mipidsi_bus_decrement_use_count(mipidsi_bus_obj_t *self); diff --git a/ports/espressif/common-hal/mipidsi/Display.c b/ports/espressif/common-hal/mipidsi/Display.c index eff06f02bf7d4..dac16162e91ff 100644 --- a/ports/espressif/common-hal/mipidsi/Display.c +++ b/ports/espressif/common-hal/mipidsi/Display.c @@ -67,7 +67,7 @@ void common_hal_mipidsi_display_construct(mipidsi_display_obj_t *self, color_format = LCD_COLOR_FMT_RGB888; } else { common_hal_mipidsi_display_deinit(self); - mp_raise_ValueError(MP_ERROR_TEXT("Color depth must be 16 or 24")); + mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_color_depth); } // Create the DPI panel for sending pixel data @@ -173,6 +173,7 @@ void common_hal_mipidsi_display_construct(mipidsi_display_obj_t *self, common_hal_digitalio_digitalinout_set_value(&self->backlight_inout, on); } } + mipidsi_bus_increment_use_count(self->bus); } void common_hal_mipidsi_display_deinit(mipidsi_display_obj_t *self) { @@ -202,6 +203,7 @@ void common_hal_mipidsi_display_deinit(mipidsi_display_obj_t *self) { self->dbi_io_handle = NULL; } + mipidsi_bus_decrement_use_count(self->bus); self->bus = NULL; self->framebuffer = NULL; } diff --git a/shared-bindings/mipidsi/Bus.c b/shared-bindings/mipidsi/Bus.c index 2f2ce046fe06c..bac3cd3c75e1b 100644 --- a/shared-bindings/mipidsi/Bus.c +++ b/shared-bindings/mipidsi/Bus.c @@ -28,6 +28,13 @@ //| :param int num_lanes: the number of data lanes to use (default 2, range 1-4) //| """ //| +// +// +// All MCUs we support only have one DSI bus but it can be shared between multiple displays. One +// display may live longer than the VM, so we need to allocate the bus outside the VM. To simplify +// memory tracking, we use a global object for the bus. +// +static mipidsi_bus_obj_t _mipidsi_bus_obj; static mp_obj_t mipidsi_bus_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { enum { ARG_frequency, ARG_num_lanes }; @@ -38,7 +45,8 @@ static mp_obj_t mipidsi_bus_make_new(const mp_obj_type_t *type, size_t n_args, s mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mipidsi_bus_obj_t *self = mp_obj_malloc(mipidsi_bus_obj_t, &mipidsi_bus_type); + _mipidsi_bus_obj.base.type = &mipidsi_bus_type; + mipidsi_bus_obj_t *self = &_mipidsi_bus_obj; mp_uint_t frequency = (mp_uint_t)mp_arg_validate_int_min(args[ARG_frequency].u_int, 1, MP_QSTR_frequency); uint8_t num_lanes = (uint8_t)mp_arg_validate_int_range(args[ARG_num_lanes].u_int, 1, 4, MP_QSTR_num_lanes); @@ -62,28 +70,8 @@ static mp_obj_t mipidsi_bus_deinit(mp_obj_t self_in) { static MP_DEFINE_CONST_FUN_OBJ_1(mipidsi_bus_deinit_obj, mipidsi_bus_deinit); -//| def __enter__(self) -> Bus: -//| """No-op used by Context Managers.""" -//| ... -//| -// Provided by context manager helper. - -//| def __exit__(self) -> None: -//| """Automatically deinitializes the hardware when exiting a context. See -//| :ref:`lifetime-and-contextmanagers` for more info.""" -//| ... -//| -static mp_obj_t mipidsi_bus_obj___exit__(size_t n_args, const mp_obj_t *args) { - (void)n_args; - common_hal_mipidsi_bus_deinit(args[0]); - return mp_const_none; -} -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mipidsi_bus___exit___obj, 4, 4, mipidsi_bus_obj___exit__); - static const mp_rom_map_elem_t mipidsi_bus_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mipidsi_bus_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, - { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mipidsi_bus___exit___obj) }, }; static MP_DEFINE_CONST_DICT(mipidsi_bus_locals_dict, mipidsi_bus_locals_dict_table); diff --git a/shared-bindings/mipidsi/Display.c b/shared-bindings/mipidsi/Display.c index a553c7ca8fd55..ad4522d653c7f 100644 --- a/shared-bindings/mipidsi/Display.c +++ b/shared-bindings/mipidsi/Display.c @@ -124,7 +124,7 @@ static mp_obj_t mipidsi_display_make_new(const mp_obj_type_t *type, size_t n_arg mp_uint_t height = (mp_uint_t)mp_arg_validate_int_min(args[ARG_height].u_int, 0, MP_QSTR_height); mp_uint_t color_depth = args[ARG_color_depth].u_int; - if (color_depth != 8 && color_depth != 16 && color_depth != 24) { + if (color_depth != 16 && color_depth != 24) { mp_raise_ValueError_varg(MP_ERROR_TEXT("Invalid %q"), MP_QSTR_color_depth); } diff --git a/shared-module/displayio/__init__.c b/shared-module/displayio/__init__.c index 3ffd4d002f534..747fdbec5a75a 100644 --- a/shared-module/displayio/__init__.c +++ b/shared-module/displayio/__init__.c @@ -41,6 +41,10 @@ #include "shared-module/aurora_epaper/aurora_framebuffer.h" #endif +#if CIRCUITPY_MIPIDSI +#include "shared-bindings/mipidsi/Display.h" +#endif + #ifdef BOARD_USE_INTERNAL_SPI #include "supervisor/spi_flash_api.h" #endif @@ -64,7 +68,7 @@ displayio_buffer_transform_t null_transform = { .transpose_xy = false }; -#if CIRCUITPY_RGBMATRIX || CIRCUITPY_IS31FL3741 || CIRCUITPY_VIDEOCORE || CIRCUITPY_PICODVI +#if CIRCUITPY_RGBMATRIX || CIRCUITPY_IS31FL3741 || CIRCUITPY_VIDEOCORE || CIRCUITPY_PICODVI || CIRCUITPY_MIPIDSI static bool any_display_uses_this_framebuffer(mp_obj_base_t *obj) { for (uint8_t i = 0; i < CIRCUITPY_DISPLAY_LIMIT; i++) { if (displays[i].display_base.type == &framebufferio_framebufferdisplay_type) { @@ -180,6 +184,10 @@ static void common_hal_displayio_release_displays_impl(bool keep_primary) { } else if (bus_type == &picodvi_framebuffer_type) { common_hal_picodvi_framebuffer_deinit(&display_buses[i].picodvi); #endif + #if CIRCUITPY_MIPIDSI + } else if (bus_type == &mipidsi_display_type) { + common_hal_mipidsi_display_deinit(&display_buses[i].mipidsi); + #endif } display_buses[i].bus_base.type = &mp_type_NoneType; } @@ -333,6 +341,13 @@ void reset_displays(void) { // Set to None, gets deinit'd up by display_base display_buses[i].bus_base.type = &mp_type_NoneType; #endif + #if CIRCUITPY_MIPIDSI + } else if (display_bus_type == &mipidsi_display_type) { + mipidsi_display_obj_t *display = &display_buses[i].mipidsi; + if (!any_display_uses_this_framebuffer(&display->base)) { + common_hal_mipidsi_display_deinit(display); + } + #endif } else { // Not an active display bus. continue;