mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-22 15:23:23 +00:00
fix: port T-Beam 1W hardware corrections
Adapt the validated fan thermostat, battery ADC calibration, GPS baud, SX1262 register patch, and 1700us PA ramp behavior from mintylinux/Meshcore-T-beam-1W-Firmware to the current board architecture. Also make Arduino 3 full-build dependency switching reliable after standard ESP32 builds and guard the release hardware contract with regression coverage.
This commit is contained in:
1 parent
74002db4e5
commit
4f13bbc1c1
13 files changed
+218
-26
No files matched your search
@@ -25,6 +25,8 @@ venv/
|
||||
# Script-generated cert bundles (see scripts/generate_cert_bundle.py)
|
||||
src/certs/x509_crt_bundle.bin
|
||||
src/helpers/esp32/WebConfigHtml.h
|
||||
# Generated by pioarduino's component-manager preflight for Arduino 3 builds.
|
||||
src/idf_component.yml
|
||||
ssl_certs/cacert.pem
|
||||
platformio.local.ini
|
||||
.cursor/*
|
||||
|
||||
@@ -2432,15 +2432,24 @@ requires_esp32_arduino3_framework() {
|
||||
prepare_esp32_arduino3_framework() {
|
||||
local env_name=$1
|
||||
local pio_env_name=$2
|
||||
local arduino3_core_url="https://github.com/espressif/arduino-esp32/releases/download/3.3.11/esp32-core-3.3.11.tar.xz"
|
||||
local arduino3_libs_url="https://github.com/espressif/arduino-esp32/releases/download/3.3.11/esp32-core-3.3.11-libs.tar.xz"
|
||||
local pio_core_dir="${PLATFORMIO_CORE_DIR:-${HOME}/.platformio}"
|
||||
local arduino_manifest="${pio_core_dir}/packages/framework-arduinoespressif32/package.json"
|
||||
|
||||
requires_esp32_arduino3_framework "$env_name" "$pio_env_name" || return 0
|
||||
|
||||
# PlatformIO's standard ESP32 platform and pioarduino publish incompatible
|
||||
# Arduino 2.x/3.x cores under the same global package name. `pio run` restores
|
||||
# the standard core automatically, but pioarduino can otherwise proceed with
|
||||
# no matching framework after a standard build. Enforce this environment's
|
||||
# explicit platform_packages pins before SCons starts.
|
||||
# Arduino 2.x/3.x cores under the same global package name. PlatformIO may
|
||||
# list both versions side by side but pioarduino only resolves the canonical
|
||||
# package directory. Replace just that conflicting framework when necessary;
|
||||
# do not force-reinstall the platform and all of its toolchains.
|
||||
echo "Ensuring Arduino-ESP32 3.3.11 dependencies for ${env_name}..."
|
||||
if ! grep -Eq '"version"[[:space:]]*:[[:space:]]*"3\.3\.11"' "$arduino_manifest" 2>/dev/null; then
|
||||
pio pkg uninstall --global --tool framework-arduinoespressif32 --no-save || true
|
||||
pio pkg install --global --tool "$arduino3_core_url"
|
||||
fi
|
||||
pio pkg install --global --tool "$arduino3_libs_url"
|
||||
pio pkg install -e "$pio_env_name"
|
||||
}
|
||||
|
||||
|
||||
@@ -1613,6 +1613,9 @@ void loop() {
|
||||
#endif
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#ifdef TBEAM_1W
|
||||
board.updateFanControl();
|
||||
#endif
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
@@ -157,6 +157,9 @@ void loop() {
|
||||
// GPS acquisition, cache refresh, and delayed shutdown are driven by the
|
||||
// sensor manager loop even though KISS itself is normally host powered.
|
||||
sensors.loop();
|
||||
#ifdef TBEAM_1W
|
||||
board.updateFanControl();
|
||||
#endif
|
||||
|
||||
if (!modem->isActuallyTransmitting() && !modem->isHostOutputBackedUp()) {
|
||||
if (!modem->isTxBusy()) {
|
||||
|
||||
@@ -321,6 +321,10 @@ void loop() {
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
#ifdef TBEAM_1W
|
||||
board.updateFanControl();
|
||||
#endif
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
@@ -192,6 +192,9 @@ void loop() {
|
||||
if (display_ready) ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#ifdef TBEAM_1W
|
||||
board.updateFanControl();
|
||||
#endif
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,18 @@ public:
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
protected:
|
||||
int16_t applyCachedTxPower(int8_t dbm) override {
|
||||
#ifdef SX126X_PA_RAMP_TIME
|
||||
// External-PA boards such as the T-Beam 1W require a slower PA ramp. Use
|
||||
// the same ramp for runtime power changes and post-reset restoration;
|
||||
// SX1262::setOutputPower() would otherwise restore RadioLib's default.
|
||||
return ((CustomSX1262 *)_radio)->setTxParams(
|
||||
dbm, static_cast<uint8_t>(SX126X_PA_RAMP_TIME));
|
||||
#else
|
||||
return RadioLibWrapper::applyCachedTxPower(dbm);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
bool success = ((CustomSX1262 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
|
||||
&& ((CustomSX1262 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
|
||||
|
||||
@@ -11,6 +11,7 @@ from __future__ import annotations
|
||||
|
||||
import io
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import struct
|
||||
@@ -417,6 +418,11 @@ def test_esp32_s3_full_profiles_inherit_dio_boot_mode():
|
||||
"prepare_esp32_arduino3_framework()", 1
|
||||
)[1].split("requires_esp32_companion_full_ota_fallback()", 1)[0]
|
||||
assert "requires_esp32_arduino3_framework" in framework_preflight
|
||||
assert 'framework-arduinoespressif32/package.json' in framework_preflight
|
||||
assert '"3\\.3\\.11"' in framework_preflight
|
||||
assert "pio pkg uninstall --global --tool framework-arduinoespressif32" in framework_preflight
|
||||
assert 'pio pkg install --global --tool "$arduino3_core_url"' in framework_preflight
|
||||
assert 'pio pkg install --global --tool "$arduino3_libs_url"' in framework_preflight
|
||||
assert 'pio pkg install -e "$pio_env_name"' in framework_preflight
|
||||
build_call = build.split(
|
||||
'print_build_flags "$pio_env_name" "$env_name"', 1
|
||||
@@ -459,6 +465,73 @@ def test_esp32_s3_full_profiles_inherit_dio_boot_mode():
|
||||
assert profile_count == 17
|
||||
|
||||
|
||||
def test_tbeam_1w_release_hardware_fixes_are_preserved():
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
variant_root = root / "variants/lilygo_tbeam_1w"
|
||||
profile = (variant_root / "platformio.ini").read_text(encoding="utf-8")
|
||||
variant = (variant_root / "variant.h").read_text(encoding="utf-8")
|
||||
pins = (variant_root / "pins_arduino.h").read_text(encoding="utf-8")
|
||||
target = (variant_root / "target.cpp").read_text(encoding="utf-8")
|
||||
board_impl = (variant_root / "TBeam1WBoard.cpp").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
wrapper = (
|
||||
root / "src/helpers/radiolib/CustomSX1262Wrapper.h"
|
||||
).read_text(encoding="utf-8")
|
||||
board_manifest = json.loads(
|
||||
(root / "boards/t_beam_1w.json").read_text(encoding="utf-8")
|
||||
)
|
||||
|
||||
# Hardware fixes advertised by the T-Beam 1W v1.17.1 release. Keep the
|
||||
# build flag and header fallback aligned so every role uses 1700 us.
|
||||
for flag in (
|
||||
"-D USE_SX1262",
|
||||
"-D SX126X_REGISTER_PATCH=1",
|
||||
"-D SX126X_PA_RAMP_TIME=0x06",
|
||||
"-D SX126X_CURRENT_LIMIT=140",
|
||||
"-D SX126X_RX_BOOSTED_GAIN=1",
|
||||
"-D MAX_LORA_TX_POWER=22",
|
||||
"-D BATT_MIN_MILLIVOLTS=6000",
|
||||
"-D BATT_MAX_MILLIVOLTS=8400",
|
||||
"-D GPS_BAUD_RATE=9600",
|
||||
):
|
||||
assert flag in profile
|
||||
assert "#define SX126X_PA_RAMP_TIME 0x06" in variant
|
||||
assert "RADIOLIB_SX126X_PA_RAMP_1700U" in variant
|
||||
|
||||
# This branch has a working board-specific Arduino variant. Protect its
|
||||
# correct default SPI pins instead of regressing to generic ESP32-S3 pins.
|
||||
assert board_manifest["build"]["variant"] == "lilygo_tbeam_1w"
|
||||
assert (
|
||||
board_manifest["build"]["arduino"]["partitions"]
|
||||
== "default_16MB.csv"
|
||||
)
|
||||
assert "static const uint8_t MISO = 12;" in pins
|
||||
assert "static const uint8_t SCK = 13;" in pins
|
||||
|
||||
# The extended ramp must survive boot, CLI power changes, and recovery.
|
||||
assert "radio.setTxParams(" in target
|
||||
assert "SX126X_PA_RAMP_TIME" in target
|
||||
assert "applyCachedTxPower(int8_t dbm) override" in wrapper
|
||||
assert "SX126X_PA_RAMP_TIME" in wrapper
|
||||
|
||||
# Preserve the hardware-tested battery divider and fan thermostat.
|
||||
assert "#define BATTERY_PIN 4" in variant
|
||||
assert "#define BATTERY_SENSE_SAMPLES 30" in variant
|
||||
assert "#define ADC_MULTIPLIER 2.9333f" in variant
|
||||
assert "#define FAN_TEMP_ON_C 45.0f" in board_impl
|
||||
assert "#define FAN_TEMP_OFF_C 41.0f" in board_impl
|
||||
assert "#define FAN_MIN_RUN_TIME_MS 5000UL" in board_impl
|
||||
for role_main in (
|
||||
"examples/companion_radio/main.cpp",
|
||||
"examples/simple_repeater/main.cpp",
|
||||
"examples/simple_room_server/main.cpp",
|
||||
"examples/kiss_modem/main.cpp",
|
||||
):
|
||||
source = (root / role_main).read_text(encoding="utf-8")
|
||||
assert "board.updateFanControl();" in source
|
||||
|
||||
|
||||
def test_release_catalog_resolves_canonical_runtime_aliases():
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
provider_path = root / "mesh-america/update-provider-release.py"
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
#ifndef FAN_TEMP_ON_C
|
||||
#define FAN_TEMP_ON_C 45.0f
|
||||
#endif
|
||||
#ifndef FAN_TEMP_OFF_C
|
||||
#define FAN_TEMP_OFF_C 41.0f
|
||||
#endif
|
||||
#ifndef FAN_MIN_RUN_TIME_MS
|
||||
#define FAN_MIN_RUN_TIME_MS 5000UL
|
||||
#endif
|
||||
#ifndef FAN_TEMP_POLL_INTERVAL_MS
|
||||
#define FAN_TEMP_POLL_INTERVAL_MS 1000UL
|
||||
#endif
|
||||
|
||||
void TBeam1WBoard::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
@@ -36,9 +49,15 @@ void TBeam1WBoard::begin() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// Initialize fan control (on by default - 1W PA can overheat)
|
||||
// Start with the fan off. updateFanControl() turns it on at the validated
|
||||
// temperature threshold and applies hysteresis, avoiding the former
|
||||
// always-on battery drain while still failing safe on an invalid reading.
|
||||
pinMode(FAN_CTRL_PIN, OUTPUT);
|
||||
digitalWrite(FAN_CTRL_PIN, HIGH);
|
||||
fan_running = false;
|
||||
fan_temperature_valid = false;
|
||||
fan_started_at_ms = 0;
|
||||
fan_last_poll_ms = 0;
|
||||
digitalWrite(FAN_CTRL_PIN, LOW);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::powerCycleRadio() {
|
||||
@@ -55,25 +74,34 @@ void TBeam1WBoard::powerCycleRadio() {
|
||||
|
||||
void TBeam1WBoard::onBeforeTransmit() {
|
||||
// RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins()
|
||||
updateFanControl();
|
||||
digitalWrite(LED_PIN, HIGH); // TX LED on
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onAfterTransmit() {
|
||||
digitalWrite(LED_PIN, LOW); // TX LED off
|
||||
updateFanControl();
|
||||
}
|
||||
|
||||
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);
|
||||
// T-Beam 1W uses a 2S battery through the GPIO4 divider. These settings
|
||||
// match the hardware-tested fork and the Meshtastic board definition.
|
||||
static bool adc_initialized = false;
|
||||
if (!adc_initialized) {
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
analogReadResolution(12);
|
||||
analogSetPinAttenuation(BATTERY_PIN, ADC_11db);
|
||||
adc_initialized = true;
|
||||
}
|
||||
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);
|
||||
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||||
raw += analogRead(BATTERY_PIN);
|
||||
delayMicroseconds(100);
|
||||
}
|
||||
raw /= BATTERY_SENSE_SAMPLES;
|
||||
return static_cast<uint16_t>(
|
||||
(static_cast<float>(raw) / 4095.0f) * 3300.0f * ADC_MULTIPLIER);
|
||||
}
|
||||
|
||||
const char* TBeam1WBoard::getManufacturerName() const {
|
||||
@@ -90,7 +118,7 @@ void TBeam1WBoard::powerOff() {
|
||||
|
||||
// Turn off LED and fan
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
digitalWrite(FAN_CTRL_PIN, LOW);
|
||||
setFanEnabled(false);
|
||||
|
||||
ESP32Board::powerOff();
|
||||
}
|
||||
@@ -137,9 +165,39 @@ bool TBeam1WBoard::isLoRaFemLnaEnabled() const {
|
||||
}
|
||||
|
||||
void TBeam1WBoard::setFanEnabled(bool enabled) {
|
||||
if (fan_running == enabled) return;
|
||||
fan_running = enabled;
|
||||
if (enabled) fan_started_at_ms = millis();
|
||||
digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW);
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::isFanEnabled() const {
|
||||
return digitalRead(FAN_CTRL_PIN) == HIGH;
|
||||
return fan_running;
|
||||
}
|
||||
|
||||
void TBeam1WBoard::updateFanControl() {
|
||||
const uint32_t now = millis();
|
||||
if (!fan_temperature_valid
|
||||
|| static_cast<uint32_t>(now - fan_last_poll_ms)
|
||||
>= FAN_TEMP_POLL_INTERVAL_MS) {
|
||||
const float temperature_c = getMCUTemperature();
|
||||
fan_last_poll_ms = now;
|
||||
if (isnan(temperature_c)) {
|
||||
// Cooling is safer than silently leaving the 1 W PA unprotected if the
|
||||
// ESP32 temperature sensor ever reports an invalid sample.
|
||||
setFanEnabled(true);
|
||||
return;
|
||||
}
|
||||
fan_last_temperature_c = temperature_c;
|
||||
fan_temperature_valid = true;
|
||||
}
|
||||
|
||||
if (fan_running) {
|
||||
if (static_cast<uint32_t>(now - fan_started_at_ms) >= FAN_MIN_RUN_TIME_MS
|
||||
&& fan_last_temperature_c < FAN_TEMP_OFF_C) {
|
||||
setFanEnabled(false);
|
||||
}
|
||||
} else if (fan_last_temperature_c >= FAN_TEMP_ON_C) {
|
||||
setFanEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@ private:
|
||||
bool lna_enabled = true;
|
||||
bool lna_driver_synced = false;
|
||||
CustomSX1262Wrapper* radio_driver = nullptr;
|
||||
bool fan_running = false;
|
||||
bool fan_temperature_valid = false;
|
||||
uint32_t fan_started_at_ms = 0;
|
||||
uint32_t fan_last_poll_ms = 0;
|
||||
float fan_last_temperature_c = 0.0f;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
@@ -55,7 +60,10 @@ public:
|
||||
bool canControlLoRaFemLna() const override;
|
||||
bool isLoRaFemLnaEnabled() const override;
|
||||
|
||||
// Fan control methods
|
||||
// Temperature-gated cooling for the external PA. Every T-Beam 1W role
|
||||
// services this from its main loop; the TX hooks also run it at the points
|
||||
// where the PA can begin heating.
|
||||
void updateFanControl();
|
||||
void setFanEnabled(bool enabled);
|
||||
bool isFanEnabled() const;
|
||||
};
|
||||
@@ -6,6 +6,7 @@ build_flags =
|
||||
-I variants/lilygo_tbeam_1w
|
||||
-D TBEAM_1W
|
||||
-D RECOVERABLE_EXTERNAL_RADIO
|
||||
-D USE_SX1262
|
||||
|
||||
; Radio - SX1262 with high-power PA (32dBm max output)
|
||||
; Note: Set SX1262 output to 22dBm max, external PA provides additional gain
|
||||
@@ -28,9 +29,15 @@ build_flags =
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=3.0
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D SX126X_REGISTER_PATCH=1
|
||||
; LilyGo specifies a PA stabilization time greater than 800us. 0x06 is
|
||||
; RadioLib's 1700us SX126x ramp setting.
|
||||
-D SX126X_PA_RAMP_TIME=0x06
|
||||
|
||||
; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_LORA_TX_POWER=22
|
||||
-D MIN_LORA_TX_POWER=-9
|
||||
|
||||
; Battery - 2S 7.4V LiPo (6.0V min, 8.4V max)
|
||||
-D BATT_MIN_MILLIVOLTS=6000
|
||||
@@ -49,6 +56,7 @@ build_flags =
|
||||
-D PIN_GPS_TX=5
|
||||
-D PIN_GPS_RX=6
|
||||
-D PIN_GPS_EN=16
|
||||
-D GPS_BAUD_RATE=9600
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
|
||||
; User interface
|
||||
|
||||
@@ -53,12 +53,19 @@ bool radio_init() {
|
||||
// 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)
|
||||
const int16_t ramp_status = radio.setTxParams(
|
||||
LORA_TX_POWER, static_cast<uint8_t>(SX126X_PA_RAMP_TIME));
|
||||
if (ramp_status != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("T-Beam 1W PA ramp configuration failed: %d",
|
||||
(int)ramp_status);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
target_radio_available = success;
|
||||
if (success) {
|
||||
board.attachRadioDriver(&radio_driver);
|
||||
// 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);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN(
|
||||
"T-Beam 1W radio pins: LDO=%d NSS=%d RST=%d BUSY=%d MISO=%d DIO1=%d CTRL=%d",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#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
|
||||
#define GPS_BAUD_RATE 9600
|
||||
|
||||
// Buttons
|
||||
#define BUTTON_PIN 0 // BUTTON 1 (boot)
|
||||
@@ -75,7 +75,7 @@
|
||||
#define BATTERY_PIN 4
|
||||
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#define ADC_MULTIPLIER 3.0
|
||||
#define ADC_MULTIPLIER 2.9333f
|
||||
|
||||
// NTC temperature sensor
|
||||
#define NTC_PIN 14
|
||||
@@ -84,8 +84,10 @@
|
||||
#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
|
||||
// Value 0x06 = RADIOLIB_SX126X_PA_RAMP_1700U
|
||||
#ifndef SX126X_PA_RAMP_TIME
|
||||
#define SX126X_PA_RAMP_TIME 0x06
|
||||
#endif
|
||||
|
||||
// Display - SH1106 OLED (128x64)
|
||||
#define USE_SH1106
|
||||
|
||||
Reference in new issue
Block a user