diff --git a/drivers/fuel_gauge/composite/Kconfig b/drivers/fuel_gauge/composite/Kconfig index 7a5ffe22c68e4c..9bc437e9b71da4 100644 --- a/drivers/fuel_gauge/composite/Kconfig +++ b/drivers/fuel_gauge/composite/Kconfig @@ -9,3 +9,17 @@ config FUEL_GAUGE_COMPOSITE depends on DT_HAS_ZEPHYR_FUEL_GAUGE_COMPOSITE_ENABLED help Enable driver for the Zephyr composite fuel gauge device. + +config FUEL_GAUGE_COMPOSITE_VOLTAGE_REUSE_TIMEOUT_MS + int "Maximum age of a cached voltage reading to use in SoC conversion" + depends on FUEL_GAUGE_COMPOSITE + default 100 + help + Since the fuel gauge API reads values one call at a time, reading the battery + voltage and charge percentage are by necessity the product of two calls. + Unfortunately voltage measurements can be noisy, so two calls back to back may + return slightly different values. This can result in the voltage and charge + percentage not exactly matching each other when querying both back to back. + To mitigate this, if a charge percentage call is made within N milliseconds of + a previous `FUEL_GAUGE_VOLTAGE` call, the voltage from the previous call is + used directly in the percentage calulation. diff --git a/drivers/fuel_gauge/composite/fuel_gauge_composite.c b/drivers/fuel_gauge/composite/fuel_gauge_composite.c index bd75193cbe471f..25c2a7d218d349 100644 --- a/drivers/fuel_gauge/composite/fuel_gauge_composite.c +++ b/drivers/fuel_gauge/composite/fuel_gauge_composite.c @@ -21,6 +21,11 @@ struct composite_config { enum battery_chemistry chemistry; }; +struct composite_data { + int voltage_val; + k_ticks_t voltage_time; +}; + static int composite_read_micro(const struct device *dev, enum sensor_channel chan, int *val) { struct sensor_value sensor_val; @@ -51,6 +56,7 @@ static int composite_get_prop(const struct device *dev, fuel_gauge_prop_t prop, { const struct composite_config *config = dev->config; int voltage, rc = 0; + int voltage_age_ms; switch (prop) { case FUEL_GAUGE_FULL_CHARGE_CAPACITY: @@ -68,13 +74,33 @@ static int composite_get_prop(const struct device *dev, fuel_gauge_prop_t prop, case FUEL_GAUGE_VOLTAGE: rc = composite_read_micro(config->battery_voltage, SENSOR_CHAN_VOLTAGE, &val->voltage); + if (rc == 0) { + data->voltage_val = voltage; + data->voltage_time = k_uptime_ticks(); + } break; case FUEL_GAUGE_ABSOLUTE_STATE_OF_CHARGE: case FUEL_GAUGE_RELATIVE_STATE_OF_CHARGE: if (config->ocv_lookup_table[0] == -1) { return -ENOTSUP; } - rc = composite_read_micro(config->battery_voltage, SENSOR_CHAN_VOLTAGE, &voltage); + /* Age of the previous voltage measurement */ + voltage_age_ms = k_ticks_to_ms_near32(k_uptime_ticks() - data->voltage_time); + if (data->voltage_time && + (voltage_age_ms < CONFIG_FUEL_GAUGE_COMPOSITE_VOLTAGE_REUSE_TIMEOUT_MS)) { + /* Re-use the latest voltage measurement rather than sampling again */ + voltage = data->voltage_val; + rc = 0; + } else { + /* Measure voltage in this call */ + rc = composite_read_micro(config->battery_voltage, SENSOR_CHAN_VOLTAGE, + &voltage); + if (rc == 0) { + data->voltage_val = voltage; + data->voltage_time = k_uptime_ticks(); + } + } + /* Convert voltage to state of charge */ val->relative_state_of_charge = battery_soc_lookup(config->ocv_lookup_table, voltage) / 1000; break; @@ -106,7 +132,9 @@ static DEVICE_API(fuel_gauge, composite_api) = { DT_INST_PROP_OR(inst, charge_full_design_microamp_hours, 0), \ .chemistry = BATTERY_CHEMISTRY_DT_GET(inst), \ }; \ - DEVICE_DT_INST_DEFINE(inst, NULL, NULL, NULL, &composite_##inst##_config, POST_KERNEL, \ + static struct composite_data composite_##inst##_data; \ + DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &composite_##inst##_data, \ + &composite_##inst##_config, POST_KERNEL, \ CONFIG_SENSOR_INIT_PRIORITY, &composite_api); DT_INST_FOREACH_STATUS_OKAY(COMPOSITE_INIT)