diff --git a/configs/sdkconfig_display_m5sticks3.defaults b/configs/sdkconfig_display_m5sticks3.defaults new file mode 100644 index 00000000..1d110aa2 --- /dev/null +++ b/configs/sdkconfig_display_m5sticks3.defaults @@ -0,0 +1,123 @@ +# display m5sticks3 +# M5Stack StickS3 (ESP32-S3-PICO-1-N8R8, 8MB flash, 8MB octal PSRAM) +# ST7789P3 135x240 SPI display, buttons KEY1/G11 + KEY2/G12 (active low) +CONFIG_APP_REPRODUCIBLE_BUILD=y +CONFIG_BF20A6_SUPPORT=n +CONFIG_BF3005_SUPPORT=n +CONFIG_BOARD_TYPE_CUSTOM=y +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y +CONFIG_BOOTLOADER_WDT_ENABLE=n +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517 +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=11 +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=5632 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=n +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n +CONFIG_BT_NIMBLE_SM_LEGACY=n +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="j" +CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS=100 +CONFIG_BUTTON_LONG_PRESS_TIME_MS=500 +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_DEBUG_MODE=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=n +CONFIG_ESP_CONSOLE_NONE=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_ERR_TO_NAME_LOOKUP=n +CONFIG_ESP_MAIN_TASK_STACK_SIZE=12288 +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=n +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=n +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=n +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=n +CONFIG_ESP_WIFI_IRAM_OPT=n +CONFIG_ESP_WIFI_NVS_ENABLED=n +CONFIG_ESP_WIFI_RX_IRAM_OPT=n +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n +CONFIG_ETH_USE_SPI_ETHERNET=n +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 +CONFIG_GC032A_SUPPORT=n +CONFIG_GC2145_SUPPORT=n +CONFIG_IDF_TARGET="esp32s3" +CONFIG_LIBC_NEWLIB_NANO_FORMAT=y +CONFIG_LOG_DEFAULT_LEVEL_NONE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +CONFIG_NT99141_SUPPORT=n +CONFIG_OV2640_SUPPORT=n +CONFIG_OV3660_SUPPORT=n +CONFIG_OV5640_SUPPORT=n +CONFIG_OV7670_SUPPORT=n +CONFIG_OV7725_SUPPORT=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitionss3.csv" +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=n +CONFIG_SC030IOT_SUPPORT=n +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256 +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 +CONFIG_SPIRAM_MEMTEST=n +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPI_MASTER_ISR_IN_IRAM=n +CONFIG_SPI_SLAVE_ISR_IN_IRAM=n +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_RX_BUFSIZE=64 +CONFIG_WIFI_PROV_BLE_SEC_CONN=n + +# ST7789P3 135x240 panel (portrait native 135x240 -> jade 240x135 after swap) +CONFIG_DISPLAY_WIDTH=240 +CONFIG_DISPLAY_HEIGHT=135 +CONFIG_DISPLAY_OFFSET_X=40 +CONFIG_DISPLAY_OFFSET_Y=53 +CONFIG_DISPLAY_INVERT=y +CONFIG_DCS_ADDRESS_MODE_MIRROR_X_SELECTED=y +# CONFIG_DCS_ADDRESS_MODE_MIRROR_Y_SELECTED is not set +CONFIG_DCS_ADDRESS_MODE_SWAP_XY_SELECTED=y +# CONFIG_DCS_ADDRESS_MODE_BGR_SELECTED is not set + +# SPI display host/pins (M5StickS3 schematic: ST7789P3) +CONFIG_DISPLAY_SPI_HOST=2 +CONFIG_DISPLAY_SPI_CLOCK=40000000 +CONFIG_DISPLAY_PIN_MOSI=39 +CONFIG_DISPLAY_PIN_CLK=40 +CONFIG_DISPLAY_PIN_CS=41 +CONFIG_DISPLAY_PIN_DC=45 +CONFIG_DISPLAY_PIN_RST=21 +CONFIG_DISPLAY_PIN_BL=38 +CONFIG_DISPLAY_PIN_MISO=-1 +CONFIG_PIN_NUM_DC=-1 +CONFIG_PIN_NUM_WR=-1 +CONFIG_PIN_NUM_CS=-1 +CONFIG_PIN_NUM_DATA0=-1 + +# Buttons: KEY1=G11 middle, KEY2=G12 right, active low +CONFIG_INPUT_BTN_A=11 +CONFIG_INPUT_BTN_B=12 +CONFIG_INPUT_FRONT_SW=-1 +# CONFIG_BUTTON_B_ACTIVE_HIGH is not set + +# M5PM1 PMU: enables the L3B rail that powers the LCD/backlight +# (M5PM1 I2C bus on StickS3: SCL=G48, SDA=G47) +CONFIG_HAS_M5PM1=y +CONFIG_I2C_SDA=47 +CONFIG_I2C_SCL=48 \ No newline at end of file diff --git a/diy/README.md b/diy/README.md index e551577a..3b4d64eb 100644 --- a/diy/README.md +++ b/diy/README.md @@ -49,6 +49,18 @@ There are a number of hardware devices that can run Jade firmware with minimal c * USB VendorID:0403 ProductID:6001 (Different to retail Jade, will require custom Electrum/HWI builds until they add support to these device IDs) * Typically supports Secure Boot V2 +### M5Stack M5StickS3 +* [Product Page](https://docs.m5stack.com/en/core/M5StickS3) +* ~$16 USD +* ESP32-S3 (8MB flash, 8MB octal PSRAM) with native USB +* ST7789P3 135x240 IPS LCD +* Two button interface (Long press front button to select) +* Includes 120mAh battery +* Fully assembled +* Uses the M5PM1 PMIC: its PYG2 output (active high) gates the L3B rail that powers the LCD +* USB VendorID:303a ProductID:4001 (Different to retail Jade, will require custom Electrum/HWI builds until they add support to these device IDs) +* Use the `configs/sdkconfig_display_m5sticks3.defaults` build profile + ### M5Stack Basic Core ![M5Stack Basic Core](img/M5Stack-Basic.jpg) * [Vendor Product Page](https://shop.m5stack.com/products/esp32-basic-core-lot-development-kit-v2-7) diff --git a/gitlab/diy_fw.yml b/gitlab/diy_fw.yml index 4b957a2a..4d5b0a80 100644 --- a/gitlab/diy_fw.yml +++ b/gitlab/diy_fw.yml @@ -30,6 +30,9 @@ build_diy_display_m5stickcplus2: build_diy_display_m5cores3: extends: .build_diy_template +build_diy_display_m5sticks3: + extends: .build_diy_template + build_diy_display_ttgo_twatchs3: extends: .build_diy_template diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 439c8266..e6ee3e8a 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -245,6 +245,14 @@ menu "Blockstream Jade" help Some devices come with the IP5306 to manage power to ESP, charge the battery, etc. + config HAS_M5PM1 + bool "Use the M5PM1 power controller" + depends on BOARD_TYPE_CUSTOM + default n + help + M5Stack StickS3 comes with the M5PM1 to manage power to the ESP, + LCD (L3B rail), battery, etc. + menu "Input Options" visible if BOARD_TYPE_CUSTOM config UI_WRAP_ALL_MENUS diff --git a/main/power.c b/main/power.c index 1dd7624b..1460611c 100644 --- a/main/power.c +++ b/main/power.c @@ -33,6 +33,9 @@ #elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) // T-Display can read battery level but lacks hardware to read charging status #include "power/tdisplay.inc" +#elif defined(CONFIG_HAS_M5PM1) +// M5 StickS3 has the M5PM1 PMIC +#include "power/m5pm1.inc" #else // Stubs for other hw boards (ie. no power management) #include "power/minimal.inc" diff --git a/main/power/m5pm1.inc b/main/power/m5pm1.inc new file mode 100644 index 00000000..60843514 --- /dev/null +++ b/main/power/m5pm1.inc @@ -0,0 +1,150 @@ +// M5PM1 PMU implementation (M5Stack StickS3) +// +// The StickS3 uses the M5PM1 PMIC (I2C addr 0x6E, SDA=G47/SCL=G48) rather than +// the AXP192/IP5306 that Jade normally targets. The M5PM1 brings up the rails +// that power the ESP32 (L0/L1/L2) automatically, but the L3B rail - which powers +// the ST7789 LCD backlight, MIC and SPK - is gated by the PMIC's PYG2 output +// (GPIO2, see the "M5PM1" pinmap on docs.m5stack.com, "PYG2_L3B_EN", and the +// StickS3 M5PM1 tutorial) and is OFF until software drives it. +// +// IMPORTANT: PYG2 is an ACTIVE-HIGH rail enable - set the GPIO output HIGH to +// enable the L3B rail, LOW to cut it. Empirically the M5PM1's GPIO_OUT +// register defaults to all-high on reset (L3B on); M5Unified/UIFlow never +// explicitly touch PYG2, relying on that default. Explicitly writing 0x00 +// kills the rail. Verified by hardware test: writing 0x04 (PYG2 HIGH) +// restores the backlight. +// +// Reference: M5PM1_Datasheet_EN.pdf, M5PM1 Arduino lib (m5stack/M5PM1), +// M5Unified Board_StickS3.cpp. +// +#include "power/i2c.inc" +#include + +#define M5PM1_ADDR 0x6E + +#define M5PM1_REG_DEVICE_ID 0x00 // R [7:0] device id +#define M5PM1_REG_PWR_CFG 0x06 // [4]LED_EN [3]BOOST [2]LDO3V3 [1]DCDC5V [0]CHG; clear on reset +#define M5PM1_REG_HOLD_CFG 0x07 // [6]BOOST [5]LDO [4..0]GPIO4..0 output-state hold; clear on reset +#define M5PM1_REG_I2C_CFG 0x09 // [4]SPD(1=400kHz) [3:0]I2C idle sleep timeout (0=disabled) +#define M5PM1_REG_WDT_CNT 0x0A // watchdog countdown seconds (0=disabled) +#define M5PM1_REG_GPIO_MODE 0x10 // [4:0] direction, 1=output, 0=input +#define M5PM1_REG_GPIO_OUT 0x11 // [4:0] output level, 1=high, 0=low +#define M5PM1_REG_GPIO_DRV 0x13 // [4:0] drive, 1=open-drain, 0=push-pull +#define M5PM1_REG_GPIO_FUNC0 0x16 // 2 bits per GPIO0..3 func: 00=GPIO 01=IRQ 10=WAKE 11=special + +#define M5PM1_PYG2 2 // GPIO2 output = L3B rail enable, ACTIVE-HIGH (1=rail on) +#define M5PM1_PYG3 3 // GPIO3 output = speaker amp pulse (hold low until audio used) + +#define M5PM1_PWR_CFG_ENABLES 0x07 // CHG + DCDC5V + LDO3V3 (BOOST left off = EXT_5V input default) +#define M5PM1_HOLD_CFG 0x64 // hold LDO + BOOST rails and the PYG2 output state + +static i2c_master_dev_handle_t m5pm1 = NULL; + +// Best-effort register write: log failures but never fail the boot - the board +// must keep running (and stay USB-detectable) even if the PMIC is not responsive. +static esp_err_t m5pm1_write(const uint8_t reg, const uint8_t val) +{ + if (!m5pm1) { + return ESP_OK; + } + + esp_err_t rc = ESP_OK; + JADE_SEMAPHORE_TAKE(i2c_mutex); + rc = _power_write_command(m5pm1, reg, val); + JADE_SEMAPHORE_GIVE(i2c_mutex); + + if (rc != ESP_OK) { + JADE_LOGE("m5pm1: i2c write reg 0x%02x failed: %u (%d)", reg, rc, rc); + } + return rc; +} + +// Drive the L3B rail (PYG2/GPIO2) to power the LCD. PYG2 is an active-high +// rail enable: HIGH supplies L3B, LOW cuts it. +static esp_err_t m5pm1_set_lcd_power(const bool enabled) +{ + (void)m5pm1_write(M5PM1_REG_GPIO_OUT, enabled ? (1 << M5PM1_PYG2) : 0x00); + return ESP_OK; // best-effort: never fail the display init for a bad PMIC +} + +esp_err_t power_init(void) +{ + esp_err_t rc = _power_i2c_init(); + if (rc != ESP_OK) { + JADE_LOGE("m5pm1: i2c bus init failed (%u/%d) - continuing without PMU", rc, rc); + return ESP_OK; + } + + rc = _power_i2c_attach_device(M5PM1_ADDR, &m5pm1); + if (rc != ESP_OK) { + JADE_LOGE("m5pm1: attach at 0x%02x failed (%u/%d) - continuing without PMU", M5PM1_ADDR, rc, rc); + m5pm1 = NULL; + return ESP_OK; + } + + // Disable the PMIC i2c idle sleep timeout and watchdog: without this the PMIC + // could drop rails / reset the board once the app stops talking to it. + (void)m5pm1_write(M5PM1_REG_I2C_CFG, 0x00); + (void)m5pm1_write(M5PM1_REG_WDT_CNT, 0x00); + + // Keep core rails asserted and the PYG2 level stable across soft resets/OTA. + (void)m5pm1_write(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_ENABLES); + (void)m5pm1_write(M5PM1_REG_HOLD_CFG, M5PM1_HOLD_CFG); + + // Configure the PMIC GPIOs used by this board (M5PM1 GPIO_FUNC0 = 0x00 => plain GPIO): + // PYG2 as push-pull output HIGH -> L3B rail on (LCD backlight + MIC + SPK powered) + // PYG3 as push-pull output LOW -> speaker amplifier held off during bring-up + (void)m5pm1_write(M5PM1_REG_GPIO_FUNC0, 0x00); + (void)m5pm1_write(M5PM1_REG_GPIO_DRV, 0x00); + (void)m5pm1_write(M5PM1_REG_GPIO_MODE, (1 << M5PM1_PYG2) | (1 << M5PM1_PYG3)); + (void)m5pm1_write(M5PM1_REG_GPIO_OUT, 1 << M5PM1_PYG2); // PYG2 HIGH enables L3B, PYG3 LOW keeps SPK off + + // Read back the PMIC GPIO registers so boot logs prove whether the writes + // stuck - expect FUNC0=0x00, MODE=0x0c, DRV=0x00, OUT=0x04 (PYG2 high = L3B on). + JADE_SEMAPHORE_TAKE(i2c_mutex); + const uint8_t m5pm1_readback_regs[] = { + M5PM1_REG_DEVICE_ID, M5PM1_REG_GPIO_FUNC0, M5PM1_REG_GPIO_MODE, M5PM1_REG_GPIO_DRV, M5PM1_REG_GPIO_OUT, + }; + for (size_t i = 0; i < sizeof(m5pm1_readback_regs); i++) { + uint8_t readback = 0; + const esp_err_t rc = _power_master_read_slave(m5pm1, m5pm1_readback_regs[i], &readback, 1); + if (rc == ESP_OK) { + JADE_LOGI("m5pm1: reg 0x%02x = 0x%02x", m5pm1_readback_regs[i], readback); + } else { + JADE_LOGE("m5pm1: readback reg 0x%02x failed: %d", m5pm1_readback_regs[i], rc); + } + } + JADE_SEMAPHORE_GIVE(i2c_mutex); + + return ESP_OK; +} + +esp_err_t power_shutdown(void) +{ + // Drop the LCD rail, then sleep + m5pm1_set_lcd_power(false); + esp_deep_sleep_start(); + return ESP_OK; +} + +esp_err_t power_screen_on(void) { return m5pm1_set_lcd_power(true); } + +esp_err_t power_backlight_on(const uint8_t brightness) { return ESP_OK; } +esp_err_t power_backlight_off(void) { return ESP_OK; } + +esp_err_t power_camera_on(void) { return ESP_OK; } +esp_err_t power_camera_off(void) { return ESP_OK; } + +uint16_t power_get_vbat(void) { return 0; } +uint8_t power_get_battery_status(void) { return 0; } +bool power_get_battery_charging(void) { return false; } +uint16_t power_get_ibat_charge(void) { return 0; } +uint16_t power_get_ibat_discharge(void) { return 0; } +uint16_t power_get_vusb(void) { return 0; } +uint16_t power_get_iusb(void) { return 0; } +uint16_t power_get_temp(void) { return 0; } + +bool usb_is_powered(void) { return true; } + +void disable_usb_host(void) {} +void enable_usb_host(void) {} \ No newline at end of file