From 1e2e9886987e24fb9ac25c8ac06fdb8586c837fd Mon Sep 17 00:00:00 2001 From: John Millington Date: Tue, 9 Jun 2026 16:09:55 +1200 Subject: [PATCH 1/3] tbeam: add LilyGo T-Beam v1.x board support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New board: LilyGo T-Beam v1.0/v1.1 (ESP32 PICO-D4 + SX1276 + AXP192) boards/esp32/ttgo_tbeam/board.conf: - SX127x loramac-node radio backend - DIO flash mode (PICO-D4 QIO bootloop workaround) - WiFi OTA opt-out (DRAM limit on classic PICO-D4) boards/esp32/ttgo_tbeam/board.overlay: - Replace AXP2101 alias with AXP192 (LDO2→LoRa 3.3V, LDO3→GPS 3.3V) - LittleFS partition on internal flash - LED polarity correction (active-low red LED) - lora-tx-led alias for TX activity flash adapters/board/ZephyrBoard.cpp: - Battery voltage via AXP192 I2C registers 0x78/0x79 (12-bit, 1.1 mV/LSB) - GPIO0 floated at boot to turn off the blue power-indicator LED CMakeLists.txt: - Allow board.conf to opt out of auto-WiFi-OTA inclusion via CONFIG_ZEPHCORE_WIFI_OTA=n for boards with limited DRAM Co-Authored-By: Claude Sonnet 4.6 --- zephcore/CMakeLists.txt | 23 +++- zephcore/adapters/board/ZephyrBoard.cpp | 53 +++++++++ zephcore/boards/esp32/ttgo_tbeam/board.conf | 35 ++++++ .../boards/esp32/ttgo_tbeam/board.overlay | 106 ++++++++++++++++++ 4 files changed, 212 insertions(+), 5 deletions(-) create mode 100644 zephcore/boards/esp32/ttgo_tbeam/board.conf create mode 100644 zephcore/boards/esp32/ttgo_tbeam/board.overlay diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 9e426f5..4e45a1e 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -323,12 +323,25 @@ endif() # Enables WiFi AP + HTTP server + MCUboot image management. # Requires --sysbuild to build MCUboot alongside the app. # Non-ESP32 builds and companion builds are unaffected. +# Boards with insufficient DRAM (e.g. classic ESP32 PICO-D4) can opt out +# by setting CONFIG_ZEPHCORE_WIFI_OTA=n in their board.conf. if(ZEPHCORE_PLATFORM_CONF MATCHES "esp32_common" AND EXTRA_CONF_FILE MATCHES "repeater") - set(ZEPHCORE_WIFI_OTA_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/wifi_ota.conf") - if(EXISTS ${ZEPHCORE_WIFI_OTA_CONF}) - list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_WIFI_OTA_CONF}) - zephcore_auto_pair_overlay("${ZEPHCORE_WIFI_OTA_CONF}") - message(STATUS " WiFi OTA: auto-enabled (ESP32 repeater)") + set(_wifi_ota_disabled FALSE) + if(ZEPHCORE_BOARD_CONF AND EXISTS "${ZEPHCORE_BOARD_CONF}") + file(STRINGS "${ZEPHCORE_BOARD_CONF}" _board_conf_lines) + if("CONFIG_ZEPHCORE_WIFI_OTA=n" IN_LIST _board_conf_lines) + set(_wifi_ota_disabled TRUE) + endif() + endif() + if(NOT _wifi_ota_disabled) + set(ZEPHCORE_WIFI_OTA_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/wifi_ota.conf") + if(EXISTS ${ZEPHCORE_WIFI_OTA_CONF}) + list(APPEND ZEPHCORE_CONF_FILES ${ZEPHCORE_WIFI_OTA_CONF}) + zephcore_auto_pair_overlay("${ZEPHCORE_WIFI_OTA_CONF}") + message(STATUS " WiFi OTA: auto-enabled (ESP32 repeater)") + endif() + else() + message(STATUS " WiFi OTA: disabled (board.conf opt-out)") endif() endif() diff --git a/zephcore/adapters/board/ZephyrBoard.cpp b/zephcore/adapters/board/ZephyrBoard.cpp index 5475ace..c74e8b8 100644 --- a/zephcore/adapters/board/ZephyrBoard.cpp +++ b/zephcore/adapters/board/ZephyrBoard.cpp @@ -64,6 +64,48 @@ static const struct device *vbat_enable_dev = NULL; #define VBAT_MV_MULTIPLIER CONFIG_ZEPHCORE_VBAT_MV_MULTIPLIER #endif #define VBAT_ADC_SAMPLES 8 +#elif DT_NODE_EXISTS(DT_NODELABEL(axp192_pmic)) +/* AXP192 PMIC: battery voltage via I2C registers 0x78/0x79. + * Used on boards without a GPIO-connected battery voltage divider, + * e.g. LilyGo T-Beam v1.x where battery management is internal to the PMIC. */ +#include + +/* AXP192 register map (datasheet section 9) */ +#define AXP192_REG_ADC_EN1 0x82U /* ADC Enable 1 */ +#define AXP192_BATT_V_ADC_EN BIT(7) /* reg 0x82 bit 7: battery voltage ADC */ +#define AXP192_REG_BATT_VH 0x78U /* battery voltage ADC high 8 bits [11:4] */ +#define AXP192_REG_BATT_VL 0x79U /* battery voltage ADC low 4 bits [7:4] */ +#define AXP192_REG_GPIO0_FUNC 0x90U /* GPIO0 function control */ +#define AXP192_GPIO0_FUNC_FLOAT 0x06U /* float / high-Z — cuts LED current path */ + +static const struct i2c_dt_spec axp192_i2c = + I2C_DT_SPEC_GET(DT_NODELABEL(axp192_pmic)); + +/* One-time AXP192 init at boot. + * + * 1. Enable battery voltage ADC (off at POR). + * 2. Float GPIO0 to turn off the red power-indicator LED. + * GPIO0 is open-drain; at POR its NMOS is conducting, sinking current + * from the VCC→LED→GPIO0 circuit and keeping the LED on. FLOAT mode + * puts GPIO0 in high-impedance, breaking the current path. */ +static int axp192_batt_adc_init(void) +{ + if (!i2c_is_ready_dt(&axp192_i2c)) { + return -ENODEV; + } + + /* Enable battery voltage ADC */ + int ret = i2c_reg_update_byte_dt(&axp192_i2c, AXP192_REG_ADC_EN1, + AXP192_BATT_V_ADC_EN, AXP192_BATT_V_ADC_EN); + if (ret < 0) { + return ret; + } + + /* Turn off red power-indicator LED (GPIO0 → float) */ + return i2c_reg_write_byte_dt(&axp192_i2c, AXP192_REG_GPIO0_FUNC, + AXP192_GPIO0_FUNC_FLOAT); +} +SYS_INIT(axp192_batt_adc_init, APPLICATION, 91); #endif /* Initialize TX LED GPIO at boot */ @@ -142,6 +184,17 @@ uint16_t ZephyrBoard::getBattMilliVolts() uint16_t mv = (uint16_t)((mult * (int64_t)raw) / 4096); LOG_DBG("Battery: raw=%d multiplier=%lld mv=%u", (int)raw, (long long)mult, mv); return mv; +#elif DT_NODE_EXISTS(DT_NODELABEL(axp192_pmic)) + /* 12-bit ADC: reg 0x78 = bits[11:4], reg 0x79 bits[7:4] = bits[3:0] */ + uint8_t vh = 0, vl = 0; + if (!i2c_is_ready_dt(&axp192_i2c) || + i2c_reg_read_byte_dt(&axp192_i2c, AXP192_REG_BATT_VH, &vh) < 0 || + i2c_reg_read_byte_dt(&axp192_i2c, AXP192_REG_BATT_VL, &vl) < 0) { + return 0; + } + uint16_t raw = ((uint16_t)vh << 4) | (vl >> 4); + /* 1.1 mV per LSB — multiply by 11 then divide by 10 to avoid floats */ + return (uint16_t)((uint32_t)raw * 11U / 10U); #else return 0; #endif diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.conf b/zephcore/boards/esp32/ttgo_tbeam/board.conf new file mode 100644 index 0000000..697cc9b --- /dev/null +++ b/zephcore/boards/esp32/ttgo_tbeam/board.conf @@ -0,0 +1,35 @@ +# LilyGo T-Beam v1.x — ESP32 PICO-D4 + SX1276 + AXP192 PMIC +# +# Hardware: +# ESP32 PICO-D4 (4MB flash) +# SX1276 on SPI3 (NSS=18, SCK=5, MISO=19, MOSI=27, RESET=23, DIO0=26, DIO1=33, DIO2=32) +# AXP192 PMIC on I2C0 — LDO2→LoRa 3.3V, LDO3→GPS 3.3V +# NEO-6M GPS on UART1 (TX=12, RX=34) +# Optional SSD1306 OLED on I2C0 (SDA=21, SCL=22) +# +# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf + +CONFIG_ZEPHCORE_BOARD_NAME="LilyGo T-Beam" +CONFIG_BT_DIS_MODEL_NUMBER_STR="LilyGo T-Beam" + +# Radio: SX1276 via loramac-node backend (not the native SX126x driver) +CONFIG_LORA_MODULE_BACKEND_NATIVE=n +CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE=y +CONFIG_ZEPHCORE_RADIO_SX127X=y + +# SX1276 PA_BOOST output: 17 dBm is the safe maximum without an external PA +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 + +# RX duty cycle not supported by the loramac-node SX127x driver +# (falls back gracefully, but disable to avoid the spurious attempt) +CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n + +# WiFi OTA (networking+HTTP+MCUboot) exceeds the PICO-D4's DRAM budget. +# Opt out of the auto-inclusion so ESP32 repeater builds stay within DRAM limits. +CONFIG_ZEPHCORE_WIFI_OTA=n + +# ESP32 PICO-D4 rev 1.0: use DIO flash mode, not QIO. +# bootloader_enable_qio_mode() polls WIP indefinitely on PICO-D4 rev 1.0, +# causing a bootloop after the "Proceeding" chip-revision message. +CONFIG_ESPTOOLPY_FLASHMODE_QIO=n +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.overlay b/zephcore/boards/esp32/ttgo_tbeam/board.overlay new file mode 100644 index 0000000..e402a67 --- /dev/null +++ b/zephcore/boards/esp32/ttgo_tbeam/board.overlay @@ -0,0 +1,106 @@ +/* + * ZephCore overlay — LilyGo T-Beam v1.x (ESP32 PICO-D4 + SX1276 + AXP192) + * + * The upstream Zephyr ttgo_tbeam DTS targets a newer T-Beam revision that + * ships with AXP2101. The v1.0/v1.1 hardware uses AXP192. This overlay: + * 1. Removes the upstream AXP2101 node + * 2. Adds AXP192 with correct T-Beam v1.x LDO assignments: + * LDO2 → SX1276 LoRa 3.3V (always-on) + * LDO3 → GPS (NEO-6M) 3.3V + * 3. Repurposes storage_partition as LittleFS + * + * SX1276 pins (from upstream DTS, verified against T-Beam v1.x schematic): + * NSS=18 SCK=5 MISO=19 MOSI=27 RESET=23 DIO0=26 DIO1=33 DIO2=32 + * If your board has RESET on GPIO14 (very early T-Beam v0.7), override + * reset-gpios in a local DTS fragment. + */ + +/* Remove the upstream AXP2101 node — wrong PMIC for v1.x hardware */ +/delete-node/ &axp2101; + +/ { + aliases { + regulator0 = &axp192_pmic; + /* GPIO4 LED is wired active-low (cathode to GPIO, anode to VCC). + * Upstream DTS has GPIO_ACTIVE_HIGH which inverts the logic and + * keeps the LED on permanently. We fix the polarity here and + * register the LED as the LoRa TX indicator so it flashes on + * each transmit in addition to the 4-second heartbeat pulse. */ + lora-tx-led = &red_led; + }; +}; + +/* Fix LED polarity: hardware is active-low, upstream DTS says active-high */ +&red_led { + gpios = <&gpio0 4 GPIO_ACTIVE_LOW>; +}; + +/* + * Flash layout — repurpose storage_partition as LittleFS. + * 192 KB at 0x3B0000: same location and size as the deleted storage_partition. + */ +/delete-node/ &storage_partition; + +&flash0 { + partitions { + lfs_partition: partition@3b0000 { + label = "lfs"; + reg = <0x3B0000 0x30000>; + }; + }; +}; + +#include "../../common/filesystem.dtsi" + +&i2c0 { + /* + * AXP192 PMIC — replaces AXP2101 from upstream DTS. + * T-Beam v1.x LDO assignments: + * DCDC1: ESP32 core VDD 3.3V + * LDO2: SX1276 LoRa VDD 3.3V + * LDO3: GPS VDD 3.3V + */ + axp192_pmic: axp192@34 { + compatible = "x-powers,axp192"; + reg = <0x34>; + status = "okay"; + + axp192_regulator: axp192_regulator { + compatible = "x-powers,axp192-regulator"; + status = "okay"; + + /* DCDC1 → ESP32 core VDD */ + DCDC1 { + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + /* LDO2 → SX1276 VDD — must be on before radio init */ + LDO2 { + regulator-init-microvolt = <3300000>; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + /* LDO3 → GPS VDD */ + LDO3 { + regulator-init-microvolt = <3300000>; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + }; + }; + }; + + /* I2C sensors — auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* WiFi — required for observer and wifi_ota roles */ +&wifi { + status = "okay"; +}; From e5f2a6f298007ea8fcc7f3057aabaaafd7dd6d6d Mon Sep 17 00:00:00 2001 From: John Millington Date: Sun, 7 Jun 2026 15:50:39 +1200 Subject: [PATCH 2/3] tbeam: fit BT companion build on ESP32 PICO-D4 BT blob reserves ~56 KB, leaving 136 KB for data+bss in dram0_0_seg. Reduce MAX_CONTACTS to 32 and MAX_CHANNELS to 8 (saves ~60 KB BSS). Enable &esp32_bt_hci in overlay (disabled by default in SoC DTSI). Co-Authored-By: Claude Sonnet 4.6 --- zephcore/boards/esp32/ttgo_tbeam/board.conf | 22 ++++++++++++++----- .../boards/esp32/ttgo_tbeam/board.overlay | 5 +++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.conf b/zephcore/boards/esp32/ttgo_tbeam/board.conf index 697cc9b..3521315 100644 --- a/zephcore/boards/esp32/ttgo_tbeam/board.conf +++ b/zephcore/boards/esp32/ttgo_tbeam/board.conf @@ -8,6 +8,12 @@ # Optional SSD1306 OLED on I2C0 (SDA=21, SCL=22) # # Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf +# +# DRAM budget note (ESP32 PICO-D4): +# BT blob reserves ~56 KB DRAM. WiFi stack uses ~80 KB BSS. +# Both together overflow by ~55 KB. Use role-specific conf to select: +# boards/esp32/ttgo_tbeam/repeater.conf — WiFi OTA, no BT +# boards/esp32/ttgo_tbeam/companion.conf — BT, no WiFi OTA CONFIG_ZEPHCORE_BOARD_NAME="LilyGo T-Beam" CONFIG_BT_DIS_MODEL_NUMBER_STR="LilyGo T-Beam" @@ -21,15 +27,21 @@ CONFIG_ZEPHCORE_RADIO_SX127X=y CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=17 # RX duty cycle not supported by the loramac-node SX127x driver -# (falls back gracefully, but disable to avoid the spurious attempt) CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE=n -# WiFi OTA (networking+HTTP+MCUboot) exceeds the PICO-D4's DRAM budget. -# Opt out of the auto-inclusion so ESP32 repeater builds stay within DRAM limits. -CONFIG_ZEPHCORE_WIFI_OTA=n - # ESP32 PICO-D4 rev 1.0: use DIO flash mode, not QIO. # bootloader_enable_qio_mode() polls WIP indefinitely on PICO-D4 rev 1.0, # causing a bootloop after the "Proceeding" chip-revision message. CONFIG_ESPTOOLPY_FLASHMODE_QIO=n CONFIG_ESPTOOLPY_FLASHMODE_DIO=y + +# Default: BT enabled for companion app. WiFi OTA disabled (DRAM constraint). +# Override with repeater.conf for WiFi OTA + no BT. +CONFIG_ZEPHCORE_WIFI_OTA=n + +# ESP32 PICO-D4 companion DRAM budget. +# BT blob reserves ~56 KB, leaving 136 KB for data+bss. +# Default 350 contacts × 184 bytes = 65 KB alone — reduce to 32 to fit. +# These are companion-role symbols; silently ignored on repeater builds. +CONFIG_ZEPHCORE_MAX_CONTACTS=32 +CONFIG_ZEPHCORE_MAX_CHANNELS=8 diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.overlay b/zephcore/boards/esp32/ttgo_tbeam/board.overlay index e402a67..1efdd2e 100644 --- a/zephcore/boards/esp32/ttgo_tbeam/board.overlay +++ b/zephcore/boards/esp32/ttgo_tbeam/board.overlay @@ -104,3 +104,8 @@ &wifi { status = "okay"; }; + +/* Bluetooth HCI — disabled by default in SoC DTSI, enable for repeater/companion */ +&esp32_bt_hci { + status = "okay"; +}; From fc5c8c9ad73561d01a08e8adce96dc96ac814526 Mon Sep 17 00:00:00 2001 From: John Millington Date: Tue, 9 Jun 2026 15:12:15 +1200 Subject: [PATCH 3/3] tbeam: wire IO38 button to input subsystem GPIO38 (gpio1 pin 6) is the user-accessible button on T-Beam v1.x. Add gpio-keys + input-longpress + input-multi-tap DT nodes to generate INPUT_KEY_A / INPUT_KEY_ENTER / INPUT_KEY_1 / INPUT_KEY_LEFT events, matching the input chain used by RAK4631/Heltec V4/Pocket builds. Short press wakes the display after auto-off and cycles pages. Long press triggers the page enter action. Two stale blank lines also removed from display.c (no logic change). Co-Authored-By: Claude Sonnet 4.6 --- .../boards/esp32/ttgo_tbeam/board.overlay | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zephcore/boards/esp32/ttgo_tbeam/board.overlay b/zephcore/boards/esp32/ttgo_tbeam/board.overlay index 1efdd2e..41a8ac3 100644 --- a/zephcore/boards/esp32/ttgo_tbeam/board.overlay +++ b/zephcore/boards/esp32/ttgo_tbeam/board.overlay @@ -15,6 +15,8 @@ * reset-gpios in a local DTS fragment. */ +#include + /* Remove the upstream AXP2101 node — wrong PMIC for v1.x hardware */ /delete-node/ &axp2101; @@ -28,6 +30,36 @@ * each transmit in addition to the 4-second heartbeat pulse. */ lora-tx-led = &red_led; }; + + /* + * T-Beam v1.x IO38 button (gpio1 pin 6). + * GPIO38 is input-only on ESP32 PICO-D4 (no internal pull-up). + * T-Beam PCB has 100 K pull-up to 3.3 V; button shorts GPIO38 to GND. + */ + buttons: buttons { + compatible = "gpio-keys"; + button0: button_0 { + label = "IO38 button"; + gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; + zephyr,code = ; + }; + }; + + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + page_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; }; /* Fix LED polarity: hardware is active-low, upstream DTS says active-high */