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

This commit is contained in:
Shane Synan 2022-12-08 01:57:49 -05:00 committed by GitHub
parent 6a470a464e
commit 5c3a5cd8f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 173 additions and 4 deletions

View File

@ -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, \

View File

@ -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;
}

View File

@ -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},

View File

@ -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();

View File

@ -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;

View File

@ -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,
}; };

View File

@ -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!");
} }

View File

@ -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;

View File

@ -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,

1 entry status name type params
1194 Function + furi_hal_power_enable_otg void
1195 Function + furi_hal_power_gauge_is_ok _Bool
1196 Function + furi_hal_power_get_bat_health_pct uint8_t
1197 Function + furi_hal_power_get_battery_charging_voltage float
1198 Function + furi_hal_power_get_battery_current float FuriHalPowerIC
1199 Function + furi_hal_power_get_battery_design_capacity uint32_t
1200 Function + furi_hal_power_get_battery_full_capacity uint32_t
1213 Function + furi_hal_power_is_otg_enabled _Bool
1214 Function + furi_hal_power_off void
1215 Function + furi_hal_power_reset void
1216 Function + furi_hal_power_set_battery_charging_voltage void float
1217 Function + furi_hal_power_shutdown void
1218 Function + furi_hal_power_sleep void
1219 Function + furi_hal_power_sleep_available _Bool

View File

@ -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",

View File

@ -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

View File

@ -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);

View File

@ -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);