Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/M5Unified.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ namespace m5
{
Display.setBrightness(brightness);
}
/// The charge state API reports not_initialized until here: the PMIC
/// identity is settled in Power.begin(), but the contract is tied to the
/// completion of M5.begin() itself.
Power._initialized = true;
}

void setTouchButtonHeightByRatio(uint8_t ratio);
Expand Down
944 changes: 715 additions & 229 deletions src/utility/Power_Class.cpp

Large diffs are not rendered by default.

227 changes: 217 additions & 10 deletions src/utility/Power_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,114 @@ namespace m5
bool direction = 0;
};

/// Battery charge state.
/// The sign carries the meaning: negative = the state could not be obtained,
/// 0 = this model can never obtain it, positive = a state was obtained.
/// @note The numeric values are fixed. They are burned into the caller's
/// translation unit as immediates, so new values may only be appended and a
/// removed value stays retired forever.
enum class charge_state_t : std::int8_t
{ not_initialized = -3 ///< M5.begin() has not completed yet.
, io_error = -2 ///< the read procedure failed. (I2C NACK, charger gate timeout, ...)
, undetermined = -1 ///< the read succeeded, but the available signals do not decide a single state.
, unsupported = 0 ///< this model can never assert any positive state.
, charging = 1 ///< charging.
, not_charging = 2 ///< not charging. (nothing more can be said)
, full = 3 ///< the charger reports the charge as complete.
, disabled = 4 ///< charging is disabled.
, discharging = 5 ///< not charging, and the battery is being drained.
, idle = 6 ///< not charging, and nothing flows in or out of the battery.
};
/// The highest defined state. Bump it when a state is appended, so that the
/// set type keeps rejecting values above it.
constexpr charge_state_t charge_state_max = charge_state_t::idle;

class charge_state_set_t;
constexpr charge_state_set_t operator|(charge_state_t a, charge_state_t b);
constexpr charge_state_set_t operator|(charge_state_set_t s, charge_state_t v);

/// A set of charge states.
/// @note Negative values and 0 are members of no set and match no set, on the
/// construction side as well as on the query side. Out of range values are
/// ignored the same way. Carrying that guarantee in the type is the reason
/// this class exists: written by hand as "(int)state & mask", io_error would
/// match almost every mask and a failed read would be reported as
/// "not charging".
class charge_state_set_t
{
public:
typedef std::uint32_t storage_type;

constexpr charge_state_set_t(void) : _bits { 0 } {}

/// @return true if the state is a member of this set. A non positive state is never a member.
constexpr bool contains(charge_state_t state) const { return (_bits & _bit_of(state)) != 0; }

/// @return true if at least one state is a member of both sets.
constexpr bool intersects(charge_state_set_t other) const { return (_bits & other._bits) != 0; }

/// @return true if this set holds no state at all.
constexpr bool empty(void) const { return _bits == 0; }

private:
constexpr explicit charge_state_set_t(storage_type bits) : _bits { bits } {}

/// Range checked bit position. Only the defined positive states map to a
/// bit: negative values, 0 and anything above charge_state_max map to no
/// bit at all, so they can neither be added to a set nor match one. The
/// check comes before the shift (a negative or too wide shift is
/// undefined behaviour).
static constexpr storage_type _bit_of(charge_state_t state)
{
return ((std::int8_t)state > 0 && (std::int8_t)state <= (std::int8_t)charge_state_max)
? (storage_type)1 << (std::int8_t)state
: (storage_type)0;
}

storage_type _bits;

friend constexpr charge_state_set_t operator|(charge_state_t a, charge_state_t b);
friend constexpr charge_state_set_t operator|(charge_state_set_t s, charge_state_t v);
};

constexpr charge_state_set_t operator|(charge_state_t a, charge_state_t b)
{ return charge_state_set_t(charge_state_set_t::_bit_of(a) | charge_state_set_t::_bit_of(b)); }

constexpr charge_state_set_t operator|(charge_state_set_t s, charge_state_t v)
{ return charge_state_set_t(s._bits | charge_state_set_t::_bit_of(v)); }

