~aleteoryx/muditaos

792a66323738ec1125f5403cbc1f4f02244adf06 — Mateusz Piesta 4 years ago 8fe0f2c
[BH-1333] CW2015 driver reactor

CW2015 fuel gauge refactor.
Fixed init logic.
M module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp +35 -5
@@ 64,6 64,7 @@ namespace hal::battery
        void sendNotification(Events event);
        SOC fetchBatterySOC() const;
        void pollFuelGauge();
        bool tryEnableCharging();

        xTimerHandle reinit_timer;
        xQueueHandle notification_channel;


@@ 88,15 89,25 @@ namespace hal::battery
                                                                               charger_gpio_chgok,
                                                                               charger_irq_pin_chgok,
                                                                               [this](const auto) {
                                                                                   sendNotification(Events::Charger);
                                                                                   /// Due to this layer performing
                                                                                   /// charging control automatically,
                                                                                   /// there is no need to notify higher
                                                                                   /// layers about \ref
                                                                                   /// MP2639B::ChargingStatus::PluggedNotCharging
                                                                                   /// event.
                                                                                   if (not tryEnableCharging()) {
                                                                                       sendNotification(
                                                                                           Events::Charger);
                                                                                   }
                                                                               }}},

          fuel_gauge_gpio{drivers::DriverGPIO::Create(fuel_gauge_irq_gpio_instance, {})},
          fuel_gauge{CW2015{drivers::DriverI2C::Create(i2c_instance, i2c_params)}}
          fuel_gauge{CW2015{*drivers::DriverI2C::Create(i2c_instance, i2c_params), battery_shutdown_threshold}}
    {

        reinit_timer = xTimerCreate("reinit_timer", reinit_poll_time, pdFALSE, this, [](TimerHandle_t xTimer) {
            BellBatteryCharger *inst = static_cast<BellBatteryCharger *>(pvTimerGetTimerID(xTimer));
            inst->fuel_gauge.reinit();
            inst->pollFuelGauge();
        });



@@ 113,6 124,8 @@ namespace hal::battery
            }
        });

        tryEnableCharging();

        pollFuelGauge();

        charger.enable_irq();


@@ 121,7 134,13 @@ namespace hal::battery

    AbstractBatteryCharger::Voltage BellBatteryCharger::getBatteryVoltage() const
    {
        return fuel_gauge.get_battery_voltage();
        if (const auto result = fuel_gauge.get_battery_voltage()) {
            return *result;
        }
        else {
            LOG_ERROR("Error during fetching battery voltage");
            return 0;
        }
    }

    AbstractBatteryCharger::SOC BellBatteryCharger::getSOC() const


