Browse Source

Add LilyGO T-Beam 1W Support

Steven Linn 6 months ago
parent
commit
f7e54ea797

+ 50 - 0
boards/t_beam_1w.json

@@ -0,0 +1,50 @@
+{
+    "build": {
+        "arduino": {
+            "ldscript": "esp32s3_out.ld",
+            "memory_type": "qio_opi"
+        },
+        "core": "esp32",
+        "extra_flags": [
+            "-DBOARD_HAS_PSRAM",
+            "-DLILYGO_TBEAM_1W",
+            "-DARDUINO_USB_CDC_ON_BOOT=1",
+            "-DARDUINO_USB_MODE=0",
+            "-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": "lilygo_tbeam_1w"
+    },
+    "connectivity": [
+        "wifi",
+        "bluetooth",
+        "lora"
+    ],
+    "debug": {
+        "openocd_target": "esp32s3.cfg"
+    },
+    "frameworks": [
+        "arduino"
+    ],
+    "name": "LilyGo TBeam-1W",
+    "upload": {
+        "flash_size": "16MB",
+        "maximum_ram_size": 327680,
+        "maximum_size": 16777216,
+        "require_upload_port": true,
+        "speed": 921600
+    },
+    "url": "http://www.lilygo.cn/",
+    "vendor": "LilyGo"
+}

+ 71 - 0
variants/lilygo_tbeam_1w/TBeam1WBoard.cpp

@@ -0,0 +1,71 @@
+#include "TBeam1WBoard.h"
+
+void TBeam1WBoard::begin() {
+  ESP32Board::begin();
+
+  // Power on radio module (must be done before radio init)
+  pinMode(SX126X_POWER_EN, OUTPUT);
+  digitalWrite(SX126X_POWER_EN, HIGH);
+  radio_powered = true;
+  delay(10);  // Allow radio to power up
+
+  // RF switch RXEN pin handled by RadioLib via setRfSwitchPins()
+
+  // Initialize LED
+  pinMode(LED_PIN, OUTPUT);
+  digitalWrite(LED_PIN, LOW);
+
+  // Initialize fan control (on by default - 1W PA can overheat)
+  pinMode(FAN_CTRL_PIN, OUTPUT);
+  digitalWrite(FAN_CTRL_PIN, HIGH);
+}
+
+void TBeam1WBoard::onBeforeTransmit() {
+  // RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins()
+  digitalWrite(LED_PIN, HIGH);  // TX LED on
+}
+
+void TBeam1WBoard::onAfterTransmit() {
+  digitalWrite(LED_PIN, LOW);   // TX LED off
+}
+
+uint16_t TBeam1WBoard::getBattMilliVolts() {
+  // T-Beam 1W uses 7.4V battery with voltage divider
+  // ADC reads through divider - adjust multiplier based on actual divider ratio
+  analogReadResolution(12);
+  uint32_t raw = 0;
+  for (int i = 0; i < 8; i++) {
+    raw += analogRead(BATTERY_PIN);
+  }
+  raw = raw / 8;
+  // Assuming voltage divider ratio from ADC_MULTIPLIER
+  // 3.3V reference, 12-bit ADC (4095 max)
+  return static_cast<uint16_t>((raw * 3300 * ADC_MULTIPLIER) / 4095);
+}
+
+const char* TBeam1WBoard::getManufacturerName() const {
+  return "LilyGo T-Beam 1W";
+}
+
+void TBeam1WBoard::powerOff() {
+  // Turn off radio LNA (CTRL pin must be LOW when not receiving)
+  digitalWrite(SX126X_RXEN, LOW);
+
+  // Turn off radio power
+  digitalWrite(SX126X_POWER_EN, LOW);
+  radio_powered = false;
+
+  // Turn off LED and fan
+  digitalWrite(LED_PIN, LOW);
+  digitalWrite(FAN_CTRL_PIN, LOW);
+
+  ESP32Board::powerOff();
+}
+
+void TBeam1WBoard::setFanEnabled(bool enabled) {
+  digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW);
+}
+
+bool TBeam1WBoard::isFanEnabled() const {
+  return digitalRead(FAN_CTRL_PIN) == HIGH;
+}

