powermgmt: Refactor battery measurement code

I've tried to clean up cruft and clarify what's being
done, prior to adding new features. The behavior should
be unchanged.

Change-Id: If8a68b7b48173d283df981a21cdd854b714e7695
This commit is contained in:
Aidan MacDonald 2021-12-12 12:30:50 +00:00
parent d6faef8205
commit 4506f2b58d
2 changed files with 278 additions and 301 deletions

View file

@ -52,11 +52,7 @@
#include "pcf50606.h"
#endif
/** Shared by sim **/
static int last_sent_battery_level = 100;
/* battery level (0-100%) */
int battery_percent = -1;
void send_battery_level_event(void);
static void set_sleep_timer(int seconds);
static bool sleeptimer_active = false;
@ -83,41 +79,6 @@ void handle_auto_poweroff(void);
static int poweroff_timeout = 0;
static long last_event_tick = 0;
#if CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
#ifdef SIMULATOR
int _battery_level(void) { return -1; }
#endif
#else
int _battery_level(void) { return -1; }
#endif
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
/*
* Average battery voltage and charger voltage, filtered via a digital
* exponential filter (aka. exponential moving average, scaled):
* avgbat = y[n] = (N-1)/N*y[n-1] + x[n]. battery_millivolts = y[n] / N.
*/
static unsigned int avgbat;
/* filtered battery voltage, millivolts */
static unsigned int battery_millivolts;
#else
#ifndef SIMULATOR
int _battery_voltage(void) { return -1; }
const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11];
const unsigned short percent_to_volt_charge[11];
#endif
#endif
#if !(CONFIG_BATTERY_MEASURE & TIME_MEASURE)
#ifdef CURRENT_NORMAL
static int powermgmt_est_runningtime_min;
int _battery_time(void) { return powermgmt_est_runningtime_min; }
#else
int _battery_time(void) { return -1; }
#endif
#endif
/* default value, mAh */
#if BATTERY_CAPACITY_INC > 0
static int battery_capacity = BATTERY_CAPACITY_DEFAULT;
#else
@ -141,170 +102,71 @@ static char power_stack[DEFAULT_STACK_SIZE/2];
#endif
static const char power_thread_name[] = "power";
static int voltage_to_battery_level(int battery_millivolts);
static void battery_status_update(void);
#if BATTERY_TYPES_COUNT > 1
void set_battery_type(int type)
{
if (type != battery_type) {
if ((unsigned)type >= BATTERY_TYPES_COUNT)
type = 0;
battery_type = type;
battery_status_update(); /* recalculate the battery status */
}
}
#if !(CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE)
int _battery_level(void) { return -1; }
#endif
static int percent_now; /* Cached to avoid polling too often */
#if BATTERY_CAPACITY_INC > 0
void set_battery_capacity(int capacity)
{
if (capacity > BATTERY_CAPACITY_MAX)
capacity = BATTERY_CAPACITY_MAX;
if (capacity < BATTERY_CAPACITY_MIN)
capacity = BATTERY_CAPACITY_MIN;
battery_capacity = capacity;
battery_status_update(); /* recalculate the battery status */
}
#endif
int get_battery_capacity(void)
{
return battery_capacity;
}
int battery_time(void)
{
#if !(CONFIG_BATTERY_MEASURE & TIME_MEASURE)
/* Note: This should not really be possible but it might occur
* as a degenerate case for targets that don't define any battery
* capacity at all..? */
if(battery_capacity <= 0)
return -1;
int _battery_time(void) { return -1; }
#endif
#if (CONFIG_BATTERY_MEASURE & TIME_MEASURE) || defined(CURRENT_NORMAL)
static int time_now; /* Cached to avoid polling too often */
#endif
return _battery_time();
}
#if !(CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE)
int _battery_voltage(void) { return -1; }
#else
/* Data for the digital exponential filter */
static int voltage_avg, voltage_now;
#endif
/* Returns battery level in percent */
/* The battery level can be obtained in two ways. If the target reports
* voltage, the battery level can be estminated using percent_to_volt_*
* curves. If the target can report the percentage directly, then that
* will be used instead of voltage-based estimation. */
int battery_level(void)
{
#ifdef HAVE_BATTERY_SWITCH
if ((power_input_status() & POWER_INPUT_BATTERY) == 0)
return -1;
#endif
return battery_percent;
return percent_now;
}
/* Tells if the battery level is safe for disk writes */
bool battery_level_safe(void)
/* The time remaining to full charge/discharge can be provided by the
* target if it has an accurate way of doing this. Otherwise, if the
* target defines a valid battery capacity and can report the charging
* and discharging current, the time remaining will be estimated based
* on the battery level and the actual current usage. */
int battery_time(void)
{
#if defined(NO_LOW_BATTERY_SHUTDOWN)
return true;
#elif CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
return (battery_percent > 0);
#elif defined(HAVE_BATTERY_SWITCH)
/* Cannot rely upon the battery reading to be valid and the
* device could be powered externally. */
return input_millivolts() > battery_level_dangerous[battery_type];
#if (CONFIG_BATTERY_MEASURE & TIME_MEASURE) || defined(CURRENT_NORMAL)
return time_now;
#else
return battery_millivolts > battery_level_dangerous[battery_type];
#endif
}
/* look into the percent_to_volt_* table and get a realistic battery level */
static int voltage_to_percent(int voltage, const short* table)
{
if (voltage <= table[0]) {
return 0;
}
else if (voltage >= table[10]) {
return 100;
}
else {
/* search nearest value */
int i = 0;
while (i < 10 && table[i+1] < voltage)
i++;
/* interpolate linear between the smaller and greater value */
/* Tens digit, 10% per entry, ones digit: interpolated */
return i*10 + (voltage - table[i])*10 / (table[i+1] - table[i]);
}
}
/* update battery level and estimated runtime, called once per minute or
* when battery capacity / type settings are changed */
static int voltage_to_battery_level(int battery_millivolts)
{
int level;
if (battery_millivolts < 0)
return -1;
#if CONFIG_CHARGING >= CHARGING_MONITOR
if (charging_state()) {
/* battery level is defined to be < 100% until charging is finished */
level = voltage_to_percent(battery_millivolts,
percent_to_volt_charge);
if (level > 99)
level = 99;
}
else
#endif /* CONFIG_CHARGING >= CHARGING_MONITOR */
{
/* DISCHARGING or error state */
level = voltage_to_percent(battery_millivolts,
percent_to_volt_discharge[battery_type]);
}
return level;
}
static void battery_status_update(void)
{
int millivolt = battery_voltage();
int level = _battery_level();
if (level < 0)
level = voltage_to_battery_level(millivolt);
#ifdef CURRENT_NORMAL
int current = battery_current();
#if CONFIG_CHARGING >= CHARGING_MONITOR
if (charging_state()) {
/* charging: remaining charging time */
if(current > 0) {
powermgmt_est_runningtime_min =
(100 - level) * battery_capacity * 60 / 100 / current;
}
}
else
#endif
/* discharging: remaining running time */
if (level >= 0 && current > 0) {
powermgmt_est_runningtime_min =
(level + battery_percent) * battery_capacity * 60 / 200 / current;
}
if (powermgmt_est_runningtime_min < 0 || current <= 0)
powermgmt_est_runningtime_min = 0;
/* Battery voltage should always be reported if available, but it is
* optional if the the target reports battery percentage directly. */
int battery_voltage(void)
{
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
return voltage_now;
#else
return -1;
#endif
battery_percent = level;
send_battery_level_event();
}
#ifdef CURRENT_NORMAL
/* Battery current can be estimated if the target defines CURRENT_NORMAL
* as the number of milliamps usually consumed by the device in a normal
* state. The target can also define other CURRENT_* values to estimate
* the power consumed by the backlight, remote display, SPDIF, etc. */
int battery_current(void)
{
#if defined(CURRENT_NORMAL)
int current = CURRENT_NORMAL;
#ifndef BOOTLOADER
@ -356,8 +218,207 @@ int battery_current(void)
#endif /* BOOTLOADER */
return current;
#else
return -1;
#endif
}
/* Initialize the battery voltage filter. This is called once
* by the power thread before entering the main polling loop. */
static void average_init(void)
{
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
voltage_now = _battery_voltage() + 15;
/* The battery voltage is usually a little lower directly after
turning on, because the disk was used heavily. Raise it by 5% */
#ifdef HAVE_DISK_STORAGE
#if CONFIG_CHARGING
if(!charger_inserted())
#endif
{
voltage_now += (percent_to_volt_discharge[battery_type][6] -
percent_to_volt_discharge[battery_type][5]) / 2;
}
#endif /* HAVE_DISK_STORAGE */
voltage_avg = voltage_now * BATT_AVE_SAMPLES;
#endif /* CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE */
}
/* Sample the battery voltage and update the filter.
* Updated once every POWER_THREAD_STEP_TICKS. */
static void average_step(bool low_battery)
{
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
int millivolts = _battery_voltage();
if(low_battery) {
voltage_now = (millivolts + voltage_now + 1) / 2;
voltage_avg += voltage_now - voltage_avg / BATT_AVE_SAMPLES;
} else {
voltage_avg += millivolts - voltage_avg / BATT_AVE_SAMPLES;
voltage_now = voltage_avg / BATT_AVE_SAMPLES;
}
#else
(void)low_battery;
#endif
}
/* Send system battery level update events on reaching certain significant
* levels. This is called by battery_status_update() and does not have to
* be called separately. */
static void send_battery_level_event(int percent)
{
static const int levels[] = { 5, 15, 30, 50, 0 };
const int *level = levels;
while (*level)
{
if (percent <= *level && last_sent_battery_level > *level) {
last_sent_battery_level = *level;
queue_broadcast(SYS_BATTERY_UPDATE, last_sent_battery_level);
break;
}
level++;
}
}
#if !(CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE)
/* Look into the percent_to_volt_* table and estimate the battery level. */
static int voltage_to_percent(int voltage, const short* table)
{
if (voltage <= table[0]) {
return 0;
}
else if (voltage >= table[10]) {
return 100;
}
else {
/* search nearest value */
int i = 0;
while (i < 10 && table[i+1] < voltage)
i++;
/* interpolate linear between the smaller and greater value */
/* Tens digit, 10% per entry, ones digit: interpolated */
return i*10 + (voltage - table[i])*10 / (table[i+1] - table[i]);
}
}
/* Convert voltage to a battery level percentage using the appropriate
* percent_to_volt_* lookup table. */
static int voltage_to_battery_level(int millivolts)
{
int level;
if (millivolts < 0)
return -1;
#if CONFIG_CHARGING >= CHARGING_MONITOR
if (charging_state()) {
/* battery level is defined to be < 100% until charging is finished */
level = voltage_to_percent(millivolts, percent_to_volt_charge);
if (level > 99)
level = 99;
}
else
#endif /* CONFIG_CHARGING >= CHARGING_MONITOR */
{
/* DISCHARGING or error state */
level = voltage_to_percent(millivolts, percent_to_volt_discharge[battery_type]);
}
return level;
}
#endif
/* Update battery percentage and time remaining information.
*
* This will be called by the power thread after polling new battery data.
* It must also be called if the battery type or capacity changes.
*/
static void battery_status_update(void)
{
#if CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
int level = _battery_level();
#elif CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
int level = voltage_to_battery_level(voltage_now);
#else
/* This should be a compile time error? */
int level = -1;
#endif
#if CONFIG_BATTERY_MEASURE & TIME_MEASURE
time_now = _battery_time();
#elif defined(CURRENT_NORMAL)
int current = battery_current();
if(level >= 0 && current > 0 && battery_capacity > 0) {
#if CONFIG_CHARGING >= CHARGING_MONITOR
if (charging_state())
time_now = (100 - level) * battery_capacity * 60 / 100 / current;
else
#endif
time_now = (level + percent_now) * battery_capacity * 60 / 200 / current;
} else {
/* not enough information to calculate time remaining */
time_now = -1;
}
#endif
percent_now = level;
send_battery_level_event(level);
}
#if BATTERY_TYPES_COUNT > 1
void set_battery_type(int type)
{
if(type < 0 || type > BATTERY_TYPES_COUNT)
type = 0;
if (type != battery_type) {
battery_type = type;
battery_status_update(); /* recalculate the battery status */
}
}
#endif
#if BATTERY_CAPACITY_INC > 0
void set_battery_capacity(int capacity)
{
if (capacity > BATTERY_CAPACITY_MAX)
capacity = BATTERY_CAPACITY_MAX;
if (capacity < BATTERY_CAPACITY_MIN)
capacity = BATTERY_CAPACITY_MIN;
if (capacity != battery_capacity) {
battery_capacity = capacity;
battery_status_update(); /* recalculate the battery status */
}
}
#endif
int get_battery_capacity(void)
{
return battery_capacity;
}
/* Tells if the battery level is safe for disk writes */
bool battery_level_safe(void)
{
#if defined(NO_LOW_BATTERY_SHUTDOWN)
return true;
#elif CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
return percent_now > 0;
#elif defined(HAVE_BATTERY_SWITCH)
/* Cannot rely upon the battery reading to be valid and the
* device could be powered externally. */
return input_millivolts() > battery_level_dangerous[battery_type];
#else
return voltage_now > battery_level_dangerous[battery_type];
#endif
}
#endif /* CURRENT_NORMAL */
/* Check to see whether or not we've received an alarm in the last second */
#ifdef HAVE_RTC_ALARM
@ -374,13 +435,13 @@ bool query_force_shutdown(void)
#if defined(NO_LOW_BATTERY_SHUTDOWN)
return false;
#elif CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
return battery_percent == 0;
return percent_now == 0;
#elif defined(HAVE_BATTERY_SWITCH)
/* Cannot rely upon the battery reading to be valid and the
* device could be powered externally. */
return input_millivolts() < battery_level_shutoff[battery_type];
#else
return battery_millivolts < battery_level_shutoff[battery_type];
return voltage_now < battery_level_shutoff[battery_type];
#endif
}
@ -391,8 +452,8 @@ bool query_force_shutdown(void)
*/
void reset_battery_filter(int millivolts)
{
avgbat = millivolts * BATT_AVE_SAMPLES;
battery_millivolts = millivolts;
voltage_avg = millivolts * BATT_AVE_SAMPLES;
voltage_now = millivolts;
battery_status_update();
}
#endif /* HAVE_BATTERY_SWITCH */
@ -529,88 +590,14 @@ static inline bool detect_charger(unsigned int pwr)
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
/* Returns filtered battery voltage [millivolts] */
int battery_voltage(void)
{
return battery_millivolts;
}
static void average_init(void)
{
/* initialize the voltages for the exponential filter */
avgbat = _battery_voltage() + 15;
#ifdef HAVE_DISK_STORAGE /* this adjustment is only needed for HD based */
/* The battery voltage is usually a little lower directly after
turning on, because the disk was used heavily. Raise it by 5% */
#if CONFIG_CHARGING
if (!charger_inserted()) /* only if charger not connected */
#endif
{
avgbat += (percent_to_volt_discharge[battery_type][6] -
percent_to_volt_discharge[battery_type][5]) / 2;
}
#endif /* HAVE_DISK_STORAGE */
avgbat = avgbat * BATT_AVE_SAMPLES;
battery_millivolts = power_history[0] = avgbat / BATT_AVE_SAMPLES;
}
static void average_step(void)
{
avgbat += _battery_voltage() - avgbat / BATT_AVE_SAMPLES;
/*
* battery_millivolts is the millivolt-scaled filtered battery value.
*/
battery_millivolts = avgbat / BATT_AVE_SAMPLES;
}
static void average_step_low(void)
{
battery_millivolts = (_battery_voltage() + battery_millivolts + 1) / 2;
avgbat += battery_millivolts - avgbat / BATT_AVE_SAMPLES;
}
static void init_battery_percent(void)
{
#if CONFIG_CHARGING
if (charger_inserted()) {
battery_percent = voltage_to_percent(battery_millivolts,
percent_to_volt_charge);
}
else
#endif
{
battery_percent = voltage_to_percent(battery_millivolts,
percent_to_volt_discharge[battery_type]);
battery_percent += battery_percent < 100;
}
}
static int power_hist_item(void)
{
return battery_millivolts;
return voltage_now;
}
#define power_history_unit() battery_millivolts
#else
int battery_voltage(void)
{
return -1;
}
static void average_init(void) {}
static void average_step(void) {}
static void average_step_low(void) {}
static void init_battery_percent(void)
{
battery_percent = _battery_level();
}
static int power_hist_item(void)
{
return battery_percent;
return percent_now;
}
#endif
@ -651,13 +638,11 @@ static inline void power_thread_step(void)
|| charger_input_state == CHARGER
#endif
) {
average_step();
/* update battery status every time an update is available */
average_step(false);
battery_status_update();
}
else if (battery_percent < 8) {
average_step_low();
/* update battery status every time an update is available */
else if (percent_now < 8) {
average_step(true);
battery_status_update();
/*
@ -703,7 +688,7 @@ static void power_thread(void)
/* initialize voltage averaging (if available) */
average_init();
/* get initial battery level value (in %) */
init_battery_percent();
battery_status_update();
/* get some initial data for the power curve */
collect_power_history();
@ -848,25 +833,6 @@ void cancel_shutdown(void)
shutdown_timeout = 0;
}
/* Send system battery level update events on reaching certain significant
levels. This must be called after battery_percent has been updated. */
void send_battery_level_event(void)
{
static const int levels[] = { 5, 15, 30, 50, 0 };
const int *level = levels;
while (*level)
{
if (battery_percent <= *level && last_sent_battery_level > *level) {
last_sent_battery_level = *level;
queue_broadcast(SYS_BATTERY_UPDATE, last_sent_battery_level);
break;
}
level++;
}
}
void set_sleeptimer_duration(int minutes)
{
set_sleep_timer(minutes * 60);

View file

@ -39,9 +39,10 @@
#define POWER_AFTER_CHARGE_TICKS (8 * HZ)
#endif
extern int battery_percent;
static bool charging = false;
static unsigned int battery_millivolts = BATT_MAXMVOLT;
static unsigned int batt_millivolts = BATT_MAXMVOLT;
static unsigned int batt_percent = 100;
static unsigned int batt_runtime = BATT_MAXRUNTIME;
void powermgmt_init_target(void) {}
@ -54,7 +55,7 @@ static void battery_status_update(void)
static unsigned int ext_power_until_tick = 0;
#endif
if TIME_BEFORE(current_tick, update_after_tick)
if(TIME_BEFORE(current_tick, update_after_tick))
return;
update_after_tick = current_tick + HZ;
@ -72,10 +73,9 @@ static void battery_status_update(void)
#endif
if (charging) {
battery_millivolts += BATT_CHARGE_STEP;
if (battery_millivolts >= BATT_MAXMVOLT) {
batt_millivolts += BATT_CHARGE_STEP;
if (batt_millivolts >= BATT_MAXMVOLT) {
charging = false;
battery_percent = 100;
#if CONFIG_CHARGING >= CHARGING_MONITOR
/* Keep external power until tick */
ext_power_until_tick = current_tick + POWER_AFTER_CHARGE_TICKS;
@ -83,23 +83,20 @@ static void battery_status_update(void)
/* Pretend the charger was disconnected */
charger_input_state = CHARGER_UNPLUGGED;
#endif
return;
}
} else {
battery_millivolts -= BATT_DISCHARGE_STEP;
if (battery_millivolts <= BATT_MINMVOLT) {
batt_millivolts -= BATT_DISCHARGE_STEP;
if (batt_millivolts <= BATT_MINMVOLT) {
charging = true;
battery_percent = 0;
#if CONFIG_CHARGING
/* Pretend the charger was connected */
charger_input_state = CHARGER_PLUGGED;
#endif
return;
}
}
battery_percent = 100 * (battery_millivolts - BATT_MINMVOLT) /
(BATT_MAXMVOLT - BATT_MINMVOLT);
batt_percent = (batt_millivolts - BATT_MINMVOLT) / (BATT_MAXMVOLT - BATT_MINMVOLT);
batt_runtime = batt_percent * BATT_MAXRUNTIME;
}
const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = { 3200 };
@ -111,15 +108,28 @@ const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] =
const unsigned short percent_to_volt_charge[11] =
{ 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, 4100, 4200, 4300 };
#if CONFIG_BATTERY_MEASURE & VOLTAGE_MEASURE
int _battery_voltage(void)
{
battery_status_update();
return battery_millivolts;
return batt_millivolts;
}
#endif
#if CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE
int _battery_level(void)
{
battery_status_update();
return batt_percent;
}
#endif
#if (CONFIG_BATTERY_MEASURE & TIME_MEASURE)
static int powermgmt_est_runningtime_min;
int _battery_time(void) { return powermgmt_est_runningtime_min; }
int _battery_time(void)
{
battery_status_update();
return batt_runtime;
}
#endif
#if CONFIG_CHARGING
@ -169,6 +179,7 @@ unsigned int input_millivolts(void)
/* Just return a safe value if battery isn't connected */
return 4050;
}
return battery_voltage();;
return battery_voltage();
}
#endif