diff --git a/boards/t-impulse-plus.json b/boards/t-impulse-plus.json new file mode 100644 index 000000000..83b289b42 --- /dev/null +++ b/boards/t-impulse-plus.json @@ -0,0 +1,53 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x8029"]], + "usb_product": "T-Impulse-Plus-nRF52840", + "mcu": "nrf52840", + "variant": "t-impulse-plus", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Lilygo T-Impulse-Plus-nRF52840", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ] + }, + "url": "https://www.lilygo.cc/", + "vendor": "Lilygo" +} diff --git a/src/Power.cpp b/src/Power.cpp index 5b2d41567..8104b53a6 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -24,6 +24,7 @@ #include "main.h" #include "meshUtils.h" #include "power/PowerHAL.h" +#include "power/SGM41562.h" #include "sleep.h" #ifdef ARCH_ESP32 // #include @@ -545,6 +546,10 @@ class AnalogBatteryLevel : public HasBatteryLevel // lastly provide a fallback to indicate external power when fully charged. virtual bool isVbusIn() override { +#ifdef HAS_SGM41562 + if (sgm41562 && sgm41562->refresh()) + return sgm41562->isInputPowerGood(); +#endif #ifdef EXT_PWR_DETECT return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE; @@ -561,6 +566,10 @@ class AnalogBatteryLevel : public HasBatteryLevel /// we can't be smart enough to say 'full'? virtual bool isCharging() override { +#ifdef HAS_SGM41562 + if (sgm41562 && sgm41562->refresh()) + return sgm41562->isCharging(); +#endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(HAS_PMU) if (hasRAK()) { return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse; @@ -767,6 +776,12 @@ bool Power::analogInit() */ bool Power::setup() { +#ifdef HAS_SGM41562 + // Initialize the charger early so AnalogBatteryLevel can read charging + // state from it. The charger does not provide battery voltage / percent — + // those still come from the platform ADC via analogInit() below. + initSGM41562(SGM41562_WIRE); +#endif bool found = false; if (axpChipInit()) { found = true; diff --git a/src/input/HapticFeedback.cpp b/src/input/HapticFeedback.cpp new file mode 100644 index 000000000..fcd215be0 --- /dev/null +++ b/src/input/HapticFeedback.cpp @@ -0,0 +1,97 @@ +#include "HapticFeedback.h" + +#ifdef HAPTIC_FEEDBACK_PIN + +#include + +#ifdef HAPTIC_FEEDBACK_ACTIVE_LOW +#define HAPTIC_FEEDBACK_ON_STATE LOW +#define HAPTIC_FEEDBACK_OFF_STATE HIGH +#else +#define HAPTIC_FEEDBACK_ON_STATE HIGH +#define HAPTIC_FEEDBACK_OFF_STATE LOW +#endif + +HapticFeedback *hapticFeedback = nullptr; + +void initHapticFeedback() +{ + if (!hapticFeedback) + hapticFeedback = new HapticFeedback(); +} + +HapticFeedback::HapticFeedback() : concurrency::OSThread("Haptic") +{ + pinMode(HAPTIC_FEEDBACK_PIN, OUTPUT); + digitalWrite(HAPTIC_FEEDBACK_PIN, HAPTIC_FEEDBACK_OFF_STATE); +} + +void HapticFeedback::motorWrite(bool on) +{ + digitalWrite(HAPTIC_FEEDBACK_PIN, on ? HAPTIC_FEEDBACK_ON_STATE : HAPTIC_FEEDBACK_OFF_STATE); +} + +void HapticFeedback::pulse(uint16_t durationMs) +{ + motorWrite(true); + pulseOffAt = millis() + durationMs; + if (pulseOffAt == 0) // 0 is the "no pulse" sentinel + pulseOffAt = 1; + scheduleNext(); +} + +void HapticFeedback::armDelayedPulse(uint16_t delayMs, uint16_t durationMs) +{ + delayedPulseAt = millis() + delayMs; + if (delayedPulseAt == 0) + delayedPulseAt = 1; + delayedPulseDuration = durationMs; + scheduleNext(); +} + +void HapticFeedback::cancelDelayedPulse() +{ + delayedPulseAt = 0; +} + +void HapticFeedback::scheduleNext() +{ + uint32_t now = millis(); + uint32_t next = 0; + if (pulseOffAt != 0) + next = pulseOffAt; + if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0)) + next = delayedPulseAt; + if (next == 0) + return; + int32_t delay = (int32_t)(next - now); + setIntervalFromNow(delay > 0 ? (unsigned long)delay : 0); +} + +int32_t HapticFeedback::runOnce() +{ + uint32_t now = millis(); + + if (pulseOffAt != 0 && (int32_t)(now - pulseOffAt) >= 0) { + motorWrite(false); + pulseOffAt = 0; + } + + if (delayedPulseAt != 0 && (int32_t)(now - delayedPulseAt) >= 0) { + uint16_t dur = delayedPulseDuration; + delayedPulseAt = 0; + pulse(dur); + } + + uint32_t next = 0; + if (pulseOffAt != 0) + next = pulseOffAt; + if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0)) + next = delayedPulseAt; + if (next == 0) + return 60 * 1000; + int32_t delay = (int32_t)(next - now); + return delay > 0 ? delay : 0; +} + +#endif // HAPTIC_FEEDBACK_PIN diff --git a/src/input/HapticFeedback.h b/src/input/HapticFeedback.h new file mode 100644 index 000000000..da542edeb --- /dev/null +++ b/src/input/HapticFeedback.h @@ -0,0 +1,35 @@ +#pragma once + +#include "configuration.h" + +#ifdef HAPTIC_FEEDBACK_PIN + +#include "concurrency/OSThread.h" +#include + +// Non-blocking pulses on a GPIO vibration motor. HAPTIC_FEEDBACK_ACTIVE_LOW inverts polarity. +class HapticFeedback : public concurrency::OSThread +{ + public: + HapticFeedback(); + void pulse(uint16_t durationMs = 30); + void armDelayedPulse(uint16_t delayMs, uint16_t durationMs = 30); + void cancelDelayedPulse(); + + protected: + int32_t runOnce() override; + + private: + uint32_t pulseOffAt = 0; + uint32_t delayedPulseAt = 0; + uint16_t delayedPulseDuration = 0; + + void motorWrite(bool on); + // Reschedule to the soonest pending event so later arms don't clobber earlier wakes. + void scheduleNext(); +}; + +extern HapticFeedback *hapticFeedback; +void initHapticFeedback(); + +#endif // HAPTIC_FEEDBACK_PIN diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 3d18f69cf..39c8ba77e 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -2,6 +2,7 @@ #include "PowerFSM.h" // needed for event trigger #include "configuration.h" #include "graphics/Screen.h" +#include "input/HapticFeedback.h" #include "modules/ExternalNotificationModule.h" #ifdef MESHTASTIC_LOCKDOWN #include "security/LockdownDisplay.h" @@ -256,6 +257,16 @@ void InputBroker::Init() } touchBacklightActive = false; }; +#endif +#if defined(HAPTIC_FEEDBACK_PIN) + // Blip on touch, second blip when long-press fires (500 ms = touchConfig.longPressTime default). + touchConfig.suppressLeadUpSound = true; + initHapticFeedback(); + touchConfig.onPress = []() { + hapticFeedback->pulse(80); + hapticFeedback->armDelayedPulse(500, 80); + }; + touchConfig.onRelease = []() { hapticFeedback->cancelDelayedPulse(); }; #endif TouchButtonThread->initButton(touchConfig); #endif diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 0c04bbbab..3f14e6fa0 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -89,6 +89,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_CARD #elif defined(TTGO_T_ECHO_PLUS) #define HW_VENDOR meshtastic_HardwareModel_T_ECHO_PLUS +#elif defined(T_IMPULSE_PLUS) +#define HW_VENDOR meshtastic_HardwareModel_T_IMPULSE_PLUS #elif defined(ELECROW_ThinkNode_M1) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1 #elif defined(ELECROW_ThinkNode_M3) diff --git a/src/power/SGM41562.cpp b/src/power/SGM41562.cpp new file mode 100644 index 000000000..4f6acf406 --- /dev/null +++ b/src/power/SGM41562.cpp @@ -0,0 +1,109 @@ +#include "SGM41562.h" + +#ifdef HAS_SGM41562 + +#include + +SGM41562 *sgm41562 = nullptr; + +bool initSGM41562(TwoWire &wire) +{ + if (sgm41562) + return true; + sgm41562 = new SGM41562(); + if (!sgm41562->begin(wire)) { + delete sgm41562; + sgm41562 = nullptr; + return false; + } + return true; +} + +bool SGM41562::readReg(uint8_t reg, uint8_t &value) +{ + wire_->beginTransmission(address_); + wire_->write(reg); + if (wire_->endTransmission(false) != 0) + return false; + if (wire_->requestFrom((int)address_, 1) != 1) + return false; + value = wire_->read(); + return true; +} + +bool SGM41562::writeReg(uint8_t reg, uint8_t value) +{ + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->write(value); + return wire_->endTransmission() == 0; +} + +bool SGM41562::updateReg(uint8_t reg, uint8_t mask, uint8_t value) +{ + uint8_t cur; + if (!readReg(reg, cur)) + return false; + cur = (cur & ~mask) | (value & mask); + return writeReg(reg, cur); +} + +bool SGM41562::begin(TwoWire &wire, uint8_t address) +{ + wire_ = &wire; + address_ = address; + + uint8_t id; + if (!readReg(REG_DEVICE_ID, id)) { + LOG_WARN("SGM41562: I2C read failed at 0x%02X", address_); + return false; + } + if (id != DEVICE_ID_EXPECTED) { + LOG_WARN("SGM41562: unexpected device ID 0x%02X (expected 0x%02X)", id, DEVICE_ID_EXPECTED); + return false; + } + LOG_INFO("SGM41562: detected at 0x%02X (id 0x%02X)", address_, id); + + // Mirror the vendor reference init sequence: PCB OTP off, NTC off, + // watchdog off, charger enabled. These match LilyGo's stock firmware + // for the T-Impulse Plus. + delay(120); + writeReg(REG_SYS_VOLTAGE_REG, 0xB7); + writeReg(REG_MISC_OP_CONTROL, 0x40); + writeReg(REG_CHARGE_TERM_TIMER, 0x1A); + writeReg(REG_POWER_ON_CFG, 0xA4); + + return refresh(); +} + +bool SGM41562::refresh() +{ + uint32_t now = millis(); + if (lastRefreshMs_ != 0 && (now - lastRefreshMs_) < 250) + return true; // cached + lastRefreshMs_ = now == 0 ? 1 : now; + + uint8_t status, fault; + if (!readReg(REG_SYSTEM_STATUS, status)) + return false; + if (!readReg(REG_FAULT, fault)) + return false; + + chargeStatus_ = static_cast((status >> SYS_STATUS_CHRG_SHIFT) & SYS_STATUS_CHRG_MASK); + inputPowerGood_ = (status & SYS_STATUS_PG) != 0; + thermalReg_ = (status & SYS_STATUS_THERM_REG) != 0; + faultMask_ = fault & 0x3F; // bits [7:6] are enter_ship_time config, not faults + return true; +} + +bool SGM41562::setChargeEnable(bool enable) +{ + return updateReg(REG_POWER_ON_CFG, POWER_ON_CFG_CHG_DISABLE, enable ? 0x00 : POWER_ON_CFG_CHG_DISABLE); +} + +bool SGM41562::setShippingModeEnable(bool enable) +{ + return updateReg(REG_MISC_OP_CONTROL, MISC_OP_SHIPPING_MODE, enable ? MISC_OP_SHIPPING_MODE : 0x00); +} + +#endif // HAS_SGM41562 diff --git a/src/power/SGM41562.h b/src/power/SGM41562.h new file mode 100644 index 000000000..30836ff09 --- /dev/null +++ b/src/power/SGM41562.h @@ -0,0 +1,102 @@ +#pragma once + +#include "configuration.h" + +#ifdef HAS_SGM41562 + +#include +#include + +// SG Micro SGM41562 — single-cell Li-ion buck charger, I²C-controlled, no +// fuel gauge. This driver exposes status (charging / input good / fault), +// charge enable, and shipping-mode control. Battery voltage/percent still +// come from the platform ADC path; the charger is plumbed in as a +// side-channel for isCharging()/isVbusIn() in AnalogBatteryLevel. +// +// Reference: SGM41562 datasheet (Cmd map + bit fields cross-verified against +// LilyGo's `Cpp_Bus_Driver::Sgm41562xx` driver, which is what their vendor +// example for this board uses). + +#ifndef SGM41562_ADDR +#define SGM41562_ADDR 0x03 // Per datasheet — unusual but correct +#endif + +#ifndef SGM41562_WIRE +#define SGM41562_WIRE Wire1 // Most boards put the PMU on the secondary bus +#endif + +class SGM41562 +{ + public: + enum class ChargeStatus : uint8_t { + NotCharging = 0b00, + Precharge = 0b01, + FastCharge = 0b10, + ChargeDone = 0b11, + }; + + bool begin(TwoWire &wire, uint8_t address = SGM41562_ADDR); + + // Re-read the system status + fault registers. Throttled internally to + // at most one I²C transaction per 250 ms — call as often as you like. + bool refresh(); + + // Status — cached from the most recent refresh(). + ChargeStatus chargeStatus() const { return chargeStatus_; } + bool isCharging() const { return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; } + bool isChargeDone() const { return chargeStatus_ == ChargeStatus::ChargeDone; } + bool isInputPowerGood() const { return inputPowerGood_; } + bool isThermalRegulation() const { return thermalReg_; } + uint8_t faultMask() const { return faultMask_; } + + // Control. + bool setChargeEnable(bool enable); + bool setShippingModeEnable(bool enable); + + private: + TwoWire *wire_ = nullptr; + uint8_t address_ = SGM41562_ADDR; + uint32_t lastRefreshMs_ = 0; + + ChargeStatus chargeStatus_ = ChargeStatus::NotCharging; + bool inputPowerGood_ = false; + bool thermalReg_ = false; + uint8_t faultMask_ = 0; + + bool readReg(uint8_t reg, uint8_t &value); + bool writeReg(uint8_t reg, uint8_t value); + bool updateReg(uint8_t reg, uint8_t mask, uint8_t value); + + // SGM41562 register addresses + static constexpr uint8_t REG_INPUT_SOURCE = 0x00; + static constexpr uint8_t REG_POWER_ON_CFG = 0x01; + static constexpr uint8_t REG_CHARGE_CURRENT = 0x02; + static constexpr uint8_t REG_DISCHARGE_TERM_CURRENT = 0x03; + static constexpr uint8_t REG_CHARGE_VOLTAGE = 0x04; + static constexpr uint8_t REG_CHARGE_TERM_TIMER = 0x05; + static constexpr uint8_t REG_MISC_OP_CONTROL = 0x06; + static constexpr uint8_t REG_SYS_VOLTAGE_REG = 0x07; + static constexpr uint8_t REG_SYSTEM_STATUS = 0x08; + static constexpr uint8_t REG_FAULT = 0x09; + static constexpr uint8_t REG_I2C_ADDR_MISC = 0x0A; + static constexpr uint8_t REG_DEVICE_ID = 0x0B; + + // Bit positions in REG_POWER_ON_CFG. + static constexpr uint8_t POWER_ON_CFG_CHG_DISABLE = 0x08; // bit 3: 1 = charging disabled + // Bit positions in REG_MISC_OP_CONTROL. + static constexpr uint8_t MISC_OP_SHIPPING_MODE = 0x20; // bit 5: 1 = enter shipping mode + // Bit positions in REG_SYSTEM_STATUS. + static constexpr uint8_t SYS_STATUS_CHRG_SHIFT = 3; + static constexpr uint8_t SYS_STATUS_CHRG_MASK = 0x03; + static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good + static constexpr uint8_t SYS_STATUS_THERM_REG = 0x01; // bit 0: thermal regulation + + static constexpr uint8_t DEVICE_ID_EXPECTED = 0x04; +}; + +extern SGM41562 *sgm41562; + +// Lazy-instantiate the global on the supplied wire. Returns true on success. +bool initSGM41562(TwoWire &wire); + +#endif // HAS_SGM41562 diff --git a/variants/nrf52840/t-impulse-plus/platformio.ini b/variants/nrf52840/t-impulse-plus/platformio.ini new file mode 100644 index 000000000..337b586dc --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/platformio.ini @@ -0,0 +1,19 @@ +[env:t-impulse-plus] +custom_meshtastic_hw_model = 135 +custom_meshtastic_hw_model_slug = T_IMPULSE_PLUS +custom_meshtastic_architecture = nrf52840 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 1 +custom_meshtastic_display_name = LILYGO T-Impulse Plus +custom_meshtastic_tags = LilyGo +custom_meshtastic_requires_dfu = true + +extends = nrf52840_base +board = t-impulse-plus +board_level = pr + +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/t-impulse-plus + -D T_IMPULSE_PLUS + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-impulse-plus> diff --git a/variants/nrf52840/t-impulse-plus/variant.cpp b/variants/nrf52840/t-impulse-plus/variant.cpp new file mode 100644 index 000000000..c50a2527d --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/variant.cpp @@ -0,0 +1,91 @@ +/* + * variant.cpp - Digital pin mapping for LilyGo T-Impulse Plus + * + * Board: T-Impulse Plus V1.0 (nRF52840) + * Hardware: + * - SSD1315 OLED + * - SX1262 (S62F) + * - MIA-M10Q GPS + * - ICM20948 IMU + * - ZD25WQ32C Flash + * - TTP223 Touch Button + * - Vibration Motor + */ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +extern "C" { +const uint32_t g_ADigitalPinMap[] = { + // D0-D6: LoRa SX1262 (S62F module) SPI + control + 2, // D0 P0.02 SX1262_RST + 29, // D1 P0.29 SX1262_DIO1 + 31, // D2 P0.31 SX1262_BUSY + 46, // D3 P1.14 SX1262_CS + 3, // D4 P0.03 SPI_SCK + 30, // D5 P0.30 SPI_MISO + 28, // D6 P0.28 SPI_MOSI + + // D7-D8: RF switch control + 45, // D7 P1.13 SX1262_RF_VC1 (TXEN) + 39, // D8 P1.07 SX1262_RF_VC2 (RXEN) + + // D9-D11: GPS (u-blox MIA-M10Q) + 44, // D9 P1.12 GPS_TX (MCU TX -> GPS RX) + 43, // D10 P1.11 GPS_RX (MCU RX <- GPS TX) + 42, // D11 P1.10 GPS_EN + + // D12-D13: Display I2C (SSD1315) + 20, // D12 P0.20 SCREEN_SDA + 15, // D13 P0.15 SCREEN_SCL + + // D14-D15: Sensor I2C (ICM20948, SGM41562) + 40, // D14 P1.08 IMU_SDA + 11, // D15 P0.11 IMU_SCL + + // D16-D17: Battery management + 5, // D16 P0.05 BATTERY_ADC + 25, // D17 P0.25 BATTERY_MEASUREMENT_CONTROL + + // D18: Touch button (TTP223) + 36, // D18 P1.04 TTP223_KEY + + // D19: Vibration motor + 22, // D19 P0.22 VIBRATION_MOTOR + + // D20: LDO enable + 14, // D20 P0.14 RT9080_EN + + // D21-D26: Flash QSPI (ZD25WQ32C) + 12, // D21 P0.12 FLASH_CS + 4, // D22 P0.04 FLASH_SCLK + 6, // D23 P0.06 FLASH_IO0 + 41, // D24 P1.09 FLASH_IO1 + 8, // D25 P0.08 FLASH_IO2 + 26, // D26 P0.26 FLASH_IO3 + + // D27-D28: Interrupt lines + 7, // D27 P0.07 ICM20948_INT + 16, // D28 P0.16 SGM41562_INT + + // D29: Boot button + 24, // D29 P0.24 BOOT +}; +} + +void initVariant() +{ + // Flash CS high (deselect) + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + // Enable battery voltage measurement + pinMode(BAT_READ, OUTPUT); + digitalWrite(BAT_READ, HIGH); + + // Enable RT9080 LDO + pinMode(D20, OUTPUT); + digitalWrite(D20, HIGH); +} \ No newline at end of file diff --git a/variants/nrf52840/t-impulse-plus/variant.h b/variants/nrf52840/t-impulse-plus/variant.h new file mode 100644 index 000000000..ff3300956 --- /dev/null +++ b/variants/nrf52840/t-impulse-plus/variant.h @@ -0,0 +1,181 @@ +#ifndef _T_IMPULSE_PLUS_H_ +#define _T_IMPULSE_PLUS_H_ +#include "WVariant.h" + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Clock Configuration +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define VARIANT_MCK (64000000ul) +#define USE_LFXO + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Pin Capacity Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PINS_COUNT (30u) +#define NUM_DIGITAL_PINS (30u) +#define NUM_ANALOG_INPUTS (1u) +#define NUM_ANALOG_OUTPUTS (0u) + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Digital Pin Mapping +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define D0 0 // P0.02 SX1262_RST +#define D1 1 // P0.29 SX1262_DIO1 +#define D2 2 // P0.31 SX1262_BUSY +#define D3 3 // P1.14 SX1262_CS +#define D4 4 // P0.03 SPI_SCK +#define D5 5 // P0.30 SPI_MISO +#define D6 6 // P0.28 SPI_MOSI +#define D7 7 // P1.13 RF_VC1 (TXEN) +#define D8 8 // P1.07 RF_VC2 (RXEN) +#define D9 9 // P1.12 GPS module TX → MCU RX +#define D10 10 // P1.11 GPS module RX ← MCU TX +#define D11 11 // P1.10 GPS_EN (active LOW) +#define D12 12 // P0.20 SCREEN_SDA +#define D13 13 // P0.15 SCREEN_SCL +#define D14 14 // P1.08 IMU_SDA +#define D15 15 // P0.11 IMU_SCL +#define D16 16 // P0.05 BATTERY_ADC +#define D17 17 // P0.25 BATTERY_CTL +#define D18 18 // P1.04 TTP223_KEY +#define D19 19 // P0.22 VIBRATION_MOTOR +#define D20 20 // P0.14 RT9080_EN +#define D21 21 // P0.12 FLASH_CS +#define D22 22 // P0.04 FLASH_SCLK +#define D23 23 // P0.06 FLASH_IO0 +#define D24 24 // P1.09 FLASH_IO1 +#define D25 25 // P0.08 FLASH_IO2 +#define D26 26 // P0.26 FLASH_IO3 +#define D27 27 // P0.07 ICM20948_INT +#define D28 28 // P0.16 SGM41562_INT +#define D29 29 // P0.24 BOOT + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// LED Configuration (no physical LED) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define LED_STATE_ON 1 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Button Configuration (TTP223 capacitive touch) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_BUTTON_TOUCH D18 +#define BUTTON_TOUCH_ACTIVE_LOW true +#define BUTTON_TOUCH_ACTIVE_PULLUP false + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Analog Pin Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_VBAT D16 // P0.05 Battery voltage sense + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// I2C Configuration +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Primary I2C: Display (SSD1315) +#define PIN_WIRE_SDA D12 // P0.20 +#define PIN_WIRE_SCL D13 // P0.15 + +// Secondary I2C: IMU (ICM20948) + PMU (SGM41562) +#define WIRE_INTERFACES_COUNT 2 +#define PIN_WIRE1_SDA D14 // P1.08 +#define PIN_WIRE1_SCL D15 // P0.11 +#define I2C_NO_RESCAN + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Display (SSD1315, compatible with SSD1306 driver) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_SCREEN 1 +#define USE_SSD1306 1 +#define OLED_TINY +#define OLED_GEOMETRY_OVERRIDE GEOMETRY_64_32 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// SPI Configuration (SX1262 LoRa) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define SPI_INTERFACES_COUNT 1 +#define PIN_SPI_SCK D4 // P0.03 +#define PIN_SPI_MISO D5 // P0.30 +#define PIN_SPI_MOSI D6 // P0.28 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// SX1262 LoRa (S62F module) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define USE_SX1262 +#define SX126X_CS D3 +#define SX126X_DIO1 D1 +#define SX126X_BUSY D2 +#define SX126X_RESET D0 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_TXEN D7 // RF_VC1 +#define SX126X_RXEN D8 // RF_VC2 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Power Management +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define BAT_READ D17 // P0.25 Battery measurement control (HIGH = enable) +#define BATTERY_PIN PIN_VBAT +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define ADC_MULTIPLIER 2.0 +#define AREF_VOLTAGE 3.6 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// GPS (u-blox MIA-M10Q) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define GPS_UBLOX +#define HAS_GPS 1 +#define GPS_RX_PIN D9 // P1.12 — MCU RX, wired to GPS module TX +#define GPS_TX_PIN D10 // P1.11 — MCU TX, wired to GPS module RX +#define PIN_GPS_EN D11 // P1.10 +#define GPS_EN_ACTIVE LOW +#define GPS_BAUDRATE 38400 +#define GPS_THREAD_INTERVAL 50 +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// On-board QSPI Flash (ZD25WQ32CEIGR) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define PIN_QSPI_SCK D22 // P0.04 +#define PIN_QSPI_CS D21 // P0.12 +#define PIN_QSPI_IO0 D23 // P0.06 +#define PIN_QSPI_IO1 D24 // P1.09 +#define PIN_QSPI_IO2 D25 // P0.08 +#define PIN_QSPI_IO3 D26 // P0.26 + +#define EXTERNAL_FLASH_DEVICES W25Q32JV_IQ +#define EXTERNAL_FLASH_USE_QSPI + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Vibration Motor (GPIO active-high) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define LED_NOTIFICATION D19 // P0.22 +#define HAPTIC_FEEDBACK_PIN LED_NOTIFICATION + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// IMU (ICM20948 on Wire1) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_ICM20948 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Charger (SGM41562 on Wire1 @ 0x03) +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define HAS_SGM41562 +#define SGM41562_WIRE Wire1 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Compatibility Definitions +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#ifdef __cplusplus +extern "C" { +#endif + +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) + +#ifdef __cplusplus +} +#endif + +#endif // _T_IMPULSE_PLUS_H_ \ No newline at end of file