From 792a66323738ec1125f5403cbc1f4f02244adf06 Mon Sep 17 00:00:00 2001 From: Mateusz Piesta Date: Thu, 3 Feb 2022 16:29:20 +0100 Subject: [PATCH] [BH-1333] CW2015 driver reactor CW2015 fuel gauge refactor. Fixed init logic. --- .../hal/battery_charger/BatteryCharger.cpp | 40 +- module-bsp/board/rt1051/common/soc_scaler.cpp | 4 +- module-bsp/board/rt1051/common/soc_scaler.hpp | 2 + module-bsp/devices/power/CW2015.cpp | 513 ++++++++---------- module-bsp/devices/power/CW2015.hpp | 58 +- module-bsp/devices/power/CW2015CHBD.hpp | 68 --- module-bsp/devices/power/CW2015Regs.hpp | 226 ++++++++ module-bsp/devices/power/MP2639B.cpp | 45 +- module-bsp/devices/power/MP2639B.hpp | 8 +- .../AbstractBatteryCharger.hpp | 2 +- 10 files changed, 593 insertions(+), 373 deletions(-) delete mode 100644 module-bsp/devices/power/CW2015CHBD.hpp create mode 100644 module-bsp/devices/power/CW2015Regs.hpp diff --git a/module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp b/module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp index ee12d593698363b7f917ab94f10b7bd5811a9904..9ca441ab83aaac28f2df0692f1a041206f5ee090 100644 --- a/module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp +++ b/module-bsp/board/rt1051/bellpx/hal/battery_charger/BatteryCharger.cpp @@ -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(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); diff --git a/module-bsp/board/rt1051/common/soc_scaler.cpp b/module-bsp/board/rt1051/common/soc_scaler.cpp index 9b72c5491dc809f375799e77b1c2326feb1ef586..206e70ffd27437c098a03a671696d5499918e0c2 100644 --- a/module-bsp/board/rt1051/common/soc_scaler.cpp +++ b/module-bsp/board/rt1051/common/soc_scaler.cpp @@ -10,8 +10,8 @@ namespace /// Scale SOC <5% to 0%. // clang-format off constexpr auto entries = std::array, 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 diff --git a/module-bsp/board/rt1051/common/soc_scaler.hpp b/module-bsp/board/rt1051/common/soc_scaler.hpp index 93701ad3475cd6267956ab7a465abecea46b7f6f..c6e03a2e963734b48ceed0c1fe388dece4e4f050 100644 --- a/module-bsp/board/rt1051/common/soc_scaler.hpp +++ b/module-bsp/board/rt1051/common/soc_scaler.hpp @@ -6,4 +6,6 @@ #include #include +constexpr inline auto battery_shutdown_threshold = 5; /// % + std::optional scale_soc(units::SOC soc); diff --git a/module-bsp/devices/power/CW2015.cpp b/module-bsp/devices/power/CW2015.cpp index bb5c4114ad4a24524dff3504538e6c92634f7574..0e32f6f980acc096c68bcbd1421c792c8a955a1e 100644 --- a/module-bsp/devices/power/CW2015.cpp +++ b/module-bsp/devices/power/CW2015.cpp @@ -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(FuelGaugeAlertCapacityPercent) << static_cast(CONFIG::shift)) & - static_cast(CONFIG::mask); - - enum class CW201xRetCode : int - { - Success = 0, - Error = 1 - }; + constexpr std::uint32_t i2c_subaddr_size = 1; - std::shared_ptr gpio; - - drivers::I2CAddress fuelGaugeAddress = {FUEL_GAUGE_I2C_ADDR, 0, i2cSubaddresSize}; } // namespace namespace bsp::devices::power { - CW2015::CW2015(std::shared_ptr 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(MODE::Sleep); - fuelGaugeAddress.subAddress = static_cast(static_cast(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 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(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 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(Registers::VCELL_H); - auto ret = i2c->Read(fuelGaugeAddress, &volt_h, sizeof(uint8_t)); - fuelGaugeAddress.subAddress = static_cast(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(volt_h) << 8) | static_cast(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(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(static_cast(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(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(MODE::NORMAL); - - /* wake up cw2015/13 from sleep mode */ - fuelGaugeAddress.subAddress = static_cast(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(CW201xRetCode::Error); + if (const auto result = wake_up(); result != RetCodes::Ok) { + return result; } - /* check CW201X_ATHD if not right */ - fuelGaugeAddress.subAddress = static_cast(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(CW201xRetCode::Error); - } - if ((RegVal & 0xf8) != CW201X_ATHD) { - //"the new CW201X_ATHD need set" - RegVal &= ~(static_cast(CONFIG::mask)); /* clear ATHD */ - RegVal |= CW201X_ATHD; /* set CW201X_ATHD */ - fuelGaugeAddress.subAddress = static_cast(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(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(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(CW201xRetCode::Error); - } - if (!(RegVal & static_cast(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(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(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(CW201xRetCode::Error); - } - if (battery_info_config_info[i] != RegVal) { - break; - } - } - - if (i != BATTERY_INFO_SIZE) { - RegVal = static_cast(MODE::Sleep); - fuelGaugeAddress.subAddress = static_cast(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(CW201xRetCode::Error); - } - - vTaskDelay(30); // delay 30ms - RegVal = static_cast(MODE::NORMAL); - fuelGaugeAddress.subAddress = static_cast(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(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(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(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(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(MODE::Sleep); - fuelGaugeAddress.subAddress = static_cast(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(CW201xRetCode::Error); - } - return static_cast(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(Registers::MODE); - auto ret = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t)); - if (ret != 1) { - LOG_ERROR("Failed to read charger mode"); - return static_cast(CW201xRetCode::Error); - } - if ((RegVal & static_cast(MODE::Sleep_mask)) == static_cast(MODE::Sleep)) { - LOG_ERROR("Charger in sleep mode during init"); - return static_cast(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(Registers::BATINFO) + i; - ret = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t)); - if (ret != 1) { - LOG_ERROR("Failed to upload battery info"); - return static_cast(CW201xRetCode::Error); - } - } + status = init_chip(); + } - /* readback & check */ - for (int i = 0; i < BATTERY_INFO_SIZE; i++) { - fuelGaugeAddress.subAddress = static_cast(Registers::BATINFO) + i; - ret = i2c->Read(fuelGaugeAddress, &RegVal, sizeof(uint8_t)); - if (ret != 1) { - LOG_ERROR("Failed to read battery info"); - return static_cast(CW201xRetCode::Error); - } - if (RegVal != battery_info_config_info[i]) { - LOG_ERROR("Battery info invalid"); - return static_cast(CW201xRetCode::Error); - } - } - /* set cw2015/cw2013 to use new battery info */ - fuelGaugeAddress.subAddress = static_cast(static_cast(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(CW201xRetCode::Error); + CW2015::RetCodes CW2015::load_profile() + { + if (const auto result = write_profile(); result != RetCodes::Ok) { + return result; } - RegVal |= static_cast(CONFIG::UFG); /* set UPDATE_FLAG */ - RegVal &= ~(static_cast(CONFIG::mask)); /* clear ATHD */ - RegVal |= CW201X_ATHD; /* set ATHD */ - fuelGaugeAddress.subAddress = static_cast(Registers::CONFIG); - ret = i2c->Write(fuelGaugeAddress, &RegVal, sizeof(uint8_t)); - if (ret != 1) { - LOG_ERROR("Failed to update ALARM treshold value"); - return static_cast(CW201xRetCode::Error); + if (const auto result = verify_profile(); result != RetCodes::Ok) { + return result; } - /* reset */ - ResetVal = static_cast(MODE::NORMAL); - fuelGaugeAddress.subAddress = static_cast(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(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(CW201xRetCode::Success); + return RetCodes::Ok; } - std::int8_t CW2015::reset_chip() + CW2015::RetCodes CW2015::reset_chip() { - uint8_t ResetVal = static_cast(MODE::Sleep); - - fuelGaugeAddress.subAddress = static_cast(Registers::MODE); - auto ret = i2c->Write(fuelGaugeAddress, &ResetVal, sizeof(uint8_t)); - if (ret != 1) - return static_cast(CW201xRetCode::Error); - vTaskDelay(1); // delay 100us. 100us is desired value but minimum resolution for delay here is 1ms - - ResetVal = static_cast(MODE::NORMAL); - fuelGaugeAddress.subAddress = static_cast(Registers::MODE); - ret = i2c->Write(fuelGaugeAddress, &ResetVal, sizeof(uint8_t)); - if (ret != 1) { - return static_cast(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 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(Registers::MODE), i2cSubaddresSize}; - uint8_t mode = static_cast(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(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 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 diff --git a/module-bsp/devices/power/CW2015.hpp b/module-bsp/devices/power/CW2015.hpp index aa0ed6487e26c2b2537fe3ba60daa12ca29d6f49..a39e84d30950cdad7c424dbdc1edd4e4eb7ff509 100644 --- a/module-bsp/devices/power/CW2015.hpp +++ b/module-bsp/devices/power/CW2015.hpp @@ -16,30 +16,68 @@ namespace bsp::devices::power class CW2015 { public: - explicit CW2015(std::shared_ptr 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 get_battery_soc(); - units::Voltage get_battery_voltage(); + std::optional get_battery_voltage(); void init_irq_pin(std::shared_ptr 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; + + 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 read(std::uint8_t reg); + bool write(std::uint8_t reg, std::uint8_t value); - std::shared_ptr i2c; - std::int8_t state{}; + drivers::DriverI2C &i2c; + RetCodes status{}; + units::SOC alert_threshold; }; } // namespace bsp::devices::power diff --git a/module-bsp/devices/power/CW2015CHBD.hpp b/module-bsp/devices/power/CW2015CHBD.hpp deleted file mode 100644 index 861bf33bac70479238052d964e14cfd71fbce1f7..0000000000000000000000000000000000000000 --- a/module-bsp/devices/power/CW2015CHBD.hpp +++ /dev/null @@ -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 diff --git a/module-bsp/devices/power/CW2015Regs.hpp b/module-bsp/devices/power/CW2015Regs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..76546ce799e1bc04eb14f88d850a530083c1d5fa --- /dev/null +++ b/module-bsp/devices/power/CW2015Regs.hpp @@ -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 +#include + +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(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(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(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; + + /* 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 diff --git a/module-bsp/devices/power/MP2639B.cpp b/module-bsp/devices/power/MP2639B.cpp index 935a0a5c472eb100434afd6b65496ef0e5e61b81..43c8e8ea7b1023e467c652f278f0dc52f6967bda 100644 --- a/module-bsp/devices/power/MP2639B.cpp +++ b/module-bsp/devices/power/MP2639B.cpp @@ -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(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 diff --git a/module-bsp/devices/power/MP2639B.hpp b/module-bsp/devices/power/MP2639B.hpp index 1b6a48ae4985dbdd88eea42947afab8763cda1ff..a0997e32890927e7e6a00b30ea046a7941b28cc6 100644 --- a/module-bsp/devices/power/MP2639B.hpp +++ b/module-bsp/devices/power/MP2639B.hpp @@ -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; }; diff --git a/module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp b/module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp index 66faf6dd81cdea00b0790a191bfa7107f7914096..9330132566f7d22c688936d560acd6c1c6abf7b1 100644 --- a/module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp +++ b/module-bsp/hal/include/hal/battery_charger/AbstractBatteryCharger.hpp @@ -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