From 4800c761b850e1c310707d5830b954456dcb277b Mon Sep 17 00:00:00 2001 From: Revers-BR Date: Tue, 24 Mar 2026 20:16:31 -0300 Subject: [PATCH 1/4] Add support for LilyGo T-HMI board and initialize display and battery management --- .../lib/mpos/board/lilygo_t_hmi.py | 119 ++++++++++++++++++ internal_filesystem/lib/mpos/main.py | 5 +- scripts/build_mpos.sh | 18 ++- 3 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 internal_filesystem/lib/mpos/board/lilygo_t_hmi.py diff --git a/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py b/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py new file mode 100644 index 00000000..622b3e0a --- /dev/null +++ b/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py @@ -0,0 +1,119 @@ +print("lilygo_t_hmi.py initialization") + +# --- POWER HOLD --- +from machine import Pin + +Pin(10, Pin.OUT, value=1) +Pin(14, Pin.OUT, value=1) + +import lcd_bus +import machine +import xpt2046 + +import mpos.ui + +import lvgl as lv + +from machine import Pin +from micropython import const +from mpos import BatteryManager + +# display settings +_WIDTH = const(240) +_HEIGHT = const(320) +_BL = const(38) +_RST = -1 +_CS = const(6) +_DC = const(7) +_WR = const(8) +_FREQ = const(20000000) +_DATA0 = const(48) +_DATA1 = const(47) +_DATA2 = const(39) +_DATA3 = const(40) +_DATA4 = const(41) +_DATA5 = const(42) +_DATA6 = const(45) +_DATA7 = const(46) +_BATTERY_PIN = const(5) + +_BUFFER_SIZE = const(28800) + +display_bus = lcd_bus.I80Bus( + dc=_DC, + wr=_WR, + cs=_CS, + data0=_DATA0, + data1=_DATA1, + data2=_DATA2, + data3=_DATA3, + data4=_DATA4, + data5=_DATA5, + data6=_DATA6, + data7=_DATA7 +) + +fb1 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_SPIRAM) +fb2 = display_bus.allocate_framebuffer(_BUFFER_SIZE, lcd_bus.MEMORY_SPIRAM) + +import drivers.display.st7789 as st7789 + +mpos.ui.main_display = st7789.ST7789( + data_bus=display_bus, + frame_buffer1=fb1, + frame_buffer2=fb2, + display_width=_WIDTH, + display_height=_HEIGHT, + backlight_pin=_BL, + color_byte_order=st7789.BYTE_ORDER_RGB, + rgb565_byte_swap=False, +) + +spi_bus = machine.SPI.Bus( + host=2, + mosi=3, + miso=4, + sck=1 +) + +touch_dev = machine.SPI.Device( + spi_bus=spi_bus, + freq=const(1000000), + cs=2 +) + +indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0) + +mpos.ui.main_display.init() +mpos.ui.main_display.set_color_inversion(False) +mpos.ui.main_display.set_backlight(100) +mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._0) # must be done after initializing display and creating the touch drivers, to ensure proper handling + +lv.init() + +print("lilygo_t_hmi.py SDCard initialization...") + +# Initialize SD card in SDIO mode +from mpos import sdcard +sdcard.init(cmd_pin=11,clk_pin=12,d0_pin=13) + +print("lilygo_t_hmi.py Battery initialization...") + + +def adc_to_voltage(raw_adc_value): + """ + The percentage calculation uses MIN_VOLTAGE = 3.15 and MAX_VOLTAGE = 4.15 + 0% at 3.15V -> raw_adc_value = 210 + 100% at 4.15V -> raw_adc_value = 310 + + 4.15 - 3.15 = 1V + 310 - 210 = 100 raw ADC steps + + So each raw ADC step is 1V / 100 = 0.01V + Offset calculation: + """ + return raw_adc_value * 0.001651 + 0.08709 + +BatteryManager.init_adc(_BATTERY_PIN, adc_to_voltage) + +print("lilygo_t_hmi.py finished") \ No newline at end of file diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index 91b6d1e1..baca0b5c 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -103,8 +103,11 @@ def detect_board(): if unique_id_prefixes == b'\x30\xae\xa4': return "odroid_go" - # Do I2C-based board detection + print("lilygo_t_hmi ?") + if unique_id_prefixes == b'\xdc\xb4\xd9': + return "lilygo_t_hmi" + # Do I2C-based board detection print("lilygo_t_watch_s3_plus ?") if i2c0 := fail_save_i2c(sda=10, scl=11): if single_address_i2c_scan(i2c0, 0x19): # IMU on 0x19, vibrator on 0x5A and scan also shows: [52, 81] diff --git a/scripts/build_mpos.sh b/scripts/build_mpos.sh index 7eb9750e..753ab551 100755 --- a/scripts/build_mpos.sh +++ b/scripts/build_mpos.sh @@ -14,6 +14,7 @@ if [ -z "$target" ]; then echo "Example: $0 macOS" echo "Example: $0 esp32" echo "Example: $0 esp32s3" + echo "Example: $0 t-hmi" echo "Example: $0 unphone" echo "Example: $0 clean" exit 1 @@ -106,14 +107,15 @@ popd echo "Refreshing freezefs..." "$codebasedir"/scripts/freezefs_mount_builtin.sh -if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; then +if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" -o "$target" == "t-hmi" ]; then partition_size="4194304" flash_size="16" extra_configs="" + CUSTOM_FLAGS="" if [ "$target" == "esp32" ]; then BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM - else # esp32s3 or unphone + elif [ "$target" == "esp32s3" -o "$target" == "unphone" ]; then if [ "$target" == "unphone" ]; then partition_size="3900000" flash_size="8" @@ -125,6 +127,17 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; extra_configs="CONFIG_MBEDTLS_HARDWARE_AES=n CONFIG_MBEDTLS_HARDWARE_SHA=n CONFIG_MBEDTLS_HARDWARE_MPI=n" # --py-freertos: add MicroPython FreeRTOS module to expose internals extra_configs="$extra_configs --py-freertos" + elif [ "$target" == "t-hmi" ]; then + BOARD=ESP32_GENERIC_S3 + BOARD_VARIANT=SPIRAM_OCT + + partition_size="4194304" + flash_size="16" + + extra_configs="CONFIG_MBEDTLS_HARDWARE_AES=n CONFIG_MBEDTLS_HARDWARE_SHA=n CONFIG_MBEDTLS_HARDWARE_MPI=n" + extra_configs="$extra_configs --py-freertos" + + CUSTOM_FLAGS='DISPLAY=st7789 INDEV=xpt2046 --enable-jtag-repl=y --enable-cdc-repl=n --enable-uart-repl=n' 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 @@ -148,6 +161,7 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; # 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 --ota --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ python3 make.py --optimize-size --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ + $CUSTOM_FLAGS \ 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 \ From 7f08f5f76bcdca02f3528f00f2262e4b81bf46e5 Mon Sep 17 00:00:00 2001 From: Revers-BR Date: Wed, 25 Mar 2026 18:56:39 -0300 Subject: [PATCH 2/4] Implement detection for LilyGO T-HMI touch screen --- internal_filesystem/lib/mpos/main.py | 71 +++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index baca0b5c..10a7bcd5 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -53,6 +53,75 @@ def single_address_i2c_scan(i2c_bus, address): print(f"scan error: {e}") return False +def detect_lilygo_t_hmi(): + from machine import Pin, SoftSPI + import time + + try: + sck = Pin(1) + mosi = Pin(3) + miso = Pin(4) + cs = Pin(2, Pin.OUT, value=1) + irq = Pin(9, Pin.IN, Pin.PULL_UP) + + spi = SoftSPI( + baudrate=500000, + polarity=0, + phase=0, + sck=sck, + mosi=mosi, + miso=miso, + ) + + def read_cmd(cmd): + tx = bytearray([cmd, 0x00, 0x00]) + rx = bytearray(3) + + cs(0) + spi.write_readinto(tx, rx) + cs(1) + + return ((rx[1] << 8) | rx[2]) >> 3 + + samples = [] + for _ in range(5): + vals = ( + read_cmd(0xD0), # X + read_cmd(0x90), # Y + read_cmd(0xB0), # Z1 + irq.value(), + ) + samples.append(vals) + print("T-HMI touch sample:", vals) + time.sleep_ms(20) + + # Observed stable idle signature on LilyGO T-HMI: + # X=0, Y=4095, Z1=0/1, IRQ=1 + signature_hits = sum( + x == 0 and y == 4095 and z in (0, 1) and irqv == 1 + for x, y, z, irqv in samples + ) + + print(f"T-HMI signature hits: {signature_hits}/5") + + if signature_hits >= 4: + print("LilyGO T-HMI touch signature matched") + return True + + except Exception as e: + print(f"LilyGO T-HMI detection failed: {e}") + + finally: + try: + Pin(1, Pin.IN, pull=None) + Pin(2, Pin.IN, pull=None) + Pin(3, Pin.IN, pull=None) + Pin(4, Pin.IN, pull=None) + Pin(9, Pin.IN, pull=None) + except Exception: + pass + + return False def fail_save_i2c(sda, scl): from machine import I2C, Pin @@ -104,7 +173,7 @@ def detect_board(): return "odroid_go" print("lilygo_t_hmi ?") - if unique_id_prefixes == b'\xdc\xb4\xd9': + if detect_lilygo_t_hmi(): return "lilygo_t_hmi" # Do I2C-based board detection From bd68714a2fafed128f562554b5bc7269f208e040 Mon Sep 17 00:00:00 2001 From: Revers-BR Date: Wed, 25 Mar 2026 18:57:04 -0300 Subject: [PATCH 3/4] Refactor LilyGO T-HMI initialization and update touch device configuration --- .../lib/mpos/board/lilygo_t_hmi.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py b/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py index 622b3e0a..62e6705f 100644 --- a/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py +++ b/internal_filesystem/lib/mpos/board/lilygo_t_hmi.py @@ -1,4 +1,8 @@ print("lilygo_t_hmi.py initialization") +# Manufacturer: https://lilygo.cc/en-us/products/t-hmi +# Hardware reference: https://www.tinytronics.nl/en/development-boards/microcontroller-boards/with-wi-fi/lilygo-t-hmi-esp32-s3-2.8-inch-ips-tft-display-met-touchscreen +# Vendor repository: https://github.com/Xinyuan-LilyGO/T-HMI + # --- POWER HOLD --- from machine import Pin @@ -8,7 +12,7 @@ import lcd_bus import machine -import xpt2046 +from drivers.indev.xpt2046 import XPT2046 import mpos.ui @@ -37,6 +41,8 @@ _DATA7 = const(46) _BATTERY_PIN = const(5) +_TOUCH_CS = const(2) + _BUFFER_SIZE = const(28800) display_bus = lcd_bus.I80Bus( @@ -79,10 +85,17 @@ touch_dev = machine.SPI.Device( spi_bus=spi_bus, freq=const(1000000), - cs=2 + cs=_TOUCH_CS ) -indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0) +indev = XPT2046( + touch_dev, + lcd_cs=_CS, + touch_cs=_TOUCH_CS, + display_width=_WIDTH, + display_height=_HEIGHT, + startup_rotation=lv.DISPLAY_ROTATION._0 +) mpos.ui.main_display.init() mpos.ui.main_display.set_color_inversion(False) From a8668500e258f4054361fdefffccd5151829c1ef Mon Sep 17 00:00:00 2001 From: Revers-BR Date: Wed, 25 Mar 2026 18:57:38 -0300 Subject: [PATCH 4/4] Remove t-hmi target configuration from build script and streamline OTA support handling --- scripts/build_mpos.sh | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/scripts/build_mpos.sh b/scripts/build_mpos.sh index 753ab551..59d4054f 100755 --- a/scripts/build_mpos.sh +++ b/scripts/build_mpos.sh @@ -14,7 +14,6 @@ if [ -z "$target" ]; then echo "Example: $0 macOS" echo "Example: $0 esp32" echo "Example: $0 esp32s3" - echo "Example: $0 t-hmi" echo "Example: $0 unphone" echo "Example: $0 clean" exit 1 @@ -107,18 +106,19 @@ popd echo "Refreshing freezefs..." "$codebasedir"/scripts/freezefs_mount_builtin.sh -if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" -o "$target" == "t-hmi" ]; then +if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" ]; then partition_size="4194304" flash_size="16" + otasupport="--ota" extra_configs="" - CUSTOM_FLAGS="" if [ "$target" == "esp32" ]; then BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM - elif [ "$target" == "esp32s3" -o "$target" == "unphone" ]; then + else # esp32s3 or unphone if [ "$target" == "unphone" ]; then partition_size="3900000" flash_size="8" + otasupport="" # too small for 2 OTA partitions + internal storage fi BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT @@ -127,17 +127,6 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" -o extra_configs="CONFIG_MBEDTLS_HARDWARE_AES=n CONFIG_MBEDTLS_HARDWARE_SHA=n CONFIG_MBEDTLS_HARDWARE_MPI=n" # --py-freertos: add MicroPython FreeRTOS module to expose internals extra_configs="$extra_configs --py-freertos" - elif [ "$target" == "t-hmi" ]; then - BOARD=ESP32_GENERIC_S3 - BOARD_VARIANT=SPIRAM_OCT - - partition_size="4194304" - flash_size="16" - - extra_configs="CONFIG_MBEDTLS_HARDWARE_AES=n CONFIG_MBEDTLS_HARDWARE_SHA=n CONFIG_MBEDTLS_HARDWARE_MPI=n" - extra_configs="$extra_configs --py-freertos" - - CUSTOM_FLAGS='DISPLAY=st7789 INDEV=xpt2046 --enable-jtag-repl=y --enable-cdc-repl=n --enable-uart-repl=n' 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 @@ -159,9 +148,7 @@ if [ "$target" == "esp32" -o "$target" == "esp32s3" -o "$target" == "unphone" -o # 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 --ota --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ - python3 make.py --optimize-size --partition-size=$partition_size --flash-size=$flash_size esp32 BOARD=$BOARD BOARD_VARIANT=$BOARD_VARIANT \ - $CUSTOM_FLAGS \ + 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 \ @@ -232,6 +219,4 @@ PY rm "$stream_wav_file".backup else echo "invalid target $target" -fi - - +fi \ No newline at end of file