diff --git a/boards/t-beam-1w.json b/boards/t-beam-1w.json new file mode 100644 index 000000000..ac1d1f15e --- /dev/null +++ b/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": "t-beam-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" +} \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 650e1cc71..ec1b9acc2 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -444,6 +444,18 @@ along with this program. If not, see . #endif #endif +// BME680 BSEC2 support detection +#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) +#if defined(RAK_4631) || defined(TBEAM_V10) + +#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1 +#define MESHTASTIC_BME680_HEADER +#else +#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0 +#define MESHTASTIC_BME680_HEADER +#endif // defined(RAK_4631) +#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) + // ----------------------------------------------------------------------------- // Global switches to turn off features for a minimized build // ----------------------------------------------------------------------------- diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index e1f07a32b..2d7996a13 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -62,6 +62,11 @@ template bool SX126xInterface::init() digitalWrite(LORA_PA_TX_EN, LOW); #endif +#ifdef RF95_FAN_EN + digitalWrite(RF95_FAN_EN, HIGH); + pinMode(RF95_FAN_EN, OUTPUT); +#endif + #if ARCH_PORTDUINO tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) { @@ -85,6 +90,13 @@ template bool SX126xInterface::init() power = -9; int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO); + +#ifdef SX126X_PA_RAMP_US + // Set custom PA ramp time for boards requiring longer stabilization (e.g., T-Beam 1W needs >800us) + if (res == RADIOLIB_ERR_NONE) { + lora.setPaRampTime(SX126X_PA_RAMP_US); + } +#endif // \todo Display actual typename of the adapter, not just `SX126x` LOG_INFO("SX126x init result %d", res); if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 41062662b..843d7b8d5 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/LTR390UVSensor.h" #endif -#if __has_include() +#if __has_include(MESHTASTIC_BME680_HEADER) #include "Sensor/BME680Sensor.h" #endif @@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV); #endif -#if __has_include() +#if __has_include(MESHTASTIC_BME680_HEADER) addSensor(i2cScanner, ScanI2C::DeviceType::BME_680); #endif #if __has_include() diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 95f3dc5f0..22330ca75 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "BME680Sensor.h" @@ -10,6 +10,7 @@ BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 int32_t BME680Sensor::runOnce() { if (!bme680.run()) { @@ -17,10 +18,13 @@ int32_t BME680Sensor::runOnce() } return 35; } +#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED) bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) { status = 0; + +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 if (!bme680.begin(dev->address.address, *bus)) checkStatus("begin"); @@ -42,12 +46,25 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) if (status == 0) LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status); +#else + bme680 = makeBME680(bus); + + if (!bme680->begin(dev->address.address)) { + LOG_ERROR("Init sensor: %s failed at begin()", sensorName); + return status; + } + + status = 1; + +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED + initI2CSensor(); return status; } bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) { +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) return false; @@ -65,9 +82,27 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; updateState(); +#else + if (!bme680->performReading()) { + LOG_ERROR("BME680Sensor::getMetrics: performReading failed"); + return false; + } + + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.has_barometric_pressure = true; + measurement->variant.environment_metrics.has_gas_resistance = true; + + measurement->variant.environment_metrics.temperature = bme680->readTemperature(); + measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity(); + measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; + measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; + +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED return true; } +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 void BME680Sensor::loadState() { #ifdef FSCom @@ -144,5 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName) else if (bme680.sensor.status > BME68X_OK) LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED #endif diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index f4ead95f7..9bef56e1e 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -1,23 +1,40 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "TelemetrySensor.h" + +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 +#include #include +#else +#include +#include +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 const uint8_t bsec_config[] = { #include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" }; - +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED class BME680Sensor : public TelemetrySensor { private: +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 Bsec2 bme680; +#else + using BME680Ptr = std::unique_ptr; + + static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } + + BME680Ptr bme680; +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED protected: +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 const char *bsecConfigFileName = "/prefs/bsec.dat"; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t accuracy = 0; @@ -34,10 +51,13 @@ class BME680Sensor : public TelemetrySensor void loadState(); void updateState(); void checkStatus(const char *functionName); +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED public: BME680Sensor(); +#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1 virtual int32_t runOnce() override; +#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; }; diff --git a/variants/esp32s3/t-beam-1w/pins_arduino.h b/variants/esp32s3/t-beam-1w/pins_arduino.h new file mode 100644 index 000000000..92b74615d --- /dev/null +++ b/variants/esp32s3/t-beam-1w/pins_arduino.h @@ -0,0 +1,25 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +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 */ diff --git a/variants/esp32s3/t-beam-1w/platformio.ini b/variants/esp32s3/t-beam-1w/platformio.ini new file mode 100644 index 000000000..54ddb6c3e --- /dev/null +++ b/variants/esp32s3/t-beam-1w/platformio.ini @@ -0,0 +1,14 @@ +; LilyGo T-Beam-1W (1 Watt LoRa with external PA) +[env:t-beam-1w] +extends = esp32s3_base +board = t-beam-1w +board_build.partitions = default_8MB.csv +board_check = true + +lib_deps = + ${esp32s3_base.lib_deps} + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/t-beam-1w + -D T_BEAM_1W diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h new file mode 100644 index 000000000..01404afcb --- /dev/null +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -0,0 +1,97 @@ +// LilyGo T-Beam-1W variant.h +// Configuration based on LilyGO utilities.h and RF documentation + +// I2C for OLED display (SH1106 at 0x3C) +#define I2C_SDA 8 +#define I2C_SCL 9 + +// GPS - Quectel L76K +#define GPS_RX_PIN 5 +#define GPS_TX_PIN 6 +#define GPS_1PPS_PIN 7 +#define GPS_WAKEUP_PIN 16 // GPS_EN_PIN in LilyGO code +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 + +// Buttons +#define BUTTON_PIN 0 // BUTTON 1 +#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 + +// TX power offset for external PA (0 = no offset, full SX1262 power) +#define TX_GAIN_LORA 10 + +#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 2.9333 + +// NTC temperature sensor +#define NTC_PIN 14 + +// Fan control +#define FAN_CTRL_PIN 41 +// Meshtastic standard fan control pin macro +#define RF95_FAN_EN FAN_CTRL_PIN + +// 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 diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 76520091c..cc6c39aa2 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -34,6 +34,8 @@ lib_deps = adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip + # renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680 + adafruit/Adafruit BME680 Library@^2.0.5 build_flags = ${arduino_base.build_flags}