Files
Meck/variants/lilygo_twatch_s3_plus/TWatchS3PlusBoard.cpp
T
2026-07-08 21:03:24 +10:00

204 lines
8.8 KiB
C++

#include <Arduino.h>
#include "TWatchS3PlusBoard.h"
#include <SensorBMA423.hpp>
#include <esp_bt.h> // TEMP power-debug: esp_bt_controller_get_status()
volatile bool TWatchS3PlusBoard::_tilt_flag = false;
volatile uint32_t TWatchS3PlusBoard::_tilt_isr_count = 0; // TEMP diagnostic
void IRAM_ATTR TWatchS3PlusBoard::onTiltISR() { _tilt_flag = true; _tilt_isr_count++; }
// ---- Wrapper-free BMA423 step counter (raw I2C) ----------------------------
// SensorLib's SensorBMA423 step-counter methods do not compile in this build,
// so the step counter is driven directly over I2C. Register/offset/mask values
// are from the Bosch BMA423 driver.
#define BMA423_REG_STEP_CNT_OUT 0x1E // 4-byte little-endian step count output
#define BMA423_REG_FEATURE_CONFIG 0x5E // 64-byte feature config stream
#define BMA423_FEATURE_LEN 64
#define BMA423_STEP_EN_BYTE 0x37 // BMA423_STEP_CNTR_OFFSET(0x36) + 1
#define BMA423_STEP_EN_BIT 0x10 // BMA423_STEP_CNTR_EN_MSK
#define BMA423_REG_POWER_CONF 0x7C // BMA4_POWER_CONF_ADDR
#define BMA423_ADV_PWR_SAVE_BIT 0x01 // BMA4_ADVANCE_POWER_SAVE_MSK
static bool bma423ReadRegs(uint8_t reg, uint8_t* buf, uint8_t len) {
Wire.beginTransmission(I2C_ADDR_ACCEL);
Wire.write(reg);
if (Wire.endTransmission(false) != 0) return false;
if (Wire.requestFrom((int)I2C_ADDR_ACCEL, (int)len) != len) return false;
for (uint8_t i = 0; i < len; i++) buf[i] = Wire.read();
return true;
}
static bool bma423WriteRegs(uint8_t reg, const uint8_t* buf, uint8_t len) {
Wire.beginTransmission(I2C_ADDR_ACCEL);
Wire.write(reg);
for (uint8_t i = 0; i < len; i++) Wire.write(buf[i]);
return Wire.endTransmission() == 0;
}
// Enable the step counter by setting its enable bit in the feature config,
// preserving every other byte (tilt lives at a different offset, 0x3A, so it is
// untouched). The feature config can only be written with advanced-power-save
// disabled, so we bracket the write and restore the prior power state after.
static void bma423EnableStepCounter() {
uint8_t pc;
if (!bma423ReadRegs(BMA423_REG_POWER_CONF, &pc, 1)) return; // save power state
uint8_t off = pc & ~BMA423_ADV_PWR_SAVE_BIT; // disable adv power save
bma423WriteRegs(BMA423_REG_POWER_CONF, &off, 1);
delay(2); // wake from low-power (>=450us)
uint8_t cfg[BMA423_FEATURE_LEN];
if (bma423ReadRegs(BMA423_REG_FEATURE_CONFIG, cfg, BMA423_FEATURE_LEN)) {
cfg[BMA423_STEP_EN_BYTE] |= BMA423_STEP_EN_BIT;
bma423WriteRegs(BMA423_REG_FEATURE_CONFIG, cfg, BMA423_FEATURE_LEN);
delay(1); // write settle
}
bma423WriteRegs(BMA423_REG_POWER_CONF, &pc, 1); // restore power state
}
void TWatchS3PlusBoard::begin() {
ESP32Board::begin();
power_init();
// BMA423 accelerometer (always-on I2C, 0x19): enable the tilt / wrist-raise
// feature and its interrupt (routed to PIN1 -> GPIO14) for raise-to-wake.
_accel = new SensorBMA423();
if (_accel->begin(Wire, I2C_ADDR_ACCEL, PIN_BOARD_SDA, PIN_BOARD_SCL)) {
_accel->setRemapAxes(SensorRemap::BOTTOM_LAYER_TOP_RIGHT_CORNER);
_accel->configAccelerometer(OperationMode::NORMAL, AccelFullScaleRange::FS_2G,
50.0f, AccelBandwidth::OSR2_AVG2, AccelPerfMode::CIC_AVG_MODE);
// INT1 pin electrical config: level trigger, active high, push-pull,
// output enabled. INT1_IO_CTRL resets to output-disabled, so without
// this the pin never drives and INPUT_PULLDOWN reads low forever.
_accel->setInterruptPinConfig(InterruptPinMap::PIN1, false, false, true, false);
pinMode(PIN_ACCEL_IRQ, INPUT_PULLDOWN);
// Attach the edge ISR BEFORE enabling the tilt source, so the first
// assertion cannot occur before the handler is armed (a missed first edge
// on a self-clearing line otherwise locks tilt-wake out permanently).
attachInterrupt(digitalPinToInterrupt(PIN_ACCEL_IRQ), onTiltISR, RISING);
_accel->enableTiltDetector(true, true);
// Enable the hardware step counter via raw I2C (SensorLib's wrapper method
// does not compile in this build). It then counts in the BMA423 feature
// engine with no CPU cost, even while the display is off.
bma423EnableStepCounter();
}
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 TWatchS3PlusBoard::power_init() {
PMU = new XPowersAXP2101(Wire, PIN_BOARD_SDA, PIN_BOARD_SCL, I2C_ADDR_PMU);
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 per the T-Watch S3 Plus PowerManage table:
// ALDO2 = display backlight, ALDO3 = display + touch,
// ALDO4 = LoRa, BLDO1 = GNSS, BLDO2 = DRV2605, ALDO1 = unused.
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); // LoRa radio
PMU->enablePowerOutput(XPOWERS_ALDO4);
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); // display + touch
PMU->enablePowerOutput(XPOWERS_ALDO3);
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); // display backlight
PMU->enablePowerOutput(XPOWERS_ALDO2);
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300); // DRV2605 haptic
PMU->enablePowerOutput(XPOWERS_BLDO2);
// GNSS (MIA-M10Q) on BLDO1 -- set the rail voltage but leave it OFF at boot.
// It is powered on demand via gpsPowerOn() when gps_enabled is set.
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
PMU->disablePowerOutput(XPOWERS_BLDO1);
PMU->disablePowerOutput(XPOWERS_DCDC2);
PMU->disablePowerOutput(XPOWERS_DCDC3);
PMU->disablePowerOutput(XPOWERS_DCDC4);
PMU->disablePowerOutput(XPOWERS_DCDC5);
PMU->disablePowerOutput(XPOWERS_ALDO1); // unused on the Plus
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_125MA);
PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
PMU->disableTSPinMeasure();
PMU->enableSystemVoltageMeasure();
PMU->enableVbusVoltageMeasure();
PMU->enableBattVoltageMeasure();
PMU->setPowerKeyPressOffTime(XPOWERS_POWEROFF_4S);
// TEMP power-debug: one-shot rail dump at boot. Rail OFF means the attached
// chip has no power at all (not merely sleeping).
Serial.printf("[PWR] rails: ALDO2(bl)=%d ALDO3(disp/touch)=%d ALDO4(LoRa)=%d BLDO1(GPS)=%d BLDO2(haptic)=%d\n",
PMU->isPowerChannelEnable(XPOWERS_ALDO2),
PMU->isPowerChannelEnable(XPOWERS_ALDO3),
PMU->isPowerChannelEnable(XPOWERS_ALDO4),
PMU->isPowerChannelEnable(XPOWERS_BLDO1),
PMU->isPowerChannelEnable(XPOWERS_BLDO2));
Serial.printf("[PWR] GPS rail (BLDO1) at boot: %s\n",
PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "ON" : "OFF (fully powered down)");
return true;
}
// TEMP power-debug probe.
void TWatchS3PlusBoard::printPowerDebug() {
if (!PMU) return;
Serial.printf("[PWR] batt=%dmV %d%% vbus=%dmV charging=%d cpu=%dMHz bt=%d gps_rail=%s\n",
PMU->getBattVoltage(), PMU->getBatteryPercent(),
PMU->getVbusVoltage(), PMU->isCharging(),
getCpuFrequencyMhz(), (int)esp_bt_controller_get_status(),
PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "ON" : "OFF");
}
void TWatchS3PlusBoard::gpsPowerOn() {
if (PMU) {
PMU->enablePowerOutput(XPOWERS_BLDO1);
delay(100); // allow the module to boot before we expect NMEA
}
}
void TWatchS3PlusBoard::gpsPowerOff() {
if (PMU) PMU->disablePowerOutput(XPOWERS_BLDO1);
}
bool TWatchS3PlusBoard::tiltFired() {
if (_tilt_flag) { // set by the GPIO14 rising-edge ISR
_tilt_flag = false;
_accel->update(); // reading the status clears the sensor INT
return true;
}
// TEMP diagnostic: periodically report the raw INT pin level and ISR count,
// to distinguish "pin stuck high / never re-arms" from "never asserts".
static unsigned long _tilt_dbg_next = 0;
if (millis() >= _tilt_dbg_next) {
_tilt_dbg_next = millis() + 5000;
Serial.printf("[TILT] pin=%d isr_count=%u\n",
digitalRead(PIN_ACCEL_IRQ), (unsigned)_tilt_isr_count);
}
return false;
}
uint32_t TWatchS3PlusBoard::getStepCount() {
uint8_t d[4];
if (!bma423ReadRegs(BMA423_REG_STEP_CNT_OUT, d, 4)) return 0;
return (uint32_t)d[0] | ((uint32_t)d[1] << 8) |
((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24);
}