+ 45 - 0
variants/lilygo_tbeam_1w/TBeam1WBoard.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include <Arduino.h>
+#include <helpers/ESP32Board.h>
+#include "variant.h"
+
+// LilyGo T-Beam 1W with SX1262 + external PA (XY16P35 module)
+//
+// Power architecture (LDO is separate chip on T-Beam board, not inside XY16P35):
+//
+//   VCC (+4.0~+8.0V) ──┬──────────────────► XY16P35 VCC pin 5 (PA direct)
+//   (USB or Battery)   │
+//                      │   ┌───────────┐
+//                      └──►│ LDO Chip  │──► +3.3V ──► XY16P35 (SX1262 + LNA)
+//                          │ EN=GPIO40 │
+//                          └───────────┘
+//                      LDO_EN (GPIO 40): H @ +1.2V~VIN, active high, not floating
+//
+// Control signals:
+//   - LDO_EN (GPIO 40): HIGH enables LDO → powers SX1262 + LNA
+//   - TCXO_EN (DIO3):   HIGH enables TCXO (set to 1.8V per Meshtastic)
+//   - CTL (GPIO 21):    HIGH=RX (LNA on), LOW=TX (LNA off)
+//   - DIO2:             AUTO via SX126X_DIO2_AS_RF_SWITCH (TX path)
+//
+// Power notes:
+//   - PA needs VCC 4.0-8.0V for full 32dBm output
+//   - USB-C (3.9-6V) marginal; 7.4V battery recommended
+//   - Battery must support 2A+ discharge for high-power TX
+
+class TBeam1WBoard : public ESP32Board {
+private:
+  bool radio_powered = false;
+
+public:
+  void begin();
+  void onBeforeTransmit() override;
+  void onAfterTransmit() override;
+  uint16_t getBattMilliVolts() override;
+  const char* getManufacturerName() const override;
+  void powerOff() override;
+
+  // Fan control methods
+  void setFanEnabled(bool enabled);
+  bool isFanEnabled() const;
+};

+ 26 - 0
variants/lilygo_tbeam_1w/pins_arduino.h

@@ -0,0 +1,26 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include <stdint.h>
+
+#define USB_VID 0x303a
+#define USB_PID 0x1001
+
+// Serial (USB CDC)
+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 = 15;   // LoRa CS
+static const uint8_t MOSI = 11;
+static const uint8_t MISO = 12;
+static const uint8_t SCK = 13;
+
+// SD Card CS
+#define SDCARD_CS 10
+
+#endif /* Pins_Arduino_h */

+ 189 - 0
variants/lilygo_tbeam_1w/platformio.ini

