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