fix(wio-tracker-l2): radios and codec idle when unused; buzzer and OTA fixes

- BLE (ESP32): fast advertising (20-30 ms) for 30 s, then 152.5 ms, as the
  nRF52 interface; the library default kept 30-60 ms forever
- Live map tiles: the WiFi drops after a minute with nothing missing
- ES8311 to standby while the I2S clocks are off, set up again before the
  amp comes on
- Buzzer: no channel (DMA out of memory) waits out the samples instead of
  spinning above the UI loop
- OTA: the image header check waits for its 16 bytes
- Comments: P10 is just the LED; the LCD SPI clock is 40 MHz on the wire

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-27 14:13:39 +02:00
co-authored by Claude Opus 5.5
parent 782f8aa567
commit 958627d6e9
10 changed files with 84 additions and 31 deletions
+1 -1
View File
@@ -110,7 +110,7 @@ static void install(HTTPClient& http, NetworkClientSecure& tls) {
if (!ok) snprintf(s_msg, sizeof(s_msg), "Out of memory"); if (!ok) snprintf(s_msg, sizeof(s_msg), "Out of memory");
while (ok && s_written < (uint32_t)len) { while (ok && s_written < (uint32_t)len) {
size_t avail = in->available(); size_t avail = in->available();
if (!avail) { if (!avail || (s_written == 0 && avail < 16)) { // the header check below wants its 16 bytes in one read
if (!in->connected() || millis() - last_data > 20000) { snprintf(s_msg, sizeof(s_msg), "Download stalled"); ok = false; break; } if (!in->connected() || millis() - last_data > 20000) { snprintf(s_msg, sizeof(s_msg), "Download stalled"); ok = false; break; }
vTaskDelay(pdMS_TO_TICKS(5)); vTaskDelay(pdMS_TO_TICKS(5));
continue; continue;
@@ -24,8 +24,9 @@
// //
// Live tiles: while the map is open, single tiles it is missing are fetched // Live tiles: while the map is open, single tiles it is missing are fetched
// the same way (one at a time, between the job's steps when there is no job), // the same way (one at a time, between the job's steps when there is no job),
// the WiFi connected on the first miss and dropped by the caller (liveEnd) // the WiFi connected on the first miss and dropped after a minute with
// once the map has been closed for a while. They go to LIVE_ROOT, not // nothing missing, or by the caller (liveEnd) once the map has been closed
// for a while. They go to LIVE_ROOT, not
// <root>, and only up to the Storage limit (LiveCache.h). // <root>, and only up to the Storage limit (LiveCache.h).
// //
// Single-TU fragment: included by ui-lvgl/UITask.cpp only (after LvglPort.h). // Single-TU fragment: included by ui-lvgl/UITask.cpp only (after LvglPort.h).
@@ -288,6 +289,7 @@ private:
struct LiveTile { int16_t z = -1; uint8_t layer = 0; int32_t x = 0, y = 0; }; // layer: 0 base, 1 trails struct LiveTile { int16_t z = -1; uint8_t layer = 0; int32_t x = 0, y = 0; }; // layer: 0 base, 1 trails
enum LiveState : uint8_t { LV_IDLE, LV_CONNECTING, LV_UP, LV_BACKOFF }; enum LiveState : uint8_t { LV_IDLE, LV_CONNECTING, LV_UP, LV_BACKOFF };
static const int LV_QUEUE = 8, LV_FAILED = 16, LV_DONE = 8; static const int LV_QUEUE = 8, LV_FAILED = 16, LV_DONE = 8;
static const uint32_t LV_IDLE_MS = 60000;
bool _live = false; bool _live = false;
LiveState _lv_state = LV_IDLE; LiveState _lv_state = LV_IDLE;
char _lv_ssid[33] = "", _lv_pass[65] = ""; char _lv_ssid[33] = "", _lv_pass[65] = "";
@@ -295,7 +297,7 @@ private:
int _lv_n = 0, _lv_failed_next = 0, _lv_done_n = 0; int _lv_n = 0, _lv_failed_next = 0, _lv_done_n = 0;
bool _lv_fetching = false; bool _lv_fetching = false;
uint8_t _lv_consec_fail = 0; uint8_t _lv_consec_fail = 0;
uint32_t _lv_since = 0; // connect start / back-off end uint32_t _lv_since = 0; // connect start / back-off end / last activity while up
void liveFailed(const LiveTile& t) { void liveFailed(const LiveTile& t) {
_lv_failed[_lv_failed_next++ % LV_FAILED] = t; _lv_failed[_lv_failed_next++ % LV_FAILED] = t;
@@ -320,7 +322,7 @@ private:
return; return;
case LV_CONNECTING: { case LV_CONNECTING: {
int ns = lvport::netState(); int ns = lvport::netState();
if (ns == lvport::NET_UP) { _lv_state = LV_UP; return; } if (ns == lvport::NET_UP) { _lv_state = LV_UP; _lv_since = millis(); return; }
if (ns == lvport::NET_FAILED || millis() - _lv_since > 20000) { if (ns == lvport::NET_FAILED || millis() - _lv_since > 20000) {
lvport::netEnd(); lvport::netEnd();
_lv_state = LV_BACKOFF; _lv_state = LV_BACKOFF;
@@ -362,8 +364,11 @@ private:
liveFailed(_lv_cur); liveFailed(_lv_cur);
} }
lvport::fetchRelease(); lvport::fetchRelease();
_lv_since = millis();
return; return;
} }
// Nothing missing for a minute: the WiFi goes (the next miss reconnects).
if (!_lv_n && millis() - _lv_since > LV_IDLE_MS) { lvport::netEnd(); _lv_state = LV_IDLE; return; }
if (!_lv_n || millis() - _last_start < 120) return; // be gentle with the server if (!_lv_n || millis() - _last_start < 120) return; // be gentle with the server
_lv_cur = _lv_q[0]; _lv_cur = _lv_q[0];
for (int i = 1; i < _lv_n; i++) _lv_q[i - 1] = _lv_q[i]; for (int i = 1; i < _lv_n; i++) _lv_q[i - 1] = _lv_q[i];
+26 -13
View File
@@ -10,6 +10,14 @@
#define ADVERT_RESTART_DELAY 1000 // millis #define ADVERT_RESTART_DELAY 1000 // millis
// As the nRF52 interface: 20-30 ms for the first 30 s (the app finds it at
// once), then 152.5 ms. Left alone, the library advertises every 30-60 ms
// for as long as nothing is connected. Units of 0.625 ms.
#define ADV_FAST_MIN 32
#define ADV_FAST_MAX 48
#define ADV_SLOW 244
#define ADV_FAST_MS 30000
void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
_pin_code = pin_code; _pin_code = pin_code;
@@ -186,15 +194,19 @@ void SerialBLEInterface::enable() {
// Start the service // Start the service
pService->start(); pService->start();
// Start advertising startAdvertising(true);
//pServer->getAdvertising()->setMinInterval(500);
//pServer->getAdvertising()->setMaxInterval(1000);
pServer->getAdvertising()->start();
adv_restart_time = 0; adv_restart_time = 0;
} }
void SerialBLEInterface::startAdvertising(bool fast) {
BLEAdvertising* adv = pServer->getAdvertising();
adv->stop(); // the interval only takes on a (re)start
adv->setMinInterval(fast ? ADV_FAST_MIN : ADV_SLOW);
adv->setMaxInterval(fast ? ADV_FAST_MAX : ADV_SLOW);
adv->start();
adv_slow_time = fast ? (millis() + ADV_FAST_MS) | 1 : 0;
}
void SerialBLEInterface::disable() { void SerialBLEInterface::disable() {
_isEnabled = false; _isEnabled = false;
@@ -204,7 +216,7 @@ void SerialBLEInterface::disable() {
pServer->disconnect(last_conn_id); pServer->disconnect(last_conn_id);
pService->stop(); pService->stop();
oldDeviceConnected = deviceConnected = false; oldDeviceConnected = deviceConnected = false;
adv_restart_time = 0; adv_restart_time = adv_slow_time = 0;
} }
size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
@@ -262,10 +274,6 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
clearBuffers(); clearBuffers();
BLE_DEBUG_PRINTLN("SerialBLEInterface -> disconnecting..."); BLE_DEBUG_PRINTLN("SerialBLEInterface -> disconnecting...");
//pServer->getAdvertising()->setMinInterval(500);
//pServer->getAdvertising()->setMaxInterval(1000);
adv_restart_time = millis() + ADVERT_RESTART_DELAY; adv_restart_time = millis() + ADVERT_RESTART_DELAY;
} else { } else {
BLE_DEBUG_PRINTLN("SerialBLEInterface -> stopping advertising"); BLE_DEBUG_PRINTLN("SerialBLEInterface -> stopping advertising");
@@ -273,7 +281,7 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
// connecting // connecting
// do stuff here on connecting // do stuff here on connecting
pServer->getAdvertising()->stop(); pServer->getAdvertising()->stop();
adv_restart_time = 0; adv_restart_time = adv_slow_time = 0;
} }
oldDeviceConnected = deviceConnected; oldDeviceConnected = deviceConnected;
} }
@@ -281,10 +289,15 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
if (adv_restart_time && millis() >= adv_restart_time) { if (adv_restart_time && millis() >= adv_restart_time) {
if (pServer->getConnectedCount() == 0) { if (pServer->getConnectedCount() == 0) {
BLE_DEBUG_PRINTLN("SerialBLEInterface -> re-starting advertising"); BLE_DEBUG_PRINTLN("SerialBLEInterface -> re-starting advertising");
pServer->getAdvertising()->start(); // re-Start advertising startAdvertising(true); // re-Start advertising
} }
adv_restart_time = 0; adv_restart_time = 0;
} }
if (adv_slow_time && (long)(millis() - adv_slow_time) >= 0) {
adv_slow_time = 0;
if (_isEnabled && pServer->getConnectedCount() == 0) startAdvertising(false);
}
return 0; return 0;
} }
+3
View File
@@ -19,6 +19,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
uint32_t _pin_code; uint32_t _pin_code;
unsigned long _last_write; unsigned long _last_write;
unsigned long adv_restart_time; unsigned long adv_restart_time;
unsigned long adv_slow_time; // when fast advertising drops to the slow interval (0 = not pending)
struct Frame { struct Frame {
uint8_t len; uint8_t len;
@@ -33,6 +34,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
Frame send_queue[FRAME_QUEUE_SIZE]; Frame send_queue[FRAME_QUEUE_SIZE];
void clearBuffers(); void clearBuffers();
void startAdvertising(bool fast);
protected: protected:
// BLESecurityCallbacks methods // BLESecurityCallbacks methods
@@ -72,6 +74,7 @@ public:
deviceConnected = false; deviceConnected = false;
oldDeviceConnected = false; oldDeviceConnected = false;
adv_restart_time = 0; adv_restart_time = 0;
adv_slow_time = 0;
_isEnabled = false; _isEnabled = false;
_last_write = 0; _last_write = 0;
last_conn_id = 0; last_conn_id = 0;
+15
View File
@@ -42,4 +42,19 @@ static bool begin(TwoWire& w, uint8_t addr) {
return true; return true;
} }
// Everything but the I2C interface off (esp-adf's es8311 suspend): DAC, ADC,
// references and the output driver. begin() brings it back, with MCLK running.
static bool standby(TwoWire& w, uint8_t addr) {
static const uint8_t SEQ[][2] = {
{ 0x32, 0x00 }, { 0x17, 0x00 }, { 0x0E, 0xFF }, { 0x12, 0x02 }, { 0x14, 0x00 },
{ 0x0D, 0xFA }, { 0x15, 0x00 }, { 0x37, 0x08 }, { 0x02, 0x10 },
{ 0x00, 0x00 }, { 0x00, 0x1F }, // state machine reset
{ 0x01, 0x30 }, { 0x01, 0x00 }, // clocks off
{ 0x45, 0x00 }, { 0x0D, 0xFC }, { 0x02, 0x00 },
};
for (size_t i = 0; i < sizeof(SEQ) / sizeof(SEQ[0]); i++)
if (!write(w, addr, SEQ[i][0], SEQ[i][1])) return false;
return true;
}
} // namespace es8311 } // namespace es8311
+24 -9
View File
@@ -426,7 +426,9 @@ static bool i2sInstall() {
} }
static void i2sUninstall() { i2s_channel_disable(s_tx); i2s_del_channel(s_tx); s_tx = nullptr; } 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) { static void i2sWrite(const void* src, size_t n, uint32_t ms) {
if (!s_tx) return; // No channel (a restart found no DMA memory): wait as long as the samples
// would play, or the task, above the UI loop, spins until the clocks stop.
if (!s_tx) { vTaskDelay(pdMS_TO_TICKS(n / (SAMPLE_RATE / 250)) + 1); return; }
size_t w; size_t w;
i2s_channel_write(s_tx, src, n, &w, ms); i2s_channel_write(s_tx, src, n, &w, ms);
} }
@@ -489,8 +491,8 @@ bool genericBuzzer::_i2sBegin() {
i2sUninstall(); i2sUninstall();
return false; return false;
} }
_clk_on_ms = millis(); _clk_on_ms = _codec_on_ms = millis();
_clk_running = true; _clk_running = _codec_on = true;
delay(CLK_SETTLE_MS); // settled before the startup sound powers the amp delay(CLK_SETTLE_MS); // settled before the startup sound powers the amp
// Above the UI loop and LVGL (priority 1), so rendering never starves it. // Above the UI loop and LVGL (priority 1), so rendering never starves it.
_i2s_ok = xTaskCreate(_taskEntry, "buzzer", 3072, this, 6, (TaskHandle_t*)&_task) == pdPASS; _i2s_ok = xTaskCreate(_taskEntry, "buzzer", 3072, this, 6, (TaskHandle_t*)&_task) == pdPASS;
@@ -610,12 +612,13 @@ void genericBuzzer::_start(const char* melody) {
if (_amp_on) { // a replay within the settle time still waits out the rest if (_amp_on) { // a replay within the settle time still waits out the rest
uint32_t since = millis() - s_amp_on_ms; uint32_t since = millis() - s_amp_on_ms;
settle = since < AUDIO_AMP_SETTLE_MS ? (uint16_t)(AUDIO_AMP_SETTLE_MS - since) : 0; settle = since < AUDIO_AMP_SETTLE_MS ? (uint16_t)(AUDIO_AMP_SETTLE_MS - since) : 0;
} else if (_clk_running && millis() - _clk_on_ms >= CLK_SETTLE_MS) { } else if (_clk_running && _codec_on && millis() - _clk_on_ms >= CLK_SETTLE_MS &&
millis() - _codec_on_ms >= CLK_SETTLE_MS) {
_amp_on = power_now = true; // claimed here, so the task keeps the clocks _amp_on = power_now = true; // claimed here, so the task keeps the clocks
settle = AUDIO_AMP_SETTLE_MS; settle = AUDIO_AMP_SETTLE_MS;
} else { } else {
_amp_pending = true; // loop() powers it once the clocks have settled _amp_pending = true; // loop() powers it once the clocks (and the codec) have settled
settle = CLK_SETTLE_MS + AUDIO_AMP_SETTLE_MS; settle = (_codec_on ? 1 : 2) * CLK_SETTLE_MS + AUDIO_AMP_SETTLE_MS;
} }
strncpy(_mel, melody, MEL_MAX - 1); strncpy(_mel, melody, MEL_MAX - 1);
_mel[MEL_MAX - 1] = 0; _mel[MEL_MAX - 1] = 0;
@@ -650,15 +653,27 @@ void genericBuzzer::stop() {
xTaskNotifyGive((TaskHandle_t)_task); xTaskNotifyGive((TaskHandle_t)_task);
} }
// The amp: on once the codec's clocks have settled (a pending start), off // The codec: to standby once the task has stopped the clocks (its analog side
// once nothing has played for AMP_LINGER_MS. // kept drawing with nothing to play), set up again once they run. The amp: on
// once the codec has settled (a pending start), off once nothing has played
// for AMP_LINGER_MS. Both off in between, so neither step pops.
void genericBuzzer::loop() { void genericBuzzer::loop() {
if (!_i2s_ok) return; if (!_i2s_ok) return;
if (_codec_on && !_clk_running && !_amp_on && !_amp_pending) {
es8311::standby(Wire, BUZZER_CODEC_ES8311);
_codec_on = false;
}
if (_amp_pending && !_codec_on && _task_playing && _clk_running && millis() - _clk_on_ms >= CLK_SETTLE_MS) {
_codec_on = es8311::begin(Wire, BUZZER_CODEC_ES8311); // a failed one is tried again next pass
_codec_on_ms = millis();
}
if (_amp_pending) { if (_amp_pending) {
bool power = false; bool power = false;
portENTER_CRITICAL(&s_mux); portENTER_CRITICAL(&s_mux);
if (!_task_playing) _amp_pending = false; // stopped before it got going if (!_task_playing) _amp_pending = false; // stopped before it got going
else if (_clk_running && millis() - _clk_on_ms >= CLK_SETTLE_MS) { _amp_pending = false; _amp_on = power = true; } else if (_clk_running && _codec_on && millis() - _clk_on_ms >= CLK_SETTLE_MS && millis() - _codec_on_ms >= CLK_SETTLE_MS) {
_amp_pending = false; _amp_on = power = true;
}
portEXIT_CRITICAL(&s_mux); portEXIT_CRITICAL(&s_mux);
if (power) { buzzerAmpPower(true); s_amp_on_ms = millis(); } if (power) { buzzerAmpPower(true); s_amp_on_ms = millis(); }
} }
+3 -1
View File
@@ -126,7 +126,7 @@ class genericBuzzer
// A speaker behind an I2S codec (BUZZER_CODEC_ES8311) instead of a // A speaker behind an I2S codec (BUZZER_CODEC_ES8311) instead of a
// PWM pin. An audio task synthesises the melody and advances its // PWM pin. An audio task synthesises the melody and advances its
// notes by samples written, so timing holds through a stalled UI // notes by samples written, so timing holds through a stalled UI
// loop; loop() only powers the amp. See buzzer.cpp. // loop; loop() powers the amp and the codec. See buzzer.cpp.
static const int MEL_MAX = 256; static const int MEL_MAX = 256;
char _mel[MEL_MAX]; // the melody playing (a copy: callers reuse buffers) char _mel[MEL_MAX]; // the melody playing (a copy: callers reuse buffers)
volatile uint32_t _req = 0; // bumped by play()/stop(); the task restarts on a change volatile uint32_t _req = 0; // bumped by play()/stop(); the task restarts on a change
@@ -138,6 +138,8 @@ class genericBuzzer
volatile bool _clk_running = false; // I2S clocking the codec volatile bool _clk_running = false; // I2S clocking the codec
volatile uint32_t _clk_on_ms = 0; volatile uint32_t _clk_on_ms = 0;
bool _i2s_ok = false; bool _i2s_ok = false;
bool _codec_on = false; // codec powered up (standby while the clocks are off); caller's thread only
uint32_t _codec_on_ms = 0;
uint32_t _amp_off_at = 0; // amp stays on this long after the last sound uint32_t _amp_off_at = 0; // amp stays on this long after the last sound
void* _task = nullptr; // TaskHandle_t void* _task = nullptr; // TaskHandle_t
@@ -158,8 +158,7 @@ void WioTrackerL2Board::begin() {
} }
void WioTrackerL2Board::setLed(bool on) { void WioTrackerL2Board::setLed(bool on) {
// P10 doubles as GNSS wakeup and idles HIGH; blink = brief LOW pulses so // active low: HIGH = off
// the GPS never sees a sustained low level
if (expander_ok) { if (expander_ok) {
expWritePin(EXP_PIN_USER_LED, !on); expWritePin(EXP_PIN_USER_LED, !on);
} }
+1 -1
View File
@@ -30,7 +30,7 @@
#define EXP_PIN_GROVE_EN 7 // output - Grove port power #define EXP_PIN_GROVE_EN 7 // output - Grove port power
#define EXP_PIN_TP_RST 8 // output - touch panel reset #define EXP_PIN_TP_RST 8 // output - touch panel reset
#define EXP_PIN_GNSS_RST 9 // output - GNSS reset (active HIGH) #define EXP_PIN_GNSS_RST 9 // output - GNSS reset (active HIGH)
#define EXP_PIN_USER_LED 10 // output - mesh/user LED (also GNSS wakeup) #define EXP_PIN_USER_LED 10 // output - mesh/user LED (active low)
#define EXP_PIN_OTG_EN 11 // output - USB OTG power #define EXP_PIN_OTG_EN 11 // output - USB OTG power
#define EXP_PIN_PA_EN 12 // output - speaker amp power #define EXP_PIN_PA_EN 12 // output - speaker amp power
#define EXP_PIN_GNSS_EN 13 // output - GNSS power #define EXP_PIN_GNSS_EN 13 // output - GNSS power
@@ -15,6 +15,7 @@
// sequenced by WioTrackerL2Board::begin() before this driver initializes. // sequenced by WioTrackerL2Board::begin() before this driver initializes.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// The SPI clock is 80 MHz / an integer: 75 MHz asked gives 40 MHz on the wire.
#ifndef L2_SPI_FREQUENCY #ifndef L2_SPI_FREQUENCY
#define L2_SPI_FREQUENCY 75000000 #define L2_SPI_FREQUENCY 75000000
#endif #endif