Skip to content

Implement getAPSVoltage and return NaN from the unavailable AXP2101 getters - #292

Merged
lovyan03 merged 4 commits into
m5stack:developfrom
ainyan03:axp2101-unavailable-as-nan
Jul 31, 2026
Merged

Implement getAPSVoltage and return NaN from the unavailable AXP2101 getters#292
lovyan03 merged 4 commits into
m5stack:developfrom
ainyan03:axp2101-unavailable-as-nan

Conversation

@ainyan03

@ainyan03 ainyan03 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Relates to #86 and #215.

Seven getters of AXP2101_Class returned 0 without reading anything:

  float AXP2101_Class::getBatteryChargeCurrent(void)
  {
return 0;
  }

A returned 0 is indistinguishable from a valid measurement of zero, which is what led to both reports.

What the ADC provides

The ADC of the AXP2101 covers these measurement points:

channel register
VBAT 0x34
TS 0x36
VBUS 0x38
VSYS 0x3A
TDIE 0x3C

Of the seven getters, one falls inside this set and six do not.

Changes

getAPSVoltage() is implemented. VSYS is the equivalent measurement point on the AXP2101 of the APS rail of the AXP192, and it was the one channel not yet used. Measured 3.776 V on a CoreS3 while the AXP2101 was supplying the system, and 4.246 V with the charger connected.

The other six return NaN, so a caller can tell an unavailable reading from a real zero with std::isnan(). Nothing inside the library calls them, so no other behaviour changes.

Documentation is added for what each getter provides. Power_Class::getBatteryCurrent() also gains a note that the value comes from an AXP192 or from a dedicated current sense IC — the INA3221 on Core2 v1.1, the INA226 on M5Tab5 — and that boards carrying neither return 0.

How the set of channels was checked

Rather than relying on the register map alone, the whole 256 byte register space was dumped on a CoreS3 with a battery attached, once while charging and once while running from the battery, and the two dumps were compared. Registers that also moved between two consecutive dumps in the same state were excluded as normal fluctuation.

Ten registers differed:

register charging on battery meaning
0x00 38 18 VBUS good bit
0x01 32 55 charge state bits
0x35 C4 5C VBAT low byte, 4036 mV → 3932 mV
0x38 0x39 13 A0 3F F4 VBUS, 5024 mV → invalid marker
0x3A 0x3B 10 96 0F 5A VSYS, 4246 mV → 3930 mV
0x49 A3 63 IRQ / PEK status
0xA4 0D 0E battery percentage, 13 % → 14 %
0xA9 20 21 fuel gauge status

Everything that responds to the charge state is a voltage, a status bit or the fuel gauge percentage, which matches the channel list above.

Verification

Measured on a CoreS3 with a battery attached, Arduino 2.0.17 (ESP-IDF 4.4):

APS=4.218  VBAT=4.008  VBUS=5.029
chgI=nan  dscI=nan  pow=nan  acinV=nan  acinI=nan  vbusI=nan

Build checked for ESP32-S3 (ESP-IDF 4.4 / 5.5), ESP32 and the PC build.

Follow-up from the review

The literal 16375, which marks an ADC channel as having nothing to read, is now named. It appeared at every such check and getAPSVoltage() added a third one, so all three sites were changed together rather than only the one that was pointed out.

While naming it, the value turned out to be narrower than where a channel actually settles. It was full scale minus 8, but VBUS with nothing connected reads 16372 on a CoreS3, which is full scale minus 11 and slips through. The way the threshold is defined is kept and the margin is widened to 32:

  static constexpr std::size_t adc_full_scale  = 0x3FFF;
  static constexpr std::size_t adc_invalid_min = adc_full_scale - 32;

That is three times the distance measured above, and a channel that does have an input stays far away from it — the closest on the same board was TS at 5563, and every other channel read below 5100.

Measured on a CoreS3 with a battery attached:

channel raw distance from full scale
VBAT 4205 12178
TS 5563 10820
VBUS 5038 11345
VSYS 4414 11969
VBUS, nothing connected 16372 11

This does not change what any getter returns today: getVBUSVoltage() is already guarded by isVBUS(), and TS and VSYS always have an input on the boards in question, so no path reached the threshold with a railed value.

…etters (m5stack#86 m5stack#215)

Seven getters of AXP2101_Class returned 0 without reading anything, which is
indistinguishable from a valid measurement of zero. That is what both issues
report.

The ADC of the AXP2101 covers VBAT ( 0x34 ), TS ( 0x36 ), VBUS ( 0x38 ),
VSYS ( 0x3A ) and TDIE ( 0x3C ).

- getAPSVoltage() now reads VSYS, which is the equivalent measurement point on
  the AXP2101 of the APS rail of the AXP192. Measured 3.776 V on a CoreS3 while
  the AXP2101 was supplying the system.
- the six getters that fall outside the set above return NaN, so that a caller
  can tell an unavailable reading from a real zero with std::isnan().
  Nothing inside the library calls them, so no other behaviour changes.
- document which readings each getter provides, and note in
  Power_Class::getBatteryCurrent() that the value comes from an AXP192 or from
  a dedicated current sense IC ( INA3221 on Core2 v1.1, INA226 on M5Tab5 ),
  and that boards without either of them return 0.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses ambiguous “0” readings from several AXP2101_Class getters by implementing the one voltage channel the AXP2101 ADC actually provides for the “APS” rail equivalent (VSYS), and by returning NaN for getters that the AXP2101 cannot measure so callers can distinguish “unavailable” from a real zero.

Changes:

  • Implement AXP2101_Class::getAPSVoltage() by reading VSYS (0x3A/0x3B) and returning volts.
  • Change six unsupported AXP2101_Class getters to return NaN instead of a hard-coded 0.
  • Add/expand documentation clarifying which measurements are available on AXP2101 and where battery current readings come from in Power_Class.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/utility/power/AXP2101_Class.hpp Documents AXP2101 ADC-supported channels and clarifies which getters return NaN as “not provided”.
src/utility/power/AXP2101_Class.cpp Implements getAPSVoltage() via VSYS ADC and updates unsupported getters to return NaN via a helper.
src/utility/Power_Class.hpp Documents that battery current comes from board hardware (AXP192 or dedicated sense IC), otherwise returns 0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utility/power/AXP2101_Class.cpp
Comment on lines +287 to +289
float volt = readRegister14(0x3A);
if (volt >= 16375) { return 0.0f; }

lovyan03 and others added 3 commits July 31, 2026 14:47
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The literal 16375 appeared at every place that checks whether a 14 bit ADC
channel returned a reading, and getAPSVoltage() added a third one. Give it a
name so that the intent is visible and the value lives in one place.

No behaviour change.
The threshold was full scale minus 8, which is narrower than the value a channel
actually settles at. Measured on a CoreS3, VBUS with nothing connected reads
16372, or full scale minus 11, so it slipped through the check and would have
been reported as a 16.372 V reading.

Keep the way the threshold is defined and widen the margin to 32. That is three
times the distance measured above, while a channel that does have an input stays
far away from it: the closest on the same board was TS at 5563, and every other
channel read below 5100.

This does not change what any of the getters return today. getVBUSVoltage() is
already guarded by isVBUS(), and TS and VSYS always have an input on the boards
in question, so no path reached the threshold with a railed value.
@lovyan03
lovyan03 merged commit 8745301 into m5stack:develop Jul 31, 2026
23 checks passed
@lovyan03
lovyan03 deleted the axp2101-unavailable-as-nan branch July 31, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants