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
41 changes: 41 additions & 0 deletions boards/lilygo_twatch_s3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_16MB.csv",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "LilyGo T-Watch S3 (16M Flash 8M PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"speed": 921600
},
"url": "https://www.lilygo.cc/products/t-watch-s3",
"vendor": "LilyGo"
}
32 changes: 32 additions & 0 deletions examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,38 @@ void UITask::loop() {
next_backlight_btn_check = millis() + 300;
}
#endif
#if defined(UI_HAS_TOUCH)
// touch nav: tap left/right/centre -> KEY_PREV/KEY_NEXT/KEY_ENTER, long press -> ENTER
{
static bool touch_was_down = false;
static int touch_down_x = 0, touch_down_y = 0;
static unsigned long touch_down_at = 0;
int tx = 0, ty = 0;
bool touch_now = (_display != NULL) && _display->getTouch(&tx, &ty);
if (touch_now && !touch_was_down) { // touch start
touch_down_x = tx; touch_down_y = ty;
touch_down_at = millis();
touch_was_down = true;
} else if (!touch_now && touch_was_down) { // touch release -> tap
touch_was_down = false;
unsigned long held = millis() - touch_down_at;
if (!_display->isOn()) {
c = checkDisplayOn(KEY_ENTER); // first tap just wakes the screen
} else if (held >= 800) {
c = handleLongPress(KEY_ENTER); // long press -> ENTER (CLI rescue in first 8s)
} else {
int w = _display->width();
if (touch_down_x < w / 3) {
c = checkDisplayOn(KEY_PREV);
} else if (touch_down_x > (2 * w) / 3) {
c = checkDisplayOn(KEY_NEXT);
} else {
c = checkDisplayOn(KEY_ENTER);
}
}
}
}
#endif

if (c != 0 && curr) {
curr->handleInput(c);
Expand Down
1 change: 1 addition & 0 deletions src/helpers/ui/DisplayDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DisplayDriver {
virtual void drawRect(int x, int y, int w, int h) = 0;
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
virtual uint16_t getTextWidth(const char* str) = 0;
virtual bool getTouch(int* x, int* y) { return false; } // touch panels override this
virtual void drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise)
int w = getTextWidth(str);
setCursor(mid_x - w/2, y);
Expand Down
64 changes: 64 additions & 0 deletions variants/lilygo_twatch_s3/TWatchS3Board.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include <Arduino.h>
#include "TWatchS3Board.h"

void TWatchS3Board::begin() {
ESP32Board::begin();
power_init();

esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) {
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
}

bool TWatchS3Board::power_init() {
PMU = new XPowersAXP2101(Wire, PIN_BOARD_SDA, PIN_BOARD_SCL, I2C_PMU_ADD);
if (!PMU->init()) {
MESH_DEBUG_PRINTLN("Warning: Failed to find AXP2101 power management");
delete PMU;
PMU = NULL;
return false;
}

PMU->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);

// Power rails (matches LilyGo / Meshtastic T-Watch S3)
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); // LoRa radio
PMU->enablePowerOutput(XPOWERS_ALDO3);
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); // sensors, display, PCF8563 RTC
PMU->enablePowerOutput(XPOWERS_ALDO2);
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); // 6-axis sensor / display rail
PMU->enablePowerOutput(XPOWERS_ALDO1);
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300); // DRV2605 haptic
PMU->enablePowerOutput(XPOWERS_BLDO2);
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); // must stay on or the radio loses power
PMU->enablePowerOutput(XPOWERS_ALDO4);

PMU->disablePowerOutput(XPOWERS_DCDC2);
PMU->disablePowerOutput(XPOWERS_DCDC3);
PMU->disablePowerOutput(XPOWERS_DCDC4);
PMU->disablePowerOutput(XPOWERS_DCDC5);
PMU->disablePowerOutput(XPOWERS_BLDO1);
PMU->disablePowerOutput(XPOWERS_DLDO1);
PMU->disablePowerOutput(XPOWERS_DLDO2);
PMU->disablePowerOutput(XPOWERS_VBACKUP);

PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
PMU->clearIrqStatus();

PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);

PMU->disableTSPinMeasure();
PMU->enableSystemVoltageMeasure();
PMU->enableVbusVoltageMeasure();
PMU->enableBattVoltageMeasure();

PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S);
return true;
}
56 changes: 56 additions & 0 deletions variants/lilygo_twatch_s3/TWatchS3Board.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#pragma once

// Pin mappings must be defined before including ESP32Board.h so begin() brings up
// Wire on the correct I2C pins and sleep() can reference P_LORA_DIO_1.

// Main I2C bus (AXP2101 PMU, PCF8563 RTC)
#define PIN_BOARD_SDA 10
#define PIN_BOARD_SCL 11
#define I2C_PMU_ADD 0x34

#ifndef PIN_PMU_IRQ
#define PIN_PMU_IRQ 21
#endif

#include <Wire.h>
#include <Arduino.h>
#include "XPowersLib.h"
#include "helpers/ESP32Board.h"
#include <driver/rtc_io.h>

class TWatchS3Board : public ESP32Board {
XPowersLibInterface* PMU = NULL;

bool power_init();

public:
void begin();

void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);

rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);

if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);
} else {
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
}

if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}

esp_deep_sleep_start();
}

uint16_t getBattMilliVolts() override {
return PMU ? PMU->getBattVoltage() : 0;
}

