diff --git a/internal_filesystem/lib/drivers/power/axp192.py b/internal_filesystem/lib/drivers/power/axp192.py new file mode 100644 index 00000000..4a1a9c65 --- /dev/null +++ b/internal_filesystem/lib/drivers/power/axp192.py @@ -0,0 +1,276 @@ +# AXP192 Power Management IC Driver for M5Stack Core2 +# I2C address: 0x34 +# Datasheet reference + M5Stack Core2 specific initialization + +from micropython import const + +# Registers +_REG_POWER_STATUS = const(0x00) +_REG_CHARGE_STATUS = const(0x01) +_REG_POWER_OUTPUT_CTRL = const(0x12) +_REG_DCDC1_VOLTAGE = const(0x26) +_REG_DCDC3_VOLTAGE = const(0x27) +_REG_LDO23_VOLTAGE = const(0x28) +_REG_VBUS_IPSOUT = const(0x30) +_REG_POWER_OFF = const(0x32) +_REG_CHARGE_CTRL1 = const(0x33) +_REG_BACKUP_CHG = const(0x35) +_REG_PEK_PARAMS = const(0x36) +_REG_ADC_ENABLE1 = const(0x82) +_REG_GPIO0_FUNCTION = const(0x90) +_REG_GPIO0_LDO_VOLTAGE = const(0x91) +_REG_GPIO1_FUNCTION = const(0x92) +_REG_GPIO2_FUNCTION = const(0x93) +_REG_GPIO_SIGNAL = const(0x94) +_REG_GPIO4_FUNCTION = const(0x95) +_REG_GPIO34_SIGNAL = const(0x96) +_REG_COULOMB_CTRL = const(0xB8) + +# ADC data registers +_REG_BAT_VOLTAGE_H = const(0x78) +_REG_BAT_CURRENT_IN_H = const(0x7A) +_REG_BAT_CURRENT_OUT_H = const(0x7C) +_REG_APS_VOLTAGE_H = const(0x7E) + +# Power output control bits (register 0x12) +_BIT_EXTEN = const(6) # EXTEN (5V boost) +_BIT_DCDC2 = const(4) +_BIT_LDO3 = const(3) +_BIT_LDO2 = const(2) +_BIT_DCDC3 = const(1) +_BIT_DCDC1 = const(0) + +# Bus power mode +_MBUS_MODE_INPUT = const(0) +_MBUS_MODE_OUTPUT = const(1) + +I2C_ADDR = const(0x34) + + +class AXP192: + """AXP192 power management driver for M5Stack Core2.""" + + def __init__(self, i2c, addr=I2C_ADDR): + self._i2c = i2c + self._addr = addr + self._buf1 = bytearray(1) + self._buf2 = bytearray(2) + + def _read_reg(self, reg): + self._buf1[0] = reg + self._i2c.readfrom_mem_into(self._addr, reg, self._buf1) + return self._buf1[0] + + def _write_reg(self, reg, val): + self._buf1[0] = val + self._i2c.writeto_mem(self._addr, reg, self._buf1) + + def _read_12bit(self, reg): + self._i2c.readfrom_mem_into(self._addr, reg, self._buf2) + return (self._buf2[0] << 4) | self._buf2[1] + + def _read_13bit(self, reg): + self._i2c.readfrom_mem_into(self._addr, reg, self._buf2) + return (self._buf2[0] << 5) | self._buf2[1] + + def init_core2(self): + """Initialize AXP192 for M5Stack Core2 hardware.""" + # VBUS-IPSOUT path: set N_VBUSEN pin control, auto VBUS current limit + self._write_reg(_REG_VBUS_IPSOUT, (self._read_reg(_REG_VBUS_IPSOUT) & 0x04) | 0x02) + + # GPIO1: Open-drain output (Touch RST control) + self._write_reg(_REG_GPIO1_FUNCTION, self._read_reg(_REG_GPIO1_FUNCTION) & 0xF8) + + # GPIO2: Open-drain output (Speaker enable control) + self._write_reg(_REG_GPIO2_FUNCTION, self._read_reg(_REG_GPIO2_FUNCTION) & 0xF8) + + # RTC battery charge: 3.0V, 200uA + self._write_reg(_REG_BACKUP_CHG, (self._read_reg(_REG_BACKUP_CHG) & 0x1C) | 0xA2) + + # Set ESP32 core voltage (DCDC1) to 3350mV + self.set_dcdc1_voltage(3350) + + # Set LCD backlight voltage (DCDC3) to 2800mV + self.set_dcdc3_voltage(2800) + + # Set LDO2 (LCD logic + SD card) to 3300mV + self.set_ldo2_voltage(3300) + + # Set LDO3 (vibration motor) to 2000mV (low to keep motor off initially) + self.set_ldo3_voltage(2000) + + # Enable LDO2 (LCD logic power) + self.set_ldo2_enable(True) + + # Enable DCDC3 (LCD backlight) + self.set_dcdc3_enable(True) + + # Disable LDO3 at startup (vibration motor off) + self.set_ldo3_enable(False) + + # Set charging current to 100mA + self.set_charge_current(0) # 0 = 100mA + + # GPIO4: NMOS open-drain output (LCD RST) + self._write_reg(_REG_GPIO4_FUNCTION, (self._read_reg(_REG_GPIO4_FUNCTION) & 0x72) | 0x84) + + # PEK parameters: power key settings + self._write_reg(_REG_PEK_PARAMS, 0x4C) + + # Enable all ADCs + self._write_reg(_REG_ADC_ENABLE1, 0xFF) + + # Check power input and configure bus power mode + if self._read_reg(_REG_POWER_STATUS) & 0x08: + self._write_reg(_REG_VBUS_IPSOUT, self._read_reg(_REG_VBUS_IPSOUT) | 0x80) + self._set_bus_power_mode(_MBUS_MODE_INPUT) + else: + self._set_bus_power_mode(_MBUS_MODE_OUTPUT) + + # Perform LCD + Touch reset sequence (both share AXP192 GPIO4) + self.set_lcd_reset(False) + import time + time.sleep_ms(100) + self.set_lcd_reset(True) + time.sleep_ms(300) # FT6336U needs ~300ms after reset to be ready + + # Enable speaker amp after init + self.set_speaker_enable(True) + + # -- Voltage setters -- + + def set_dcdc1_voltage(self, mv): + """Set DCDC1 voltage (ESP32 core). Range: 700-3500mV, step 25mV.""" + val = max(0, min(127, (mv - 700) // 25)) + self._write_reg(_REG_DCDC1_VOLTAGE, (self._read_reg(_REG_DCDC1_VOLTAGE) & 0x80) | val) + + def set_dcdc3_voltage(self, mv): + """Set DCDC3 voltage (LCD backlight). Range: 700-3500mV, step 25mV.""" + val = max(0, min(127, (mv - 700) // 25)) + self._write_reg(_REG_DCDC3_VOLTAGE, (self._read_reg(_REG_DCDC3_VOLTAGE) & 0x80) | val) + + def set_ldo2_voltage(self, mv): + """Set LDO2 voltage. Range: 1800-3300mV, step 100mV.""" + val = max(0, min(15, (mv - 1800) // 100)) + self._write_reg(_REG_LDO23_VOLTAGE, (self._read_reg(_REG_LDO23_VOLTAGE) & 0x0F) | (val << 4)) + + def set_ldo3_voltage(self, mv): + """Set LDO3 voltage. Range: 1800-3300mV, step 100mV.""" + val = max(0, min(15, (mv - 1800) // 100)) + self._write_reg(_REG_LDO23_VOLTAGE, (self._read_reg(_REG_LDO23_VOLTAGE) & 0xF0) | val) + + # -- Power output enable/disable -- + + def _set_power_output(self, bit, enable): + reg = self._read_reg(_REG_POWER_OUTPUT_CTRL) + if enable: + reg |= (1 << bit) + else: + reg &= ~(1 << bit) + self._write_reg(_REG_POWER_OUTPUT_CTRL, reg) + + def set_dcdc1_enable(self, enable): + self._set_power_output(_BIT_DCDC1, enable) + + def set_dcdc3_enable(self, enable): + self._set_power_output(_BIT_DCDC3, enable) + + def set_ldo2_enable(self, enable): + self._set_power_output(_BIT_LDO2, enable) + + def set_ldo3_enable(self, enable): + self._set_power_output(_BIT_LDO3, enable) + + # -- GPIO control (used for peripherals) -- + + def set_lcd_reset(self, state): + """Control LCD reset via AXP192 GPIO4.""" + data = self._read_reg(_REG_GPIO34_SIGNAL) + if state: + data |= 0x02 + else: + data &= ~0x02 + self._write_reg(_REG_GPIO34_SIGNAL, data) + + def set_touch_reset(self, state): + """Control touch controller reset via AXP192 GPIO4 (shared with LCD).""" + self.set_lcd_reset(state) + + def set_speaker_enable(self, state): + """Control speaker amplifier enable via AXP192 GPIO2.""" + data = self._read_reg(_REG_GPIO_SIGNAL) + if state: + data |= 0x04 + else: + data &= ~0x04 + self._write_reg(_REG_GPIO_SIGNAL, data) + + def _set_bus_power_mode(self, mode): + if mode == _MBUS_MODE_INPUT: + # GPIO0 LDO output, pull up N_VBUSEN to disable 5V from BUS + data = self._read_reg(0x91) + self._write_reg(0x91, (data & 0x0F) | 0xF0) + data = self._read_reg(0x90) + self._write_reg(0x90, (data & 0xF8) | 0x02) + # Enable EXTEN for 5V boost + data = self._read_reg(_REG_POWER_OUTPUT_CTRL) + self._write_reg(_REG_POWER_OUTPUT_CTRL, data | 0x40) + else: + # Disable 5V boost + data = self._read_reg(_REG_POWER_OUTPUT_CTRL) + self._write_reg(_REG_POWER_OUTPUT_CTRL, data & 0xBF) + # GPIO0 floating, external pulldown enables BUS_5V supply + data = self._read_reg(0x90) + self._write_reg(0x90, (data & 0xF8) | 0x01) + + # -- Charging -- + + def set_charge_current(self, level): + """Set charge current. 0=100mA, 1=190mA, ..., 8=780mA, etc.""" + data = self._read_reg(_REG_CHARGE_CTRL1) + data = (data & 0xF0) | (level & 0x0F) + self._write_reg(_REG_CHARGE_CTRL1, data) + + # -- Battery / power readings -- + + def get_battery_voltage(self): + """Get battery voltage in volts.""" + return self._read_12bit(_REG_BAT_VOLTAGE_H) * 1.1 / 1000.0 + + def get_battery_current(self): + """Get net battery current in mA (positive=charging, negative=discharging).""" + current_in = self._read_13bit(_REG_BAT_CURRENT_IN_H) * 0.5 + current_out = self._read_13bit(_REG_BAT_CURRENT_OUT_H) * 0.5 + return current_in - current_out + + def is_charging(self): + return bool(self._read_reg(_REG_POWER_STATUS) & 0x04) + + def is_vbus_present(self): + return bool(self._read_reg(_REG_POWER_STATUS) & 0x20) + + def get_battery_level(self): + """Estimate battery percentage (simple linear approximation).""" + v = self.get_battery_voltage() + if v < 3.2: + return 0 + pct = (v - 3.12) * 100.0 + return min(100, max(0, int(pct))) + + # -- Screen brightness via DCDC3 -- + + def set_screen_brightness(self, percent): + """Set screen brightness 0-100% by adjusting DCDC3 voltage (2500-3300mV).""" + percent = max(0, min(100, percent)) + mv = 2500 + int(percent * 8) # 2500mV-3300mV + self.set_dcdc3_voltage(mv) + + # -- Power control -- + + def power_off(self): + """Cut all power except RTC (LDO1).""" + self._write_reg(_REG_POWER_OFF, self._read_reg(_REG_POWER_OFF) | 0x80) + + def set_vibration(self, enable): + """Enable/disable vibration motor via LDO3.""" + self.set_ldo3_enable(enable) diff --git a/internal_filesystem/lib/mpos/board/m5stack_core2.py b/internal_filesystem/lib/mpos/board/m5stack_core2.py new file mode 100644 index 00000000..e4368ffa --- /dev/null +++ b/internal_filesystem/lib/mpos/board/m5stack_core2.py @@ -0,0 +1,178 @@ +# Hardware initialization for M5Stack Core2 +# Manufacturer's website at https://docs.m5stack.com/en/core/core2 +# ESP32-D0WDQ6-V3, 16MB Flash, 8MB PSRAM +# Display: ILI9342C 320x240 SPI +# Touch: FT6336U (I2C 0x38) +# Power: AXP192 (I2C 0x34) +# Speaker: NS4168 (I2S) +# Mic: SPM1423 (PDM, CLK=0, DATA=34) +# IMU: MPU6886 (I2C 0x68) + +import time + +import drivers.display.ili9341 as ili9341 +import lcd_bus +import lvgl as lv +import machine +import mpos.ui +from machine import I2C, Pin +from micropython import const +from mpos import AudioManager, InputManager, SensorManager + +# I2C bus (shared: AXP192, Touch, IMU, RTC) +I2C_SDA = const(21) +I2C_SCL = const(22) +I2C_FREQ = const(400000) + +# Display settings (SPI) +SPI_BUS = const(1) # SPI2 +SPI_FREQ = const(40000000) +LCD_SCLK = const(18) +LCD_MOSI = const(23) +LCD_DC = const(15) +LCD_CS = const(5) +LCD_TYPE = const(2) # ILI9341 type 2 + +TFT_HOR_RES = const(320) +TFT_VER_RES = const(240) + +# I2S Speaker (NS4168) +I2S_BCLK = const(12) +I2S_LRCK = const(0) +I2S_DATA_OUT = const(2) + +# Mic (SPM1423 PDM) +MIC_CLK = const(0) +MIC_DATA = const(34) + +# IMU +MPU6886_ADDR = const(0x68) + +# ============================== +# Step 1: AXP192 Power Management +# ============================== +print("m5stack_core2.py init AXP192 power management") +# All I2C devices (AXP192, Touch, IMU) share one bus on host 0 +i2c_bus = I2C(0, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=I2C_FREQ) + +from drivers.power.axp192 import AXP192 +axp = AXP192(i2c_bus) +axp.init_core2() + +# ============================== +# Step 2: Display (ILI9342C via SPI) +# ============================== +print("m5stack_core2.py init SPI display") +try: + spi_bus = machine.SPI.Bus(host=SPI_BUS, mosi=LCD_MOSI, sck=LCD_SCLK) +except Exception as e: + print(f"Error initializing SPI bus: {e}") + print("Attempting hard reset in 3sec...") + time.sleep(3) + machine.reset() + +display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, freq=SPI_FREQ, dc=LCD_DC, cs=LCD_CS) + + +# M5Stack Core2 uses ILI9342C with same orientation table as Fire +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 + ) + + +# Note: LCD reset and backlight are handled by AXP192 (GPIO4=reset, DCDC3=backlight) +# No reset_pin or backlight_pin needed here +mpos.ui.main_display = 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, +) +mpos.ui.main_display.init(LCD_TYPE) +mpos.ui.main_display.set_power(True) +mpos.ui.main_display.set_color_inversion(True) + +lv.init() + +# ============================== +# Step 3: Touch (FT6336U) +# ============================== +print("m5stack_core2.py init touch (FT6336U)") +import i2c as i2c_lvgl +import drivers.indev.ft6x36 as ft6x36 +import pointer_framework + +# Create LVGL I2C bus wrapper, then replace its internal bus with our shared I2C(0) +# instance so all devices (AXP192, touch, IMU) share the same hardware I2C controller. +touch_i2c_bus = i2c_lvgl.I2C.Bus(host=0, sda=I2C_SDA, scl=I2C_SCL, freq=I2C_FREQ, use_locks=False) +touch_i2c_bus._bus = i2c_bus + +touch_dev = i2c_lvgl.I2C.Device(bus=touch_i2c_bus, dev_id=ft6x36.I2C_ADDR, reg_bits=ft6x36.BITS) +indev = ft6x36.FT6x36(touch_dev, startup_rotation=pointer_framework.lv.DISPLAY_ROTATION._0) +InputManager.register_indev(indev) + +# ============================== +# Step 4: Audio (I2S Speaker + PDM Mic) +# ============================== +print("m5stack_core2.py init audio") + +# I2S speaker output (NS4168, enabled via AXP192 GPIO2) +i2s_output_pins = { + 'ws': I2S_LRCK, + 'sck': I2S_BCLK, + 'sd': I2S_DATA_OUT, +} +AudioManager.add( + AudioManager.Output( + name="speaker", + kind="i2s", + i2s_pins=i2s_output_pins, + ) +) +AudioManager.set_volume(40) + +# PDM microphone input (SPM1423) +i2s_input_pins = { + 'ws': MIC_CLK, + 'sd_in': MIC_DATA, +} +AudioManager.add( + AudioManager.Input( + name="mic", + kind="i2s", + i2s_pins=i2s_input_pins, + ) +) + +# TODO: add startup sound (RTTTL not supported via I2S, needs WAV file) + +# ============================== +# Step 5: IMU (MPU6886) +# ============================== +print("m5stack_core2.py init IMU") +SensorManager.init( + i2c_bus=i2c_bus, + address=MPU6886_ADDR, + mounted_position=SensorManager.FACING_EARTH, +) + +# ============================== +# Step 6: Battery (via AXP192) +# ============================== +print("m5stack_core2.py init battery monitoring") +from mpos import BatteryManager + +def axp_adc_to_voltage(adc_value): + """Read battery voltage from AXP192 instead of ADC pin.""" + return axp.get_battery_voltage() + +# Use a dummy pin (35) - the actual reading comes from axp via the conversion function +BatteryManager.init_adc(35, axp_adc_to_voltage) + +print("m5stack_core2.py finished") diff --git a/internal_filesystem/lib/mpos/main.py b/internal_filesystem/lib/mpos/main.py index 10a7bcd5..10c007ca 100644 --- a/internal_filesystem/lib/mpos/main.py +++ b/internal_filesystem/lib/mpos/main.py @@ -172,59 +172,75 @@ def detect_board(): if unique_id_prefixes == b'\x30\xae\xa4': return "odroid_go" - print("lilygo_t_hmi ?") - if detect_lilygo_t_hmi(): - 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] - return "lilygo_t_watch_s3_plus" # example MAC address: D0:CF:13:33:36:306 - restore_i2c(sda=10, scl=11) - - print("matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?") - if i2c0 := fail_save_i2c(sda=39, scl=38): - if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen - return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660" - restore_i2c(sda=39, scl=38) # fix pin 39 (data0) breaking lilygo_t_display_s3's display - - print("waveshare_esp32_s3_touch_lcd_2 ?") - if i2c0 := fail_save_i2c(sda=48, scl=47): - # IO48 is floating on matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 - if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(i2c0, 0x6B): # CST816S touch screen and IMU - return "waveshare_esp32_s3_touch_lcd_2" - restore_i2c(sda=48, scl=47) # fix pin 47 (data6) and 48 (data7) breaking lilygo_t_display_s3's display - - print("m5stack_fire ?") - if i2c0 := fail_save_i2c(sda=21, scl=22): - if single_address_i2c_scan(i2c0, 0x68): # IMU (MPU6886) - return "m5stack_fire" - restore_i2c(sda=21, scl=22) - - print("fri3d_2024 ?") - if i2c0 := fail_save_i2c(sda=9, scl=18): - if single_address_i2c_scan(i2c0, 0x6A): # ) 0x15: CST8 touch, 0x6A: IMU - return "fri3d_2026" - if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38) - return "fri3d_2024" - restore_i2c(sda=9, scl=18) + # 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. + is_esp32s3 = "S3" in sys.implementation._machine.upper() + + if is_esp32s3: + print("lilygo_t_hmi ?") + if detect_lilygo_t_hmi(): + 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] + return "lilygo_t_watch_s3_plus" # example MAC address: D0:CF:13:33:36:306 + restore_i2c(sda=10, scl=11) + + print("matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?") + if i2c0 := fail_save_i2c(sda=39, scl=38): + if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen + return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660" + restore_i2c(sda=39, scl=38) # fix pin 39 (data0) breaking lilygo_t_display_s3's display + + print("waveshare_esp32_s3_touch_lcd_2 ?") + if i2c0 := fail_save_i2c(sda=48, scl=47): + # IO48 is floating on matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 + if single_address_i2c_scan(i2c0, 0x15) and single_address_i2c_scan(i2c0, 0x6B): # CST816S touch screen and IMU + return "waveshare_esp32_s3_touch_lcd_2" + restore_i2c(sda=48, scl=47) # fix pin 47 (data6) and 48 (data7) breaking lilygo_t_display_s3's display + + print("fri3d_2024 ?") + if i2c0 := fail_save_i2c(sda=9, scl=18): + if single_address_i2c_scan(i2c0, 0x6A): # ) 0x15: CST8 touch, 0x6A: IMU + return "fri3d_2026" + if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38) + return "fri3d_2024" + restore_i2c(sda=9, scl=18) + + else: # not is_esp32s3 + + print("m5stack_core2 ?") + if i2c0 := fail_save_i2c(sda=21, scl=22): + if single_address_i2c_scan(i2c0, 0x34): # AXP192 power management (Core2 has it, Fire doesn't) + return "m5stack_core2" + + print("m5stack_fire ?") + if i2c0 := fail_save_i2c(sda=21, scl=22): + if single_address_i2c_scan(i2c0, 0x68): # IMU (MPU6886) + return "m5stack_fire" + restore_i2c(sda=21, scl=22) # On devices without I2C, we use known GPIO states from machine import Pin - print("(emulated) lilygo_t_display_s3 ?") - try: - # 2 buttons have PCB pull-ups so they'll be high unless pressed - pin0 = Pin(0, Pin.IN) - pin14 = Pin(14, Pin.IN) - if pin0.value() == 1 and pin14.value() == 1: - return "lilygo_t_display_s3" # display gets confused by the i2c stuff below - except Exception as e: - print(f"lilygo_t_display_s3 detection got exception: {e}") + if is_esp32s3: + print("(emulated) lilygo_t_display_s3 ?") + try: + # 2 buttons have PCB pull-ups so they'll be high unless pressed + pin0 = Pin(0, Pin.IN) + pin14 = Pin(14, Pin.IN) + if pin0.value() == 1 and pin14.value() == 1: + return "lilygo_t_display_s3" # display gets confused by the i2c stuff below + except Exception as e: + print(f"lilygo_t_display_s3 detection got exception: {e}") print("Unknown board: couldn't detect known I2C devices or unique_id prefix") + # EXECUTION STARTS HERE print(f"MicroPythonOS {BuildInfo.version.release} running lib/mpos/main.py")