Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions configs/sdkconfig_display_m5sticks3.defaults
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions diy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions gitlab/diy_fw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions main/power.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
150 changes: 150 additions & 0 deletions main/power/m5pm1.inc
Original file line number Diff line number Diff line change
@@ -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 <esp_sleep.h>

#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) {}