@@ 151,7 170,7 @@ namespace hal::battery
            return ChargingStatus::PluggedNotCharging;
        }
        else if (charger_status == MP2639B::ChargingStatus::Charging && current_soc >= 100) {
            LOG_WARN("The charger reports 'Charging state', but the battery SOC is 100");
            LOG_INFO("The charger reports 'Charging state', but the battery SOC is 100");
            return ChargingStatus::ChargingDone;
        }
        else {


@@ 188,7 207,7 @@ namespace hal::battery
    /// chip.
    void BellBatteryCharger::pollFuelGauge()
    {
        if (fuel_gauge.poll() != 0) {
        if (not fuel_gauge) {
            LOG_WARN("Battery completely depleted, starting the re-initialization procedure");
            xTimerStart(reinit_timer, default_timeout);
        }


@@ 196,6 215,17 @@ namespace hal::battery
            LOG_INFO("Fuel gauge initialization success");
        }
    }

    bool BellBatteryCharger::tryEnableCharging()
    {
        if (charger.get_charge_status() == MP2639B::ChargingStatus::PluggedNotCharging) {
            charger.enable_charging(true);
            return true;
        }

        return false;
    }

    BaseType_t charger_irq()
    {
        return BellBatteryCharger::getWorkerQueueHandle().post(BellBatteryCharger::IrqEvents::Charger);

M module-bsp/board/rt1051/common/soc_scaler.cpp => module-bsp/board/rt1051/common/soc_scaler.cpp +2 -2
@@ 10,8 10,8 @@ namespace
    /// Scale SOC <5% to 0%.
    // clang-format off
    constexpr auto entries = std::array<utils::Entry<std::uint8_t>, 3>{{
            {{0, 4}, {0, 0}},
            {{5, 99}, {1, 99}},
            {{0, battery_shutdown_threshold-1}, {0, 0}},
            {{battery_shutdown_threshold, 99}, {1, 99}},
            {{100, 100}, {100, 100}}
    }};
    // clang-format on

M module-bsp/board/rt1051/common/soc_scaler.hpp => module-bsp/board/rt1051/common/soc_scaler.hpp +2 -0
@@ 6,4 6,6 @@
#include <Units.hpp>
#include <optional>

constexpr inline auto battery_shutdown_threshold = 5; /// %

std::optional<units::SOC> scale_soc(units::SOC soc);

M module-bsp/devices/power/CW2015.cpp => module-bsp/devices/power/CW2015.cpp +241 -272
@@ 2,102 2,67 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CW2015.hpp"
#include "CW2015CHBD.hpp"
#include "CW2015Regs.hpp"
#include "FreeRTOS.h"
#include "task.h"

/// CW2015 tips&tricks
/// 1. Setting POR bits in Mode register doesn't seem to reset the state of registers. Nevertheless it is required to
/// perform proper profile update.
/// 2. Setting POR, QRST or SLEEP bits always need to be followed with the wake-up. Slight delay (1ms is enough) between
/// two commands is also needed.
/// 3. Loading proper battery profile is crucial to the operation of the chip. It needs to be loaded each time the chip
/// is powered.

namespace
{
    using namespace bsp::power;
    auto constexpr FuelGaugeAlertCapacityPercent                                                         = 5;
    constexpr std::uint32_t i2cSubaddresSize                                                             = 1;
    constexpr inline auto MAGIC_LOOP_VALUE_DEFINED_BY_CHIP_VENDOR                                        = 30;
    constexpr inline auto MAGIC_VALUE_DEFINING_MAXIMUM_BATTERY_CAPACITY_IN_PERCENT                       = 100;
    constexpr inline auto MAGIC_VALUE_REPRESENTING_VOLTAGE_RESOLUTION_FOR_AD_CONVERTER_IN_MICROVOLTS     = 305;
    constexpr inline auto MAGIC_VALUE_REPRESENTING_DECIMAL_MULTIPLIER_TO_CONVERT_MICROVOLTS_TO_MILIVOLTS = 1000;
    constexpr inline auto MAGIC_VALUE_DEFINING_HOW_MANY_SAMPLES_ARE_TAKEN_TO_CALCULATE_MEDIAN            = 3;

    constexpr inline auto CW201X_ATHD =
        (static_cast<uint8_t>(FuelGaugeAlertCapacityPercent) << static_cast<uint8_t>(CONFIG::shift)) &
        static_cast<uint8_t>(CONFIG::mask);

    enum class CW201xRetCode : int
    {
        Success = 0,
        Error   = 1
    };
    constexpr std::uint32_t i2c_subaddr_size = 1;

    std::shared_ptr<drivers::DriverGPIO> gpio;

    drivers::I2CAddress fuelGaugeAddress = {FUEL_GAUGE_I2C_ADDR, 0, i2cSubaddresSize};
} // namespace

namespace bsp::devices::power
{
    CW2015::CW2015(std::shared_ptr<drivers::DriverI2C> i2c) : i2c{std::move(i2c)}
    using namespace CW2015Regs;

    CW2015::CW2015(drivers::DriverI2C &i2c, units::SOC soc) : i2c{i2c}, alert_threshold{soc}
    {
        state = init_chip();
        status = init_chip();
    }
    CW2015::~CW2015()
    {
        // enable sleep
        uint8_t mode                = static_cast<uint8_t>(MODE::Sleep);
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(static_cast<std::uint32_t>(Registers::MODE));
        auto ret                    = i2c->Write(fuelGaugeAddress, &mode, sizeof(uint8_t));

        if (ret != sizeof(uint8_t)) {
            LOG_ERROR("failed to put fuel gauge IC to sleep");
        }
        sleep();
    }
    std::optional<units::SOC> CW2015::get_battery_soc()
    {
        uint8_t level, reset_loop = 0;

        /// Added here to test how the quick start procedure affects SOC calculation. It probably should be moved to a
        /// different place, where the current draw is significantly smaller.
        quick_start();

        // Only higher byte of SOC register pair is needed here. Accuracy will be enough
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::SOC_H);
        const auto ret              = i2c->Read(fuelGaugeAddress, &level, sizeof(uint8_t));

        if (ret != sizeof(uint8_t)) {
            return {};
        }

        if (level > MAGIC_VALUE_DEFINING_MAXIMUM_BATTERY_CAPACITY_IN_PERCENT) {
            // error in getting capacity in %. It should be less than
            // MAGIC_VALUE_DEFINING_MAXIMUM_BATTERY_CAPACITY_IN_PERCENT
            reset_loop++;
            if (reset_loop > 5) {
                if (reset_chip()) // por ic
                    level = 0;
                reset_loop = 0;
            }
            return {};
        if (const auto result = read(SOC::ADDRESS_H)) {
            return *result;
        }
        else {
            reset_loop = 0;
            return std::nullopt;
        }

        return level;
    }
    units::Voltage CW2015::get_battery_voltage()
    std::optional<units::Voltage> CW2015::get_battery_voltage()
    {
        constexpr auto median_samples          = 3;
        constexpr auto micro_to_milli_ratio    = 1000;
        constexpr auto adc_conversion_constant = 305;

        uint8_t volt_h, volt_l;
        uint32_t ADMin = 0, ADMax = 0, ADResult = 0;

        /* Filter data - median*/
        for (int i = 0; i < MAGIC_VALUE_DEFINING_HOW_MANY_SAMPLES_ARE_TAKEN_TO_CALCULATE_MEDIAN; i++) {
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::VCELL_H);
            auto ret                    = i2c->Read(fuelGaugeAddress, &volt_h, sizeof(uint8_t));
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::VCELL_L);
            ret += i2c->Read(fuelGaugeAddress, &volt_l, sizeof(uint8_t));
            if (ret != sizeof(uint16_t)) {
                LOG_ERROR("failed to read fuel gauge alert");
        /// Filter data using median
        /// https://en.wikipedia.org/wiki/Median
        for (int i = 0; i < median_samples; i++) {
            const auto lsb = read(VCELL::ADDRESS_L);
            const auto msb = read(VCELL::ADDRESS_H);
            if (not lsb or not msb) {
                return std::nullopt;
            }

            uint16_t ADValue = (static_cast<uint16_t>(volt_h) << 8) | static_cast<uint16_t>(volt_l);
            const auto vtt = VCELL{*lsb, *msb};

            const auto ADValue = vtt.get_voltage();
            if (i == 0) {
                ADMin = ADValue;
                ADMax = ADValue;


@@ 115,232 80,95 @@ namespace bsp::devices::power
        ADResult -= ADMin;
        ADResult -= ADMax;

        /* Note: if above looks strange than it has to be explained that this is step sister of mean value called median
         * (ref: https://en.wikipedia.org/wiki/Median) */

        return static_cast<int>(ADResult * MAGIC_VALUE_REPRESENTING_VOLTAGE_RESOLUTION_FOR_AD_CONVERTER_IN_MICROVOLTS /
                                MAGIC_VALUE_REPRESENTING_DECIMAL_MULTIPLIER_TO_CONVERT_MICROVOLTS_TO_MILIVOLTS); // mV
        return ADResult * adc_conversion_constant / micro_to_milli_ratio; // mV
    }
    void CW2015::clear_irq()
    CW2015::RetCodes CW2015::clear_irq()
    {
        uint8_t rrt;

        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(static_cast<std::uint32_t>(Registers::SOC_H));
        auto ret                    = i2c->Read(fuelGaugeAddress, &rrt, sizeof(uint8_t));
        if (ret != sizeof(uint8_t)) {
            LOG_ERROR("failed to read fuel gauge alert");
        const auto lsb = read(RRT_ALERT::ADDRESS_L);
        const auto msb = read(RRT_ALERT::ADDRESS_H);
        if (not lsb or not msb) {
            return RetCodes::CommunicationError;
        }
        // clear flag
        rrt &= ~static_cast<uint8_t>(RRT_ALRT::nALRT);

        ret = i2c->Write(fuelGaugeAddress, &rrt, sizeof(uint8_t));
        if (ret != sizeof(uint8_t)) {
            LOG_ERROR("failed to reset fuel gauge alert flag");
        auto rrt = RRT_ALERT{*lsb, *msb};
        if (not write(RRT_ALERT::ADDRESS_H, rrt.clear_alert().get_raw())) {
            return RetCodes::CommunicationError;
        }

        return RetCodes::Ok;
    }

    std::int8_t CW2015::init_chip()
    CW2015::RetCodes CW2015::init_chip()
    {
        uint8_t i, RegVal = static_cast<uint8_t>(MODE::NORMAL);

        /* wake up cw2015/13 from sleep mode */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
        auto ret                    = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Fuel gauge: write CW201X_REG_MODE error!");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        if (const auto result = wake_up(); result != RetCodes::Ok) {
            return result;
        }

        /* check CW201X_ATHD if not right */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::CONFIG);
        ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Fuel gauge: read CW201X_REG_CONFIG error!");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        }
        if ((RegVal & 0xf8) != CW201X_ATHD) {
            //"the new CW201X_ATHD need set"
            RegVal &= ~(static_cast<uint8_t>(CONFIG::mask)); /* clear ATHD */
            RegVal |= CW201X_ATHD;                           /* set CW201X_ATHD */
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::CONFIG);
            ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
            if (ret != 1) {
                LOG_ERROR("Fuel gauge: write CW201X_REG_CONFIG error!");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
        /// Clear any pending interrupts(soc alarm)
        if (const auto result = clear_irq(); result != RetCodes::Ok) {
            return result;
        }

        /* check config_update_flag if not right */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::CONFIG);
        ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Fuel gauge: read CW201X_REG_CONFIG error!");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        }
        if (!(RegVal & static_cast<uint8_t>(CONFIG::UFG))) {
            //"update flag for new battery info need set"
            if (update_chip_config()) {
                LOG_ERROR("Fuel gauge: update_config_info error!");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
        if (const auto result = set_soc_threshold(alert_threshold); result != RetCodes::Ok) {
            return result;
        }
        else {
            for (i = 0; i < BATTERY_INFO_SIZE; i++) {
                fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::BATINFO) + i;
                ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
                if (ret != 1) {
                    LOG_ERROR("Fuel gauge: read CW201X_REG_BATINFO error!");
                    return static_cast<uint8_t>(CW201xRetCode::Error);
                }
                if (battery_info_config_info[i] != RegVal) {
                    break;
                }
            }

            if (i != BATTERY_INFO_SIZE) {
                RegVal                      = static_cast<uint8_t>(MODE::Sleep);
                fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
                ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
                if (ret != 1) {
                    LOG_ERROR("Fuel gauge: write CW201X_REG_MODE error!");
                    return static_cast<uint8_t>(CW201xRetCode::Error);
                }

                vTaskDelay(30); // delay 30ms

                RegVal                      = static_cast<uint8_t>(MODE::NORMAL);
                fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
                ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
                if (ret != 1) {
                    LOG_ERROR("Fuel gauge: write CW201X_REG_MODE error!");
                    return static_cast<uint8_t>(CW201xRetCode::Error);
                }

                //"update flag for new battery info need set"
                if (update_chip_config()) {
                    LOG_ERROR("Fuel gauge: update_config_info error!");
                    return static_cast<uint8_t>(CW201xRetCode::Error);
        if (const auto update = is_update_needed()) {
            if (*update) {
                LOG_INFO("Loading battery profile...");
                if (const auto res = load_profile(); res != RetCodes::Ok) {
                    return res;
                }
            }
        }
        /* check SOC if not equal 255 */
        for (i = 0; i < MAGIC_LOOP_VALUE_DEFINED_BY_CHIP_VENDOR; i++) {
            vTaskDelay(100); // delay 100ms
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::SOC_H);
            ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
            if (ret != 1) {
                LOG_ERROR("Fuel gauge: read CW201X_REG_SOC error!");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
            else if (RegVal <= MAGIC_VALUE_DEFINING_MAXIMUM_BATTERY_CAPACITY_IN_PERCENT)
                break;
        else {
            return RetCodes::CommunicationError;
        }

        if (i >= MAGIC_LOOP_VALUE_DEFINED_BY_CHIP_VENDOR) {
            RegVal                      = static_cast<uint8_t>(MODE::Sleep);
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
            ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
            if (ret != 1) {
                LOG_ERROR("Fuel gauge: write CW201X_REG_MODE error!");
            }
            return static_cast<uint8_t>(CW201xRetCode::Error);
        }
        return static_cast<uint8_t>(CW201xRetCode::Success);
        return wait_for_ready();
    }
    std::int8_t CW2015::update_chip_config()

    void CW2015::reinit()
    {
        uint8_t ResetVal = 0;
        uint8_t RegVal   = 0;

        /* make sure no in sleep mode */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
        auto ret                    = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Failed to read charger mode");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        }
        if ((RegVal & static_cast<uint8_t>(MODE::Sleep_mask)) == static_cast<uint8_t>(MODE::Sleep)) {
            LOG_ERROR("Charger in sleep mode during init");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        }
        /* update new battery info */
        for (int i = 0; i < BATTERY_INFO_SIZE; i++) {
            RegVal                      = battery_info_config_info[i];
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::BATINFO) + i;
            ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
            if (ret != 1) {
                LOG_ERROR("Failed to upload battery info");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
        }
        status = init_chip();
    }

        /* readback & check */
        for (int i = 0; i < BATTERY_INFO_SIZE; i++) {
            fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::BATINFO) + i;
            ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
            if (ret != 1) {
                LOG_ERROR("Failed to read battery info");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
            if (RegVal != battery_info_config_info[i]) {
                LOG_ERROR("Battery info invalid");
                return static_cast<uint8_t>(CW201xRetCode::Error);
            }
        }
        /* set cw2015/cw2013 to use new battery info */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(static_cast<std::uint32_t>(Registers::CONFIG));
        ret                         = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Failed to switch to custom battery info");
            return static_cast<uint8_t>(CW201xRetCode::Error);
    CW2015::RetCodes CW2015::load_profile()
    {
        if (const auto result = write_profile(); result != RetCodes::Ok) {
            return result;
        }

        RegVal |= static_cast<uint8_t>(CONFIG::UFG);     /* set UPDATE_FLAG */
        RegVal &= ~(static_cast<uint8_t>(CONFIG::mask)); /* clear ATHD */
        RegVal |= CW201X_ATHD;                           /* set ATHD */
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::CONFIG);
        ret                         = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Failed to update ALARM treshold value");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        if (const auto result = verify_profile(); result != RetCodes::Ok) {
            return result;
        }

        /* reset */
        ResetVal                    = static_cast<uint8_t>(MODE::NORMAL);
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
        ret                         = i2c->Write(fuelGaugeAddress, &ResetVal, sizeof(uint8_t));
        if (ret != 1) {
            LOG_ERROR("Failed to set charger to NORMAL mode");
            return static_cast<uint8_t>(CW201xRetCode::Error);
        if (const auto result = set_update_flag(); result != RetCodes::Ok) {
            return result;
        }
        /// Invoking reset procedure seems crucial for the chip to correctly load the new profile and show correct SOC
        /// at startup
        if (const auto result = reset_chip(); result != RetCodes::Ok) {
            return result;
        }

        vTaskDelay(1); // delay 100us. 100us is desired value but minimum resolution for delay here is 1ms

        return static_cast<uint8_t>(CW201xRetCode::Success);
        return RetCodes::Ok;
    }
    std::int8_t CW2015::reset_chip()
    CW2015::RetCodes CW2015::reset_chip()
    {
        uint8_t ResetVal = static_cast<uint8_t>(MODE::Sleep);

        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
        auto ret                    = i2c->Write(fuelGaugeAddress, &ResetVal, sizeof(uint8_t));
        if (ret != 1)
            return static_cast<uint8_t>(CW201xRetCode::Error);
        vTaskDelay(1); // delay 100us. 100us is desired value but minimum resolution for delay here is 1ms

        ResetVal                    = static_cast<uint8_t>(MODE::NORMAL);
        fuelGaugeAddress.subAddress = static_cast<std::uint32_t>(Registers::MODE);
        ret                         = i2c->Write(fuelGaugeAddress, &ResetVal, sizeof(uint8_t));
        if (ret != 1) {
            return static_cast<uint8_t>(CW201xRetCode::Error);
        if (not write(Mode::ADDRESS, Mode{}.set_power_on_reset().get_raw())) {
            return RetCodes::CommunicationError;
        }
        vTaskDelay(1); // delay 100us. 100us is desired value but minimum resolution for delay here is 1ms

        return init_chip();
        vTaskDelay(pdMS_TO_TICKS(1));
        if (not write(Mode::ADDRESS, Mode{}.set_wake_up().get_raw())) {
            return RetCodes::CommunicationError;
        }
        return RetCodes::Ok;
    }
    void CW2015::irq_handler()
    {
        clear_irq();
        if (const auto result = clear_irq(); result != RetCodes::Ok) {
            LOG_ERROR("Error during handling irq, %s", magic_enum::enum_name(result).data());
        }
    }
    void CW2015::init_irq_pin(std::shared_ptr<drivers::DriverGPIO> gpio, uint32_t pin)
    {


@@ 353,21 181,162 @@ namespace bsp::devices::power

        gpio->EnableInterrupt(1 << pin);
    }
    std::int8_t CW2015::quick_start()
    CW2015::RetCodes CW2015::quick_start()
    {
        drivers::I2CAddress address = {
            FUEL_GAUGE_I2C_ADDR, static_cast<std::uint32_t>(Registers::MODE), i2cSubaddresSize};
        uint8_t mode = static_cast<uint8_t>(MODE::QSTRT);
        auto result  = i2c->Write(fuelGaugeAddress, &mode, sizeof(uint8_t));
        if (result != 1) {
            return result;
        if (not write(Mode::ADDRESS, Mode{}.set_quick_start().get_raw())) {
            return RetCodes::CommunicationError;
        }
        vTaskDelay(pdMS_TO_TICKS(1));
        mode = static_cast<uint8_t>(MODE::NORMAL);
        return i2c->Write(fuelGaugeAddress, &mode, sizeof(uint8_t));
        if (not write(Mode::ADDRESS, Mode{}.set_wake_up().get_raw())) {
            return RetCodes::CommunicationError;
        }
        return RetCodes::Ok;
    }
    CW2015::RetCodes CW2015::poll() const
    {
        return status;
    }

    CW2015::RetCodes CW2015::sleep()
    {
        return write(Mode::ADDRESS, Mode{}.set_sleep().get_raw()) ? RetCodes::Ok : RetCodes::CommunicationError;
    }

    CW2015::RetCodes CW2015::wake_up()
    {
        return write(Mode::ADDRESS, Mode{}.set_wake_up().get_raw()) ? RetCodes::Ok : RetCodes::CommunicationError;
    }

    CW2015::RetCodes CW2015::set_soc_threshold(const std::uint8_t threshold)
    {
        const auto result = read(Config::ADDRESS);
        if (not result) {
            return RetCodes::CommunicationError;
        }
        /// Update only if the current one is different than requested
        auto config = Config{*result};
        if (config.get_alert_threshold() != threshold) {
            return write(Config::ADDRESS, config.set_threshold(threshold).get_raw()) ? RetCodes::Ok
                                                                                     : RetCodes::CommunicationError;
        }
        return RetCodes::Ok;
    }
    CW2015::TriState CW2015::is_update_needed()
    {
        const auto result = read(Config::ADDRESS);
        if (not result) {
            return std::nullopt;
        }
        /// Inverted logic here, low state means CW2015 needs battery profile updated
        return not Config{*result}.is_ufg_set();
    }
    CW2015::TriState CW2015::is_sleep_mode()
    {
        const auto result = read(Mode::ADDRESS);
        if (not result) {
            return std::nullopt;
        }

        return Mode{*result}.is_sleep();
    }

    /// This will tell CW2015 to use the new battery profile
    CW2015::RetCodes CW2015::set_update_flag()
    {
        const auto result = read(Config::ADDRESS);
        if (not result) {
            return RetCodes::CommunicationError;
        }
        return write(Config::ADDRESS, Config{*result}.set_ufg(true).get_raw()) ? RetCodes::Ok
                                                                               : RetCodes::CommunicationError;
    }
    std::int8_t CW2015::poll() const
    CW2015::RetCodes CW2015::wait_for_ready()
    {
        return state;
        constexpr auto max_battery_soc = 100;
        constexpr auto poll_retries    = 20U;
        constexpr auto poll_interval   = pdMS_TO_TICKS(50); /// 20 * 50ms = 1sec
        RetCodes ret_code{RetCodes::NotReady};

        std::int8_t poll_count{poll_retries};
        while (poll_count-- > 0) {
            const auto soc     = get_battery_soc();
            const auto voltage = get_battery_voltage();
            if (not soc or not voltage) {
                return RetCodes::CommunicationError;
            }
            else if (*soc <= max_battery_soc and *voltage > 0) {
                return RetCodes::Ok;
            }
            vTaskDelay(poll_interval);
        }
        return ret_code;
    }
    CW2015::RetCodes CW2015::verify_profile()
    {
        BATTINFO profile{};
        RetCodes ret_code{RetCodes::Ok};

        const auto result = std::all_of(
            profile.cbegin(), profile.cend(), [this, ret_code, reg = BATTINFO::ADDRESS](const auto &e) mutable {
                const auto result = read(reg++);
                if (not result) {
                    ret_code = RetCodes::CommunicationError;
                    return false;
                }
                if (*result != e) {
                    ret_code = RetCodes::ProfileInvalid;
                    return false;
                }
                return true;
            });

        return ret_code;
    }
    void CW2015::dumpRegisters()
    {
        const auto mode   = read(Mode::ADDRESS);
        const auto config = read(Config::ADDRESS);
        const auto soc    = read(SOC::ADDRESS_H);
        LOG_DEBUG("Mode: 0x%x", *mode);
        LOG_DEBUG("Config: 0x%x", *config);
        LOG_DEBUG("SOC: 0x%x", *soc);
    }

    CW2015::RetCodes CW2015::write_profile()
    {
        BATTINFO profile{};

        const auto result =
            std::all_of(profile.cbegin(), profile.cend(), [this, reg = BATTINFO::ADDRESS](const auto &e) mutable {
                return write(reg++, e);
            });

        return result ? RetCodes::Ok : RetCodes::CommunicationError;
    }

    std::optional<std::uint8_t> CW2015::read(const std::uint8_t reg)
    {
        const drivers::I2CAddress fuelGaugeAddress = {ADDRESS, reg, i2c_subaddr_size};
        std::uint8_t ret_val{};
        if (const auto result = i2c.Read(fuelGaugeAddress, &ret_val, i2c_subaddr_size); result == i2c_subaddr_size) {
            return ret_val;
        }
        else {
            return std::nullopt;
        }
    }
    bool CW2015::write(const std::uint8_t reg, const std::uint8_t value)
    {
        const drivers::I2CAddress fuelGaugeAddress = {ADDRESS, reg, i2c_subaddr_size};
        return i2c.Write(fuelGaugeAddress, &value, i2c_subaddr_size) == i2c_subaddr_size;
    }
    CW2015::operator bool() const
    {
        return status == RetCodes::Ok;
    }
    CW2015::RetCodes CW2015::calibrate()
    {
        return quick_start();
    }

} // namespace bsp::devices::power

M module-bsp/devices/power/CW2015.hpp => module-bsp/devices/power/CW2015.hpp +48 -10
@@ 16,30 16,68 @@ namespace bsp::devices::power
    class CW2015
    {
      public:
        explicit CW2015(std::shared_ptr<drivers::DriverI2C> i2c);
        enum class RetCodes : std::uint8_t
        {
            Ok,
            CommunicationError,
            ProfileInvalid,
            NotReady
        };

        CW2015(drivers::DriverI2C &i2c, units::SOC soc);
        ~CW2015();

        CW2015(const CW2015 &)     = delete;
        CW2015(CW2015 &&) noexcept = default;
        CW2015 &operator=(const CW2015 &) = delete;
        CW2015 &operator=(CW2015 &&) noexcept = delete;

        /// Returns the result of the chip initialization
        std::int8_t poll() const;
        RetCodes poll() const;
        /// Returns true if the chip initialization success, otherwise false
        explicit operator bool() const;
        /// Explicitly triggers chip initialization procedure. The result can be polled by \ref poll or using bool
        /// operator
        void reinit();

        RetCodes calibrate();

        std::optional<units::SOC> get_battery_soc();

        units::Voltage get_battery_voltage();
        std::optional<units::Voltage> get_battery_voltage();

        void init_irq_pin(std::shared_ptr<drivers::DriverGPIO> gpio, uint32_t pin);
        void irq_handler();

      private:
        void clear_irq();
        /// Might return valid value which then can be tested for true of false
        using TriState = std::optional<bool>;

        RetCodes clear_irq();

        RetCodes init_chip();
        RetCodes reset_chip();
        RetCodes load_profile();
        RetCodes verify_profile();
        RetCodes write_profile();

        RetCodes quick_start();
        RetCodes sleep();
        RetCodes wake_up();
        RetCodes set_soc_threshold(std::uint8_t threshold);
        RetCodes set_update_flag();
        RetCodes wait_for_ready();
        TriState is_update_needed();
        TriState is_sleep_mode();

        std::int8_t init_chip();
        std::int8_t reset_chip();
        std::int8_t update_chip_config();
        void dumpRegisters();

        std::int8_t quick_start();
        std::optional<std::uint8_t> read(std::uint8_t reg);
        bool write(std::uint8_t reg, std::uint8_t value);

        std::shared_ptr<drivers::DriverI2C> i2c;
        std::int8_t state{};
        drivers::DriverI2C &i2c;
        RetCodes status{};
        units::SOC alert_threshold;
    };

} // namespace bsp::devices::power

D module-bsp/devices/power/CW2015CHBD.hpp => module-bsp/devices/power/CW2015CHBD.hpp +0 -68
@@ 1,68 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once

namespace bsp::power
{

    constexpr inline auto FUEL_GAUGE_I2C_ADDR = 0x62;
    constexpr inline auto BATTERY_INFO_SIZE   = 64;

    enum class Registers
    {
        VERSION    = 0x00,
        VCELL_H    = 0x02,
        VCELL_L    = 0x03,
        SOC_H      = 0x04,
        SOC_L      = 0x05,
        RRT_ALRT_H = 0x06,
        RRT_ALRT_L = 0x07,
        CONFIG     = 0x08,
        MODE       = 0x0A,
        BATINFO    = 0x10
    };

    enum class VCELL
    {
        mask  = 0x3FFF, // battery voltage. 305uV resolution
        shift = 0
    };

    enum class SOC
    {
        mask  = 0xFFFF, // MSB - remaining charge in %. LSB Precise value - each bit is 1/256%
        shift = 0
    };

    enum class RRT_ALRT
    {
        mask  = 0x1FFF, // remaining battery time value. 1 minute per LSB accuracy
        shift = 0,
        nALRT = (1 << 15) // Alert flag. Active low
    };

    enum class CONFIG
    {
        mask  = 0xF8, // low level threshold (0% - 31%)
        shift = 3,
        UFG   = (1 << 1) // battery information update state
    };

    enum class MODE
    {
        Sleep_mask = (0x3 << 6),
        Sleep      = (0x03 << 6), // write 11b to force sleep mode
        NORMAL     = (0x00 << 6), // write 00b to force normal mode
        QSTRT      = (0x03 << 4), // write 11b to enter QSTRT mode
        POR        = 0x0F         // write 1111b to restart IC
    };

    /* got from ODM init code */
    constexpr uint8_t battery_info_config_info[BATTERY_INFO_SIZE] = {
        // profile_DEM50X_2nd_20211012
        0x15, 0x15, 0x6E, 0x67, 0x65, 0x62, 0x60, 0x60, 0x5F, 0x5E, 0x5B, 0x59, 0x55, 0x50, 0x41, 0x33,
        0x2A, 0x26, 0x24, 0x27, 0x31, 0x46, 0x55, 0x5B, 0x47, 0x4A, 0x0A, 0x3E, 0x38, 0x58, 0x59, 0x63,
        0x67, 0x63, 0x62, 0x64, 0x3D, 0x1B, 0x6F, 0x15, 0x07, 0x21, 0x54, 0x85, 0x8F, 0x90, 0x90, 0x44,
        0x63, 0x86, 0x94, 0x99, 0x80, 0x89, 0xBC, 0xCB, 0x2F, 0x00, 0x64, 0xA5, 0xB5, 0xC1, 0x46, 0xAE};

} // namespace bsp::power

A module-bsp/devices/power/CW2015Regs.hpp => module-bsp/devices/power/CW2015Regs.hpp +226 -0
@@ 0,0 1,226 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once

#include <array>
#include <Units.hpp>

namespace bsp::devices::power::CW2015Regs
{
    constexpr inline auto ADDRESS = 0x62;

    struct Config
    {
        constexpr static auto ADDRESS = 0x08;

        explicit Config(const std::uint8_t raw) : reg{raw}
        {}

        units::SOC get_alert_threshold() const
        {
            return (reg & alert_mask) >> alert_shift;
        }

        Config &set_threshold(const units::SOC soc)
        {
            reg &= ~alert_mask;
            reg |= soc << alert_shift;
            return *this;
        }

        bool is_ufg_set() const
        {
            return (reg & ufg_mask) != 0;
        }

        Config &set_ufg(bool val)
        {
            reg &= ~ufg_mask;
            reg |= val << ufg_shift;
            return *this;
        }

        std::uint8_t get_raw() const
        {
            return reg;
        }

      private:
        std::uint8_t reg{};

        static constexpr std::uint8_t alert_mask  = 0xF8; // low level threshold (0% - 31%)
        static constexpr std::uint8_t alert_shift = 3;

        static constexpr std::uint8_t ufg_shift = 1;
        static constexpr std::uint8_t ufg_mask  = 1 << ufg_shift;
    };

    struct Mode
    {
        constexpr static auto ADDRESS = 0x0A;

        Mode() = default;
        explicit Mode(const std::uint8_t raw) : reg{raw}
        {}

        bool is_sleep() const
        {
            return (reg & sleep_mask) == sleep_mask;
        }

        Mode &set_wake_up()
        {
            reg &= ~sleep_mask;
            return *this;
        }

        Mode &set_sleep()
        {
            reg |= sleep_mask;
            return *this;
        }

        Mode &set_quick_start()
        {
            reg |= qstrt_mask;
            return *this;
        }

        Mode &set_power_on_reset()
        {
            reg |= por_mask;
            return *this;
        }

        std::uint8_t get_raw() const
        {
            return reg;
        }

      private:
        std::uint8_t reg{};
        static constexpr std::uint8_t sleep_shift = 6;
        static constexpr std::uint8_t sleep_mask  = 0b11 << sleep_shift;

        static constexpr std::uint8_t qstrt_shift = 4;
        static constexpr std::uint8_t qstrt_mask  = 0b11 << qstrt_shift;

        static constexpr std::uint8_t por_shift = 0;
        static constexpr std::uint8_t por_mask  = 0b1111 << por_shift;
    };

    struct RRT_ALERT
    {
        constexpr static auto ADDRESS_H = 0x06;
        constexpr static auto ADDRESS_L = 0x07;

        RRT_ALERT(const std::uint8_t lsb, const std::uint8_t msb) : reg{static_cast<uint16_t>(lsb | (msb << 8))}
        {}
        explicit RRT_ALERT(const std::uint16_t reg) : reg{reg}
        {}

        bool is_alert_triggered() const
        {
            return (reg & 0x8000) == 0;
        }

        RRT_ALERT &clear_alert()
        {
            reg |= 0x8000;
            return *this;
        }

        std::uint16_t rrt() const
        {
            return reg & 0x1FFF;
        }

        std::uint8_t get_raw() const
        {
            return reg;
        }

      private:
        std::uint16_t reg{};
    };

    struct VCELL
    {
        constexpr static auto ADDRESS_H = 0x02;
        constexpr static auto ADDRESS_L = 0x03;

        VCELL(const std::uint8_t lsb, const std::uint8_t msb) : reg{static_cast<uint16_t>(lsb | (msb << 8))}
        {}
        explicit VCELL(const std::uint16_t reg) : reg{reg}
        {}

        units::Voltage get_voltage() const
        {
            return reg & 0x3FFF;
        }

        std::uint8_t get_raw() const
        {
            return reg;
        }

      private:
        const std::uint16_t reg{};
    };

    struct SOC
    {
        constexpr static auto ADDRESS_H = 0x04;
        constexpr static auto ADDRESS_L = 0x05;

        SOC(const std::uint8_t lsb, const std::uint8_t msb) : reg{static_cast<uint16_t>(lsb | (msb << 8))}
        {}
        explicit SOC(const std::uint16_t reg) : reg{reg}
        {}

        units::Voltage get_soc() const
        {
            return (reg & 0xFF00) >> 8U;
        }

      private:
        const std::uint16_t reg{};
    };

    class BATTINFO
    {
        constexpr static auto BATTERY_INFO_SIZE = 64;
        using Type                              = const std::array<std::uint8_t, BATTERY_INFO_SIZE>;

        /* got from ODM init code */
        constexpr static Type config_info = {
            // profile_DEM50X_2nd_20211012
            0x15, 0x15, 0x6E, 0x67, 0x65, 0x62, 0x60, 0x60, 0x5F, 0x5E, 0x5B, 0x59, 0x55, 0x50, 0x41, 0x33,
            0x2A, 0x26, 0x24, 0x27, 0x31, 0x46, 0x55, 0x5B, 0x47, 0x4A, 0x0A, 0x3E, 0x38, 0x58, 0x59, 0x63,
            0x67, 0x63, 0x62, 0x64, 0x3D, 0x1B, 0x6F, 0x15, 0x07, 0x21, 0x54, 0x85, 0x8F, 0x90, 0x90, 0x44,
            0x63, 0x86, 0x94, 0x99, 0x80, 0x89, 0xBC, 0xCB, 0x2F, 0x00, 0x64, 0xA5, 0xB5, 0xC1, 0x46, 0xAE};

      public:
        constexpr static auto ADDRESS = 0x10;

        Type::const_iterator begin() const noexcept
        {
            return config_info.begin();
        }

        Type::const_iterator end() const noexcept
        {
            return config_info.end();
        }

        Type::const_iterator cbegin()
        {
            return config_info.cbegin();
        }

        Type::const_iterator cend()
        {
            return config_info.cend();
        }
    };
} // namespace bsp::devices::power::CW2015Regs

M module-bsp/devices/power/MP2639B.cpp => module-bsp/devices/power/MP2639B.cpp +32 -13
@@ 8,8 8,12 @@ namespace bsp::devices::power
    MP2639B::MP2639B(const Configuration &conf) : configuration{conf}
    {
        irq_filter_timer =
            xTimerCreate("irq_charger_filter", pdMS_TO_TICKS(500), pdFALSE, this, [](TimerHandle_t xTimer) {
            xTimerCreate("irq_charger_filter", pdMS_TO_TICKS(50), pdFALSE, this, [](TimerHandle_t xTimer) {
                MP2639B *inst = static_cast<MP2639B *>(pvTimerGetTimerID(xTimer));
                /// Charging disable is done automatically if the conditions are met.
                if (not inst->is_valid_voltage()) {
                    inst->enable_charging(false);
                }
                inst->configuration.notification(inst->get_charge_status());
            });



@@ 20,8 24,8 @@ namespace bsp::devices::power
        mode_pin_params.pin      = configuration.mode_pin;
        configuration.mode_gpio->ConfPin(mode_pin_params);

        /// Set the initial charging state
        enable_charging(configuration.acok_gpio->ReadPin(configuration.acok_pin) == 0u);
        /// Always start with charging disabled
        enable_charging(false);
    }

    MP2639B::~MP2639B()


@@ 34,19 38,21 @@ namespace bsp::devices::power
    }
    MP2639B::ChargingStatus MP2639B::get_charge_status()
    {
        /// For more info, please refer to the Table 1 "Charging Status Indication" in reference manual.

        ChargingStatus status{};

        const auto acok  = configuration.acok_gpio->ReadPin(configuration.acok_pin);
        const auto chgok = configuration.chgok_gpio->ReadPin(configuration.chgok_pin);
        const auto valid_voltage = is_valid_voltage();
        const auto charging      = is_charging();

        if (acok == 1 && chgok == 1) {
        if (not valid_voltage) {
            status = ChargingStatus::Discharging;
        }
        else if (acok == 0 && chgok == 0) {
            status = ChargingStatus::Charging;
        else if (valid_voltage and is_charging_enabled()) {
            status = charging ? ChargingStatus::Charging : ChargingStatus::Complete;
        }
        else if (acok == 0 && chgok == 1) {
            status = ChargingStatus::Complete;
        else if (valid_voltage and not is_charging_enabled()) {
            status = ChargingStatus::PluggedNotCharging;
        }
        // TODO: add error condition, i.e when chgok blinks at 1Hz
        return status;


@@ 76,18 82,31 @@ namespace bsp::devices::power
        CHGOKPinConfig.pin      = configuration.chgok_pin;
        configuration.chgok_gpio->ConfPin(CHGOKPinConfig);

        /* Enable interrupts */
        configuration.chgok_gpio->EnableInterrupt(1U << configuration.chgok_pin);
        configuration.acok_gpio->EnableInterrupt(1U << configuration.acok_pin);
    }

    void MP2639B::handle_irq()
    {
        enable_charging(configuration.acok_gpio->ReadPin(configuration.acok_pin) == 0u);

        if (xTimerIsTimerActive(irq_filter_timer) == pdFALSE) {
            xTimerStart(irq_filter_timer, pdMS_TO_TICKS(100));
        }
    }
    bool MP2639B::is_charging_enabled() const
    {
        return configuration.mode_gpio->ReadPin(configuration.mode_pin) == 0;
    }
    bool MP2639B::is_valid_voltage() const
    {
        /// ACOK -> Valid Input Supply Indicator. A logic Low on this pin indicates the presence of a valid input power
        /// supply.
        return configuration.acok_gpio->ReadPin(configuration.acok_pin) == 0;
    }
    bool MP2639B::is_charging() const
    {
        /// CHGOK -> Charging Completion Indicator. A logic Low indicates charging operation. The pin will become an
        /// open drain once the charge is completed or suspended.
        return configuration.chgok_gpio->ReadPin(configuration.chgok_pin) == 0;
    }

} // namespace bsp::devices::power

M module-bsp/devices/power/MP2639B.hpp => module-bsp/devices/power/MP2639B.hpp +6 -2
@@ 18,12 18,13 @@ namespace bsp::devices::power
    class MP2639B
    {
      public:
        enum class ChargingStatus
        enum class ChargingStatus : std::uint8_t
        {
            Discharging,
            Charging,
            Complete,
            Error
            PluggedNotCharging, /// Charger plugged-in, input voltage valid but charging explicitly disabled
            Error,
        };

        struct Configuration


@@ 58,6 59,9 @@ namespace bsp::devices::power
        void handle_irq();

      private:
        bool is_charging() const;
        bool is_valid_voltage() const;
        bool is_charging_enabled() const;
        Configuration configuration;
        xTimerHandle irq_filter_timer;
    };

M module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp => module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp +1 -1
@@ 22,7 22,7 @@ namespace hal::battery
            Discharging,
            Charging,
            ChargingDone,
            PluggedNotCharging
            PluggedNotCharging /// Charger plugged-in, input voltage valid but charging explicitly disabled
        };

        enum class Events : std::uint8_t