diff --git a/boards/t-beam-bpf.json b/boards/t-beam-bpf.json new file mode 100644 index 000000000..50afb7900 --- /dev/null +++ b/boards/t-beam-bpf.json @@ -0,0 +1,39 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DLILYGO_TBEAM_BPF", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-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": "t-beam-bpf" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino"], + "name": "LilyGo TBeam-BPF", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "http://www.lilygo.cn/", + "vendor": "LilyGo" +} diff --git a/src/Power.cpp b/src/Power.cpp index 3a2bea10c..350208dcd 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1401,6 +1401,22 @@ bool Power::axpChipInit() PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist PMU->disablePowerOutput(XPOWERS_VBACKUP); + } else if (HW_VENDOR == meshtastic_HardwareModel_TBEAM_BPF) { + // T-Beam BPF rail map (per schematic LilyGo_TBeam_BPF r2025-05-08): + // DCDC1 -> ESP32 + OLED 3V3 (always on, protected) + // ALDO2 -> MicroSD 3V3 (OFF at reset, must enable) + // ALDO4 -> L76K GNSS 3V3 (OFF at reset, must enable) + // ALDO1/3, BLDO1/2, DLDO1 -> user headers / unused at boot, leave at reset defaults. + // LoRa power is outside the PMU (external P-MOSFET switched by RF95_POWER_EN / IO16). + PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO4); + + PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); + PMU->enablePowerOutput(XPOWERS_ALDO2); + + // Make sure nothing's driving into an unused rail + PMU->disablePowerOutput(XPOWERS_DCDC5); + PMU->disablePowerOutput(XPOWERS_DLDO1); } // disable all axp chip interrupt diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index b65917dad..6625d4303 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -227,6 +227,16 @@ static void applyLoraRegion(meshtastic_Config_LoRaConfig_RegionCode region, bool void menuHandler::LoraRegionPicker(uint32_t duration) { +#ifdef HAS_HAM_2M_ONLY + // Hardware is restricted to the amateur 2m band - offer only the 2m regions + // so the user cannot pick a sub-GHz region the RF path cannot emit or receive. + static const LoraRegionOption regionOptions[] = { + {"Back", OptionsAction::Back}, + {"ITU1_2M (144-146)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M}, + {"ITU2_2M (144-148)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU2_2M}, + {"ITU3_2M (144-148)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M}, + }; +#else static const LoraRegionOption regionOptions[] = { {"Back", OptionsAction::Back}, {"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US}, @@ -265,6 +275,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) {"ITU3_70CM (430-450)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU3_70CM}, }; +#endif constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]); static std::array regionLabels{}; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index e9e8f7d34..9ee69636d 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1960,6 +1960,13 @@ void NodeDB::installDefaultDeviceState() memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); owner.has_is_unmessagable = true; owner.is_unmessagable = false; + +#ifdef HAS_HAM_2M_ONLY + // Ham-band-only hardware defaults to licensed operation. The user can still flip this off later + // (e.g. a commercial operator on an adjacent allocation who wants to keep encryption on) - we + // only set the default here, not on every boot. + owner.is_licensed = true; +#endif } // We reserve a few nodenums for future use diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 65549207d..cbd792878 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -113,6 +113,11 @@ void RF95Interface::setTransmitEnable(bool txon) /// \return true if initialisation succeeded. bool RF95Interface::init() { +#ifdef RF95_POWER_EN + pinMode(RF95_POWER_EN, OUTPUT); + digitalWrite(RF95_POWER_EN, HIGH); +#endif + RadioLibInterface::init(); #if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT) @@ -335,6 +340,10 @@ bool RF95Interface::sleep() setStandby(); // First cancel any active receiving/sending lora->sleep(); +#ifdef RF95_POWER_EN + digitalWrite(RF95_POWER_EN, LOW); +#endif + #ifdef RF95_FAN_EN digitalWrite(RF95_FAN_EN, 0); #endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index e58a9b520..4cc4461cf 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -198,6 +198,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #elif defined(T_BEAM_1W) #define HW_VENDOR meshtastic_HardwareModel_TBEAM_1_WATT +#elif defined(T_BEAM_BPF) +#define HW_VENDOR meshtastic_HardwareModel_TBEAM_BPF #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER #elif defined(HELTEC_V4) diff --git a/variants/esp32s3/t-beam-bpf/pins_arduino.h b/variants/esp32s3/t-beam-bpf/pins_arduino.h new file mode 100644 index 000000000..195ef2896 --- /dev/null +++ b/variants/esp32s3/t-beam-bpf/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// UART1 (qwiic) +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +// I2C for OLED and sensors +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; + +// Default SPI mapped to Radio/SD +static const uint8_t SS = 1; // LoRa CS +static const uint8_t MOSI = 11; +static const uint8_t MISO = 13; +static const uint8_t SCK = 12; + +// SD Card CS +#define SDCARD_CS 10 + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/t-beam-bpf/platformio.ini b/variants/esp32s3/t-beam-bpf/platformio.ini new file mode 100644 index 000000000..ef12d0ba8 --- /dev/null +++ b/variants/esp32s3/t-beam-bpf/platformio.ini @@ -0,0 +1,23 @@ +; LilyGo T-Beam-BPF (144-148Mhz) ~5 Watt (37 dBm) +[env:t-beam-bpf] +custom_meshtastic_hw_model = 124 +custom_meshtastic_hw_model_slug = TBEAM_BPF +custom_meshtastic_architecture = esp32s3 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 3 +custom_meshtastic_display_name = LILYGO T-Beam BPF +custom_meshtastic_images = tbeam-1w.svg +custom_meshtastic_tags = LilyGo + +extends = esp32s3_base +board = t-beam-bpf +board_build.partitions = default_16MB.csv +board_check = true + +lib_deps = + ${esp32s3_base.lib_deps} + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/t-beam-bpf + -D T_BEAM_BPF diff --git a/variants/esp32s3/t-beam-bpf/variant.h b/variants/esp32s3/t-beam-bpf/variant.h new file mode 100644 index 000000000..d4f316ae8 --- /dev/null +++ b/variants/esp32s3/t-beam-bpf/variant.h @@ -0,0 +1,70 @@ +// LilyGo T-Beam-BPF variant.h +// Configuration based on LilyGO utilities.h and RF documentation + +// Hardware is restricted to the amateur 2m band (144-148 MHz). +#define HAS_HAM_2M_ONLY 1 + +// I2C for OLED display (SH1106 at 0x3C) +#define I2C_SDA 8 +#define I2C_SCL 9 + +// GPS - Quectel L76K. Per schematic sheet 7: + +#define GPS_RX_PIN 5 +#define GPS_TX_PIN 6 +#define GPS_1PPS_PIN 7 +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 + +// Buttons +#define BUTTON_PIN 0 // BUTTON 1 +#define ALT_BUTTON_PIN 3 // BUTTON 2 + +// SPI (shared by LoRa and SD) +#define SPI_MOSI 11 +#define SPI_SCK 12 +#define SPI_MISO 13 +#define SPI_CS 10 + +// SD Card +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +// #define SDCARD_CS SPI_CS (already defined in pins_arduino.h) + +// LoRa Radio - SX1278 144-148Mhz +#define USE_RF95 +#define LORA_SCK SPI_SCK +#define LORA_MISO SPI_MISO +#define LORA_MOSI SPI_MOSI +#define LORA_CS 1 +#define LORA_RESET 18 +#define LORA_IRQ 14 +#define LORA_DIO0 LORA_IRQ +#define LORA_DIO1 21 +#define RF95_RXEN 39 // LNA enable - HIGH during RX + +// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()! +// GPIO 16 powers the SX1278 via LDO +#define RF95_POWER_EN 16 + +// +27dBm PA +// LilyGo Docs specify SX1278 power must be capped at 10 +#define RF95_MAX_POWER 10 +// PA curve mapping (TX values 1-10) +// Measurements taken with 5V USB power (max 37 dBm on battery power) +// Input values: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +// Output values: 0, 34, 35, 36, 36, 37, 37, 38, 38, 38 +#define NUM_PA_POINTS 10 +#define TX_GAIN_LORA 0, 32, 32, 32, 31, 31, 30, 30, 29, 28 + +// Display - SH1106 OLED (128x64) +#define USE_SH1106 +#define OLED_WIDTH 128 +#define OLED_HEIGHT 64 + +// 32768 Hz crystal present +#define HAS_32768HZ 1 + +// PMU +#define HAS_AXP2101 +// #define PMU_IRQ 4 // Leave disabled for now