[Bug]: seeed_xiao_s3 samples the GPS standby pin as battery voltage
Category
Hardware Compatibility
Hardware
Seeed XIAO ESP32S3 (seeed-xiao-s3)
Firmware Version
develop at d96c690a9 (2.8.1.d96c690), and 2.8.0.74578a8
Description
variants/esp32s3/seeed_xiao_s3/variant.h points the battery ADC at GPIO1, and the same
file gives GPIO1 to the GPS standby line. A board with no INA on the battery then reports
a battery that is not there.
variant.h:39 #define BATTERY_PIN -1
variant.h:40 #define ADC_CHANNEL ADC_CHANNEL_0
variant.h:56 #define PIN_GPS_STANDBY 1On ESP32 the read goes through ADC_CHANNEL, not BATTERY_PIN (Power.cpp:106-114,
espAdcRead()), and ADC1_CHANNEL_0_GPIO_NUM is 1. The -1 disables nothing, because
every guard is #ifdef BATTERY_PIN. GPS.cpp:1243 drives PIN_GPS_STANDBY as an output.
Measured on two units, neither with a battery:
A — XIAO S3 on the Seeed expansion board with the L76K, mains 5 V. has_battery: true,
is_charging: true, battery_voltage_mv between 5338 and 5838, unbroken across 11 days
of uptime.
B — XIAO S3, bare, on USB. Every boot logs Power: battery hardware detected, then
corrects itself:
position.gps_mode |
phantom persists | firmware log |
|---|---|---|
ENABLED (1) |
81 s | Battery: usbPower=0, isCharging=0, batMv=3084, batPct=0 |
NOT_PRESENT (2) |
20 s | cleared before the 50 s battery log fires |
Two boots per row, on 2.8.0.74578a8 and again on a develop build at d96c690a9. 3084 mV
is 1542 mV at the pin times the default ADC_MULTIPLIER 2.0. For those 81 s, unit B is a
node with no battery announcing 0 percent.
Unit A sits above chargingVolt, so DeviceTelemetry.cpp:106 masks the percentage to
101; :114-116 sends the voltage unmasked, so the 5.5 V does leave the node.
Scope
With an INA present the ADC is never read — getBattVoltage() returns getINAVoltage()
first (Power.cpp:435), and getBatteryPercent() goes through getBattVoltage(). So
this only affects XIAO S3 boards without one, which is the stock board.
It is also the only variant of the 63 with an ADC_CHANNEL whose BATTERY_PIN is -1; the
other 62 name the GPIO that matches the channel. heltec_wireless_paper, its _v1 and
ELECROW-ThinkNode-M9 look like the same mismatch at first glance but set
BAT_MEASURE_ADC_UNIT, so their channels resolve on ADC2 and are correct.
I think this is also what #10733 ran into — two XIAO S3 with a LiPo on the pads and no battery level. It was closed by the stale bot without a diagnosis.
How it got here
#6070 added all three defines in one commit, whose message reads "Define BATTERY_PIN and
don't block a random GPIO". #9122 later renamed ADC1_GPIO1_CHANNEL to the equivalent
ADC_CHANNEL_0 and changed nothing else here.
Two ways out, both measured on unit B
Drop the three defines from the variant. No Use analog input line, no battery
detected, battery_level 101 in both GPS modes. But analogInit() returns false without
BATTERY_PIN (Power.cpp:949-953), so batteryLevel is never set and the INA path from
#6070 goes with it.
Gate the analog path on a pin that exists — #if defined(BATTERY_PIN) && (BATTERY_PIN >= 0)
— and leave batteryLevel = &analogLevel in place. Unit B then logs
Power: battery hardware absent (USB-only) from the first second and never claims a
battery, while a fuel gauge would still report. This needs no variant change and gives the
same -1 in nrf52840/ME25LS01-4Y10TD, ME25LS01-4Y10TD_e-ink, MS24SF1,
rp2040/seeed_xiao_rp2040 and rp2350/seeed_xiao_rp2350 a defined meaning; on those the
-1 currently reaches analogRead() (Power.cpp:465, :489). I have no hardware for any
of those five, and the LPCOMP wake path that also calls battery_adcEnable()
(main-nrf52.cpp:566) is only built by variants that have a real BATTERY_PIN, so it
keeps linking.
#9903 approaches the same problem from the other end by giving the INA its own init path.
Both branches build seeed-xiao-s3 clean and are on hardware here. Happy to open either,
or neither if you would rather fold it into #9903.
Source: meshtastic/firmware