diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 3b81ab54..f8871aa4 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -177,6 +177,7 @@ set(CPPSRC ${COMMON}/utility.cpp ${COMMON}/wm8731.cpp ${COMMON}/ads1110.cpp + ${COMMON}/max17055.cpp ${COMMON}/battery.cpp ${COMMON}/performance_counter.cpp ${COMMON}/bmpfile.cpp diff --git a/firmware/application/apps/ui_battinfo.cpp b/firmware/application/apps/ui_battinfo.cpp index 3130f279..bb9c3339 100644 --- a/firmware/application/apps/ui_battinfo.cpp +++ b/firmware/application/apps/ui_battinfo.cpp @@ -53,7 +53,7 @@ void BattinfoView::update_result() { return; } bool uichg = false; - battery::BatteryManagement::getBatteryInfo(percent, voltage, current, isCharging); + battery::BatteryManagement::getBatteryInfo(percent, voltage, current); // update text fields if (percent <= 100) text_percent.set(to_string_dec_uint(percent) + " %"); @@ -69,8 +69,8 @@ void BattinfoView::update_result() { labels_opt.hidden(false); text_current.hidden(false); text_charge.hidden(false); - text_current.set(to_string_dec_int(current) + " mA"); - text_charge.set(isCharging ? "charge" : "discharge"); + text_current.set(to_string_decimal(current / 100000.0, 3) + " mA"); + text_charge.set(current >= 0 ? "Charging" : "Discharging"); labels_opt.hidden(false); } else { if (!labels_opt.hidden()) uichg = true; @@ -80,7 +80,7 @@ void BattinfoView::update_result() { } if (uichg) set_dirty(); // to update status bar too, send message in behalf of batt manager - BatteryStateMessage msg{percent, isCharging, voltage}; + BatteryStateMessage msg{percent, current >= 0, voltage}; EventDispatcher::send_message(msg); } diff --git a/firmware/application/apps/ui_battinfo.hpp b/firmware/application/apps/ui_battinfo.hpp index a64600da..979abf3e 100644 --- a/firmware/application/apps/ui_battinfo.hpp +++ b/firmware/application/apps/ui_battinfo.hpp @@ -50,7 +50,6 @@ class BattinfoView : public View { uint8_t percent = 0; uint16_t voltage = 0; int32_t current = 0; - bool isCharging = false; Labels labels{ {{2 * 8, 1 * 16}, "Percent:", Theme::getInstance()->fg_light->foreground}, diff --git a/firmware/application/apps/ui_debug.cpp b/firmware/application/apps/ui_debug.cpp index cb8793f6..33112e93 100644 --- a/firmware/application/apps/ui_debug.cpp +++ b/firmware/application/apps/ui_debug.cpp @@ -225,6 +225,8 @@ uint32_t RegistersWidget::reg_read(const uint32_t register_number) { return radio::debug::second_if::register_read(register_number); case CT_SI5351: return portapack::clock_generator.read_register(register_number); + case CT_BATTERY: + return battery::BatteryManagement::read_register(register_number); case CT_AUDIO: return audio::debug::reg_read(register_number); } @@ -246,6 +248,9 @@ void RegistersWidget::reg_write(const uint32_t register_number, const uint32_t v case CT_SI5351: portapack::clock_generator.write_register(register_number, value); break; + case CT_BATTERY: + battery::BatteryManagement::write_register(register_number, value); + break; case CT_AUDIO: audio::debug::reg_write(register_number, value); break; @@ -459,6 +464,10 @@ void DebugPeripheralsMenuView::on_populate() { {si5351x, Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this, si5351x]() { nav_.push(si5351x, RegistersWidgetConfig{CT_SI5351, 188, 96, 8}); }}, {audio::debug::codec_name(), Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push(audio::debug::codec_name(), RegistersWidgetConfig{CT_AUDIO, audio::debug::reg_count(), audio::debug::reg_count(), audio::debug::reg_bits()}); }}, }); + if (battery::BatteryManagement::detectedModule() == battery::BatteryManagement::BatteryModules::BATT_MAX17055) { + add_item( + {"MAX17055", Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals_details, [this]() { nav_.push("MAX17055", RegistersWidgetConfig{CT_BATTERY, 256, 16, 16}); }}); + } set_max_rows(2); // allow wider buttons } @@ -496,10 +505,10 @@ void DebugMenuView::on_populate() { {"Peripherals", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_peripherals, [this]() { nav_.push(); }}, {"Pers. Memory", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_memory, [this]() { nav_.push(); }}, //{ "Radio State", ui::Theme::getInstance()->bg_darkest->foreground, nullptr, [this](){ nav_.push(); } }, - {"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push(); }}, {"SD Card", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_sdcard, [this]() { nav_.push(); }}, {"Temperature", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_temperature, [this]() { nav_.push(); }}, {"Touch Test", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_notepad, [this]() { nav_.push(); }}, + {"Reboot", ui::Theme::getInstance()->fg_darkcyan->foreground, &bitmap_icon_setup, [this]() { nav_.push(); }}, }); for (auto const& gridItem : ExternalItemsMenuLoader::load_external_items(app_location_t::DEBUG, nav_)) { diff --git a/firmware/application/apps/ui_debug.hpp b/firmware/application/apps/ui_debug.hpp index 9a2c0368..25007bbd 100644 --- a/firmware/application/apps/ui_debug.hpp +++ b/firmware/application/apps/ui_debug.hpp @@ -139,6 +139,7 @@ typedef enum { CT_MAX283X, CT_SI5351, CT_AUDIO, + CT_BATTERY, } chip_type_t; struct RegistersWidgetConfig { diff --git a/firmware/application/portapack.hpp b/firmware/application/portapack.hpp index 3457bd74..7a446ed4 100644 --- a/firmware/application/portapack.hpp +++ b/firmware/application/portapack.hpp @@ -34,6 +34,7 @@ #include "usb_serial.hpp" #include "ads1110.hpp" +#include "max17055.hpp" #include "radio.hpp" #include "clock_manager.hpp" diff --git a/firmware/common/battery.cpp b/firmware/common/battery.cpp index 19be9fc1..307d34ea 100644 --- a/firmware/common/battery.cpp +++ b/firmware/common/battery.cpp @@ -2,6 +2,7 @@ #include "event_m0.hpp" #include "portapack.hpp" #include "ads1110.hpp" +#include "max17055.hpp" // uncomment if you want to emulate batt management system // #define USE_BATT_EMULATOR @@ -14,6 +15,7 @@ constexpr uint32_t BATTERY_UPDATE_INTERVAL = 30000; BatteryManagement::BatteryModules BatteryManagement::detected_ = BatteryManagement::BATT_NONE; ads1110::ADS1110 battery_ads1110{portapack::i2c0, 0x48}; +max17055::MAX17055 battery_max17055{portapack::i2c0, 0x36}; Thread* BatteryManagement::thread = nullptr; @@ -23,6 +25,9 @@ void BatteryManagement::init() { if (battery_ads1110.detect()) { battery_ads1110.init(); detected_ = BATT_ADS1110; + } else if (battery_max17055.detect()) { + battery_max17055.init(); + detected_ = BATT_MAX17055; } // add new supported module detect + init here @@ -40,11 +45,15 @@ void BatteryManagement::init() { } // sets the values, it the currend module supports it. -bool BatteryManagement::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& isCharging) { - if (detected_ == BATT_NONE) return false; - if (detected_ == BATT_ADS1110) { +bool BatteryManagement::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current) { + if (detected_ == BATT_NONE) { + return false; + } else if (detected_ == BATT_ADS1110) { battery_ads1110.getBatteryInfo(batteryPercentage, voltage); return true; + } else if (detected_ == BATT_MAX17055) { + battery_max17055.getBatteryInfo(batteryPercentage, voltage, current); + return true; } // add new module query here @@ -59,18 +68,30 @@ bool BatteryManagement::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& vol } #endif - (void)isCharging; // keep the compiler calm (void)current; return false; } +uint16_t BatteryManagement::read_register(const uint8_t reg) { + if (detected_ == BATT_MAX17055) { + return battery_max17055.read_register(reg); + } + return 0xFFFF; +} + +bool BatteryManagement::write_register(const uint8_t reg, const uint16_t value) { + if (detected_ == BATT_MAX17055) { + return battery_max17055.write_register(reg, value); + } + return false; +} + uint8_t BatteryManagement::getPercent() { if (detected_ == BATT_NONE) return 102; uint8_t batteryPercentage = 0; - bool isCharging = false; uint16_t voltage = 0; int32_t current = 0; - getBatteryInfo(batteryPercentage, voltage, current, isCharging); + getBatteryInfo(batteryPercentage, voltage, current); return batteryPercentage; } @@ -78,10 +99,9 @@ uint16_t BatteryManagement::getVoltage() { if (detected_ == BATT_NONE) return 0; if (detected_ == BATT_NONE) return 102; uint8_t batteryPercentage = 0; - bool isCharging = false; uint16_t voltage = 0; int32_t current = 0; - getBatteryInfo(batteryPercentage, voltage, current, isCharging); + getBatteryInfo(batteryPercentage, voltage, current); return voltage; } @@ -89,14 +109,13 @@ msg_t BatteryManagement::timer_fn(void* arg) { (void)arg; if (!detected_) return 0; uint8_t batteryPercentage = 102; - bool isCharging = false; uint16_t voltage = 0; int32_t current = 0; chThdSleepMilliseconds(1000); // wait ui for fully load while (1) { - if (BatteryManagement::getBatteryInfo(batteryPercentage, voltage, current, isCharging)) { + if (BatteryManagement::getBatteryInfo(batteryPercentage, voltage, current)) { // send local message - BatteryStateMessage msg{batteryPercentage, isCharging, voltage}; + BatteryStateMessage msg{batteryPercentage, current >= 0, voltage}; EventDispatcher::send_message(msg); } chThdSleepMilliseconds(BATTERY_UPDATE_INTERVAL); diff --git a/firmware/common/battery.hpp b/firmware/common/battery.hpp index 2ea05cca..c77b4f63 100644 --- a/firmware/common/battery.hpp +++ b/firmware/common/battery.hpp @@ -32,13 +32,17 @@ class BatteryManagement { enum BatteryModules { BATT_NONE = 0, BATT_ADS1110 = 1, + BATT_MAX17055 = 2, BATT_EMULATOR = 254 }; static void init(); static bool isDetected() { return detected_ != BATT_NONE; } - static bool getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current, bool& isCharging); + static BatteryModules detectedModule() { return detected_; } + static bool getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current); static uint16_t getVoltage(); static uint8_t getPercent(); + static uint16_t read_register(const uint8_t reg); + static bool write_register(const uint8_t reg, const uint16_t value); private: static void create_thread(); diff --git a/firmware/common/max17055.cpp b/firmware/common/max17055.cpp new file mode 100644 index 00000000..bfbf1ebd --- /dev/null +++ b/firmware/common/max17055.cpp @@ -0,0 +1,753 @@ +/* + +Copyright (C) 2024 jLynx. +* +This file is part of PortaPack. +* +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. +* +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +* +You should have received a copy of the GNU General Public License +along with this program; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, +Boston, MA 02110-1301, USA. +*/ +#include "max17055.hpp" +#include "utility.hpp" +#include +#include +#include + +namespace battery { +namespace max17055 { + +void MAX17055::init() { + if (!detected_) { + detected_ = detect(); + } else { + config(); + setHibCFG(0x0000); + + // Design Capacity Register + setDesignCapacity(__MAX17055_Design_Capacity__); + + // ModelCfg Register + setModelCfg(__MAX17055_Battery_Model__); + + // IChgTerm Register + setChargeTerminationCurrent(__MAX17055_Termination_Current__); + + // VEmpty Register + setEmptyVoltage(__MAX17055_Empty_Voltage__); + + // VRecovery Register + setRecoveryVoltage(__MAX17055_Recovery_Voltage__); + + // Set Minimum Voltage + setMinVoltage(__MAX17055_Min_Voltage__); + + // Set Maximum Voltage + setMaxVoltage(__MAX17055_Max_Voltage__); + + // Set Maximum Current + setMaxCurrent(__MAX17055_Max_Current__); + + // Set Minimum SOC + setMinSOC(__MAX17055_Min_SOC__); + + // Set Maximum SOC + setMaxSOC(__MAX17055_Max_SOC__); + + // Set Minimum Temperature + setMinTemperature(__MAX17055_Min_Temperature__); + + // Set Maximum Temperature + setMaxTemperature(__MAX17055_Max_Temperature__); + + // Clear Bits + statusClear(); + } +} + +bool MAX17055::detect() { + uint8_t _MAX17055_Data[2]; + + // Get Data from IC + if (readMultipleRegister(0x00, _MAX17055_Data, 2, false)) { + detected_ = true; + return true; + } + detected_ = false; + return false; +} + +bool bitRead(uint8_t value, uint8_t bit) { + return (value >> bit) & 0x01; +} + +void bitSet(uint16_t& value, uint8_t bit) { + value |= (1 << bit); +} + +void bitSet(uint8_t& value, uint8_t bit) { + value |= (1 << bit); +} + +void bitClear(uint16_t& value, uint8_t bit) { + value &= ~(1 << bit); +} + +uint16_t MAX17055::read_register(const uint8_t reg) { + const std::array tx{reg}; + std::array rx{0x00, 0x00}; + + bus.transmit(bus_address, tx.data(), tx.size()); + bus.receive(bus_address, rx.data(), rx.size()); + + // Combine the two bytes into a 16-bit value + // little-endian format (LSB first) + return static_cast((rx[1] << 8) | rx[0]); +} + +bool MAX17055::write_register(const uint8_t reg, const uint16_t value) { + std::array tx; + tx[0] = reg; + tx[1] = value & 0xFF; // Low byte + tx[2] = (value >> 8) & 0xFF; // High byte + + bool success = bus.transmit(bus_address, tx.data(), tx.size()); + return success; +} + +bool MAX17055::readMultipleRegister(uint8_t reg, uint8_t* data, uint8_t length, bool endTransmission) { + if (bus.transmit(bus_address, ®, 1)) { + if (bus.receive(bus_address, data, length)) { + if (endTransmission) { + // bus.stop(); // Testing if we need this line + // Perform any necessary end transmission actions + // For example, you can use bus.endTransmission() + } + return true; + } + } + return false; +} + +bool MAX17055::writeMultipleRegister(uint8_t reg, const uint8_t* data, uint8_t length) { + uint8_t buffer[length + 1]; + buffer[0] = reg; + memcpy(buffer + 1, data, length); + + if (bus.transmit(bus_address, buffer, length + 1)) { + // Perform any necessary end transmission actions + // For example, you can use bus.endTransmission() + return true; + } + return false; +} + +void MAX17055::getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current) { + voltage = averageVoltage(); + batteryPercentage = stateOfCharge(); + current = instantCurrent(); +} + +bool MAX17055::setEmptyVoltage(uint16_t _Empty_Voltage) { + // Set Voltage Value + uint16_t _Raw_Voltage = ((uint16_t)((uint16_t)_Empty_Voltage * 100) << 7) & 0b1111111110000000; + + // Define Data Variable + uint8_t MAX17055_RAW_Data[2]; + + // Handle Data + MAX17055_RAW_Data[1] = (uint8_t)(_Raw_Voltage >> 8); + MAX17055_RAW_Data[0] = (uint8_t)(_Raw_Voltage & 0x00FF); + + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x3A, MAX17055_Current_Data, 2, true); + + // Clear Current Value + MAX17055_Current_Data[0] &= 0b01111111; + MAX17055_Current_Data[1] &= 0b00000000; + + // Define Data Variable + uint8_t MAX17055_Handle_Data[2]; + + // Handle Data + MAX17055_Handle_Data[0] = MAX17055_Current_Data[0] | MAX17055_RAW_Data[0]; + MAX17055_Handle_Data[1] = MAX17055_Current_Data[1] | MAX17055_RAW_Data[1]; + + // Set Register + bool _Result = writeMultipleRegister(0x3A, MAX17055_Handle_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setRecoveryVoltage(uint16_t _Recovery_Voltage) { + // Handle Value + _Recovery_Voltage = _Recovery_Voltage * 1000 / 40; + + // Set Voltage Value + uint16_t _Raw_Voltage = ((uint16_t)_Recovery_Voltage); + + // Define Data Variable + uint8_t MAX17055_RAW_Data[2]; + + // Handle Data + MAX17055_RAW_Data[1] = (uint8_t)(_Raw_Voltage >> 8); + MAX17055_RAW_Data[0] = (uint8_t)(_Raw_Voltage & 0x00FF); + + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x3A, MAX17055_Current_Data, 2, true); + + // Clear Current Value + MAX17055_Current_Data[0] &= 0b10000000; + MAX17055_Current_Data[1] &= 0b11111111; + + // Define Data Variable + uint8_t MAX17055_Handle_Data[2]; + + // Handle Data + MAX17055_Handle_Data[0] = MAX17055_Current_Data[0] | MAX17055_RAW_Data[0]; + MAX17055_Handle_Data[1] = MAX17055_Current_Data[1] | MAX17055_RAW_Data[1]; + + // Set Register + bool _Result = writeMultipleRegister(0x3A, MAX17055_Handle_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMinVoltage(uint16_t _Minimum_Voltage) { + // Set Voltage Value + uint8_t _Raw_Voltage = (uint8_t)(_Minimum_Voltage * 1000 / 20); + + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x01, MAX17055_Current_Data, 2, true); + + // Set Voltage Value + MAX17055_Current_Data[0] = _Raw_Voltage; + + // Set Register + bool _Result = writeMultipleRegister(0x01, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMaxVoltage(uint16_t _Maximum_Voltage) { + // Set Voltage Value + uint8_t _Raw_Voltage = (uint8_t)(_Maximum_Voltage * 1000 / 20); + + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x01, MAX17055_Current_Data, 2, true); + + // Set Voltage Value + MAX17055_Current_Data[1] = _Raw_Voltage; + + // Set Register + bool _Result = writeMultipleRegister(0x01, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMaxCurrent(uint16_t _Maximum_Current) { + // Set Current Value + uint8_t _Raw_Current = (uint8_t)(_Maximum_Current * 1000 / 40); + + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0xB4, MAX17055_Current_Data, 2, true); + + // Set Current Value + MAX17055_Current_Data[1] = _Raw_Current; + + // Set Register + bool _Result = writeMultipleRegister(0xB4, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setChargeTerminationCurrent(uint16_t _Charge_Termination_Current) { + // Handle Raw Data + uint16_t _RAW_Data = (uint16_t)(_Charge_Termination_Current * 1000000 * __MAX17055_Resistor__ / 1.5625); + + // Declare Default Data Array + uint8_t _Data[2]; + + // Set Data Low/High Byte + _Data[0] = ((_RAW_Data & (uint16_t)0x00FF)); + _Data[1] = ((_RAW_Data & (uint16_t)0xFF00) >> 8); + + // Set Register + bool _Result = writeMultipleRegister(0x1E, _Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setDesignCapacity(const uint16_t _Capacity) { + // Set Raw + uint16_t _Raw_Cap = (uint16_t)_Capacity * 2; + + // Declare Default Data Array + uint8_t _Data[2]; + + // Set Data Low/High Byte + _Data[0] = ((_Raw_Cap & (uint16_t)0x00FF)); + _Data[1] = ((_Raw_Cap & (uint16_t)0xFF00) >> 8); + + // Set Register + bool _Result = writeMultipleRegister(0x18, _Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMinSOC(uint8_t _Minimum_SOC) { + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x03, MAX17055_Current_Data, 2, true); + + // Scale Value + uint8_t _MinSOC = (_Minimum_SOC / 100) * 255; + + // Set Voltage Value + MAX17055_Current_Data[0] = _MinSOC; + + // Set Register + bool _Result = writeMultipleRegister(0x03, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMaxSOC(uint8_t _Maximum_SOC) { + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x03, MAX17055_Current_Data, 2, true); + + // Scale Value + uint8_t _MaxSOC = (_Maximum_SOC / 100) * 255; + + // Set Voltage Value + MAX17055_Current_Data[1] = _MaxSOC; + + // Set Register + bool _Result = writeMultipleRegister(0x03, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMinTemperature(uint8_t _Minimum_Temperature) { + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x02, MAX17055_Current_Data, 2, true); + + // Set Voltage Value + MAX17055_Current_Data[0] = _Minimum_Temperature; + + // Set Register + bool _Result = writeMultipleRegister(0x02, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setMaxTemperature(uint8_t _Maximum_Temperature) { + // Define Data Variable + uint8_t MAX17055_Current_Data[2]; + + // Read Current Register + readMultipleRegister(0x02, MAX17055_Current_Data, 2, true); + + // Set Voltage Value + MAX17055_Current_Data[1] = _Maximum_Temperature; + + // Set Register + bool _Result = writeMultipleRegister(0x02, MAX17055_Current_Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setModelCfg(const uint8_t _Model_ID) { + // Declare Variable + uint16_t _Data_Set = 0x00; + + // Set Charge Voltage + bitSet(_Data_Set, 10); + + // Set Battery Model + if (_Model_ID == 0) { + bitClear(_Data_Set, 4); + bitClear(_Data_Set, 5); + bitClear(_Data_Set, 6); + bitClear(_Data_Set, 7); + } + if (_Model_ID == 2) { + bitClear(_Data_Set, 4); + bitSet(_Data_Set, 5); + bitClear(_Data_Set, 6); + bitClear(_Data_Set, 7); + } + if (_Model_ID == 6) { + bitClear(_Data_Set, 4); + bitSet(_Data_Set, 5); + bitSet(_Data_Set, 6); + bitClear(_Data_Set, 7); + } + + // Declare Default Data Array + uint8_t _Data[2]; + + // Set Data Low/High Byte + _Data[0] = ((_Data_Set & (uint16_t)0x00FF)); + _Data[1] = ((_Data_Set & (uint16_t)0xFF00) >> 8); + + // Set Register + bool _Result = writeMultipleRegister(0xDB, _Data, 2); + + // End Function + return _Result; +} + +bool MAX17055::setHibCFG(const uint16_t _Config) { + // Declare Default Data Array + uint8_t _Data[2]; + + // Set Data Low/High Byte + _Data[0] = ((_Config & (uint16_t)0x00FF)); + _Data[1] = ((_Config & (uint16_t)0xFF00) >> 8); + + // Set Register + bool _Result = writeMultipleRegister(0xBA, _Data, 2); + + // End Function + return _Result; +} + +void MAX17055::config(void) { + // Declare Default Data Array + uint8_t _Config1[2]; + uint8_t _Config2[2]; + + // Set Default Data + _Config1[0] = 0b00000000; + _Config1[1] = 0b00000000; + _Config2[0] = 0b00011000; + _Config2[1] = 0b00000110; + + // Set Configuration bits [Config1] + if (MAX17055_Ber) bitSet(_Config1[0], 0); + if (MAX17055_Bei) bitSet(_Config1[0], 1); + if (MAX17055_Aen) bitSet(_Config1[0], 2); + if (MAX17055_FTHRM) bitSet(_Config1[0], 3); + if (MAX17055_ETHRM) bitSet(_Config1[0], 4); + if (MAX17055_COMMSH) bitSet(_Config1[0], 6); + if (MAX17055_SHDN) bitSet(_Config1[0], 7); + if (MAX17055_Tex) bitSet(_Config1[1], 0); + if (MAX17055_Ten) bitSet(_Config1[1], 1); + if (MAX17055_AINSH) bitSet(_Config1[1], 2); + if (MAX17055_IS) bitSet(_Config1[1], 3); + if (MAX17055_VS) bitSet(_Config1[1], 4); + if (MAX17055_TS) bitSet(_Config1[1], 5); + if (MAX17055_SS) bitSet(_Config1[1], 6); + if (MAX17055_TSel) bitSet(_Config1[1], 7); + + // Set Configuration bits [Config2] + if (MAX17055_CPMode) bitSet(_Config2[0], 1); + if (MAX17055_LDMDL) bitSet(_Config2[0], 5); + if (MAX17055_TAIrtEN) bitSet(_Config2[0], 6); + if (MAX17055_dSOCen) bitSet(_Config2[0], 7); + if (MAX17055_DPEn) bitSet(_Config2[1], 4); + if (MAX17055_AtRateEn) bitSet(_Config2[1], 5); + + // Config1 Setting + writeMultipleRegister(0x1D, _Config1, 2); + writeMultipleRegister(0xBB, _Config2, 2); +} + +uint16_t MAX17055::instantVoltage(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x09); + + // Calculate Measurement + uint16_t _Value = ((uint32_t)_Measurement_Raw * 1.25 / 16); + + // End Function + return _Value; +} + +uint16_t MAX17055::averageVoltage(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x19); + + // Calculate Measurement + uint16_t _Value = ((uint32_t)_Measurement_Raw * 1.25 / 16); + + // End Function + return _Value; +} + +uint16_t MAX17055::emptyVoltage(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x3A); + _Measurement_Raw = ((_Measurement_Raw & 0xFF80) >> 7); + + // Calculate Measurement + uint16_t _Value = ((uint32_t)_Measurement_Raw * 10); + + // End Function + return _Value; +} + +uint16_t MAX17055::recoveryVoltage(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x3A); + + _Measurement_Raw = (_Measurement_Raw & 0x7F); + + // Calculate Measurement + uint16_t _Value = ((uint32_t)_Measurement_Raw * 40); + + // End Function + return _Value; +} + +int32_t MAX17055::instantCurrent(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x0A); + + // Convert to signed int16_t (two's complement) + int32_t _Signed_Raw = static_cast(_Measurement_Raw); + + int32_t _Value = (_Signed_Raw * 15625) / (__MAX17055_Resistor__ * 100); + + // End Function + return _Value; +} + +int32_t MAX17055::averageCurrent(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x0B); + + // Convert to signed int16_t (two's complement) + int32_t _Signed_Raw = static_cast(_Measurement_Raw); + + int32_t _Value = (_Signed_Raw * 15625) / (__MAX17055_Resistor__ * 100); + + // End Function + return _Value; +} + +uint16_t MAX17055::stateOfCharge(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x06); // RepSOC + + // Calculate Measurement + uint8_t _Value = (_Measurement_Raw >> 8) & 0xFF; + + // End Function + return _Value; +} + +uint16_t MAX17055::averageStateOfCharge(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x0E); + + // Calculate Measurement + uint16_t _Value = ((uint32_t)_Measurement_Raw * 100 / 256); + + // End Function + return _Value; +} + +uint16_t MAX17055::instantCapacity(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x05); + + // Calculate Data + uint16_t _Value = _Measurement_Raw * 5 / 1000 / __MAX17055_Resistor__; + + // End Function + return _Value; +} + +uint16_t MAX17055::designCapacity(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x18); + + // Calculate Data + uint16_t _Value = _Measurement_Raw * 5 / 1000 / __MAX17055_Resistor__; + + // End Function + return _Value; +} + +uint16_t MAX17055::fullCapacity(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x35); + + // Calculate Data + uint16_t _Value = _Measurement_Raw * 5 / 1000 / __MAX17055_Resistor__; + + // End Function + return _Value; +} + +uint16_t MAX17055::icTemperature(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x08); + + // Declare Variables + bool _Signiture = false; + + // Control for Negative Value + if ((_Measurement_Raw >> 12) == 0xF) { + // Calculate Data + _Measurement_Raw = 0xFFFF - _Measurement_Raw; + + // Assign Signiture + _Signiture = true; + } + + // Calculate Data + uint16_t _Value = ((uint32_t)_Measurement_Raw * 100 / 256); + + // Assign Signiture + if (_Signiture) _Value = -_Value; + + // End Function + return _Value; +} + +uint16_t MAX17055::timeToEmpty(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x11); + + // Calculate Data + uint16_t _Value = ((uint32_t)_Measurement_Raw * 5625 / 60 / 60 / 100); + + // End Function + return _Value; +} + +uint16_t MAX17055::timeToFull(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x20); + + // Calculate Data + uint16_t _Value = ((uint32_t)_Measurement_Raw * 5625 / 60 / 60 / 100); + + // End Function + return _Value; +} + +uint16_t MAX17055::batteryAge(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x07); + + // End Function + return _Measurement_Raw; +} + +uint16_t MAX17055::chargeCycle(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x17); + + // End Function + return _Measurement_Raw; +} + +bool MAX17055::statusControl(const uint8_t _Status) { + // Define Data Variable + uint8_t _Status_Register[2] = {0x00, 0x00}; + + // Read Status Register + readMultipleRegister(0x00, _Status_Register, 2, false); + + // Control for Status + if (_Status == MAX17055_POR) + return bitRead(_Status_Register[0], 1); + else if (_Status == MAX17055_IMin) + return bitRead(_Status_Register[0], 2); + else if (_Status == MAX17055_IMax) + return bitRead(_Status_Register[0], 6); + else if (_Status == MAX17055_VMin) + return bitRead(_Status_Register[1], 0); + else if (_Status == MAX17055_VMax) + return bitRead(_Status_Register[1], 4); + else if (_Status == MAX17055_TMin) + return bitRead(_Status_Register[1], 1); + else if (_Status == MAX17055_TMax) + return bitRead(_Status_Register[1], 5); + else if (_Status == MAX17055_SOC_Min) + return bitRead(_Status_Register[1], 2); + else if (_Status == MAX17055_SOC_Max) + return bitRead(_Status_Register[1], 6); + else if (_Status == MAX17055_SOC_Change) + return bitRead(_Status_Register[0], 7); + else if (_Status == MAX17055_Bat_Status) + return bitRead(_Status_Register[0], 3); + else if (_Status == MAX17055_Bat_Insert) + return bitRead(_Status_Register[1], 3); + else if (_Status == MAX17055_Bat_Remove) + return bitRead(_Status_Register[1], 7); + + // End Function + return false; +} + +void MAX17055::statusClear(void) { + // Define Data Variable + const uint8_t _Status_Register[2] = {0x00, 0x00}; + + // Write Status Register + writeMultipleRegister(0x00, _Status_Register, 2); +} + +uint16_t MAX17055::chargeTerminationCurrent(void) { + // Get Data from IC + uint16_t _Measurement_Raw = read_register(0x1E); + + // Calculate Data + uint16_t Value = (((uint32_t)_Measurement_Raw * 1.5625) / __MAX17055_Resistor__); + + // End Function + return Value; +} + +} /* namespace max17055 */ +} // namespace battery \ No newline at end of file diff --git a/firmware/common/max17055.hpp b/firmware/common/max17055.hpp new file mode 100644 index 00000000..d84b9491 --- /dev/null +++ b/firmware/common/max17055.hpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2024 jLynx. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __MAX17055_H__ +#define __MAX17055_H__ + +#include +#include +#include + +#include "i2c_pp.hpp" + +#define MAX17055_POR 0 +#define MAX17055_IMin 1 +#define MAX17055_IMax 2 +#define MAX17055_VMin 3 +#define MAX17055_VMax 4 +#define MAX17055_TMin 5 +#define MAX17055_TMax 6 +#define MAX17055_SOC_Min 7 +#define MAX17055_SOC_Max 8 +#define MAX17055_SOC_Change 9 +#define MAX17055_Bat_Status 10 +#define MAX17055_Bat_Insert 11 +#define MAX17055_Bat_Remove 12 + +// Design Parameters +//---------------------------------------------- + +// Define Battery Capacity +#ifndef __MAX17055_Design_Capacity__ +#define __MAX17055_Design_Capacity__ 1500 // Battery Capacity +#endif + +// Define Gauge Resistor +#ifndef __MAX17055_Resistor__ +#define __MAX17055_Resistor__ 0.01 // Shunt Resistor +#endif + +// Define Minimum Voltage +#ifndef __MAX17055_Min_Voltage__ +#define __MAX17055_Min_Voltage__ 3.0 // Minimum Voltage +#endif + +// Define Maximum Voltage +#ifndef __MAX17055_Max_Voltage__ +#define __MAX17055_Max_Voltage__ 4.2 // Maximum Voltage +#endif + +// Define Empty Voltage +#ifndef __MAX17055_Empty_Voltage__ +#define __MAX17055_Empty_Voltage__ 3.0 // Empty Voltage +#endif + +// Define Recovery Voltage +#ifndef __MAX17055_Recovery_Voltage__ +#define __MAX17055_Recovery_Voltage__ 3.7 // Recovery Voltage +#endif + +// Define Maximum Current +#ifndef __MAX17055_Max_Current__ +#define __MAX17055_Max_Current__ 3.0 // Maximum Current +#endif + +// Define Termination Current +#ifndef __MAX17055_Termination_Current__ +#define __MAX17055_Termination_Current__ 0.1 // Termination Current +#endif + +// Define Minimum Temperature +#ifndef __MAX17055_Min_Temperature__ +#define __MAX17055_Min_Temperature__ -20 // Minimum Temperature +#endif + +// Define Maximum Temperature +#ifndef __MAX17055_Max_Temperature__ +#define __MAX17055_Max_Temperature__ 60 // Maximum Temperature +#endif + +// Define Battery Model +#ifndef __MAX17055_Battery_Model__ +#define __MAX17055_Battery_Model__ 0 // Battery Model +#endif + +// Define Limits +//---------------------------------------------- + +// Define Minimum SOC +#ifndef __MAX17055_Min_SOC__ +#define __MAX17055_Min_SOC__ 20 // Minimum SOC +#endif + +// Define Maximum SOC +#ifndef __MAX17055_Max_SOC__ +#define __MAX17055_Max_SOC__ 90 // Maximum SOC +#endif + +// Config1 (0x1D) Configuration +//---------------------------------------------- + +// Enable alert on battery removal when the IC is mounted host side. +// When Ber = 1, a battery-removal condition, as detected by the AIN pin voltage, triggers an alert. +#ifndef MAX17055_Ber +#define MAX17055_Ber 0 +#endif + +// Enable alert on battery insertion when the IC is mounted host side. +// When Bei = 1, a battery-insertion condition, as detected by the AIN pin voltage, triggers an alert. +#ifndef MAX17055_Bei +#define MAX17055_Bei 0 +#endif + +// When Aen = 1, violation of any of the alert threshold register values +// by temperature, voltage, or SOC triggers an alert. This bit +// affects the ALRT pin operation only. The Smx, Smn, Tmx, +// Tmn, Vmx, Vmn, Imx, and Imn bits of the Status register +// (00h) are not disabled. +#ifndef MAX17055_Aen +#define MAX17055_Aen 1 +#endif + +// This allows the host to control the bias of the thermistor switch or +// enable fast detection of battery removal. Set FTHRM = 1 +// to always enable the thermistor bias switch. With a standard 10kΩ thermistor, +// this adds an additional ~200μA to the current drain of the circuit. +#ifndef MAX17055_FTHRM +#define MAX17055_FTHRM 0 +#endif + +// Set to logic 1 to enable the automatic THRM output bias and AIN measurement. +#ifndef MAX17055_ETHRM +#define MAX17055_ETHRM 0 +#endif + +// Set to logic 1 to force the device to enter shutdown mode if both +// SDA and SCL are held low for more than timeout of the +// ShdnTimer register. +#ifndef MAX17055_COMMSH +#define MAX17055_COMMSH 0 +#endif + +// Write this bit to logic 1 to force a shutdown of the device after timeout of the ShdnTimer register (default 45s delay). +#ifndef MAX17055_SHDN +#define MAX17055_SHDN 0 +#endif + +// When set to 1, the fuel gauge requires external temperature measurements to be +// written from the host. When set to 0, the IC's own measurements as used as selected by Config.TSEL. +#ifndef MAX17055_Tex +#define MAX17055_Tex 0 +#endif + +// Set to 1 and set ETHRM or FTHRM to 1 to enable temperature measurements selected by Config.TSel. +#ifndef MAX17055_Ten +#define MAX17055_Ten 1 +#endif + +// Set to 1 to enable device shutdown when the IC is mounted host side and the battery is removed. +#ifndef MAX17055_AINSH +#define MAX17055_AINSH 0 +#endif + +// When IS = 1, current alerts can only be cleared through software. When IS = 0, +// current alerts are cleared automatically when the thresholdis no longer exceeded. +#ifndef MAX17055_IS +#define MAX17055_IS 0 +#endif + +// When VS = 1, voltage alerts can only be cleared through software. When VS = 0, +// voltage alerts are cleared automatically when the threshold is no longer exceeded. +#ifndef MAX17055_VS +#define MAX17055_VS 0 +#endif + +// When TS = 1, temperature alerts can only be cleared through software. WhenTS = 0, +// temperature alerts are cleared automatically whenthe threshold is no longer exceeded. +#ifndef MAX17055_TS +#define MAX17055_TS 0 +#endif + +// When SS = 1, SOC alerts can only be cleared through software. When SS = 0, +// SOC alerts are cleared automatically when the threshold is no longer exceeded. +#ifndef MAX17055_SS +#define MAX17055_SS 0 +#endif + +// 0 to use internal die temperature. 1 to use temperature information from thermistor. +// ETHRM bit should be set to 1 when TSel is 1. +#ifndef MAX17055_TSel +#define MAX17055_TSel 0 +#endif + +// Config1 (0xBB) Configuration +//---------------------------------------------- + +// Set to 1 to enable constant-power mode. If it is set to 0, +// AtRate/AvgCurrent is used for (At)TTE/(At) QResidual/(At)AvSOC/(At)AvCap. +#ifndef MAX17055_CPMode +#define MAX17055_CPMode 0 +#endif + +// Host sets this bit to 1 in order to initiate firmware to finish processing a newly loaded model. +// Firmware clears this bit to zero to indicate that model loading is finished. +#ifndef MAX17055_LDMDL +#define MAX17055_LDMDL 0 +#endif + +// Set this bit to 1 to enable temperature based alerts. +// Write this bit to 0 to disable temperature alerts. This bit is set to 1 at power-up. +#ifndef MAX17055_TAIrtEN +#define MAX17055_TAIrtEN 1 +#endif + +// Set this bit to 1 to enable alert output with the Status.dSOCi bit function. +// Write this bit to 0 to disable alert output with the Status. dSOCi bit. This bit is set to 0 at power-up. +#ifndef MAX17055_dSOCen +#define MAX17055_dSOCen 0 +#endif + +// When this bit is set to 0, Dynamic Power calculations are disabled and registers +// MaxPeakPower/SusPeakPower/MPPCurrent/ SPPCurrent can be used as general purpose memory. +#ifndef MAX17055_DPEn +#define MAX17055_DPEn 0 +#endif + +// When this bit is set to 0, AtRate calculations are disabled and registers +// AtQResidual/AtTTE/AtAvSOC/AtAvCap can be used as general purpose memory. +#ifndef MAX17055_AtRateEn +#define MAX17055_AtRateEn 0 +#endif + +namespace battery { +namespace max17055 { + +using address_t = uint8_t; + +class MAX17055 { + public: + constexpr MAX17055(I2C& bus, const I2C::address_t bus_address) + : bus(bus), bus_address(bus_address), detected_(false) {} + + void init(); + bool detect(); + bool isDetected() const { return detected_; } + + uint16_t readVoltage(); + uint8_t readPercentage(); + void getBatteryInfo(uint8_t& batteryPercentage, uint16_t& voltage, int32_t& current); + + uint16_t instantVoltage(void); + uint16_t averageVoltage(void); + uint16_t emptyVoltage(void); + uint16_t recoveryVoltage(void); + int32_t instantCurrent(void); + int32_t averageCurrent(void); + uint16_t stateOfCharge(void); + uint16_t averageStateOfCharge(void); + uint16_t instantCapacity(void); + uint16_t designCapacity(void); + uint16_t fullCapacity(void); + uint16_t icTemperature(void); + uint16_t timeToEmpty(void); + uint16_t timeToFull(void); + uint16_t batteryAge(void); + uint16_t chargeCycle(void); + bool statusControl(const uint8_t _Status); + void statusClear(void); + uint16_t chargeTerminationCurrent(void); + uint16_t read_register(const uint8_t reg); + bool write_register(const uint8_t reg, const uint16_t value); + + private: + I2C& bus; + const I2C::address_t bus_address; + bool detected_; + + bool readRegister(uint8_t reg, uint16_t& value); + bool readMultipleRegister(uint8_t reg, uint8_t* data, uint8_t length, bool endTransmission); + bool writeMultipleRegister(uint8_t reg, const uint8_t* data, uint8_t length); + + bool setEmptyVoltage(uint16_t _Empty_Voltage); + bool setRecoveryVoltage(uint16_t _Recovery_Voltage); + bool setMinVoltage(uint16_t _Minimum_Voltage); + bool setMaxVoltage(uint16_t _Maximum_Voltage); + bool setMaxCurrent(uint16_t _Maximum_Current); + bool setChargeTerminationCurrent(uint16_t _Charge_Termination_Current); + bool setDesignCapacity(const uint16_t _Capacity); + bool setMinSOC(uint8_t _Minimum_SOC); + bool setMaxSOC(uint8_t _Maximum_SOC); + bool setMinTemperature(uint8_t _Minimum_Temperature); + bool setMaxTemperature(uint8_t _Maximum_Temperature); + bool setModelCfg(const uint8_t _Model_ID); + bool setHibCFG(const uint16_t _Config); + void config(void); +}; + +} /* namespace max17055 */ +} // namespace battery +#endif /* __MAX17055_H__ */ \ No newline at end of file