Просмотр исходного кода

FuriHal, Power, UnitTests: battery charging voltage limit API (#2063)

Shane Synan 3 лет назад
Родитель
Сommit
5c3a5cd8f7

+ 1 - 1
applications/debug/unit_tests/minunit.h

@@ -316,7 +316,7 @@ void minunit_print_fail(const char* error);
     MU__SAFE_BLOCK(                                                                               \
     MU__SAFE_BLOCK(                                                                               \
         double minunit_tmp_e; double minunit_tmp_r; minunit_assert++; minunit_tmp_e = (expected); \
         double minunit_tmp_e; double minunit_tmp_r; minunit_assert++; minunit_tmp_e = (expected); \
         minunit_tmp_r = (result);                                                                 \
         minunit_tmp_r = (result);                                                                 \
-        if(fabs(minunit_tmp_e - minunit_tmp_r) > MINUNIT_EPSILON) {                               \
+        if(fabs(minunit_tmp_e - minunit_tmp_r) > (double)MINUNIT_EPSILON) {                       \
             int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);                         \
             int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);                         \
             snprintf(                                                                             \
             snprintf(                                                                             \
                 minunit_last_message,                                                             \
                 minunit_last_message,                                                             \

+ 62 - 0
applications/debug/unit_tests/power/power_test.c

@@ -0,0 +1,62 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include "../minunit.h"
+
+static void power_test_deinit(void) {
+    // Try to reset to default charging voltage
+    furi_hal_power_set_battery_charging_voltage(4.208f);
+}
+
+MU_TEST(test_power_charge_voltage_exact) {
+    // Power of 16mV charge voltages get applied exactly
+    // (bq25896 charge controller works in 16mV increments)
+    //
+    // This test may need adapted if other charge controllers are used in the future.
+    for(uint16_t charge_mv = 3840; charge_mv <= 4208; charge_mv += 16) {
+        float charge_volt = (float)charge_mv / 1000.0f;
+        furi_hal_power_set_battery_charging_voltage(charge_volt);
+        mu_assert_double_eq(charge_volt, furi_hal_power_get_battery_charging_voltage());
+    }
+}
+
+MU_TEST(test_power_charge_voltage_floating_imprecision) {
+    // 4.016f should act as 4.016 V, even with floating point imprecision
+    furi_hal_power_set_battery_charging_voltage(4.016f);
+    mu_assert_double_eq(4.016f, furi_hal_power_get_battery_charging_voltage());
+}
+
+MU_TEST(test_power_charge_voltage_inexact) {
+    // Charge voltages that are not power of 16mV get truncated down
+    furi_hal_power_set_battery_charging_voltage(3.841f);
+    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
+
+    furi_hal_power_set_battery_charging_voltage(3.900f);
+    mu_assert_double_eq(3.888, furi_hal_power_get_battery_charging_voltage());
+
+    furi_hal_power_set_battery_charging_voltage(4.200f);
+    mu_assert_double_eq(4.192, furi_hal_power_get_battery_charging_voltage());
+}
+
+MU_TEST(test_power_charge_voltage_invalid_clamped) {
+    // Out-of-range charge voltages get clamped to 3.840 V and 4.208 V
+    furi_hal_power_set_battery_charging_voltage(3.808f);
+    mu_assert_double_eq(3.840, furi_hal_power_get_battery_charging_voltage());
+
+    // NOTE: Intentionally picking a small increment above 4.208 V to reduce the risk of an
+    // unhappy battery if this fails.
+    furi_hal_power_set_battery_charging_voltage(4.240f);
+    mu_assert_double_eq(4.208, furi_hal_power_get_battery_charging_voltage());
+}
+
+MU_TEST_SUITE(test_power_suite) {
+    MU_RUN_TEST(test_power_charge_voltage_exact);
+    MU_RUN_TEST(test_power_charge_voltage_floating_imprecision);
+    MU_RUN_TEST(test_power_charge_voltage_inexact);
+    MU_RUN_TEST(test_power_charge_voltage_invalid_clamped);
+    power_test_deinit();
+}
+
+int run_minunit_test_power() {
+    MU_RUN_SUITE(test_power_suite);
+    return MU_EXIT_CODE;
+}

+ 2 - 0
applications/debug/unit_tests/test_index.c

@@ -19,6 +19,7 @@ int run_minunit_test_stream();
 int run_minunit_test_storage();
 int run_minunit_test_storage();
 int run_minunit_test_subghz();
 int run_minunit_test_subghz();
 int run_minunit_test_dirwalk();
 int run_minunit_test_dirwalk();
+int run_minunit_test_power();
 int run_minunit_test_protocol_dict();
 int run_minunit_test_protocol_dict();
 int run_minunit_test_lfrfid_protocols();
 int run_minunit_test_lfrfid_protocols();
 int run_minunit_test_nfc();
 int run_minunit_test_nfc();
@@ -44,6 +45,7 @@ const UnitTest unit_tests[] = {
     {.name = "subghz", .entry = run_minunit_test_subghz},
     {.name = "subghz", .entry = run_minunit_test_subghz},
     {.name = "infrared", .entry = run_minunit_test_infrared},
     {.name = "infrared", .entry = run_minunit_test_infrared},
     {.name = "nfc", .entry = run_minunit_test_nfc},
     {.name = "nfc", .entry = run_minunit_test_nfc},
+    {.name = "power", .entry = run_minunit_test_power},
     {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict},
     {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict},
     {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols},
     {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols},
     {.name = "bit_lib", .entry = run_minunit_test_bit_lib},
     {.name = "bit_lib", .entry = run_minunit_test_bit_lib},

+ 15 - 0
applications/services/power/power_service/power.c

@@ -12,6 +12,20 @@ void power_draw_battery_callback(Canvas* canvas, void* context) {
 
 
     if(power->info.gauge_is_ok) {
     if(power->info.gauge_is_ok) {
         canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
         canvas_draw_box(canvas, 2, 2, (power->info.charge + 4) / 5, 4);
+        if(power->info.voltage_battery_charging < 4.2) {
+            // Battery charging voltage is modified, indicate with cross pattern
+            canvas_invert_color(canvas);
+            uint8_t battery_bar_width = (power->info.charge + 4) / 5;
+            bool cross_odd = false;
+            // Start 1 further in from the battery bar's x position
+            for(uint8_t x = 3; x <= battery_bar_width; x++) {
+                // Cross pattern is from the center of the battery bar
+                // y = 2 + 1 (inset) + 1 (for every other)
+                canvas_draw_dot(canvas, x, 3 + (uint8_t)cross_odd);
+                cross_odd = !cross_odd;
+            }
+            canvas_invert_color(canvas);
+        }
         if(power->state == PowerStateCharging) {
         if(power->state == PowerStateCharging) {
             canvas_set_bitmap_mode(canvas, 1);
             canvas_set_bitmap_mode(canvas, 1);
             canvas_set_color(canvas, ColorWhite);
             canvas_set_color(canvas, ColorWhite);
@@ -132,6 +146,7 @@ static bool power_update_info(Power* power) {
     info.capacity_full = furi_hal_power_get_battery_full_capacity();
     info.capacity_full = furi_hal_power_get_battery_full_capacity();
     info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
     info.current_charger = furi_hal_power_get_battery_current(FuriHalPowerICCharger);
     info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
     info.current_gauge = furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge);
+    info.voltage_battery_charging = furi_hal_power_get_battery_charging_voltage();
     info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
     info.voltage_charger = furi_hal_power_get_battery_voltage(FuriHalPowerICCharger);
     info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
     info.voltage_gauge = furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge);
     info.voltage_vbus = furi_hal_power_get_usb_voltage();
     info.voltage_vbus = furi_hal_power_get_usb_voltage();

+ 1 - 0
applications/services/power/power_service/power.h

@@ -41,6 +41,7 @@ typedef struct {
     float current_charger;
     float current_charger;
     float current_gauge;
     float current_gauge;
 
 
+    float voltage_battery_charging;
     float voltage_charger;
     float voltage_charger;
     float voltage_gauge;
     float voltage_gauge;
     float voltage_vbus;
     float voltage_vbus;

+ 1 - 0
applications/settings/power_settings_app/scenes/power_settings_scene_battery_info.c

@@ -7,6 +7,7 @@ static void power_settings_scene_battery_info_update_model(PowerSettingsApp* app
         .gauge_voltage = app->info.voltage_gauge,
         .gauge_voltage = app->info.voltage_gauge,
         .gauge_current = app->info.current_gauge,
         .gauge_current = app->info.current_gauge,
         .gauge_temperature = app->info.temperature_gauge,
         .gauge_temperature = app->info.temperature_gauge,
+        .charging_voltage = app->info.voltage_battery_charging,
         .charge = app->info.charge,
         .charge = app->info.charge,
         .health = app->info.health,
         .health = app->info.health,
     };
     };

+ 10 - 0
applications/settings/power_settings_app/views/battery_info.c

@@ -68,6 +68,16 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
             drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA");
             drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA");
     } else if(drain_current != 0) {
     } else if(drain_current != 0) {
         snprintf(header, 20, "...");
         snprintf(header, 20, "...");
+    } else if(data->charging_voltage < 4.2) {
+        // Non-default battery charging limit, mention it
+        snprintf(emote, sizeof(emote), "Charged!");
+        snprintf(header, sizeof(header), "Limited to");
+        snprintf(
+            value,
+            sizeof(value),
+            "%ld.%ldV",
+            (uint32_t)(data->charging_voltage),
+            (uint32_t)(data->charging_voltage * 10) % 10);
     } else {
     } else {
         snprintf(header, sizeof(header), "Charged!");
         snprintf(header, sizeof(header), "Charged!");
     }
     }

+ 1 - 0
applications/settings/power_settings_app/views/battery_info.h

@@ -9,6 +9,7 @@ typedef struct {
     float gauge_voltage;
     float gauge_voltage;
     float gauge_current;
     float gauge_current;
     float gauge_temperature;
     float gauge_temperature;
+    float charging_voltage;
     uint8_t charge;
     uint8_t charge;
     uint8_t health;
     uint8_t health;
 } BatteryInfoModel;
 } BatteryInfoModel;

+ 2 - 0
firmware/targets/f7/api_symbols.csv

@@ -1194,6 +1194,7 @@ Function,+,furi_hal_power_enable_external_3_3v,void,
 Function,+,furi_hal_power_enable_otg,void,
 Function,+,furi_hal_power_enable_otg,void,
 Function,+,furi_hal_power_gauge_is_ok,_Bool,
 Function,+,furi_hal_power_gauge_is_ok,_Bool,
 Function,+,furi_hal_power_get_bat_health_pct,uint8_t,
 Function,+,furi_hal_power_get_bat_health_pct,uint8_t,
+Function,+,furi_hal_power_get_battery_charging_voltage,float,
 Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC
 Function,+,furi_hal_power_get_battery_current,float,FuriHalPowerIC
 Function,+,furi_hal_power_get_battery_design_capacity,uint32_t,
 Function,+,furi_hal_power_get_battery_design_capacity,uint32_t,
 Function,+,furi_hal_power_get_battery_full_capacity,uint32_t,
 Function,+,furi_hal_power_get_battery_full_capacity,uint32_t,
@@ -1212,6 +1213,7 @@ Function,+,furi_hal_power_is_charging_done,_Bool,
 Function,+,furi_hal_power_is_otg_enabled,_Bool,
 Function,+,furi_hal_power_is_otg_enabled,_Bool,
 Function,+,furi_hal_power_off,void,
 Function,+,furi_hal_power_off,void,
 Function,+,furi_hal_power_reset,void,
 Function,+,furi_hal_power_reset,void,
+Function,+,furi_hal_power_set_battery_charging_voltage,void,float
 Function,+,furi_hal_power_shutdown,void,
 Function,+,furi_hal_power_shutdown,void,
 Function,+,furi_hal_power_sleep,void,
 Function,+,furi_hal_power_sleep,void,
 Function,+,furi_hal_power_sleep_available,_Bool,
 Function,+,furi_hal_power_sleep_available,_Bool,

+ 26 - 3
firmware/targets/f7/furi_hal/furi_hal_power.c

@@ -341,6 +341,20 @@ bool furi_hal_power_is_otg_enabled() {
     return ret;
     return ret;
 }
 }
 
 
+float furi_hal_power_get_battery_charging_voltage() {
+    furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
+    float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
+    furi_hal_i2c_release(&furi_hal_i2c_handle_power);
+    return ret;
+}
+
+void furi_hal_power_set_battery_charging_voltage(float voltage) {
+    furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
+    // Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated
+    bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f));
+    furi_hal_i2c_release(&furi_hal_i2c_handle_power);
+}
+
 void furi_hal_power_check_otg_status() {
 void furi_hal_power_check_otg_status() {
     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
     furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
     if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power))
     if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power))
@@ -470,10 +484,10 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context)
 
 
     if(sep == '.') {
     if(sep == '.') {
         property_value_out(&property_context, NULL, 2, "format", "major", "2");
         property_value_out(&property_context, NULL, 2, "format", "major", "2");
-        property_value_out(&property_context, NULL, 2, "format", "minor", "0");
+        property_value_out(&property_context, NULL, 2, "format", "minor", "1");
     } else {
     } else {
         property_value_out(&property_context, NULL, 3, "power", "info", "major", "1");
         property_value_out(&property_context, NULL, 3, "power", "info", "major", "1");
-        property_value_out(&property_context, NULL, 3, "power", "info", "minor", "0");
+        property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1");
     }
     }
 
 
     uint8_t charge = furi_hal_power_get_pct();
     uint8_t charge = furi_hal_power_get_pct();
@@ -481,7 +495,7 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context)
 
 
     const char* charge_state;
     const char* charge_state;
     if(furi_hal_power_is_charging()) {
     if(furi_hal_power_is_charging()) {
-        if(charge < 100) {
+        if((charge < 100) && (!furi_hal_power_is_charging_done())) {
             charge_state = "charging";
             charge_state = "charging";
         } else {
         } else {
             charge_state = "charged";
             charge_state = "charged";
@@ -491,6 +505,8 @@ void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context)
     }
     }
 
 
     property_value_out(&property_context, NULL, 2, "charge", "state", charge_state);
     property_value_out(&property_context, NULL, 2, "charge", "state", charge_state);
+    uint16_t charge_voltage = (uint16_t)(furi_hal_power_get_battery_charging_voltage() * 1000.f);
+    property_value_out(&property_context, "%u", 2, "charge", "voltage", charge_voltage);
     uint16_t voltage =
     uint16_t voltage =
         (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f);
         (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f);
     property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage);
     property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage);
