From 8af8a02d10f38f4a280cc225c2b2d46482ab51d3 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 31 Mar 2026 21:12:47 +0200 Subject: [PATCH 1/3] Handle "mpos.main" errors and wait for 5 seconds On a new, unsupported device, you may get a reboot loop. Because if the "mpos.main" can't detect the board it just continues and tries to init the display. In by case (on a CYD) it results in a hard crash -> a boot loop. This loop is short and avoids to be able to use "mpremote cp"... It looks like: ``` ... sys.version=3.4.0; LVGL (9.3.0) MicroPython (1.25.0) Binding compiled on 2026-04-01 sys.implementation=(name='micropython', version=(1, 25, 0, ''), _machine='Generic ESP32 module with SPIRAM with ESP32', _mpy=11014, _build='ESP32_GENERIC-SPIRAM') Free space on root filesystem: total_space=0 / used_space=0 / free_space=0 bytes RAM: 110656 free, 640 allocated, 111296 total Passing execution over to mpos.main MicroPythonOS 0.9.0 running lib/mpos/main.py unPhone ? odroid_go ? m5stack_core2 ? Try to I2C initialized on sda=21 scl=22 fail_save_i2c ok Attempt to write a single byte to I2C bus address 0x34... No device at this address: [Errno 116] ETIMEDOUT m5stack_fire ? Try to I2C initialized on sda=21 scl=22 fail_save_i2c ok Attempt to write a single byte to I2C bus address 0x68... No device at this address: [Errno 116] ETIMEDOUT Unknown board: couldn't detect known I2C devices or unique_id prefix A fatal error occurred. The crash dump printed below may be used to help determine what caused it. If you are not already running the most recent version of MicroPython, consider upgrading. New versions often fix bugs. To learn more about how to debug and/or report this crash visit the wiki page at: https://github.com/micropython/micropython/wiki/ESP32-debugging LVGL MicroPython IDF version : 67c1de1e Machine : Generic ESP32 module with SPIRAM with ESP32 Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled. ... ``` The interesting line is `Unknown board: couldn't detect known I2C devices or unique_id prefix` so that `detect_board()` returns `None` but the code just continues. This PR will raise and error, that will be captured in the `main.py` and we end in the REPL. This looks like: ``` ... sys.version=3.4.0; LVGL (9.3.0) MicroPython (1.25.0) Binding compiled on 2026-04-01 sys.implementation=(name='micropython', version=(1, 25, 0, ''), _machine='Generic ESP32 module with SPIRAM with ESP32', _mpy=11014, _build='ESP32_GENERIC-SPIRAM') Free space on root filesystem: total_space=0 / used_space=0 / free_space=0 bytes RAM: 110656 free, 640 allocated, 111296 total Passing execution over to mpos.main MicroPythonOS 0.9.0 running lib/mpos/main.py unPhone ? odroid_go ? m5stack_core2 ? Try to I2C initialized on sda=21 scl=22 fail_save_i2c ok Attempt to write a single byte to I2C bus address 0x34... No device at this address: [Errno 116] ETIMEDOUT m5stack_fire ? Try to I2C initialized on sda=21 scl=22 fail_save_i2c ok Attempt to write a single byte to I2C bus address 0x68... No device at this address: [Errno 116] ETIMEDOUT Unknown board: couldn't detect known I2C devices or unique_id prefix Traceback (most recent call last): File "main.py", line 32, in File "mpos/main.py", line 253, in RuntimeError: No board detected, exit initialization! Error in mpos.main, sleep for 5 seconds and end then... LVGL (9.3.0) MicroPython (1.25.0) Binding compiled on 2026-04-01; Generic ESP32 module with SPIRAM with ESP32 Type "help()" for more information. >>> ``` --- internal_filesystem/lib/mpos/main.py | 3 +++ internal_filesystem/main.py | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index e7b83948..ab474efd 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -247,6 +247,9 @@ def detect_board(): print(f"Detected {board} system, importing mpos.board.{board}") DeviceInfo.set_hardware_id(board) __import__(f"mpos.board.{board}") +else: + # It makes no sense to continue, because we have no display etc... + raise RuntimeError("No board detected, exit initialization!") # Allow LVGL M:/path/to/file or M:relative/path/to/file to work for image set_src etc import mpos.fs_driver diff --git a/internal_filesystem/main.py b/internal_filesystem/main.py index c44ee8a6..f0fcb96b 100644 --- a/internal_filesystem/main.py +++ b/internal_filesystem/main.py @@ -28,4 +28,11 @@ ) print("Passing execution over to mpos.main") -import mpos.main # noqa: F401 +try: + import mpos.main # noqa: F401 +except Exception as e: + sys.print_exception(e) + import time + print(f"Error in mpos.main, sleep for 5 seconds and end then...") + time.sleep(5) + From 3ecb0f602a82be33f714bcd85239e4a974bc7e7c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 31 Mar 2026 21:12:47 +0200 Subject: [PATCH 2/3] Add "esp32-small" build target Seems to be just another hacky way to expand the build process. Maybe the whole thing should be restructured? The idea is to add a target for CYD that has 4MB flash and no PSRAM. On the other side the normal "esp32" build runs here ... So i really don't know it it's needed... --- scripts/build_mpos.sh | 68 ++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/scripts/build_mpos.sh b/scripts/build_mpos.sh index 4308b4a9..8827fbcc 100755 --- a/scripts/build_mpos.sh +++ b/scripts/build_mpos.sh @@ -8,14 +8,15 @@ target="$1" buildtype="$2" if [ -z "$target" ]; then - echo "Usage: $0 target" - echo "Usage: $0 " - echo "Example: $0 unix" - echo "Example: $0 macOS" - echo "Example: $0 esp32" - echo "Example: $0 esp32s3" - echo "Example: $0 unphone" - echo "Example: $0 clean" + echo "Usage: $0 target" + echo "Usage: $0 " + echo "Example: $0 unix" + echo "Example: $0 macOS" + echo "Example: $0 esp32" + echo "Example: $0 esp32-small" + echo "Example: $0 esp32s3" + echo "Example: $0 unphone" + echo "Example: $0 clean" exit 1 fi @@ -106,20 +107,27 @@ popd echo "Refreshing freezefs..." "$codebasedir"/scripts/freezefs_mount_builtin.sh -if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; then - partition_size="4194304" - flash_size="16" +if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" -o "$target" == "esp32-small" ]; then + partition_size="4194304" + flash_size="16" otasupport="--ota" extra_configs="" - if [ "$target" == "esp32" ]; then + if [ "$target" == "esp32" ]; then BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM + elif [ "$target" == "esp32-small" ]; then + # No PSRAM, so do not set SPIRAM-specific options + BOARD=ESP32_GENERIC + BOARD_VARIANT= + partition_size="3900000" + flash_size="4" + otasupport="" # too small for 2 OTA partitions + internal storage else # esp32s3 or unphone - if [ "$target" == "unphone" ]; then - partition_size="3900000" - flash_size="8" + if [ "$target" == "unphone" ]; then + partition_size="3900000" + flash_size="8" otasupport="" # too small for 2 OTA partitions + internal storage - fi + fi BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT # These options disable hardware AES, SHA and MPI because they give warnings in QEMU: [AES] Error reading from GDMA buffer @@ -128,6 +136,12 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; # --py-freertos: add MicroPython FreeRTOS module to expose internals extra_configs="$extra_configs --py-freertos" fi + + if [ "$BOARD_VARIANT" == "SPIRAM" -o "$BOARD_VARIANT" == "SPIRAM_OCT" ]; then + # Camera only works on boards configured with spiram, otherwise the build breaks + extra_configs="$extra_configs USER_C_MODULE=$codebasedir/micropython-camera-API/src/micropython.cmake" + fi + manifest=$(readlink -f "$codebasedir"/manifests/manifest.py) frozenmanifest="FROZEN_MANIFEST=$manifest" # Comment this out if you want to make a build without any frozen files, just an empty MicroPython + whatever files you have on the internal storage echo "Note that you can also prevent the builtin filesystem from being mounted by umounting it and creating a builtin/ folder." @@ -148,17 +162,17 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y # CONFIG_ADC_MIC_TASK_CORE=1 because with the default (-1) it hangs the CPU # CONFIG_SPIRAM_XIP_FROM_PSRAM: load entire firmware into RAM to reduce SD vs PSRAM contention (recommended at https://github.com/MicroPythonOS/MicroPythonOS/issues/17) - python3 make.py "$otasupport" --optimize-size --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ - USER_C_MODULE="$codebasedir"/micropython-camera-API/src/micropython.cmake \ - USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake \ - USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake \ - CONFIG_FREERTOS_USE_TRACE_FACILITY=y \ - CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y \ - CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y \ - CONFIG_ADC_MIC_TASK_CORE=1 \ - $extra_configs \ - "$frozenmanifest" - + set -x + python3 make.py $otasupport --optimize-size --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ + USER_C_MODULE="$codebasedir"/secp256k1-embedded-ecdh/micropython.cmake \ + USER_C_MODULE="$codebasedir"/c_mpos/micropython.cmake \ + CONFIG_FREERTOS_USE_TRACE_FACILITY=y \ + CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y \ + CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y \ + CONFIG_ADC_MIC_TASK_CORE=1 \ + $extra_configs \ + "$frozenmanifest" + set +x popd elif [ "$target" == "unix" -o "$target" == "macOS" ]; then manifest=$(readlink -f "$codebasedir"/manifests/manifest.py) From edeaa364e48e3c2bae9c606c5328754fe9c40db1 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 31 Mar 2026 21:14:02 +0200 Subject: [PATCH 3/3] Support Cheap Yellow Display see: https://github.com/MicroPythonOS/MicroPythonOS/discussions/88 --- internal_filesystem/lib/mpos/board/cyd.py | 174 ++++++++++++++++++++++ internal_filesystem/lib/mpos/main.py | 4 + 2 files changed, 178 insertions(+) create mode 100644 internal_filesystem/lib/mpos/board/cyd.py diff --git a/internal_filesystem/lib/mpos/board/cyd.py b/internal_filesystem/lib/mpos/board/cyd.py new file mode 100644 index 00000000..eadade09 --- /dev/null +++ b/internal_filesystem/lib/mpos/board/cyd.py @@ -0,0 +1,174 @@ +print("cyd.py initialization") +""" +Cheap Yellow Display: + https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display +Tested with "ESP32-2432S028" + + * Display: ili9341 320x240 + * Touch: xpt2046 + +Original author: https://github.com/jedie +""" + +import time + +import drivers.display.ili9341 as ili9341 +import lcd_bus +import lvgl as lv +import machine +import mpos.ui +from drivers.indev.xpt2046 import XPT2046 +from machine import ADC, Pin +from micropython import const +from mpos import InputManager + +# Display (HSPI) +SPI_HOST = const(0) +SPI_FREQ = const(8_000_000) # 8MHz max for ILI9341 +SPI_DC = const(2) # TFT_RS / TFT_DC +SPI_CS = const(15) # TFT_CS +SPI_RST = const(15) # No reset pin, so use CS pin +SPI_SCK = const(14) # TFT_SCK +SPI_MISO = const(12) # TFT_SDO / TFT_MISO +SPI_MOSI = const(13) # TFT_SDI / TFT_MOSI + +BACKLIGHT_PIN = const(21) # TFT_BL (also on P3) +DISPLAY_WIDTH = const(320) +DISPLAY_HEIGHT = const(240) +LCD_TYPE = const(2) # ILI9341 type 2 + +# Touch Screen (XPT2046) +TOUCH_SPI_HOST = const(1) +TOUCH_SPI_FREQ = const(1_000_000) # 1MHz +TOUCH_SPI_SCK = const(25) +TOUCH_SPI_MOSI = const(32) +TOUCH_SPI_MISO = const(39) +TOUCH_SPI_CS = const(33) +TOUCH_SPI_INT = const(36) + +# SD Card (VSPI) +SDCARD_SLOT = const(2) +SDCARD_SCK = const(18) +SDCARD_MISO = const(19) +SDCARD_MOSI = const(23) +SDCARD_CS = const(5) + +# RGB LED (active low) +LED_RED = const(4) +LED_GREEN = const(16) +LED_BLUE = const(17) + +# Light Sensor +LIGHTSENSOR_ADC_PIN = const(34) + +# Speaker (amplified) +SPEAKER_PIN = const(26) + +# Buttons +BUTTON_BOOT = const(0) + +# Connectors +P3_IO35 = const(35) # Input only +P3_IO22 = const(22) +CN1_IO22 = const(22) +CN1_IO27 = const(27) +P1_IO1 = const(1) # TX (maybe usable as GPIO) +P1_IO3 = const(3) # RX (maybe usable as GPIO) + + +# RGB LED at the back +red_led = Pin(LED_RED, Pin.OUT) +green_led = Pin(LED_GREEN, Pin.OUT) +blue_led = Pin(LED_BLUE, Pin.OUT) + +# RGB LED (and backlight) will also work with machine.PWM for dimming + +# Turn on all LEDs (active low): +red_led.on() +green_led.on() +blue_led.on() + + +# Read light sensor +lightsensor = ADC(LIGHTSENSOR_ADC_PIN, atten=ADC.ATTN_0DB) +print(f"{lightsensor.read_uv()=}") + + +print("cyd.py machine.SPI.Bus() initialization") +try: + spi_bus = machine.SPI.Bus( + host=TOUCH_SPI_HOST, sck=SPI_SCK, mosi=SPI_MOSI, miso=SPI_MISO + ) +except Exception as e: + print(f"Error initializing SPI bus: {e}") + print("Attempting hard reset in 3sec...") + time.sleep(3) + machine.reset() + +print("cyd.py lcd_bus.SPIBus() initialization") +display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=SPI_DC, cs=SPI_CS) + +print("cyd.py ili9341.ILI9341() initialization") +try: + mpos.ui.main_display = ili9341.ILI9341( + data_bus=display_bus, + display_width=DISPLAY_WIDTH, + display_height=DISPLAY_HEIGHT, + color_space=lv.COLOR_FORMAT.RGB565, + color_byte_order=ili9341.BYTE_ORDER_BGR, + rgb565_byte_swap=True, + reset_pin=SPI_RST, + reset_state=ili9341.STATE_LOW, + backlight_pin=BACKLIGHT_PIN, + backlight_on_state=ili9341.STATE_PWM, + ) +except Exception as e: + print(f"Error initializing ILI9341: {e}") + print("Attempting hard reset in 3sec...") + time.sleep(3) + machine.reset() + + +print("cyd.py display.init()") +mpos.ui.main_display.init(LCD_TYPE) +mpos.ui.main_display.set_power(True) +mpos.ui.main_display.set_color_inversion(False) +mpos.ui.main_display.set_backlight(100) + +print("cyd.py lv.init() initialization") +lv.init() + +print("cyd.py Touch initialization") +touch_dev = machine.SPI.Device(spi_bus=spi_bus, freq=TOUCH_SPI_FREQ, cs=TOUCH_SPI_CS) + +indev = XPT2046( + touch_dev, + lcd_cs=SPI_CS, + touch_cs=TOUCH_SPI_CS, + display_width=DISPLAY_WIDTH, + display_height=DISPLAY_HEIGHT, + startup_rotation=lv.DISPLAY_ROTATION._0, +) + + +group = lv.group_create() +group.set_default() +# +# # Create and set up the input device +# indev = lv.indev_create() +# indev.set_type(lv.INDEV_TYPE.KEYPAD) +# +indev.set_group( + group +) # is this needed? maybe better to move the default group creation to main.py so it's available everywhere... +# disp = lv.display_get_default() # NOQA +# indev.set_display(disp) # different from display +indev.enable(True) # NOQA +InputManager.register_indev(indev) + +# Turn off all LEDs to indicate initialization is done: +red_led.off() +green_led.off() +blue_led.off() + +print("\ncyd.py init finished\n") diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index ab474efd..3b48e58d 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -172,6 +172,10 @@ def detect_board(): if unique_id_prefixes == b'\x30\xae\xa4': return "odroid_go" + print("cyd - Cheap Yellow Display ?") + if unique_id_prefixes == b'\xec\xe34': # FIXME: Use other detection! MAC seems to be random! + return "cyd" + # Do I2C-based board detection # IMPORTANT: ESP32 GPIO 6-11 are internal SPI flash pins and will cause WDT reset if used. # ESP32-S3 has more usable GPIOs (up to 48). Detect chip variant first to skip unsafe probes.