/// The highest state must stay inside the storage of the set type.
static_assert((int)charge_state_max < (int)(sizeof(charge_state_set_t::storage_type) * 8)
, "charge_state_t no longer fits in charge_state_set_t::storage_type");

/// Every positive state. Use it to ask whether a state was obtained at all.
constexpr charge_state_set_t charge_states_known
= charge_state_t::charging | charge_state_t::not_charging
| charge_state_t::full | charge_state_t::disabled
| charge_state_t::discharging | charge_state_t::idle;

/// The states that mean "charging". (a future trickle state would be added here)
constexpr charge_state_set_t charge_states_any_charging
= charge_state_set_t() | charge_state_t::charging;

/// The states that mean "not charging".
/// full belongs here: it only claims that the charger reports completion, not
/// that nothing is being drawn from the battery.
constexpr charge_state_set_t charge_states_any_not_charging
= charge_state_t::not_charging | charge_state_t::full | charge_state_t::disabled
| charge_state_t::discharging | charge_state_t::idle;

/// Whether a battery is attached.
/// The sign rule is the same as charge_state_t.
enum class battery_presence_t : std::int8_t
{ not_initialized = -3 ///< M5.begin() has not completed yet.
, io_error = -2 ///< the read procedure failed.
, undetermined = -1 ///< the read succeeded, but the presence is not decided yet.
, unsupported = 0 ///< this model can never tell.
, absent = 1 ///< no battery is attached.
, present = 2 ///< a battery is attached.
};

class Power_Class
{
friend M5Unified;
Expand All @@ -80,12 +188,23 @@ namespace m5
, pmic_m5pm1
};

/// @deprecated No function returns this any more; it will be removed in the
/// next release. Use charge_state_t. (no attribute is attached on purpose)
enum is_charging_t
{ is_discharging = 0
, is_charging
, charge_unknown
};

/// The charge control paths a model provides. (see getChargeControlCaps)
/// @note This mask is deliberately separate from the state set, so that a
/// state value never gets tied to a bit position.
enum charge_control_capability_t : std::uint8_t
{ cap_set_charge_enable = 1u << 0
, cap_set_charge_current = 1u << 1
, cap_set_charge_voltage = 1u << 2
};

bool begin(void);

/// Set power output of the external ports.
Expand Down Expand Up @@ -163,23 +282,93 @@ namespace m5

/// set battery charge enable.
/// @param enable true=enable / false=disable
void setBatteryCharge(bool enable);
/// @return true if the path exists and the write succeeded.
/// @note false only means that the requested setting did not take effect;
/// it says nothing about what the hardware currently holds.
bool setBatteryCharge(bool enable);

/// set battery charge current
/// @param max_mA milli ampere.
/// @param applied_mA optional. receives the step that was applied.
/// @return true if the path exists and the write succeeded.
/// @note The highest step not exceeding max_mA is selected; a request below
/// the lowest step is clamped up to it instead of being rejected. 0 is not a
/// step: use setBatteryCharge(false) to stop charging.
/// @note applied_mA is left untouched when false is returned.
/// @note CoreMatrix selects 180 mA below 650 mA, otherwise 650 mA.
/// @note ToughC5 selects 180 mA below 830 mA, otherwise 830 mA.
/// @attention Non-functioning models : CoreInk , M5Paper , M5Stack(with non I2C IP5306)
void setChargeCurrent(std::uint16_t max_mA);
/// @note 0 is not a step: where a current path exists it selects the
/// lowest one, and a warning is logged once. Use setBatteryCharge(false)
/// to stop charging.
/// @note Tab5 / Tab5X select 500 mA below 1000 mA, otherwise 1000 mA.
/// @note StampS3Bat selects 200 mA below 650 mA, otherwise 650 mA.
/// @attention Returns false on models without a current control path; see
/// getChargeControlCaps(). The M5Stack with a non-I2C IP5306 also returns false.
bool setChargeCurrent(std::uint16_t max_mA, std::uint16_t* applied_mA = nullptr);