@@ -567,6 +583,13 @@ void furi_hal_power_debug_get(PropertyValueCallback out, void* context) {
         "charger",
         "charger",
         "vbat",
         "vbat",
         bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power));
         bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power));
+    property_value_out(
+        &property_context,
+        "%d",
+        2,
+        "charger",
+        "vreg",
+        bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power));
     property_value_out(
     property_value_out(
         &property_context,
         &property_context,
         "%d",
         "%d",

+ 16 - 0
firmware/targets/furi_hal_include/furi_hal_power.h

@@ -121,6 +121,22 @@ void furi_hal_power_check_otg_status();
  */
  */
 bool furi_hal_power_is_otg_enabled();
 bool furi_hal_power_is_otg_enabled();
 
 
+/** Get battery charging voltage in V
+ *
+ * @return     voltage in V
+ */
+float furi_hal_power_get_battery_charging_voltage();
+
+/** Set battery charging voltage in V
+ *
+ * Invalid values will be clamped to the nearest valid value.
+ *
+ * @param      voltage[in]  voltage in V
+ *
+ * @return     voltage in V
+ */
+void furi_hal_power_set_battery_charging_voltage(float voltage);
+
 /** Get remaining battery battery capacity in mAh
 /** Get remaining battery battery capacity in mAh
  *
  *
  * @return     capacity in mAh
  * @return     capacity in mAh

+ 27 - 0
lib/drivers/bq25896.c

@@ -132,6 +132,33 @@ bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle) {
     return bq25896_regs.r03.OTG_CONFIG;
     return bq25896_regs.r03.OTG_CONFIG;
 }
 }
 
 
+uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle) {
+    furi_hal_i2c_read_reg_8(
+        handle, BQ25896_ADDRESS, 0x06, (uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
+    return (uint16_t)bq25896_regs.r06.VREG * 16 + 3840;
+}
+
+void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage) {
+    if(vreg_voltage < 3840) {
+        // Minimum value is 3840 mV
+        bq25896_regs.r06.VREG = 0;
+    } else {
+        // Find the nearest voltage value (subtract offset, divide into sections)
+        // Values are truncated downward as needed (e.g. 4200mV -> 4192 mV)
+        bq25896_regs.r06.VREG = (uint8_t)((vreg_voltage - 3840) / 16);
+    }
+
+    // Do not allow values above 23 (0x17, 4208mV)
+    // Exceeding 4.2v will overcharge the battery!
+    if(bq25896_regs.r06.VREG > 23) {
+        bq25896_regs.r06.VREG = 23;
+    }
+
+    // Apply changes
+    furi_hal_i2c_write_reg_8(
+        handle, BQ25896_ADDRESS, 0x06, *(uint8_t*)&bq25896_regs.r06, BQ25896_I2C_TIMEOUT);
+}
+
 bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) {
 bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle) {
     furi_hal_i2c_read_reg_8(
     furi_hal_i2c_read_reg_8(
         handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT);
         handle, BQ25896_ADDRESS, 0x0C, (uint8_t*)&bq25896_regs.r0C, BQ25896_I2C_TIMEOUT);

+ 9 - 0
lib/drivers/bq25896.h

@@ -36,6 +36,15 @@ void bq25896_disable_otg(FuriHalI2cBusHandle* handle);
 /** Is otg enabled */
 /** Is otg enabled */
 bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle);
 bool bq25896_is_otg_enabled(FuriHalI2cBusHandle* handle);
 
 
+/** Get VREG (charging) voltage in mV */
+uint16_t bq25896_get_vreg_voltage(FuriHalI2cBusHandle* handle);
+
+/** Set VREG (charging) voltage in mV
+ *
+ * Valid range: 3840mV - 4208mV, in steps of 16mV
+ */
+void bq25896_set_vreg_voltage(FuriHalI2cBusHandle* handle, uint16_t vreg_voltage);
+
 /** Check OTG BOOST Fault status */
 /** Check OTG BOOST Fault status */
 bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle);
 bool bq25896_check_otg_fault(FuriHalI2cBusHandle* handle);