fix(wio-tracker-l2): USB power detection works - charge bolt, Power row, GPS hint

- The AW35615 USB-C controller (FUSB302-style registers, ID 0x91) only
  reports VBUSOK with its measure block on; POWER resets to bandgap only,
  so isExternalPowered() was always false. Board init turns it on
- Status bar shows a charge bolt on the cable, Diagnostics > Live has a
  Power row, the GPS screen notes that charging disturbs GPS, and the
  low-battery warning is skipped on USB as intended
- Roadmap: USB detection closed; GPS indoors still worse than the L1
  even on battery, left open for later

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-26 22:12:05 +02:00
co-authored by Claude Opus 5.5
parent 70921cd865
commit f849ee0b00
6 changed files with 45 additions and 16 deletions
+11 -7
View File
@@ -196,13 +196,12 @@ this roadmap is done; the Cardputer follows whatever the spike shows.
## Backlog (found along the way) ## Backlog (found along the way)
- [ ] USB power detection: `WioTrackerL2Board::isExternalPowered()` reads - [x] USB power detection (fixed 2026-09-26): the AW35615 at 0x22 has a
STATUS0 (0x40) bit 7 of the chip at I2C 0x22 and always gets 0 with FUSB302-style register map (device ID 0x91); VBUSOK (STATUS0 bit 7)
USB plugged in (0x40 reads 0x01). Needed for a charging indicator and needs the measure block on, and POWER (0x0B) resets to 0x01. Board
for skipping the low-battery shutdown on the cable. Find the right init sets PWR[1..2]; STATUS0 then reads 0x80 on USB. Used by the
chip / register (devices on the bus: 0x14, 0x18, 0x21, 0x22, 0x2c, status bar (charge bolt), Diagnostics > Live "Power", the GPS screen
0x34, 0x48, 0x5d). Found 2026-09-26. Also wanted for a hint on the hint and the low-battery warning (skipped on the cable).
GPS screen while on the cable (see below).
- [x] GPS screen (2026-09-26): Home > GPS and Settings > System > GPS - [x] GPS screen (2026-09-26): Home > GPS and Settings > System > GPS
details -- sky plot, C/N0 bar per satellite, fix / TTFF / DOPs, details -- sky plot, C/N0 bar per satellite, fix / TTFF / DOPs,
per-constellation counts (helpers/sensors/GpsSky.h, -D GPS_SKYVIEW; per-constellation counts (helpers/sensors/GpsSky.h, -D GPS_SKYVIEW;
@@ -214,6 +213,11 @@ this roadmap is done; the Cardputer follows whatever the spike shows.
seconds, first fix after 377 s. Screen, WiFi, Bluetooth and LoRa TX seconds, first fix after 377 s. Screen, WiFi, Bluetooth and LoRa TX
were ruled out. Cause: noise from USB power / the charger -- a board were ruled out. Cause: noise from USB power / the charger -- a board
property, not fixable in firmware. Test GPS on battery. property, not fixable in firmware. Test GPS on battery.
- [ ] GPS indoors, still open (user, 2026-09-26): even on battery the L2
does worse than the L1 with the same L76K -- first fix after 377 s by
a window, 4 of 22 satellites used, the fix drops and comes back.
Something is still off (antenna / placement / another noise source);
look again later with the GPS screen, e.g. side by side with the L1.
Ideas to come back to (2026-09-26): Ideas to come back to (2026-09-26):
@@ -10,7 +10,7 @@ namespace diagview {
enum : uint8_t { TAB_LIVE, TAB_SYSTEM, TAB_FONT, TAB_COUNT }; enum : uint8_t { TAB_LIVE, TAB_SYSTEM, TAB_FONT, TAB_COUNT };
static uint8_t s_tab = TAB_LIVE; // kept across visits static uint8_t s_tab = TAB_LIVE; // kept across visits
static lv_obj_t* s_list = nullptr; static lv_obj_t* s_list = nullptr;
static const int EXTRA = 3; // GPS, last reset, last crash (L2 only) static const int EXTRA = 4; // GPS, power, last reset, last crash (L2 only)
static lv_obj_t* s_vals[diag::MAX_ROWS + EXTRA]; static lv_obj_t* s_vals[diag::MAX_ROWS + EXTRA];
static int s_rows = 0; static int s_rows = 0;
@@ -34,6 +34,7 @@ static int allRows(diag::Row* rows, bool gps_on) {
if (!gps_on) extraRow(rows, n, "GPS", "off"); if (!gps_on) extraRow(rows, n, "GPS", "off");
else if (!data) extraRow(rows, n, "GPS", "no data (%lu B)", (unsigned long)c); else if (!data) extraRow(rows, n, "GPS", "no data (%lu B)", (unsigned long)c);
else extraRow(rows, n, "GPS", "%s, %ld sats", gps.isValid() ? "fix" : "no fix", gps.satellitesCount()); else extraRow(rows, n, "GPS", "%s, %ld sats", gps.isValid() ? "fix" : "no fix", gps.satellitesCount());
extraRow(rows, n, "Power", "%s", board.isExternalPowered() ? "USB" : "battery");
#else #else
(void)gps_on; (void)gps_on;
#endif #endif
+5 -1
View File
@@ -348,7 +348,7 @@ void UITask::refreshGps() {
if (off) lv_obj_remove_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN); if (off) lv_obj_remove_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
else lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(s_on_btn, LV_OBJ_FLAG_HIDDEN);
char used[48] = "", info[200] = ""; char used[48] = "", info[240] = "";
if (!g) { if (!g) {
lv_label_set_text(s_status, "Not available"); lv_label_set_text(s_status, "Not available");
lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0); lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0);
@@ -387,6 +387,10 @@ void UITask::refreshGps() {
o += snprintf(info + o, sizeof(info) - o, "%.0f m - %.1f km/h\n", g->alt_m, g->speed_kmh); o += snprintf(info + o, sizeof(info) - o, "%.0f m - %.1f km/h\n", g->alt_m, g->speed_kmh);
if (g->utc_valid) o += snprintf(info + o, sizeof(info) - o, "UTC %02u:%02u:%02u", g->utc_h, g->utc_m, g->utc_s); if (g->utc_valid) o += snprintf(info + o, sizeof(info) - o, "UTC %02u:%02u:%02u", g->utc_h, g->utc_m, g->utc_s);
} }
if (g && !off && _board->isExternalPowered()) { // measured: on the cable the L76K never decodes indoors
size_t o = strlen(info);
snprintf(info + o, sizeof(info) - o, "%sOn USB power: charging disturbs GPS", o ? "\n" : "");
}
lv_label_set_text(s_used, used); lv_label_set_text(s_used, used);
lv_label_set_text(s_info, info); lv_label_set_text(s_info, info);
+1 -1
View File
@@ -616,7 +616,7 @@ void UITask::refreshStatusBar() {
case battery::VOLTAGE: snprintf(level, sizeof(level), " %u.%02u V", mv / 1000, (mv % 1000) / 10); break; case battery::VOLTAGE: snprintf(level, sizeof(level), " %u.%02u V", mv / 1000, (mv % 1000) / 10); break;
default: break; default: break;
} }
lv_label_set_text_fmt(_status_batt, "%s%s", batt, level); lv_label_set_text_fmt(_status_batt, "%s%s%s", _board->isExternalPowered() ? LV_SYMBOL_CHARGE " " : "", batt, level);
// Status icons, as the original's status bar (ui-new): Bluetooth (bright // Status icons, as the original's status bar (ui-new): Bluetooth (bright
// when the app is connected), GPS (green with a fix), the alarm, mute, then // when the app is connected), GPS (green with a fix), the alarm, mute, then
+25 -6
View File
@@ -125,6 +125,20 @@ void WioTrackerL2Board::begin() {
Wire.beginTransmission(0x22); Wire.beginTransmission(0x22);
aw_ok = Wire.endTransmission() == 0; aw_ok = Wire.endTransmission() == 0;
if (aw_ok) {
// AW35615 = FUSB302-style register map. VBUSOK (STATUS0 bit 7) only
// works with the measure block powered: POWER (0x0B) resets to 0x01
// (bandgap only), which reads "no USB" forever. Add the receiver /
// current references and the measure block (PWR[1..2]).
// Verified on hardware: device ID 0x91; STATUS0 0x01 -> 0x80 on USB.
int pw = awRead(0x0B);
if (pw >= 0) {
Wire.beginTransmission(0x22);
Wire.write((uint8_t)0x0B);
Wire.write((uint8_t)(pw | 0x07));
Wire.endTransmission();
}
}
MESH_DEBUG_PRINTLN("WioTrackerL2Board: init done"); MESH_DEBUG_PRINTLN("WioTrackerL2Board: init done");
esp_reset_reason_t reason = esp_reset_reason(); esp_reset_reason_t reason = esp_reset_reason();
@@ -156,13 +170,18 @@ int WioTrackerL2Board::expReadInputs() {
return lo | (hi << 8); return lo | (hi << 8);
} }
bool WioTrackerL2Board::isExternalPowered() { int WioTrackerL2Board::awRead(uint8_t reg) {
if (!aw_ok) return false; if (!aw_ok) return -1;
Wire.beginTransmission(0x22); Wire.beginTransmission(0x22);
Wire.write((uint8_t)0x40); // STATUS0 Wire.write(reg);
if (Wire.endTransmission() != 0) return false; if (Wire.endTransmission() != 0) return -1;
if (Wire.requestFrom(0x22, 1) != 1) return false; if (Wire.requestFrom(0x22, 1) != 1) return -1;
return (Wire.read() & 0x80) != 0; // VBUSOK bit return Wire.read();
}
bool WioTrackerL2Board::isExternalPowered() {
int st0 = awRead(0x40); // STATUS0
return st0 >= 0 && (st0 & 0x80) != 0; // VBUSOK
} }
bool WioTrackerL2Board::readWakeButton() { bool WioTrackerL2Board::readWakeButton() {
@@ -84,6 +84,7 @@ public:
// VBUS presence via the AW35615 USB-C controller (I2C 0x22) // VBUS presence via the AW35615 USB-C controller (I2C 0x22)
bool isExternalPowered() override; bool isExternalPowered() override;
int awRead(uint8_t reg); // AW35615 register, -1 on error
bool expanderOK() const { return expander_ok; } bool expanderOK() const { return expander_ok; }