From 52fc4eea4c33727eaa2ce08b87c64896ab6a16d7 Mon Sep 17 00:00:00 2001 From: ainyan03 <205502311+ainyan03@users.noreply.github.com> Date: Sun, 20 Sep 2026 02:49:27 +0000 Subject: [PATCH] Power: output setters return bool, add getPowerOutputCaps() setExtOutput / setUsbOutput / setExtPortBusConfig / setVibration report whether the requested state was reached instead of discarding the I2C, IO expander and PMIC results. getPowerOutputCaps() tells a model without the path apart from a failed request, in the same shape as the charge setters. - false: model without the path, I2C failure, protection cancel (Core2 / Tough low battery, CoreS3 family without a battery), AW9523 lock not available or request superseded (CoreS3), unconfirmed PMIC identity, or a port_mask selecting none of the ports. - Multi-port models attempt every selected port and AND the results. - Core2 / Tough: the protection readings are checked reads; a reading that fails refuses the enable request. Units whose INA3221 did not respond keep the previous behaviour (current check skipped). - AXP192 / AXP2101 rail setters: every step is attempted so that a disable still reaches the enable bit; disable reports only the enable bit, enable reports both writes. - setVibration: a nonzero level is clamped to the rail minimum instead of silently switching the rail off. - Checked getters added: AXP192 getBatteryLevel(int8_t*), INA3221 getShuntMilliVoltage(ch, int32_t*), M5PM1 getGPIOOutputLatch(pin, bool*). - build_test exercises the new API. Breaking: return types void -> bool (rebuild dependants); ext_none is now a no-op returning false on every model; setVibration(1..109) on AXP192 drives the motor at the rail minimum instead of stopping it. --- examples/Test/build_test/main/main.cpp | 16 ++ src/utility/Power_Class.hpp | 52 +++- src/utility/Power_Class.inl | 378 +++++++++++++++++-------- src/utility/power/AXP192_Class.hpp | 34 ++- src/utility/power/AXP192_Class.inl | 91 +++--- src/utility/power/AXP2101_Class.hpp | 20 +- src/utility/power/AXP2101_Class.inl | 35 +-- src/utility/power/INA3221_Class.hpp | 3 + src/utility/power/INA3221_Class.inl | 9 + src/utility/power/M5PM1_Class.hpp | 2 + src/utility/power/M5PM1_Class.inl | 13 +- 11 files changed, 436 insertions(+), 217 deletions(-) diff --git a/examples/Test/build_test/main/main.cpp b/examples/Test/build_test/main/main.cpp index 206e00c5..bf370a21 100644 --- a/examples/Test/build_test/main/main.cpp +++ b/examples/Test/build_test/main/main.cpp @@ -70,8 +70,24 @@ static void test_power(void) M5.Power.setLed(64); M5.Power.setExtOutput(true); M5.Power.setExtPower(true); + (void)M5.Power.getPowerOutputCaps(); + (void)M5.Power.setUsbOutput(true); + (void)M5.Power.setVibration(1); + (void)M5.Power.setExtPortBusConfig({}); M5.Power.setBatteryCharge(true); M5.Power.setChargeCurrent(500); +#if !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32C6) \ + && !defined(CONFIG_IDF_TARGET_ESP32C61) && !defined(CONFIG_IDF_TARGET_ESP32P4) + int8_t battery_level; + int32_t shunt_mv; + (void)M5.Power.Axp192.getBatteryLevel(&battery_level); + (void)M5.Power.Ina3221[0].getShuntMilliVoltage(0, &shunt_mv); +#endif +#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C5) \ + || defined(CONFIG_IDF_TARGET_ESP32C61) || defined(CONFIG_IDF_TARGET_ESP32P4) + bool gpio_high; + (void)M5.Power.M5pm1.getGPIOOutputLatch(m5::M5PM1_Class::gpio0, &gpio_high); +#endif (void)M5.Power.getVBUSVoltage(); (void)M5.Power.getBatteryLevel(); (void)M5.Power.getBatteryVoltage(); diff --git a/src/utility/Power_Class.hpp b/src/utility/Power_Class.hpp index 04d3b772..ffd5192a 100644 --- a/src/utility/Power_Class.hpp +++ b/src/utility/Power_Class.hpp @@ -205,17 +205,44 @@ namespace m5 , cap_set_charge_voltage = 1u << 2 }; + /// The power output paths a model provides. (see getPowerOutputCaps) + /// @note This mask is deliberately separate from ext_port_mask_t, so that a + /// port bit never gets tied to a capability bit. + enum power_output_capability_t : std::uint8_t + { cap_set_ext_output = 1u << 0 + , cap_set_usb_output = 1u << 1 + , cap_set_ext_port_bus = 1u << 2 + , cap_set_vibration = 1u << 3 + }; + bool begin(void); /// Set power output of the external ports. /// @param enable true=output / false=input - /// @param port_mask for M5Station. ext_port (bitmask). + /// @param port_mask for M5Station / M5PowerHub / Tab5 family / CoreP4X / ChainCaptain. ext_port (bitmask). + /// @return true if the requested state was reached on every selected port. + /// @note A successful return means the writes were accepted; state is not read back + /// (except when disabling the CoreS3 family output). + /// @note false means a model without the path (see getPowerOutputCaps()), + /// an I2C failure, or a protection check that cancelled the request + /// (Core2 / Tough on a low battery, the CoreS3 family without a battery). + /// Core2 / Tough with AXP2101: when the INA3221 did not respond at begin(), + /// the current-direction part of that check is skipped. + /// On the CoreS3 family it is also false when the request could not take the + /// AW9523 lock or was superseded by a later request while it waited. + /// Every selected port is still attempted; false says at least one + /// did not take effect, not which one. + /// @note port_mask is honoured only on models with individually switched ports + /// (M5Station, PowerHub, Tab5 family, CoreP4X, ChainCaptain); there a mask + /// that selects none of the model's ports returns false without touching + /// anything. Models with a single output ignore the mask, except that + /// ext_none returns false on every model. /// @note On the CoreS3 family (CoreS3 / CoreS3 SE / StackChan), disabling an enabled output blocks for /// about 200 ms (the boost converter is stopped first and the bus is left to discharge before the /// switch-over), and enabling without a battery may block for up to 1 s while the protection check /// waits for the TS reading to settle. The switch-over is serialized with setUsbOutput and the /// internal speaker enable, so those may wait for it as well. - void setExtOutput(bool enable, ext_port_mask_t port_mask = (ext_port_mask_t)0xFF); + bool setExtOutput(bool enable, ext_port_mask_t port_mask = (ext_port_mask_t)0xFF); /// deprecated : Change to "setExtOutput" [[deprecated("Change to setExtOutput")]] @@ -227,9 +254,12 @@ namespace m5 /// Set power output of the main USB port. /// @param enable true=output / false=input + /// @return true if the requested state was reached. + /// @note false means a model without the path (see getPowerOutputCaps()), an I2C failure, + /// or, on the CoreS3 family, failure to acquire the AW9523 lock. /// @attention for M5Stack CoreS3 main USB port. /// @attention ※ Not for M5Station/M5Tab external USB. - void setUsbOutput(bool enable); + bool setUsbOutput(bool enable); /// Get power output of the main USB port. /// @return true=output enabled / false=output disabled @@ -439,12 +469,24 @@ namespace m5 /// Set the configuration of the external port bus. /// @param config Configuration of the external port bus. + /// @return true if the configuration was written. + /// @note false means a model without the path (see getPowerOutputCaps()) or an I2C failure. /// @attention for M5PowerHub. - void setExtPortBusConfig(const ext_port_bus_t& config); + bool setExtPortBusConfig(const ext_port_bus_t& config); /// Operate the vibration motor /// @param level Vibration strength of the motor. (0=stop) - void setVibration(uint8_t level); + /// @return true if the requested level was applied. + /// @note false means a model without a motor (see getPowerOutputCaps()) or an I2C failure. + /// @note A nonzero level is clamped to the rail minimum: 1800 mV on AXP192 + /// LDO3 and 500 mV on AXP2101 DLDO1. + bool setVibration(uint8_t level); + + /// Get which power output paths this model provides. + /// @return bitmask of power_output_capability_t. 0 = no output path, or M5.begin() has not completed yet. + /// @note A set bit says the path exists, not that a call on it will succeed: + /// the setters still return false on an I2C failure or a cancelled request. + std::uint8_t getPowerOutputCaps(void); pmic_t getType(void) const { return _pmic; } diff --git a/src/utility/Power_Class.inl b/src/utility/Power_Class.inl index 6420984e..7949beb7 100644 --- a/src/utility/Power_Class.inl +++ b/src/utility/Power_Class.inl @@ -951,14 +951,15 @@ namespace m5 ~_core_s3_lock_t(void) { if (locked) { xSemaphoreGive(mutex); } } }; - static void _core_s3_output_locked(uint8_t mask, bool enable) + // @return true = the requested state is on the device (every write acknowledged). + static bool _core_s3_output_locked(uint8_t mask, bool enable) { static constexpr const uint8_t port0_reg = 0x02; static constexpr const uint8_t port1_reg = 0x03; static constexpr const uint8_t port1_bitmask_boost = 0b10000000; // BOOST_EN uint8_t orig[2]; - if (!M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, orig, sizeof(orig), i2c_freq)) { return; } + if (!M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, orig, sizeof(orig), i2c_freq)) { return false; } uint8_t buf[2] = { (uint8_t)(orig[0] | mask), (uint8_t)(orig[1] | port1_bitmask_boost) }; @@ -1001,14 +1002,14 @@ namespace m5 m5gfx::delay(1); } }; - if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, orig[1] & ~port1_bitmask_boost, i2c_freq)) { restore(); return; } + if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, orig[1] & ~port1_bitmask_boost, i2c_freq)) { restore(); return false; } m5gfx::delay(200); uint8_t cur[2]; - if (!M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, cur, sizeof(cur), i2c_freq)) { restore(); return; } - if (cur[1] & port1_bitmask_boost) { return; } // BOOST_EN が立て直されている: 放電を保証できないので出力を有効のまま残す + if (!M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, cur, sizeof(cur), i2c_freq)) { restore(); return false; } + if (cur[1] & port1_bitmask_boost) { return false; } // BOOST_EN が立て直されている: 放電を保証できないので出力を有効のまま残す (要求は未達) cur[0] &= ~mask; - if (!M5.In_I2C.writeRegister(aw9523_i2c_addr, port0_reg, cur, sizeof(cur), i2c_freq)) { restore(); } - return; + if (!M5.In_I2C.writeRegister(aw9523_i2c_addr, port0_reg, cur, sizeof(cur), i2c_freq)) { restore(); return false; } + return true; } } // 2 バイト一括書きは途中失敗で片方だけ反映され得るので 1 バイトずつ書き、先頭が失敗したら止める。 @@ -1016,21 +1017,18 @@ namespace m5 // 「出力 EN=1 で boost 停止」(出力が死んでいるのに有効と報告される) にはならない。 if (enable) { - if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq)) { return; } - M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq); - } - else - { - if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq)) { return; } - M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq); + if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq)) { return false; } + return M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq); } + if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq)) { return false; } + return M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq); // Axp2101.setReg0x20Bit0(enable); } - static void _core_s3_output(uint8_t mask, bool enable) + static bool _core_s3_output(uint8_t mask, bool enable) { _core_s3_lock_t lock; - if (lock.locked) { _core_s3_output_locked(mask, enable); } + return lock.locked && _core_s3_output_locked(mask, enable); } // 無バッテリーで BUS_OUT (TS で検出) に外部 5V が来ている間は、有効化すると自身の給電を断つので取り消す。 @@ -1051,8 +1049,9 @@ namespace m5 return raw > 4000; // 2.0V } - // @return false = cancel した - static bool _core_s3_set_ext_output(AXP2101_Class& axp, bool enable) + // @return true = 要求状態へ遷移できた。false = cancel した / 排他を取れない / 後発の要求に破棄された / 書込失敗。 + // cancel だけは呼び出し側で警告を出すため、cancelled で区別して返す。 + static bool _core_s3_set_ext_output(AXP2101_Class& axp, bool enable, bool* cancelled) { // 判定と切替を同じ排他区間で行う (判定後に別タスクの off が割り込むと、古い判定で有効化してしまう)。 // TS の ADC 値は実電圧に最大 0.7s ほど遅れる。自身 (または並行する別タスク) の off 直後は古い 5V を @@ -1062,20 +1061,20 @@ namespace m5 static uint32_t generation = 0; uint32_t my_generation = 0; uint32_t t0 = 0; + *cancelled = false; for (bool first = true; ; first = false) { { _core_s3_lock_t lock; - if (!lock.locked) { return true; } + if (!lock.locked) { return false; } if (first) { my_generation = ++generation; t0 = m5gfx::millis(); } // 待ち時間の起点は排他取得後 (mutex 待ちを含めない) - else if (my_generation != generation) { return true; } + else if (my_generation != generation) { return false; } if (!enable || !_core_s3_ext_output_unsafe(axp)) { - _core_s3_output_locked(_core_s3_bus_en, enable); - return true; + return _core_s3_output_locked(_core_s3_bus_en, enable); } } - if ((m5gfx::millis() - t0) >= 1000) { return false; } + if ((m5gfx::millis() - t0) >= 1000) { *cancelled = true; return false; } m5gfx::delay(20); } } @@ -1090,67 +1089,90 @@ namespace m5 #endif - void Power_Class::setExtOutput(bool enable, ext_port_mask_t port_mask) + // Accumulates the per-port results of a masked request: false until a port + // was selected, then the AND of every selected port. + struct _port_result_t { + bool any = false; + bool ok = true; + void add(bool r) { any = true; ok &= r; } + operator bool(void) const { return any && ok; } + }; + + bool Power_Class::setExtOutput(bool enable, ext_port_mask_t port_mask) + { + // Contract: true only when every selected port reached the requested state. + // Ports are attempted in turn without stopping at the first failure (best effort), + // so false says "at least one did not take effect", not which one. + // A mask that selects none of this model's ports is false (nothing was done). + bool result = false; #if defined (M5UNIFIED_PC_BUILD) (void)enable; (void)port_mask; #else + if (_identity_unconfirmed) { return false; } // see begin(): the PMIC never answered its ID probe + if (port_mask == ext_port_mask_t::ext_none) { return false; } // nothing selected switch (M5.getBoard()) { #if defined (CONFIG_IDF_TARGET_ESP32P4) case board_t::board_M5CoreP4X: { auto& ioe1 = M5.getIOExpander(0); + _port_result_t r; if (port_mask & ext_port_mask_t::ext_PA) { - ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); - ioe1.setDirection(M5IOE1_Class::gpio5, true); - ioe1.digitalWrite(M5IOE1_Class::gpio5, enable); + r.add(ioe1.setHighImpedance(M5IOE1_Class::gpio5, false)); + r.add(ioe1.setDirection(M5IOE1_Class::gpio5, true)); + r.add(ioe1.digitalWrite(M5IOE1_Class::gpio5, enable)); } if (port_mask & ext_port_mask_t::ext_USB) { - ioe1.setHighImpedance(M5IOE1_Class::gpio2, false); - ioe1.setDirection(M5IOE1_Class::gpio2, true); - ioe1.digitalWrite(M5IOE1_Class::gpio2, enable); + r.add(ioe1.setHighImpedance(M5IOE1_Class::gpio2, false)); + r.add(ioe1.setDirection(M5IOE1_Class::gpio2, true)); + r.add(ioe1.digitalWrite(M5IOE1_Class::gpio2, enable)); } + result = r; } break; case board_t::board_M5Tab5: case board_t::board_M5Tab5X: - if (port_mask & ext_port_mask_t::ext_PA) { - auto& ioe = M5.getIOExpander(0); - ioe.setPullMode(2, enable ? IOExpander_Base::pull_up : IOExpander_Base::pull_down); - ioe.digitalWrite(2, enable); - } - if (M5.getBoard() == board_t::board_M5Tab5X - && (port_mask & ext_port_mask_t::ext_EXT)) - { - auto& ioe = M5.getIOExpander(0); - ioe.setHighImpedance(3, false); - ioe.setDirection(3, true); - ioe.digitalWrite(3, enable); - } - if (port_mask & ext_port_mask_t::ext_USB) - { - auto& ioe = M5.getIOExpander(1); - ioe.setPullMode(3, enable ? IOExpander_Base::pull_up : IOExpander_Base::pull_down); - ioe.digitalWrite(3, enable); + _port_result_t r; + if (port_mask & ext_port_mask_t::ext_PA) + { + auto& ioe = M5.getIOExpander(0); + r.add(ioe.setPullMode(2, enable ? IOExpander_Base::pull_up : IOExpander_Base::pull_down)); + r.add(ioe.digitalWrite(2, enable)); + } + if (M5.getBoard() == board_t::board_M5Tab5X + && (port_mask & ext_port_mask_t::ext_EXT)) + { + auto& ioe = M5.getIOExpander(0); + r.add(ioe.setHighImpedance(3, false)); + r.add(ioe.setDirection(3, true)); + r.add(ioe.digitalWrite(3, enable)); + } + if (port_mask & ext_port_mask_t::ext_USB) + { + auto& ioe = M5.getIOExpander(1); + r.add(ioe.setPullMode(3, enable ? IOExpander_Base::pull_up : IOExpander_Base::pull_down)); + r.add(ioe.digitalWrite(3, enable)); + } + result = r; } break; #elif defined (CONFIG_IDF_TARGET_ESP32C6) case board_t::board_ArduinoNessoN1: - M5.getIOExpander(1).digitalWrite(2, enable); // 2 = EXT_PWR_EN + result = M5.getIOExpander(1).digitalWrite(2, enable); // 2 = EXT_PWR_EN break; #elif defined (CONFIG_IDF_TARGET_ESP32C5) case board_t::board_M5ToughC5: if (_pmic == pmic_t::pmic_m5pm1) { - M5pm1.setExtOutput(enable); + result = M5pm1.setExtOutput(enable); } break; @@ -1158,9 +1180,9 @@ namespace m5 case board_t::board_M5CoreMatrix: { /// IOE1 G5 gates the Grove port power (both the 3.3V rail and the 5V boost) auto& ioe1 = M5.getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); - ioe1.setDirection(M5IOE1_Class::gpio5, true); - ioe1.digitalWrite(M5IOE1_Class::gpio5, enable); + result = ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); + result &= ioe1.setDirection(M5IOE1_Class::gpio5, true); + result &= ioe1.digitalWrite(M5IOE1_Class::gpio5, enable); } break; @@ -1171,7 +1193,9 @@ namespace m5 case board_t::board_M5StackCoreS3SE: case board_t::board_M5StackChan: { - if (!_core_s3_set_ext_output(Axp2101, enable)) + bool cancelled; + result = _core_s3_set_ext_output(Axp2101, enable, &cancelled); + if (cancelled) { ESP_LOGW("Power","setExtPower(true) is canceled."); } @@ -1183,105 +1207,157 @@ namespace m5 case board_t::board_M5PaperColor: if (_pmic == pmic_t::pmic_m5pm1) { - M5pm1.setExtOutput(enable); + result = M5pm1.setExtOutput(enable); } break; case board_t::board_M5ChainCaptain: { + _port_result_t r; if (port_mask & ext_port_mask_t::ext_PA) { - M5pm1.setGPIOOutput(M5PM1_Class::gpio0, enable); + r.add(M5pm1.setGPIOOutput(M5PM1_Class::gpio0, enable)); } if (port_mask & (ext_port_mask_t::ext_PB1 | ext_port_mask_t::ext_PB2)) { - M5pm1.setGPIOOutput(M5PM1_Class::gpio3, enable); + r.add(M5pm1.setGPIOOutput(M5PM1_Class::gpio3, enable)); } - const bool boost_enabled = M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio0) - || M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio3); - M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio4, boost_enabled); + if (r.any) + { /// the boost follows the latched port states, so it is only touched when a port was selected. + /// If a latch cannot be read the boost is left as it is (false): switching it on a guess + /// could cut a port that is still enabled. + bool pa, pb; + if (M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio0, &pa) && M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio3, &pb)) + { + r.add(M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio4, pa || pb)); + } + else { r.add(false); } + } + result = r; break; } case board_t::board_M5StampS3Bat: // Use G1 Control 5V output - M5pm1.setGPIOOutput(M5PM1_Class::gpio1, enable); + result = M5pm1.setGPIOOutput(M5PM1_Class::gpio1, enable); break; case board_t::board_M5PowerHub: - if (port_mask & ext_port_mask_t::ext_USB) - { - M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x01, enable, i2c_freq); - } - if (port_mask & ext_port_mask_t::ext_PA) - { - M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x02, enable, i2c_freq); - } - if (port_mask & ext_port_mask_t::ext_PC1) { - M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x03, enable, i2c_freq); - } - if (port_mask & ext_port_mask_t::ext_PWR485 || port_mask & ext_port_mask_t::ext_PWRCAN) { - M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x04, enable, i2c_freq); + _port_result_t r; + if (port_mask & ext_port_mask_t::ext_USB) + { + r.add(M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x01, enable, i2c_freq)); + } + if (port_mask & ext_port_mask_t::ext_PA) + { + r.add(M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x02, enable, i2c_freq)); + } + if (port_mask & ext_port_mask_t::ext_PC1) + { + r.add(M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x03, enable, i2c_freq)); + } + if (port_mask & ext_port_mask_t::ext_PWR485 || port_mask & ext_port_mask_t::ext_PWRCAN) { + r.add(M5.In_I2C.writeRegister8(powerhub_i2c_addr, 0x04, enable, i2c_freq)); + } + result = r; } break; #elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) case board_t::board_M5Paper: if (enable) { m5gfx::gpio_hi(M5Paper_EXT5V_ENABLE_PIN); } else { m5gfx::gpio_lo(M5Paper_EXT5V_ENABLE_PIN); } + result = true; break; case board_t::board_M5StackCore2: case board_t::board_M5Tough: { + // The protection check reads the PMIC; a failed read is treated as + // "cannot tell" and the enable request is refused without writing + // (a folded-to-zero reading would pass the check on the unsafe side). bool cancel = false; + bool ok = true; if (_pmic == pmic_axp2101) { - cancel = (enable && (Ina3221[0].getShuntVoltage(0) < 0.0f || Ina3221[0].getShuntVoltage(1) < 0.0f) && (8 >= Axp2101.getBatteryLevel())); + if (enable) { + // Without an INA3221 the previous behaviour is kept: omit the + // current-direction check. A detected device must remain readable. + // The battery level is read only once a negative current is seen. + if (Ina3221[0].isEnabled()) { + int32_t sh0 = 0, sh1 = 0; + if (!Ina3221[0].getShuntMilliVoltage(0, &sh0) || !Ina3221[0].getShuntMilliVoltage(1, &sh1)) { + ESP_LOGW("Power", "setExtOutput(true): protection reading failed, refused."); + break; + } + if (sh0 < 0 || sh1 < 0) { + uint8_t level; + if (!Axp2101.readRegister(0xA4, &level, 1)) { // same reading as getBatteryLevel() + ESP_LOGW("Power", "setExtOutput(true): protection reading failed, refused."); + break; + } + cancel = (8 >= (int8_t)level); + } + } + } if (!cancel) { - Axp2101.setBLDO2(enable * 3300); + result = Axp2101.setBLDO2(enable * 3300); break; } } else { // If ACIN is false and VBUS current is detected and the battery is low, power supply from Core to the outside is inhibited. // This is because supplying power externally consumes battery power when there is no power supply from ACIN and power is supplied from VBUS. // ※ If receiving power from M-Bus, PortA, etc., there is no need to setExtPower to true. - cancel = (enable && !Axp192.isACIN() && (0.0f < Axp192.getVBUSCurrent()) && (8 >= Axp192.getBatteryLevel())); - if (!cancel) { - Axp192.writeRegister8(0x90, enable ? 0x02 : 0x07); // GPIO0 : enable=LDO / disable=float + if (enable) { + uint8_t r00, vbus[2]; int8_t level; + if (!Axp192.readRegister(0x00, &r00, 1) || !Axp192.readRegister(0x5C, vbus, 2) || !Axp192.getBatteryLevel(&level)) { + ESP_LOGW("Power", "setExtOutput(true): protection reading failed, refused."); + break; + } + cancel = !(r00 & 0x80) && (((vbus[0] << 4) | vbus[1]) > 0) && (8 >= level); } + // GPIO0 : enable=LDO / disable=float. On failure EXTEN below is still written (best effort). + if (!cancel) { ok = Axp192.writeRegister8(0x90, enable ? 0x02 : 0x07); } } if (cancel) { ESP_LOGW("Power","setExtPower(true) is canceled."); break; } + result = Axp192.setEXTEN(enable) && ok; } - NON_BREAK; + break; case board_t::board_M5StickC: case board_t::board_M5StickCPlus: - Axp192.setEXTEN(enable); + result = Axp192.setEXTEN(enable); break; case board_t::board_M5Station: - for (int i = 0; i < 5; ++i) { - if (port_mask & (1 << i)) { Axp192.setGPIO(i, enable); } - } - if (port_mask & ext_port_mask_t::ext_USB) - { - if (enable) { m5gfx::gpio_hi(GPIO_NUM_12); } // GPIO12 = M5Station USB power control - else { m5gfx::gpio_lo(GPIO_NUM_12); } - } - if (port_mask & ext_port_mask_t::ext_MAIN) - { - Axp192.setEXTEN(enable); + _port_result_t r; + for (int i = 0; i < 5; ++i) + { + if (port_mask & (1 << i)) { r.add(Axp192.setGPIO(i, enable)); } + } + if (port_mask & ext_port_mask_t::ext_USB) + { + if (enable) { m5gfx::gpio_hi(GPIO_NUM_12); } // GPIO12 = M5Station USB power control + else { m5gfx::gpio_lo(GPIO_NUM_12); } + r.add(true); + } + if (port_mask & ext_port_mask_t::ext_MAIN) + { + r.add(Axp192.setEXTEN(enable)); + } + result = r; } + break; #endif default: break; } #endif + return result; } bool Power_Class::getExtOutput(void) @@ -1374,27 +1450,26 @@ namespace m5 return false; } - void Power_Class::setUsbOutput(bool enable) + bool Power_Class::setUsbOutput(bool enable) { (void)enable; + if (_identity_unconfirmed) { return false; } // see begin(): the PMIC never answered its ID probe switch (M5.getBoard()) { #if defined (CONFIG_IDF_TARGET_ESP32P4) case board_t::board_M5CoreP4X: - M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio2, enable); - break; + return M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio2, enable); #endif #if defined (CONFIG_IDF_TARGET_ESP32S3) case board_t::board_M5StackCoreS3: case board_t::board_M5StackCoreS3SE: case board_t::board_M5StackChan: - _core_s3_output(_core_s3_usb_en, enable); - break; + return _core_s3_output(_core_s3_usb_en, enable); #endif default: - break; + return false; } } @@ -3738,8 +3813,10 @@ namespace m5 } } - void Power_Class::setExtPortBusConfig(const ext_port_bus_t& config) + bool Power_Class::setExtPortBusConfig(const ext_port_bus_t& config) { + (void)config; + if (_identity_unconfirmed) { return false; } // see begin(): the PMIC never answered its ID probe switch (M5.getBoard()) { #if defined(CONFIG_IDF_TARGET_ESP32S3) case board_t::board_M5PowerHub: { @@ -3749,52 +3826,123 @@ namespace m5 buf[2] = config.currentLimit & 0xFF; buf[3] = config.enable; buf[4] = config.direction; - M5.In_I2C.writeRegister(powerhub_i2c_addr, 0x20, buf, sizeof(buf), i2c_freq); - } break; + return M5.In_I2C.writeRegister(powerhub_i2c_addr, 0x20, buf, sizeof(buf), i2c_freq); + } #endif default: - break; + return false; } } - void Power_Class::setVibration(uint8_t level) + bool Power_Class::setVibration(uint8_t level) { + if (_identity_unconfirmed) { return false; } // see begin(): the PMIC never answered its ID probe #if !defined (M5UNIFIED_PC_BUILD) && defined (CONFIG_IDF_TARGET_ESP32S3) if (M5.getBoard() == board_t::board_M5StopWatch) { // M5IOE1 PWM1 (0x1B/0x1C) -> pin IO9 / G9 motor; duty 12-bit in [11:0], EN=bit7 of high byte. auto& ioe1 = static_cast(M5.getIOExpander(0)); if (level == 0) { - ioe1.setPwmDuty12bit(M5IOE1_Class::pwm_ch1, 0, pwm_polarity_t::normal, false); - } else { - // PWM needs IO9 in output mode (M5IOE1 pin index 8 -> GPIO_MODE_H bit0). - ioe1.setHighImpedance(M5IOE1_Class::gpio9, false); - ioe1.setDirection(M5IOE1_Class::gpio9, true); - uint16_t duty12 = static_cast((static_cast(level) * 0x0FFFu) / 255u); - ioe1.setPwmDuty12bit(M5IOE1_Class::pwm_ch1, duty12); + return ioe1.setPwmDuty12bit(M5IOE1_Class::pwm_ch1, 0, pwm_polarity_t::normal, false); } - return; + // PWM needs IO9 in output mode (M5IOE1 pin index 8 -> GPIO_MODE_H bit0). + if (!ioe1.setHighImpedance(M5IOE1_Class::gpio9, false)) { return false; } + if (!ioe1.setDirection(M5IOE1_Class::gpio9, true)) { return false; } + uint16_t duty12 = static_cast((static_cast(level) * 0x0FFFu) / 255u); + return ioe1.setPwmDuty12bit(M5IOE1_Class::pwm_ch1, duty12); } #endif #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) if (M5.getBoard() == board_t::board_M5StackCore2) { + // A non-zero level is clamped up to the lowest voltage the rail can + // produce, so that every non-zero request drives the motor rather than + // silently mapping to "rail off" (AXP192 LDO3 starts at 1800 mV, + // AXP2101 DLDO1 at 500 mV). uint32_t mv = level ? 480 + level * 12 : 0; switch (_pmic) { case pmic_t::pmic_axp192: - Axp192.setLDO3(mv); - break; + if (mv && mv < 1800) { mv = 1800; } + return Axp192.setLDO3(mv); case pmic_t::pmic_axp2101: - Axp2101.setDLDO1(mv); - break; + if (mv && mv < 500) { mv = 500; } + return Axp2101.setDLDO1(mv); default: break; } } +#endif + (void)level; + return false; + } + + std::uint8_t Power_Class::getPowerOutputCaps(void) + { +#if defined (M5UNIFIED_PC_BUILD) + return 0; // the setters are stubs on the PC build +#else + if (!_initialized) { return 0; } + switch (M5.getBoard()) + { +#if defined (CONFIG_IDF_TARGET_ESP32P4) + case board_t::board_M5CoreP4X: + return cap_set_ext_output | cap_set_usb_output; + + case board_t::board_M5Tab5: + case board_t::board_M5Tab5X: + return cap_set_ext_output; + +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_ArduinoNessoN1: + return cap_set_ext_output; + +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + case board_t::board_M5ToughC5: + return (_pmic == pmic_t::pmic_m5pm1) ? cap_set_ext_output : 0; + +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + case board_t::board_M5CoreMatrix: + return cap_set_ext_output; + +#elif defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5StackCoreS3: + case board_t::board_M5StackCoreS3SE: + case board_t::board_M5StackChan: + return cap_set_ext_output | cap_set_usb_output; + + case board_t::board_M5StopWatch: + return ((_pmic == pmic_t::pmic_m5pm1) ? cap_set_ext_output : 0) | cap_set_vibration; + + case board_t::board_M5StickS3: + case board_t::board_M5PaperColor: + return (_pmic == pmic_t::pmic_m5pm1) ? cap_set_ext_output : 0; + + case board_t::board_M5ChainCaptain: + case board_t::board_M5StampS3Bat: + return cap_set_ext_output; + + case board_t::board_M5PowerHub: + return cap_set_ext_output | cap_set_ext_port_bus; + +#elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) + case board_t::board_M5StackCore2: + return cap_set_ext_output | cap_set_vibration; + + case board_t::board_M5Paper: + case board_t::board_M5Tough: + case board_t::board_M5StickC: + case board_t::board_M5StickCPlus: + case board_t::board_M5Station: + return cap_set_ext_output; +#endif + + default: + return 0; + } #endif } diff --git a/src/utility/power/AXP192_Class.hpp b/src/utility/power/AXP192_Class.hpp index 988967d0..23469f6c 100644 --- a/src/utility/power/AXP192_Class.hpp +++ b/src/utility/power/AXP192_Class.hpp @@ -21,8 +21,13 @@ namespace m5 bool begin(void); /// Get the remaining battery power. - /// @return 0-100 level + /// @return 0-100 level. -1 on I2C failure, but the value can also go slightly + /// negative while a nearly empty battery is being charged, so -1 is ambiguous: + /// use getBatteryLevel(int8_t*) where the difference matters. std::int8_t getBatteryLevel(void); + /// @return false on I2C failure or a null pointer (level is left untouched). + /// The level may be slightly negative while a nearly empty battery is being charged. + bool getBatteryLevel(std::int8_t* level); /// set battery charge enable. /// @param enable true=enable / false=disable @@ -65,29 +70,30 @@ namespace m5 /// set LDOio0 voltage /// @param voltage milli volt. (0 - 3300). - inline void setLDO0(int voltage) { _set_LDO(0, voltage); } + inline bool setLDO0(int voltage) { return _set_LDO(0, voltage); } /// set LDO2 voltage /// @param voltage milli volt. (0 - 3300). - inline void setLDO2(int voltage) { _set_LDO(2, voltage); } + inline bool setLDO2(int voltage) { return _set_LDO(2, voltage); } /// set LDO3 voltage /// @param voltage milli volt. (0 - 3300). - inline void setLDO3(int voltage) { _set_LDO(3, voltage); } + inline bool setLDO3(int voltage) { return _set_LDO(3, voltage); } - inline void setGPIO(uint8_t gpio_num, bool state) { if (gpio_num < 3) { _set_GPIO0_2(gpio_num, state); } else { _set_GPIO3_4(gpio_num - 3, state); } } - inline void setGPIO0(bool state) { _set_GPIO0_2(0, state); } - inline void setGPIO1(bool state) { _set_GPIO0_2(1, state); } - inline void setGPIO2(bool state) { _set_GPIO0_2(2, state); } - inline void setGPIO3(bool state) { _set_GPIO3_4(0, state); } - inline void setGPIO4(bool state) { _set_GPIO3_4(1, state); } + inline bool setGPIO(uint8_t gpio_num, bool state) { return (gpio_num < 3) ? _set_GPIO0_2(gpio_num, state) : _set_GPIO3_4(gpio_num - 3, state); } + inline bool setGPIO0(bool state) { return _set_GPIO0_2(0, state); } + inline bool setGPIO1(bool state) { return _set_GPIO0_2(1, state); } + inline bool setGPIO2(bool state) { return _set_GPIO0_2(2, state); } + inline bool setGPIO3(bool state) { return _set_GPIO3_4(0, state); } + inline bool setGPIO4(bool state) { return _set_GPIO3_4(1, state); } void powerOff(void); void setAdcState(bool enable); void setAdcRate( std::uint8_t rate ); - void setEXTEN(bool enable); + /// @return false on I2C failure. + bool setEXTEN(bool enable); void setBACKUP(bool enable); bool isACIN(void); @@ -119,10 +125,10 @@ namespace m5 std::size_t readRegister32(std::uint8_t addr); void _set_DCDC(std::uint8_t num, int voltage); - void _set_LDO(std::uint8_t num, int voltage); + bool _set_LDO(std::uint8_t num, int voltage); void _set_LDO2_LDO3(std::uint8_t num, int voltage); - void _set_GPIO0_2(std::uint8_t num, bool state); - void _set_GPIO3_4(std::uint8_t num, bool state); + bool _set_GPIO0_2(std::uint8_t num, bool state); + bool _set_GPIO3_4(std::uint8_t num, bool state); }; } diff --git a/src/utility/power/AXP192_Class.inl b/src/utility/power/AXP192_Class.inl index a1db7646..ee00e5c7 100644 --- a/src/utility/power/AXP192_Class.inl +++ b/src/utility/power/AXP192_Class.inl @@ -59,40 +59,39 @@ namespace m5 } /// @param num 0:LDOio0 ; 2:LDO2 ; 3=LDO3 - void AXP192_Class::_set_LDO(std::uint8_t num, int voltage) + bool AXP192_Class::_set_LDO(std::uint8_t num, int voltage) { - if (num > 3 || num == 1) return; + if (num > 3 || num == 1) return false; std::uint8_t reg_volt = (num == 0) ? 0x91 : 0x28; voltage -= 1800; /// convert voltage to value std::uint_fast8_t val = (voltage < 0) ? 0 : std::min(voltage / 100, 0x0F); - std::uint_fast8_t now = readRegister8(reg_volt); - if (num == 3) - { /// LDO3 - now = (now & 0xF0) + val; - } - else - { /// LDOio0 , LDO2 - now = (now & 0x0F) | (val << 4); + // Every step is attempted even after a failure so that a disable request + // still reaches the enable bit. Enable reports the AND of both writes; + // disable reports only whether the enable bit was cleared. + std::uint8_t now; + bool ok = readRegister(reg_volt, &now, 1); + if (ok) + { + if (num == 3) + { /// LDO3 + now = (now & 0xF0) + val; + } + else + { /// LDOio0 , LDO2 + now = (now & 0x0F) | (val << 4); + } + ok = writeRegister8(reg_volt, now); } - writeRegister8(reg_volt, now); if (num) { // LDO2 , LDO3 std::uint_fast8_t reg12bit = 1 << num; - if (voltage < 0) - { - bitOff(0x12, reg12bit); - } - else - { - bitOn(0x12, reg12bit); - } - } - else - { // LDOio0 - writeRegister8(0x90, (voltage < 0) ? 0x07 : 0x02 ); /// floating or LDO + return (voltage < 0) ? bitOff(0x12, reg12bit) : (bitOn(0x12, reg12bit) && ok); } + // LDOio0 + bool enabled = writeRegister8(0x90, (voltage < 0) ? 0x07 : 0x02 ); /// floating or LDO + return (voltage < 0) ? enabled : (enabled && ok); } /// @param num 0=LDO2 / 1=LDO3 @@ -123,27 +122,21 @@ namespace m5 } /// @param num 0=GPIO0 / 1=GPIO1 / 2=GPIO2 - void AXP192_Class::_set_GPIO0_2(std::uint8_t num, bool state) + bool AXP192_Class::_set_GPIO0_2(std::uint8_t num, bool state) { static constexpr uint8_t reg[] = { 0x90, 0x92, 0x93 }; - writeRegister8(reg[num], state ? 0x06 : 0x05); // floating or LOW + return writeRegister8(reg[num], state ? 0x06 : 0x05); // floating or LOW } /// @param num 0=GPIO3 / 1=GPIO4 - void AXP192_Class::_set_GPIO3_4(std::uint8_t num, bool state) + bool AXP192_Class::_set_GPIO3_4(std::uint8_t num, bool state) { uint32_t bit = num ? 2 : 1; - if (state) - { - bitOn(0x96, bit); - } - else - { - bitOff(0x96, bit); - } - uint_fast8_t mask = num ? ~0x0C : ~0x03; - uint_fast8_t reg0x95 = readRegister8(0x95) & mask; - writeRegister8(0x95, reg0x95 | (num ? 0x84 : 0x81)); // set GPIO mode + bool ok = state ? bitOn(0x96, bit) : bitOff(0x96, bit); + uint8_t mask = num ? 0xF3 : 0xFC; // clear the mode bits of GPIO4 / GPIO3 + uint8_t reg0x95; + if (!readRegister(0x95, ®0x95, 1)) { return false; } + return writeRegister8(0x95, (reg0x95 & mask) | (num ? 0x84 : 0x81)) && ok; // set GPIO mode } bool AXP192_Class::setBatteryCharge(bool enable) @@ -204,10 +197,10 @@ namespace m5 return true; } - std::int8_t AXP192_Class::getBatteryLevel(void) + bool AXP192_Class::getBatteryLevel(std::int8_t* level) { std::uint8_t buf[4]; - if (!readRegister(0x78, buf, 4)) { return -1; } + if (level == nullptr || !readRegister(0x78, buf, 4)) { return false; } std::uint_fast16_t voltage = (buf[0] << 4) + buf[1]; std::uint_fast16_t current = (buf[2] << 5) + buf[3]; @@ -217,7 +210,14 @@ namespace m5 : 0; if (current > 16) { res -= 16; } - return (res < 100) ? res : 100; + *level = (res < 100) ? res : 100; + return true; + } + + std::int8_t AXP192_Class::getBatteryLevel(void) + { + std::int8_t level; + return getBatteryLevel(&level) ? level : -1; } bool AXP192_Class::isCharging(void) @@ -241,18 +241,11 @@ namespace m5 writeRegister8(0x84, (buf & ~(0xc0)) | (rate & 0xc0)); } - void AXP192_Class::setEXTEN(bool enable) + bool AXP192_Class::setEXTEN(bool enable) { static constexpr std::uint8_t add = 0x12; static constexpr std::uint8_t bit = 1 << 6; - if (enable) - { - bitOn(add, bit); - } - else - { - bitOff(add, bit); - } + return enable ? bitOn(add, bit) : bitOff(add, bit); } bool AXP192_Class::getEXTEN(void) diff --git a/src/utility/power/AXP2101_Class.hpp b/src/utility/power/AXP2101_Class.hpp index 1857136d..82f00085 100644 --- a/src/utility/power/AXP2101_Class.hpp +++ b/src/utility/power/AXP2101_Class.hpp @@ -123,14 +123,14 @@ namespace m5 bool readPmuStatus2(std::uint8_t* value); - inline void setALDO1(int voltage) { _set_LDO(0, voltage); } - inline void setALDO2(int voltage) { _set_LDO(1, voltage); } - inline void setALDO3(int voltage) { _set_LDO(2, voltage); } - inline void setALDO4(int voltage) { _set_LDO(3, voltage); } - inline void setBLDO1(int voltage) { _set_LDO(4, voltage); } - inline void setBLDO2(int voltage) { _set_LDO(5, voltage); } - inline void setDLDO1(int voltage) { _set_DLDO(0, voltage); } - inline void setDLDO2(int voltage) { _set_DLDO(1, voltage); } + inline bool setALDO1(int voltage) { return _set_LDO(0, voltage); } + inline bool setALDO2(int voltage) { return _set_LDO(1, voltage); } + inline bool setALDO3(int voltage) { return _set_LDO(2, voltage); } + inline bool setALDO4(int voltage) { return _set_LDO(3, voltage); } + inline bool setBLDO1(int voltage) { return _set_LDO(4, voltage); } + inline bool setBLDO2(int voltage) { return _set_LDO(5, voltage); } + inline bool setDLDO1(int voltage) { return _set_DLDO(0, voltage); } + inline bool setDLDO2(int voltage) { return _set_DLDO(1, voltage); } inline bool getALDO1Enabled(void) { return _get_LDOEn(0); } inline bool getALDO2Enabled(void) { return _get_LDOEn(1); } @@ -220,8 +220,8 @@ namespace m5 std::size_t readRegister14(std::uint8_t addr); std::size_t readRegister16(std::uint8_t addr); - void _set_LDO(std::uint8_t num, int voltage); - void _set_DLDO(std::uint8_t num, int voltage); + bool _set_LDO(std::uint8_t num, int voltage); + bool _set_DLDO(std::uint8_t num, int voltage); bool _get_LDOEn(std::uint8_t num); bool setIRQEnRegister(std::uint64_t registerEn, bool enable); diff --git a/src/utility/power/AXP2101_Class.inl b/src/utility/power/AXP2101_Class.inl index 3564c59b..252518cd 100644 --- a/src/utility/power/AXP2101_Class.inl +++ b/src/utility/power/AXP2101_Class.inl @@ -43,46 +43,37 @@ namespace m5 } // 0=ALDO1 ~ 3=ALDO4 / 4=BLDO1 / 5=BLDO2 - void AXP2101_Class::_set_LDO(std::uint8_t num, int voltage) + bool AXP2101_Class::_set_LDO(std::uint8_t num, int voltage) { - if (num > 5) return; + if (num > 5) return false; std::uint8_t reg_volt = num + 0x92; voltage -= 500; /// convert voltage to value std::uint_fast8_t val = (voltage < 0) ? 0 : std::min(voltage / 100, 0x1E); - writeRegister8(reg_volt, val); + // The enable bit is written even when the voltage write failed. An enable + // request reports the AND of both writes; a disable request reports only + // whether the enable bit was cleared. + // (An enable request may then bring the rail up at its previous voltage + // and still report false: the requested state was not reached.) + bool ok = writeRegister8(reg_volt, val); std::uint_fast8_t reg90bit = 1 << num; - if (voltage < 0) - { - bitOff(0x90, reg90bit); - } - else - { - bitOn(0x90, reg90bit); - } + return (voltage < 0) ? bitOff(0x90, reg90bit) : (bitOn(0x90, reg90bit) && ok); } - void AXP2101_Class::_set_DLDO(std::uint8_t num, int voltage) + bool AXP2101_Class::_set_DLDO(std::uint8_t num, int voltage) { - if (num > 1) return; + if (num > 1) return false; std::uint8_t reg_volt = num + 0x99; voltage -= 500; /// convert voltage to value std::uint_fast8_t val = (voltage < 0) ? 0 : std::min(voltage / (num ? 50 : 100), num ? 0x13 : 0x1C); - writeRegister8(reg_volt, val); + bool ok = writeRegister8(reg_volt, val); uint8_t reg = 0x90 + num; uint8_t bit = num ? 0x01 : 0x80; - if (voltage < 0) - { - bitOff(reg, bit); - } - else - { - bitOn(reg, bit); - } + return (voltage < 0) ? bitOff(reg, bit) : (bitOn(reg, bit) && ok); } bool AXP2101_Class::_get_LDOEn(std::uint8_t num) diff --git a/src/utility/power/INA3221_Class.hpp b/src/utility/power/INA3221_Class.hpp index 30ce47a7..ffc18e6b 100644 --- a/src/utility/power/INA3221_Class.hpp +++ b/src/utility/power/INA3221_Class.hpp @@ -36,6 +36,9 @@ namespace m5 /// @return voltage [mV] float getBusVoltage(uint8_t channel); float getShuntVoltage(uint8_t channel); + /// @return false on I2C failure, a null pointer, or an invalid channel (value is left untouched). + /// @note The unit follows getShuntMilliVoltage(uint8_t): raw register * 5. + bool getShuntMilliVoltage(uint8_t channel, int32_t* value); float getCurrent(uint8_t channel); int_fast16_t getBusMilliVoltage(uint8_t channel); diff --git a/src/utility/power/INA3221_Class.inl b/src/utility/power/INA3221_Class.inl index 12d62843..76ba6056 100644 --- a/src/utility/power/INA3221_Class.inl +++ b/src/utility/power/INA3221_Class.inl @@ -50,6 +50,15 @@ namespace m5 return res; } + bool INA3221_Class::getShuntMilliVoltage(uint8_t channel, int32_t* value) + { + if (channel >= INA3221_CH_NUM_MAX || value == nullptr) { return false; } + std::uint8_t buf[2]; + if (!readRegister(INA3221_CH1_SHUNT_V + (channel * 2), buf, 2)) { return false; } + *value = (int32_t)(int16_t)((buf[0] << 8) | buf[1]) * 5; + return true; + } + float INA3221_Class::getBusVoltage(uint8_t channel) { return getBusMilliVoltage(channel) / 1000.0f; diff --git a/src/utility/power/M5PM1_Class.hpp b/src/utility/power/M5PM1_Class.hpp index f5cc813a..411f299f 100644 --- a/src/utility/power/M5PM1_Class.hpp +++ b/src/utility/power/M5PM1_Class.hpp @@ -128,6 +128,8 @@ namespace m5 /// get PM1 GPIO output latch level, not the physical input level. bool getGPIOOutputLatch(gpio_t pin); + /// @return false before begin(), on I2C failure, or for an invalid pin (high is left untouched). + bool getGPIOOutputLatch(gpio_t pin, bool* high); /// set the PWM frequency in Hz. /// @note The frequency is shared by both PWM channels, so changing it also diff --git a/src/utility/power/M5PM1_Class.inl b/src/utility/power/M5PM1_Class.inl index 3c9ea107..f41c406c 100644 --- a/src/utility/power/M5PM1_Class.inl +++ b/src/utility/power/M5PM1_Class.inl @@ -197,8 +197,17 @@ namespace m5 bool M5PM1_Class::getGPIOOutputLatch(gpio_t pin) { - if (!_init || !is_valid_gpio(pin)) { return false; } - return readRegister8(M5PM1_REG_GPIO_OUT) & (1 << gpio_num(pin)); + bool high; + return getGPIOOutputLatch(pin, &high) && high; + } + + bool M5PM1_Class::getGPIOOutputLatch(gpio_t pin, bool* high) + { + if (!_init || !is_valid_gpio(pin) || high == nullptr) { return false; } + std::uint8_t v; + if (!readRegister(M5PM1_REG_GPIO_OUT, &v, 1)) { return false; } + *high = v & (1 << gpio_num(pin)); + return true; } bool M5PM1_Class::setPwmFrequency(std::uint16_t frequency)