From 1d9ba8f814f8e39b0618371ae7075c97675eee1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Cebri=C3=A1n?= Date: Sat, 7 Feb 2026 17:28:56 +0100 Subject: [PATCH 1/5] Force M5Stack-Fire board detection for ESP32 M5Stack-Fire board detection should be: i2c0 = I2C(0, sda=Pin(21), scl=Pin(22)) if {0x68} <= set(i2c0.scan()): # IMU (MPU6886) return "m5stack-fire" But there are some pin incompatibilities between ESP32 and ESP32-S3 boards that don't allow I2C scan. --- internal_filesystem/lib/mpos/main.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index 360a7423..fff0c65b 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -31,16 +31,7 @@ def detect_board(): if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS return "linux" elif sys.platform == "esp32": - from machine import Pin, I2C - i2c0 = I2C(0, sda=Pin(48), scl=Pin(47)) - if {0x15, 0x6B} <= set(i2c0.scan()): # touch screen and IMU (at least, possibly more) - return "waveshare_esp32_s3_touch_lcd_2" - else: - i2c0 = I2C(0, sda=Pin(9), scl=Pin(18)) - if {0x6B} <= set(i2c0.scan()): # IMU (plus possibly the Communicator's LANA TNY at 0x38) - return "fri3d_2024" - else: # if {0x6A} <= set(i2c0.scan()): # IMU (plus a few others, to be added later, but this should work) - return "fri3d_2026" + return "m5stack_fire" board = detect_board() From 96fb2d19376c6e32bb6e81dd9f4d636618848289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Cebri=C3=A1n?= Date: Sat, 7 Feb 2026 17:32:31 +0100 Subject: [PATCH 2/5] Add support for M5Stack-Fire board M5Stack-Fire uses an ILI9342 instead of an ILI9341. Therefore, lvgl_micropython should include "_ili9341_init_type3.py". --- .../lib/mpos/board/m5stack_fire.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 internal_filesystem/lib/mpos/board/m5stack_fire.py diff --git a/internal_filesystem/lib/mpos/board/m5stack_fire.py b/internal_filesystem/lib/mpos/board/m5stack_fire.py new file mode 100644 index 00000000..2b60bed9 --- /dev/null +++ b/internal_filesystem/lib/mpos/board/m5stack_fire.py @@ -0,0 +1,148 @@ +# Hardware initialization for ESP32 M5Stack-Fire board +# Manufacturer's website at https://https://docs.m5stack.com/en/core/fire_v2.7 +import ili9341 +import lcd_bus +import machine + +import lvgl as lv +import task_handler + +import mpos.ui +import mpos.ui.focus_direction +from mpos import InputManager + +# Pin configuration +SPI_BUS = 1 # SPI2 +SPI_FREQ = 40000000 +LCD_SCLK = 18 +LCD_MOSI = 23 +LCD_DC = 27 +LCD_CS = 14 +LCD_BL = 32 +LCD_RST = 33 +LCD_TYPE = 3 # ILI9341 type 3 (M5Stack-Fire ILI9342) + +TFT_HOR_RES=320 +TFT_VER_RES=240 + +spi_bus = machine.SPI.Bus( + host=SPI_BUS, + mosi=LCD_MOSI, + sck=LCD_SCLK +) +display_bus = lcd_bus.SPIBus( + spi_bus=spi_bus, + freq=SPI_FREQ, + dc=LCD_DC, + cs=LCD_CS +) + +mpos.ui.main_display = ili9341.ILI9341( + data_bus=display_bus, + display_width=TFT_HOR_RES, + display_height=TFT_VER_RES, + color_space=lv.COLOR_FORMAT.RGB565, + color_byte_order=ili9341.BYTE_ORDER_BGR, + rgb565_byte_swap=True, + reset_pin=LCD_RST, + reset_state=ili9341.STATE_LOW, + backlight_pin=LCD_BL, + backlight_on_state=ili9341.STATE_PWM +) +mpos.ui.main_display.init(LCD_TYPE) +mpos.ui.main_display.set_power(True) +mpos.ui.main_display.set_color_inversion(True) +mpos.ui.main_display.set_backlight(25) + +lv.init() + +# Button handling code: +from machine import Pin +import time + +btn_a = Pin(39, Pin.IN, Pin.PULL_UP) # A +btn_b = Pin(38, Pin.IN, Pin.PULL_UP) # B +btn_c = Pin(37, Pin.IN, Pin.PULL_UP) # C + +# Key repeat configuration +# This whole debounce logic is only necessary because LVGL 9.2.2 seems to have an issue where +# the lv_keyboard widget doesn't handle PRESSING (long presses) properly, it loses focus. +REPEAT_INITIAL_DELAY_MS = 300 # Delay before first repeat +REPEAT_RATE_MS = 100 # Interval between repeats +last_key = None +last_state = lv.INDEV_STATE.RELEASED +key_press_start = 0 # Time when key was first pressed +last_repeat_time = 0 # Time of last repeat event + +# Read callback +# Warning: This gets called several times per second, and if it outputs continuous debugging on the serial line, +# that will break tools like mpremote from working properly to upload new files over the serial line, thus needing a reflash. +def keypad_read_cb(indev, data): + global last_key, last_state, key_press_start, last_repeat_time + since_last_repeat = 0 + + # Check buttons + current_key = None + current_time = time.ticks_ms() + if btn_a.value() == 0: + current_key = lv.KEY.PREV + elif btn_b.value() == 0: + current_key = lv.KEY.ENTER + elif btn_c.value() == 0: + current_key = lv.KEY.NEXT + + if (btn_a.value() == 0) and (btn_c.value() == 0): + current_key = lv.KEY.ESC + + # Key repeat logic + if current_key: + if current_key != last_key: + # New key press + data.key = current_key + data.state = lv.INDEV_STATE.PRESSED + last_key = current_key + last_state = lv.INDEV_STATE.PRESSED + key_press_start = current_time + last_repeat_time = current_time + else: # same key + # Key held: Check for repeat + elapsed = time.ticks_diff(current_time, key_press_start) + since_last_repeat = time.ticks_diff(current_time, last_repeat_time) + if elapsed >= REPEAT_INITIAL_DELAY_MS and since_last_repeat >= REPEAT_RATE_MS: + # Send a new PRESSED/RELEASED pair for repeat + data.key = current_key + data.state = lv.INDEV_STATE.PRESSED if last_state == lv.INDEV_STATE.RELEASED else lv.INDEV_STATE.RELEASED + last_state = data.state + last_repeat_time = current_time + else: + # No repeat yet, send RELEASED to avoid PRESSING + data.state = lv.INDEV_STATE.RELEASED + last_state = lv.INDEV_STATE.RELEASED + else: + # No key pressed + data.key = last_key if last_key else lv.KEY.ENTER + data.state = lv.INDEV_STATE.RELEASED + last_key = None + last_state = lv.INDEV_STATE.RELEASED + key_press_start = 0 + last_repeat_time = 0 + + # Handle ESC for back navigation (only on initial PRESSED) + if last_state == lv.INDEV_STATE.PRESSED: + if current_key == lv.KEY.ESC and since_last_repeat == 0: + mpos.ui.back_screen() + +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_read_cb(keypad_read_cb) +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) + +print("m5stack_fire.py finished") From 2e134312df7fe1d305f08eb967ea2f770a63b374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Cebri=C3=A1n?= Date: Sat, 7 Feb 2026 17:37:03 +0100 Subject: [PATCH 3/5] Update build script for ESP32 based M5Stack-Fire --- scripts/build_mpos.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build_mpos.sh b/scripts/build_mpos.sh index 797c592b..1780eb3d 100755 --- a/scripts/build_mpos.sh +++ b/scripts/build_mpos.sh @@ -97,7 +97,7 @@ if [ "$target" == "esp32" ]; then manifest=$(readlink -f "$codebasedir"/manifests/manifest.py) frozenmanifest="FROZEN_MANIFEST=$manifest" echo "Note that you can also prevent the builtin filesystem from being mounted by umounting it and creating a builtin/ folder." - # Build for https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-2. + # Build for https://https://docs.m5stack.com/en/core/fire_v2.7. # See https://github.com/lvgl-micropython/lvgl_micropython # --ota: support Over-The-Air updates # --partition size: both OTA partitions are 4MB @@ -112,7 +112,7 @@ if [ "$target" == "esp32" ]; then # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y pushd "$codebasedir"/lvgl_micropython/ rm -rf lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/ - python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=cst816s 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 "$frozenmanifest" + python3 make.py --ota --partition-size=4194304 --flash-size=16 esp32 BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM DISPLAY=ili9341 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 "$frozenmanifest" popd echo "Grepping..." pwd From f84bc99790cb884840c23b2b43b4bf96a55749f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Cebri=C3=A1n?= Date: Sun, 8 Feb 2026 11:38:12 +0100 Subject: [PATCH 4/5] Simplify support for M5Stack-Fire board Avoid new "_ili9341_init_type3.py" file dependency in lvgl_micropython by following suggestion: https://github.com/lvgl-micropython/lvgl_micropython/issues/527 --- internal_filesystem/lib/mpos/board/m5stack_fire.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal_filesystem/lib/mpos/board/m5stack_fire.py b/internal_filesystem/lib/mpos/board/m5stack_fire.py index 2b60bed9..2d3f0094 100644 --- a/internal_filesystem/lib/mpos/board/m5stack_fire.py +++ b/internal_filesystem/lib/mpos/board/m5stack_fire.py @@ -20,7 +20,7 @@ LCD_CS = 14 LCD_BL = 32 LCD_RST = 33 -LCD_TYPE = 3 # ILI9341 type 3 (M5Stack-Fire ILI9342) +LCD_TYPE = 2 # ILI9341 type 2 TFT_HOR_RES=320 TFT_VER_RES=240 @@ -37,7 +37,16 @@ cs=LCD_CS ) -mpos.ui.main_display = ili9341.ILI9341( +# M5Stack-Fire ILI9342 uses ILI9341 type 2 with a modified orientation table. +class ILI9341(ili9341.ILI9341): + _ORIENTATION_TABLE = ( + 0x00, + 0x40 | 0x20, # _MADCTL_MX | _MADCTL_MV + 0x80 | 0x40, # _MADCTL_MY | _MADCTL_MX + 0x80 | 0x20 # _MADCTL_MY | _MADCTL_MV + ) + +mpos.ui.main_display = ILI9341( data_bus=display_bus, display_width=TFT_HOR_RES, display_height=TFT_VER_RES, From f355ddfe7fb5ba1535e202fc161d2a27238fc8bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Cebri=C3=A1n?= Date: Sun, 8 Feb 2026 13:26:28 +0100 Subject: [PATCH 5/5] Add M5Stack-Fire board detection for ESP32 --- internal_filesystem/lib/mpos/main.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index fff0c65b..de964d59 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -31,7 +31,24 @@ def detect_board(): if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS return "linux" elif sys.platform == "esp32": - return "m5stack_fire" + from machine import Pin, I2C + # Check for ESP32 boards first to avoid conflicts with SPI flash reserved pins (GPIO6 to GPIO11) + try: + i2c0 = I2C(0, sda=Pin(21), scl=Pin(22)) + if {0x68} <= set(i2c0.scan()): # IMU (MPU6886) + return "m5stack_fire" + except ValueError: # GPIO22 doesn't exist in ESP32-S3 + pass + # Check for ESP32-S3 boards + i2c0 = I2C(0, sda=Pin(48), scl=Pin(47)) + if {0x15, 0x6B} <= set(i2c0.scan()): # touch screen and IMU (at least, possibly more) + return "waveshare_esp32_s3_touch_lcd_2" + else: + i2c0 = I2C(0, sda=Pin(9), scl=Pin(18)) + if {0x6B} <= set(i2c0.scan()): # IMU (plus possibly the Communicator's LANA TNY at 0x38) + return "fri3d_2024" + else: # if {0x6A} <= set(i2c0.scan()): # IMU (plus a few others, to be added later, but this should work) + return "fri3d_2026" board = detect_board()