/// set battery charge voltage
/// @param max_mV milli volt.
/// @attention Non-functioning models : CoreInk , M5Paper , M5Stack(with non I2C IP5306)
void setChargeVoltage(std::uint16_t max_mV);
/// @param applied_mV optional. receives the step that was applied.
/// @return true if the path exists and the write succeeded.
/// @note The step selection rule is the same as setChargeCurrent.
/// @note applied_mV is left untouched when false is returned.
/// @attention Returns false on models without a voltage control path; see
/// getChargeControlCaps(). The M5Stack with a non-I2C IP5306 also returns false.
bool setChargeVoltage(std::uint16_t max_mV, std::uint16_t* applied_mV = nullptr);

/// Get which charge control paths this model provides.
/// @return bitmask of charge_control_capability_t. 0 = no control path,
/// which is also what is returned before M5.begin() has completed.
/// @note Only the set side is declared here; there is no readback getter
/// yet, so no readback capability is advertised.
std::uint8_t getChargeControlCaps(void);

/// Get the battery charge state.
/// @return a single charge_state_t value. Never a set.
/// @note The idiom is a set test on both sides:
/// @code
/// auto s = M5.Power.getChargeState();
/// if (charge_states_any_charging.contains(s)) { /* charging */ }
/// else if (charge_states_any_not_charging.contains(s)) { /* not charging (how detailed depends on the model) */ }
/// else { /* could not be obtained */ }
/// @endcode
/// Comparing against a single value ( s == charge_state_t::not_charging )
/// is model dependent and breaks silently when a model learns to report a
/// more detailed state, so both branches use a set.
/// @attention The last branch merges four different reasons:
/// not_initialized (called too early) / io_error (not readable right now) /
/// undetermined (not enough evidence) / unsupported (model can never tell).
/// Look at the value itself to tell them apart.
charge_state_t getChargeState(void);

/// Get the set of states this model can report.
/// @param caps output parameter, receives the set of reportable states.
/// @return false before M5.begin() has completed. caps is left untouched then.
/// @note An empty set after M5.begin() means getChargeState() always answers
/// unsupported. The set is decided once during M5.begin() and does not
/// change afterwards: a communication failure is reported as io_error and
/// never shrinks the set. The one exception is a board that carries either
/// an AXP192 or an AXP2101 whose chip-ID probes all failed during
/// M5.begin(): it runs with the board default (AXP192) until an explicit
/// M5.Power.begin() gets a positive ID, which can widen the set.
bool getChargeStateCaps(charge_state_set_t* caps);

/// @return true if the caps could be obtained and they contain the state.
bool canReport(charge_state_t state);

/// Get whether the battery is currently charging or not.
/// @attention Non-functioning models : CoreInk , M5Paper , M5Stack(with non I2C IP5306)
is_charging_t isCharging(void);
/// @return true only while charging. Everything else - full, disabled,
/// unknown, and a failed read - returns false.
/// @note This is charge_states_any_charging.contains(getChargeState()).
/// @attention Always false on models that cannot report charging; see
/// canReport(charge_state_t::charging). The M5Stack with a non-I2C IP5306
/// also returns false (io_error).
bool isCharging(void);

/// Get whether a battery is attached.
/// @note The existing sentinels of getBatteryVoltage() (0 / -1) are kept
/// unchanged for compatibility; this is the typed way to ask.
battery_presence_t getBatteryPresence(void);

/// Get VBUS voltage
/// @return VBUS voltage [mV] / -1=not supported model
Expand Down Expand Up @@ -262,18 +451,30 @@ namespace m5
private:
/// CoreS3 family: AW9523 のビット操作を setExtOutput / setUsbOutput と同じ排他区間で行う (スピーカー制御用)
static void _core_s3_aw9523_bit(uint8_t reg, uint8_t mask, bool on);
/// Battery current with I2C error reporting, for the boards whose charge
/// state is decided by the current. @return false = not readable / no path.
bool _readBatteryCurrent(std::int32_t* mA);
/// The state procedure itself. getChargeState() wraps it with the check
/// that the answer is one of the advertised capabilities.
charge_state_t _getChargeState(void);
std::int32_t _getBatteryAdcRaw(void);
void _powerOff(bool withTimer);
void _timerSleep(void);