const char* getManufacturerName() const override {
return "LilyGo T-Watch S3";
}
};
94 changes: 94 additions & 0 deletions variants/lilygo_twatch_s3/TWatchS3Display.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#pragma once

#include <helpers/ui/LGFXDisplay.h>

#define LGFX_USE_V1
#include <LovyanGFX.hpp>

// ST7789 240x240 on SPI3 + PWM backlight (GPIO 45) + FT5x06/FT6x36 capacitive
// touch on a separate I2C bus (Wire1: SDA 39 / SCL 40).
class LGFX_TWatchS3 : public lgfx::LGFX_Device {
lgfx::Panel_ST7789 _panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
lgfx::Touch_FT5x06 _touch_instance;

public:
LGFX_TWatchS3(void) {
{
auto cfg = _bus_instance.config();
cfg.spi_host = SPI3_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 40000000;
cfg.freq_read = 16000000;
cfg.spi_3wire = true;
cfg.use_lock = true;
cfg.dma_channel = SPI_DMA_CH_AUTO;
cfg.pin_sclk = 18;
cfg.pin_mosi = 13;
cfg.pin_miso = -1;
cfg.pin_dc = 38;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}

{
auto cfg = _panel_instance.config();
cfg.pin_cs = 12;
cfg.pin_rst = -1;
cfg.pin_busy = -1;
// ST7789 GRAM is 240x320; memory_height must be 320 so the 80px rotation
// offset is applied (otherwise a strip of the panel shows noise).
cfg.memory_width = 240;
cfg.memory_height = 320;
cfg.panel_width = 240;
cfg.panel_height = 240;
cfg.offset_x = 0;
cfg.offset_y = 0;
cfg.offset_rotation = 1;
cfg.readable = false;
cfg.invert = true;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = false;
_panel_instance.config(cfg);
}

{
auto cfg = _light_instance.config();
cfg.pin_bl = 45;
cfg.invert = false;
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance);
}

{
auto cfg = _touch_instance.config();
cfg.x_min = 0;
cfg.x_max = 239;
cfg.y_min = 0;
cfg.y_max = 239;
cfg.pin_int = 16;
cfg.pin_rst = -1;
cfg.bus_shared = false;
cfg.offset_rotation = 2; // touch IC mounted 180deg vs LCD's horizontal axis
cfg.i2c_port = 1;
cfg.i2c_addr = 0x38;
cfg.pin_sda = 39;
cfg.pin_scl = 40;
cfg.freq = 400000;
_touch_instance.config(cfg);
_panel_instance.setTouch(&_touch_instance);
}

setPanel(&_panel_instance);
}
};

class TWatchS3Display : public LGFXDisplay {
LGFX_TWatchS3 disp;
public:
TWatchS3Display() : LGFXDisplay(240, 240, disp) {}
};
76 changes: 76 additions & 0 deletions variants/lilygo_twatch_s3/platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
[LilyGo_TWatchS3]
extends = esp32_base
board = lilygo_twatch_s3
build_flags =
${esp32_base.build_flags}
${sensor_base.build_flags}
-I variants/lilygo_twatch_s3
-D LILYGO_TWATCH_S3
-D T_WATCH_S3
-D BOARD_HAS_PSRAM=1
-D CORE_DEBUG_LEVEL=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D ESP32_CPU_FREQ=80 ; 80MHz keeps BLE + USB working while reducing power
; ---- LoRa SX1262 ----
-D USE_SX1262
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8f
-D SX126X_CURRENT_LIMIT=140
-D SX126X_RX_BOOSTED_GAIN=1
-D P_LORA_NSS=5
-D P_LORA_DIO_1=9 ; SX1262 IRQ
-D P_LORA_RESET=8
-D P_LORA_BUSY=7
-D P_LORA_SCLK=3
-D P_LORA_MISO=4
-D P_LORA_MOSI=1
; ---- Display (ST7789 240x240) + capacitive touch via LovyanGFX ----
-D DISPLAY_CLASS=TWatchS3Display
-D UI_HAS_TOUCH=1
-D PIN_PMU_IRQ=21
; ---- No GPS / env sensors on the base watch ----
-D ENV_INCLUDE_GPS=0
-D ENV_INCLUDE_AHTX0=0
-D ENV_INCLUDE_BME280=0
-D ENV_INCLUDE_BMP280=0
-D ENV_INCLUDE_SHTC3=0
-D ENV_INCLUDE_SHT4X=0
-D ENV_INCLUDE_LPS22HB=0
-D ENV_INCLUDE_INA3221=0
-D ENV_INCLUDE_INA219=0
-D ENV_INCLUDE_INA226=0
-D ENV_INCLUDE_INA260=0
-D ENV_INCLUDE_MLX90614=0
-D ENV_INCLUDE_VL53L0X=0
-D ENV_INCLUDE_BME680=0
-D ENV_INCLUDE_BMP085=0
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/lilygo_twatch_s3>
+<helpers/sensors/*.cpp>
+<helpers/ui/LGFXDisplay.cpp>
lib_deps =
${esp32_base.lib_deps}
${sensor_base.lib_deps}
lewisxhe/XPowersLib @ ^0.2.7
lovyan03/LovyanGFX @ ^1.2.0

[env:LilyGo_TWatchS3_companion_radio_ble]
extends = LilyGo_TWatchS3
build_flags =
${LilyGo_TWatchS3.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D OFFLINE_QUEUE_SIZE=256
build_src_filter = ${LilyGo_TWatchS3.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${LilyGo_TWatchS3.lib_deps}
densaugeo/base64 @ ~1.4.0
Loading