fix(wio-tracker-l2): stop the I2S clock between sounds; GPS and TCXO fixes

The speaker's I2S channel was only disabled when idle, which leaves MCLK
running. Its fractional divider (160 MHz / 39 1/16 = 4.096 MHz) makes a
comb of lines every 256 kHz: one at 869.632 MHz, in the EU narrow mesh
channel, lifted the LoRa noise floor from ~-102 to ~-85 dBm, and others
land near GPS L1 and BeiDou B1. The channel is now deleted and its pins
held low after each sound, and made again for the next one. Measured:
noise floor -102 dBm, outdoor first fix 87 s (was 620 s and more).

Also: TCXO at 3.0 V (the L2's part, as Meshtastic has it); GLONASS
enabled on the L76K after each power-up; begin() on a running receiver
no longer resets it (a reset threw away the acquisition in progress).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-27 13:54:25 +02:00
co-authored by Claude Opus 5.5
parent 002f478231
commit b50ecc83d5
4 changed files with 39 additions and 5 deletions
+13 -2
View File
@@ -426,11 +426,22 @@ static bool i2sInstall() {
}
static void i2sUninstall() { i2s_channel_disable(s_tx); i2s_del_channel(s_tx); s_tx = nullptr; }
static void i2sWrite(const void* src, size_t n, uint32_t ms) {
if (!s_tx) return;
size_t w;
i2s_channel_write(s_tx, src, n, &w, ms);
}
static void i2sStop() { i2s_channel_disable(s_tx); } // clocks off
static void i2sRestart() { i2s_channel_enable(s_tx); } // auto-clear left the buffers silent
// Clocks off between sounds: a disabled channel still drives MCLK, and its
// fractional divider (160 MHz / 39 1/16) makes a comb of lines every 256 kHz
// right up the LoRa band -- 869.632 MHz sat in the EU narrow channel, ~15 dB
// over the Wio Tracker L2's noise floor. So the channel goes, the pins held
// low, and a sound makes it again.
static void i2sStop() {
if (!s_tx) return;
i2sUninstall();
const int pins[] = { PIN_I2S_MCLK, PIN_I2S_BCK, PIN_I2S_WS, PIN_I2S_DOUT };
for (int p : pins) { gpio_reset_pin((gpio_num_t)p); pinMode(p, OUTPUT); digitalWrite(p, LOW); }
}
static void i2sRestart() { if (!s_tx) i2sInstall(); }
#else
static const i2s_port_t I2S_PORT = I2S_NUM_0;
static bool i2sInstall() {
+1 -1
View File
@@ -29,7 +29,7 @@ build_flags = ${esp32_base.build_flags}
-D P_LORA_MISO=5
-D P_LORA_MOSI=6
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_DIO3_TCXO_VOLTAGE=3.0 ; the L2 TCXO is a 3 V part (Meshtastic PR #11960)
-D SX126X_CURRENT_LIMIT=140
-D SX126X_RX_BOOSTED_GAIN=1
-D ENV_INCLUDE_GPS=1
+18 -1
View File
@@ -23,6 +23,7 @@ void buzzerAmpPower(bool on) { board.setSpeakerAmp(on); }
#endif
void L2GpsProvider::begin() {
if (_started && board.gnssPowered() && !_uart_off) return; // already running
board.setGnssPower(true); // with the power-up reset
if (_uart_off) { // back from stop(): the UART again
Serial1.setRxBufferSize(1024);
@@ -30,6 +31,20 @@ void L2GpsProvider::begin() {
_uart_off = false;
}
MicroNMEALocationProvider::begin();
_started = true;
_cfg_pending = true;
_cfg_rx = rxChars();
}
// The L76K starts with GPS + BeiDou only; GLONASS as well gives it a third
// more satellites. Sent once it has booted (NMEA arriving), again after each
// power-up (not saved in the module).
void L2GpsProvider::loop() {
MicroNMEALocationProvider::loop();
if (_cfg_pending && rxChars() - _cfg_rx > 200) {
_cfg_pending = false;
sendSentence("$PCAS04,7"); // GPS + BeiDou + GLONASS
}
}
void L2GpsProvider::stop() {
@@ -41,7 +56,9 @@ void L2GpsProvider::stop() {
board.setGnssPower(false);
}
void L2GpsProvider::reset() { board.gnssReset(); }
// The sensor manager resets right after every begin(); begin() already did
// the power-up one, and a second would restart a running receiver.
void L2GpsProvider::reset() { }
bool L2GpsProvider::isEnabled() { return board.gnssPowered(); }
bool radio_init() {
+7 -1
View File
@@ -16,15 +16,21 @@
// The L76K's power and reset lines are on the IO expander, out of the NMEA
// provider's reach: begin() / stop() / reset() drive them here, so GPS off
// (Settings, duty cycling) really powers the receiver down.
// (Settings, duty cycling) really powers the receiver down. begin() on a
// running receiver does nothing: a reset there (the app setting "gps" again)
// threw away the acquisition in progress.
class L2GpsProvider : public MicroNMEALocationProvider {
bool _uart_off = false;
bool _started = false; // begin() ran (the boot one does the reset)
bool _cfg_pending = false; // constellations to set once the module talks
uint32_t _cfg_rx = 0;
public:
using MicroNMEALocationProvider::MicroNMEALocationProvider;
void begin() override;
void stop() override;
void reset() override;
bool isEnabled() override;
void loop() override;
};
extern WioTrackerL2Board board;