#if defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61)
/// Check whether a battery is actually attached (non-blocking).
/// @return 1=present / 0=absent / -1=not yet determined
std::int8_t _batteryPresent(void);
/// @param io_ok optional. receives false if any read of this evaluation failed.
/// @return 1=present / 0=absent / -1=not yet determined (cached across failed reads)
std::int8_t _batteryPresent(bool* io_ok = nullptr);
/// Drop the transient presence evidence (streak, sample baseline, counters); the verdict is kept.
void _bp_dropEvidence(void);
/// Read the raw charger CHG_STAT line. @return false=not readable
bool _readChargeStat(bool* level);
/// Whether the VBAT node is confirmed collapsed (false when unreadable).
bool _vbatNodeDown(void);
/// @param io_ok optional. receives false when the node state could not be read.
bool _vbatNodeDown(bool* io_ok = nullptr);
/// CHG_STAT behind the battery presence gate. (ToughC5 / CoreMatrix)
charge_state_t _chargeStateFromChgStat(void);
/// Battery presence. -1 = not yet determined.
std::int8_t _batt_present = -1;
/// Tick when charging last stopped (0 = at reset, which clears PWR_CFG).
Expand All @@ -294,6 +495,12 @@ namespace m5
float _readExtValue(ext_port_mask_t port_mask, bool is_voltage);

float _adc_ratio = 0;
/// true once M5Unified::begin() has completed (set there, not in
/// Power_Class::begin()). _pmic alone cannot tell a pmic_unknown model
/// from a not yet initialized one.
bool _initialized = false;
bool _identity_settled = false; ///< a probe answered with a positive chip ID (AXP192 / AXP2101 boards)
bool _identity_unconfirmed = false; ///< every chip-ID probe failed: the board default is provisional (see begin())
std::uint8_t _wakeupPin = 255;
std::uint8_t _rtcIntPin = 255;
pmic_t _pmic = pmic_t::pmic_unknown;
Expand Down
16 changes: 14 additions & 2 deletions src/utility/power/AW32001_Class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,26 @@ namespace m5
}
}

bool AW32001_Class::setChargeCurrent(std::uint16_t max_mA)
bool AW32001_Class::getBatteryCharge(bool* enabled)
{
uint8_t reg_value = 0;
if (!_init || enabled == nullptr) { return false; }
if (!readRegister(AW32001_REG_PWR_CFG, &reg_value, 1)) { return false; }
// bit3 disables charging, so the enable state is its inverse.
*enabled = (reg_value & (1 << 3)) == 0;
return true;
}

bool AW32001_Class::setChargeCurrent(std::uint16_t max_mA, std::uint16_t* applied_mA)
{
if (!_init) return false;
int value = max_mA / 8; // Convert mA to register value (8mA per step)
if (value > 0) { value -= 1; // 0 = 8mA, 63 = 512mA
if (value >= 64) value = 63; // max value is 512mA (8 + 63*8)
}
return writeRegister8(AW32001_REG_CHR_CUR, value);
if (!writeRegister8(AW32001_REG_CHR_CUR, value)) { return false; }
if (applied_mA) { *applied_mA = (std::uint16_t)((value + 1) * 8); }
return true;
}

bool AW32001_Class::setChargeVoltage(std::uint16_t max_mV)
Expand Down
10 changes: 9 additions & 1 deletion src/utility/power/AW32001_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,17 @@ namespace m5
/// @param enable true=enable / false=disable
bool setBatteryCharge(bool enable);

/// get battery charge enable state with I2C error reporting.
/// (PWR_CFG bit3, inverted : the register bit disables charging)
/// @param enabled output parameter, receives the charge enable state.
/// @return false on I2C failure.
bool getBatteryCharge(bool* enabled);

/// set battery charge current
/// @param max_mA milli ampere. (8 - 512).
bool setChargeCurrent(std::uint16_t max_mA);
/// @param applied_mA optional. receives the step that was applied.
/// @return false on I2C failure. applied_mA is left untouched then.
bool setChargeCurrent(std::uint16_t max_mA, std::uint16_t* applied_mA = nullptr);

/// set battery charge voltage
/// @param max_mV milli volt. (3600 - 4545).
Expand Down
Loading
Loading