@@ -0,0 +1,189 @@
+[LilyGo_TBeam_1W]
+extends = esp32_base
+board = t_beam_1w
+build_flags =
+  ${esp32_base.build_flags}
+  -I variants/lilygo_tbeam_1w
+  -D TBEAM_1W
+
+  ; Radio - SX1262 with high-power PA (32dBm max output)
+  ; Note: Set SX1262 output to 22dBm max, external PA provides additional gain
+  -D RADIO_CLASS=CustomSX1262
+  -D WRAPPER_CLASS=CustomSX1262Wrapper
+  -D P_LORA_DIO_1=1
+  -D P_LORA_NSS=15
+  -D P_LORA_RESET=3
+  -D P_LORA_BUSY=38
+  -D P_LORA_SCLK=13
+  -D P_LORA_MISO=12
+  -D P_LORA_MOSI=11
+
+  ; RF switch configuration:
+  ;   DIO2 controls TX path (PA enable) via SX126X_DIO2_AS_RF_SWITCH
+  ;   GPIO21 controls RX path (LNA enable) via SX126X_RXEN
+  ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX
+  -D SX126X_DIO2_AS_RF_SWITCH=true
+  -D SX126X_RXEN=21
+  -D SX126X_DIO3_TCXO_VOLTAGE=1.8
+  -D SX126X_CURRENT_LIMIT=140
+  -D SX126X_RX_BOOSTED_GAIN=1
+
+  ; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total
+  -D LORA_TX_POWER=22
+
+  ; Display - SH1106 OLED at 0x3C
+  -D DISPLAY_CLASS=SH1106Display
+
+  ; I2C pins
+  -D PIN_BOARD_SDA=8
+  -D PIN_BOARD_SCL=9
+
+  ; GPS - L76K module
+  ; GNSS_TXD (IO5) = GPS transmits → MCU RX
+  ; GNSS_RXD (IO6) = GPS receives → MCU TX
+  -D PIN_GPS_TX=5
+  -D PIN_GPS_RX=6
+  -D PIN_GPS_EN=16
+  -D ENV_INCLUDE_GPS=1
+
+  ; User interface
+  -D PIN_USER_BTN=17
+
+build_src_filter = ${esp32_base.build_src_filter}
+  +<../variants/lilygo_tbeam_1w>
+  +<helpers/ui/SH1106Display.cpp>
+  +<helpers/ui/MomentaryButton.cpp>
+  +<helpers/sensors>
+
+lib_deps =
+  ${esp32_base.lib_deps}
+  adafruit/Adafruit SH110X @ ~2.1.13
+  stevemarple/MicroNMEA @ ~2.0.6
+
+; === LILYGO T-Beam 1W Repeater ===
+[env:LilyGo_TBeam_1W_repeater]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.build_flags}
+  -D ADVERT_NAME='"T-Beam 1W Repeater"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=50
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<../examples/simple_repeater>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  ${esp32_ota.lib_deps}
+
+; === LILYGO T-Beam 1W Room Server ===
+[env:LilyGo_TBeam_1W_room_server]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.build_flags}
+  -D ADVERT_NAME='"T-Beam 1W Room"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D ROOM_PASSWORD='"hello"'
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<../examples/simple_room_server>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  ${esp32_ota.lib_deps}
+
+; === LILYGO T-Beam 1W Companion Radio (USB) ===
+[env:LilyGo_TBeam_1W_companion_radio_usb]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.build_flags}
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=350
+  -D MAX_GROUP_CHANNELS=40
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+; === LILYGO T-Beam 1W Companion Radio (BLE) ===
+[env:LilyGo_TBeam_1W_companion_radio_ble]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.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
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D BLE_DEBUG_LOGGING=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<helpers/esp32/*.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+; === LILYGO T-Beam 1W Companion Radio (WiFi) ===
+[env:LilyGo_TBeam_1W_companion_radio_wifi]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.build_flags}
+  -I examples/companion_radio/ui-new
+  -D MAX_CONTACTS=350
+  -D MAX_GROUP_CHANNELS=40
+  -D WIFI_DEBUG_LOGGING=1
+  -D WIFI_SSID='"myssid"'
+  -D WIFI_PWD='"mypwd"'
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<helpers/esp32/*.cpp>
+  +<../examples/companion_radio/*.cpp>
+  +<../examples/companion_radio/ui-new/*.cpp>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  densaugeo/base64 @ ~1.4.0
+
+; === LILYGO T-Beam 1W Repeater with ESPNow Bridge ===
+[env:LilyGo_TBeam_1W_repeater_bridge_espnow]
+extends = LilyGo_TBeam_1W
+build_flags =
+  ${LilyGo_TBeam_1W.build_flags}
+  -D ADVERT_NAME='"T-Beam 1W ESPNow Bridge"'
+  -D ADVERT_LAT=0.0
+  -D ADVERT_LON=0.0
+  -D ADMIN_PASSWORD='"password"'
+  -D MAX_NEIGHBOURS=50
+  -D WITH_ESPNOW_BRIDGE=1
+  -D PERSISTANT_GPS=1
+  -D ENV_SKIP_GPS_DETECT=1
+;  -D BRIDGE_DEBUG=1
+;  -D MESH_PACKET_LOGGING=1
+;  -D MESH_DEBUG=1
+build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+  +<helpers/bridges/ESPNowBridge.cpp>
+  +<../examples/simple_repeater>
+lib_deps =
+  ${LilyGo_TBeam_1W.lib_deps}
+  ${esp32_ota.lib_deps}

+ 64 - 0
variants/lilygo_tbeam_1w/target.cpp

@@ -0,0 +1,64 @@
+#include <Arduino.h>
+#include "target.h"
+
+TBeam1WBoard board;
+
+#ifdef DISPLAY_CLASS
+  DISPLAY_CLASS display;
+  MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
+#endif
+
+static SPIClass spi;
+
+RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
+
+WRAPPER_CLASS radio_driver(radio, board);
+
+ESP32RTCClock fallback_clock;
+AutoDiscoverRTCClock rtc_clock(fallback_clock);
+
+#if ENV_INCLUDE_GPS
+  #include <helpers/sensors/MicroNMEALocationProvider.h>
+  MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
+  EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
+#else
+  EnvironmentSensorManager sensors;
+#endif
+
+bool radio_init() {
+  fallback_clock.begin();
+  rtc_clock.begin(Wire);
+
+  // Initialize SPI for radio
+  spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
+
+  // GPS serial initialized by EnvironmentSensorManager::begin()
+
+  bool success = radio.std_init(&spi);
+  if (success) {
+    // T-Beam 1W has external PA requiring longer ramp time (>800us recommended)
+    // RADIOLIB_SX126X_PA_RAMP_800U = 0x05
+    radio.setTxParams(LORA_TX_POWER, RADIOLIB_SX126X_PA_RAMP_800U);
+  }
+  return success;
+}
+
+uint32_t radio_get_rng_seed() {
+  return radio.random(0x7FFFFFFF);
+}
+
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
+  radio.setFrequency(freq);
+  radio.setSpreadingFactor(sf);
+  radio.setBandwidth(bw);
+  radio.setCodingRate(cr);
+}
+
+void radio_set_tx_power(uint8_t dbm) {
+  radio.setOutputPower(dbm);
+}
+
+mesh::LocalIdentity radio_new_identity() {
+  RadioNoiseListener rng(radio);
+  return mesh::LocalIdentity(&rng);
+}

+ 27 - 0
variants/lilygo_tbeam_1w/target.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#define RADIOLIB_STATIC_ONLY 1
+#include <RadioLib.h>
+#include <helpers/radiolib/RadioLibWrappers.h>
+#include <helpers/radiolib/CustomSX1262Wrapper.h>
+#include <helpers/AutoDiscoverRTCClock.h>
+#include <helpers/sensors/EnvironmentSensorManager.h>
+#include "TBeam1WBoard.h"
+
+#ifdef DISPLAY_CLASS
+  #include <helpers/ui/SH1106Display.h>
+  #include <helpers/ui/MomentaryButton.h>
+  extern DISPLAY_CLASS display;
+  extern MomentaryButton user_btn;
+#endif
+
+extern TBeam1WBoard board;
+extern WRAPPER_CLASS radio_driver;
+extern AutoDiscoverRTCClock rtc_clock;
+extern EnvironmentSensorManager sensors;
+
+bool radio_init();
+uint32_t radio_get_rng_seed();
+void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
+void radio_set_tx_power(uint8_t dbm);
+mesh::LocalIdentity radio_new_identity();

+ 96 - 0
variants/lilygo_tbeam_1w/variant.h

@@ -0,0 +1,96 @@
+// LilyGo T-Beam-1W variant.h
+// Configuration based on Meshtastic PR #8967 and LilyGO documentation
+
+#pragma once
+
+// I2C for OLED display (SH1106 at 0x3C)
+#define I2C_SDA 8
+#define I2C_SCL 9
+
+// GPS - Quectel L76K
+// GNSS_TXD (IO5) = GPS transmits → MCU RX (setPins rxPin)
+// GNSS_RXD (IO6) = GPS receives → MCU TX (setPins txPin)
+#define PIN_GPS_TX 5       // MCU receives from GPS TX
+#define PIN_GPS_RX 6       // MCU transmits to GPS RX
+#define PIN_GPS_PPS 7      // GPS PPS output
+#define PIN_GPS_EN 16      // GPS wake-up/enable (GPS_EN_PIN in LilyGO code)
+#define HAS_GPS 1
+#define GPS_BAUDRATE 9600
+
+// Buttons
+#define BUTTON_PIN 0         // BUTTON 1 (boot)
+#define BUTTON_PIN_ALT 17    // BUTTON 2
+
+// SPI (shared by LoRa and SD)
+#define SPI_MOSI 11
+#define SPI_SCK 13
+#define SPI_MISO 12
+#define SPI_CS 10
+
+// SD Card
+#define HAS_SDCARD
+#define SDCARD_USE_SPI1
+#define SDCARD_CS SPI_CS
+
+// LoRa Radio - SX1262 with 1W PA
+#define USE_SX1262
+
+#define LORA_SCK SPI_SCK
+#define LORA_MISO SPI_MISO
+#define LORA_MOSI SPI_MOSI
+#define LORA_CS 15
+#define LORA_RESET 3
+#define LORA_DIO1 1
+#define LORA_BUSY 38
+
+// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()!
+// GPIO 40 powers the SX1262 + PA module via LDO
+#define SX126X_POWER_EN 40
+
+#ifdef USE_SX1262
+#define SX126X_CS LORA_CS
+#define SX126X_DIO1 LORA_DIO1
+#define SX126X_BUSY LORA_BUSY
+#define SX126X_RESET LORA_RESET
+
+// RF switching configuration for 1W PA module
+// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH)
+// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX
+// Truth table: DIO2=1,CTRL=0 -> TX (PA on, LNA off)
+//              DIO2=0,CTRL=1 -> RX (PA off, LNA on)
+#define SX126X_DIO2_AS_RF_SWITCH
+#define SX126X_RXEN 21  // LNA enable - HIGH during RX
+
+// TCXO voltage - required for radio init
+#define SX126X_DIO3_TCXO_VOLTAGE 1.8
+
+#define SX126X_MAX_POWER 22
+#endif
+
+// LED
+#define LED_PIN 18
+#define LED_STATE_ON 1  // HIGH = ON
+
+// Battery ADC
+#define BATTERY_PIN 4
+#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
+#define BATTERY_SENSE_SAMPLES 30
+#define ADC_MULTIPLIER 3.0
+
+// NTC temperature sensor
+#define NTC_PIN 14
+
+// Fan control
+#define FAN_CTRL_PIN 41
+
+// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
+// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
+#define SX126X_PA_RAMP_US 0x05
+
+// Display - SH1106 OLED (128x64)
+#define USE_SH1106
+#define OLED_WIDTH 128
+#define OLED_HEIGHT 64
+
+// 32768 Hz crystal present
+#define HAS_32768HZ 1