summaryrefslogtreecommitdiffstats
path: root/firmware/powermgmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/powermgmt.c')
-rw-r--r--firmware/powermgmt.c819
1 files changed, 411 insertions, 408 deletions
diff --git a/firmware/powermgmt.c b/firmware/powermgmt.c
index 2a15b9dfb4..0db2f03a7b 100644
--- a/firmware/powermgmt.c
+++ b/firmware/powermgmt.c
@@ -8,6 +8,7 @@
* $Id$
*
* Copyright (C) 2002 by Heikki Hannikainen, Uwe Freese
+ * Revisions copyright (C) 2005 by Gerald Van Baren
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
@@ -41,6 +42,22 @@
#include "fmradio.h"
#endif
+/*
+ * Define DEBUG_FILE to create a csv (spreadsheet) with battery information
+ * in it (one sample per minute). This is only for very low level debug.
+ */
+#undef DEBUG_FILE
+#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
+#include "file.h"
+#define DEBUG_FILE_NAME "/powermgmt.csv"
+#define DEBUG_MESSAGE_LEN 133
+static char debug_message[DEBUG_MESSAGE_LEN];
+#define DEBUG_STACK ((0x1000)/sizeof(long))
+static int fd; /* write debug information to this file */
+#else
+#define DEBUG_STACK 0
+#endif
+
long last_event_tick;
void reset_poweroff_timer(void)
@@ -48,7 +65,7 @@ void reset_poweroff_timer(void)
last_event_tick = current_tick;
}
-#ifdef SIMULATOR
+#ifdef SIMULATOR /***********************************************************/
int battery_level(void)
{
@@ -80,19 +97,14 @@ void set_car_adapter_mode(bool setting)
(void)setting;
}
-#else /* not SIMULATOR */
-
-int battery_capacity = BATTERY_CAPACITY_MIN; /* only a default value */
-int battery_level_cached = -1; /* battery level of this minute, updated once
- per minute */
-static bool car_adapter_mode_enabled = false;
+#else /* not SIMULATOR ******************************************************/
static const int poweroff_idle_timeout_value[15] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 30, 45, 60
};
-static const short percent_to_volt_decharge[BATTERY_TYPES_COUNT][11] =
+static const short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] =
/* voltages (centivolt) of 0%, 10%, ... 100% when charging disabled */
{
#if CONFIG_BATTERY == BATT_LIION2200
@@ -110,50 +122,44 @@ static const short percent_to_volt_decharge[BATTERY_TYPES_COUNT][11] =
#endif
};
-void set_battery_capacity(int capacity)
-{
- battery_capacity = capacity;
- if (battery_capacity > BATTERY_CAPACITY_MAX)
- battery_capacity = BATTERY_CAPACITY_MAX;
- if (battery_capacity < BATTERY_CAPACITY_MIN)
- battery_capacity = BATTERY_CAPACITY_MIN;
-}
-
-#if BATTERY_TYPES_COUNT > 1
static int battery_type = 0;
-void set_battery_type(int type)
-{
- if (type != battery_type) {
- battery_type = type;
- battery_level_cached = -1; /* reset on type change */
- }
-}
+#if defined(HAVE_CHARGE_CTRL) || CONFIG_BATTERY == BATT_LIION2200
+charge_state_type charge_state; /* charging mode */
+int charge_timer; /* charge timer (minutes, dec to zero) */
#endif
-#if defined(HAVE_CHARGE_CTRL) || CONFIG_BATTERY == BATT_LIION2200
-int charge_state = 0; /* at the beginning, the
- charger does nothing */
+#ifdef HAVE_CHARGING
+/* Flag that the charger has been plugged in */
+static bool charger_was_inserted = false; /* for power off logic */
+static bool charger_power_is_on; /* for car adapter mode */
#endif
+/* Power history: power_history[0] is the newest sample */
+unsigned short power_history[POWER_HISTORY_LEN];
+
#ifdef HAVE_CHARGE_CTRL
+int long_delta; /* long term delta battery voltage */
+int short_delta; /* short term delta battery voltage */
+
char power_message[POWER_MESSAGE_LEN] = ""; /* message that's shown in
debug menu */
-char charge_restart_level = CHARGE_RESTART_HI; /* level at which charging
+static char charge_restart_level = CHARGE_RESTART_HI;
+ /* percentage at which charging
starts */
-int powermgmt_last_cycle_startstop_min = 25; /* how many minutes ago was the
+int powermgmt_last_cycle_startstop_min = 0; /* how many minutes ago was the
charging started or
stopped? */
int powermgmt_last_cycle_level = 0; /* which level had the
batteries at this time? */
bool trickle_charge_enabled = true;
-int trickle_sec = 0; /* how many seconds should the
+int trickle_sec = 0; /* how many seconds should the
charger be enabled per
minute for trickle
charging? */
-static const short percent_to_volt_charge[11] =
/* voltages (centivolt) of 0%, 10%, ... 100% when charging enabled */
+static const short percent_to_volt_charge[11] =
{
/* values guessed, see
http://www.seattlerobotics.org/encoder/200210/LiIon2.pdf until someone
@@ -161,23 +167,64 @@ static const short percent_to_volt_charge[11] =
476, 544, 551, 556, 561, 564, 566, 576, 582, 584, 585 /* NiMH */
};
-void enable_trickle_charge(bool on)
-{
- trickle_charge_enabled = on;
-}
-#endif /* HAVE_CHARGE_CTRL */
+#endif /* HAVE_CHARGE_CTRL || CONFIG_BATTERY == BATT_LIION2200 */
+
+/*
+ * Average battery voltage and charger voltage, filtered via a digital
+ * exponential filter.
+ */
+unsigned int battery_centivolts;/* filtered battery voltage, centvolts */
+static unsigned int avgbat; /* average battery voltage */
+#define BATT_AVE_SAMPLES 32 /* filter constant / @ 2Hz sample rate */
+
+int battery_capacity = BATTERY_CAPACITY_MIN; /* only a default value */
-static long power_stack[DEFAULT_STACK_SIZE/sizeof(long)];
+/* battery level (0-100%) of this minute, updated once per minute */
+static int battery_percent = -1;
+
+static bool car_adapter_mode_enabled = false;
+
+static char power_stack[DEFAULT_STACK_SIZE + DEBUG_STACK];
static const char power_thread_name[] = "power";
-static int poweroff_timeout = 0;
+static int poweroff_timeout = 0;
static long last_charge_time = 0;
int powermgmt_est_runningtime_min = -1;
static bool sleeptimer_active = false;
static unsigned long sleeptimer_endtick;
-unsigned short power_history[POWER_HISTORY_LEN];
+#ifdef HAVE_CHARGE_CTRL
+
+void enable_deep_discharge(bool on)
+{
+ charge_restart_level = on ? CHARGE_RESTART_LO : CHARGE_RESTART_HI;
+}
+
+void enable_trickle_charge(bool on)
+{
+ trickle_charge_enabled = on;
+}
+#endif /* HAVE_CHARGE_CTRL */
+
+#if BATTERY_TYPES_COUNT > 1
+void set_battery_type(int type)
+{
+ if (type != battery_type) {
+ battery_type = type;
+ battery_percent = -1; /* reset on type change */
+ }
+}
+#endif
+
+void set_battery_capacity(int capacity)
+{
+ battery_capacity = capacity;
+ if (battery_capacity > BATTERY_CAPACITY_MAX)
+ battery_capacity = BATTERY_CAPACITY_MAX;
+ if (battery_capacity < BATTERY_CAPACITY_MIN)
+ battery_capacity = BATTERY_CAPACITY_MIN;
+}
int battery_time(void)
{
@@ -208,23 +255,9 @@ int voltage_to_percent(int voltage, const short* table)
/* update battery level, called only once per minute */
void battery_level_update(void)
{
- int level = 0;
- unsigned short c = 0;
- int i;
-#if BATTERY_TYPES_COUNT == 1 /* single type */
- const int battery_type = 0;
-#endif
-
- /* calculate maximum over last 3 minutes (skip empty samples) */
- for (i = 0; i < 3; i++)
- if (power_history[POWER_HISTORY_LEN-1-i] > c)
- c = power_history[POWER_HISTORY_LEN-1-i];
-
- if (c)
- level = c;
- else /* history was empty, get a fresh sample */
- level = (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000;
+ int level;
+ level = battery_centivolts;
if(level > BATTERY_LEVEL_FULL)
level = BATTERY_LEVEL_FULL;
@@ -232,27 +265,27 @@ void battery_level_update(void)
level = BATTERY_LEVEL_EMPTY;
#ifdef HAVE_CHARGE_CTRL
- if (charge_state == 0) { /* decharge */
+ if (charge_state == DISCHARGING) {
level = voltage_to_percent(level,
- percent_to_volt_decharge[battery_type]);
+ percent_to_volt_discharge[battery_type]);
}
- else if (charge_state == 1) { /* charge */
+ else if (charge_state == CHARGING) {
level = voltage_to_percent(level, percent_to_volt_charge);
}
- else {/* in trickle charge, the battery is per definition 100% full */
- battery_level_cached = level = 100;
+ else {/* in trickle charge, the battery is by definition 100% full */
+ battery_percent = level = 100;
}
#else
+ /* always use the discharge table */
level = voltage_to_percent(level,
- percent_to_volt_decharge[battery_type]);
- /* always use the decharge table */
+ percent_to_volt_discharge[battery_type]);
#endif
#ifndef HAVE_MMC /* this adjustment is only needed for HD based */
- if (battery_level_cached == -1) { /* first run of this procedure */
- /* the battery voltage is usually a little lower directly after
- turning on, because the disk was used heavily raise it by 5 % */
- battery_level_cached = (level > 95) ? 100 : level + 5;
+ if (battery_percent == -1) { /* first run of this procedure */
+ /* The battery voltage is usually a little lower directly after
+ turning on, because the disk was used heavily. Raise it by 5. % */
+ battery_percent = (level > 95) ? 100 : level + 5;
}
else
#endif
@@ -260,14 +293,14 @@ void battery_level_update(void)
/* the level is allowed to be -1 of the last value when usb not
connected and to be -3 of the last value when usb is connected */
if (usb_inserted()) {
- if (level < battery_level_cached - 3)
- level = battery_level_cached - 3;
+ if (level < battery_percent - 3)
+ level = battery_percent - 3;
}
else {
- if (level < battery_level_cached - 1)
- level = battery_level_cached - 1;
+ if (level < battery_percent - 1)
+ level = battery_percent - 1;
}
- battery_level_cached = level;
+ battery_percent = level;
}
}
@@ -275,21 +308,16 @@ void battery_level_update(void)
int battery_level(void)
{
#ifdef HAVE_CHARGE_CTRL
- if ((charge_state==1) && (battery_level_cached==100))
+ if ((charge_state == CHARGING) && (battery_percent == 100))
return 99;
#endif
- return battery_level_cached;
+ return battery_percent;
}
/* Tells if the battery level is safe for disk writes */
bool battery_level_safe(void)
{
- /* I'm pretty sure we don't want an average over a long time here */
- if (power_history[POWER_HISTORY_LEN-1])
- return power_history[POWER_HISTORY_LEN-1] > BATTERY_LEVEL_DANGEROUS;
- else
- return adc_read(ADC_UNREG_POWER) > (BATTERY_LEVEL_DANGEROUS * 10000L) /
- BATTERY_SCALE_FACTOR;
+ return battery_centivolts > BATTERY_LEVEL_DANGEROUS;
}
void set_poweroff_timeout(int timeout)
@@ -329,10 +357,12 @@ int get_sleep_timer(void)
static void handle_auto_poweroff(void)
{
long timeout = poweroff_idle_timeout_value[poweroff_timeout]*60*HZ;
- int mpeg_stat = mpeg_status();
+ int mpeg_stat = mpeg_status();
+#ifdef HAVE_CHARGING
bool charger_is_inserted = charger_inserted();
- static bool charger_was_inserted = false;
+#endif
+#ifdef HAVE_CHARGING
/* The was_inserted thing prevents the unit to shut down immediately
when the charger is extracted */
if(charger_is_inserted || charger_was_inserted)
@@ -340,13 +370,14 @@ static void handle_auto_poweroff(void)
last_charge_time = current_tick;
}
charger_was_inserted = charger_is_inserted;
+#endif
if(timeout &&
#ifdef CONFIG_TUNER
(radio_get_status() != FMRADIO_PLAYING) &&
#endif
!usb_inserted() &&
- (mpeg_stat == 0 ||
+ ((mpeg_stat == 0) ||
((mpeg_stat == (MPEG_STATUS_PLAY | MPEG_STATUS_PAUSE)) &&
!sleeptimer_active)))
{
@@ -365,6 +396,7 @@ static void handle_auto_poweroff(void)
if(TIME_AFTER(current_tick, sleeptimer_endtick))
{
mpeg_stop();
+#ifdef HAVE_CHARGING
if(charger_is_inserted)
{
DEBUGF("Sleep timer timeout. Stopping...\n");
@@ -372,6 +404,7 @@ static void handle_auto_poweroff(void)
backlight_off(); /* Nighty, nighty... */
}
else
+#endif
{
DEBUGF("Sleep timer timeout. Shutting off...\n");
/* Make sure that the disk isn't spinning when
@@ -390,8 +423,6 @@ void set_car_adapter_mode(bool setting)
car_adapter_mode_enabled = setting;
}
-static bool charger_power_is_on;
-
#ifdef HAVE_CHARGING
static void car_adapter_mode_processing(void)
{
@@ -442,13 +473,38 @@ static void car_adapter_mode_processing(void)
}
#endif
+/*
+ * Estimate how much current we are drawing just to run.
+ */
+static int runcurrent(void)
+{
+ int current;
+
+ current = CURRENT_NORMAL;
+ if(usb_inserted()) {
+ current = CURRENT_USB;
+ }
+#ifdef HAVE_CHARGE_CTRL
+ if ((backlight_get_timeout() == 1) || /* LED always on */
+ (charger_inserted() && backlight_get_on_when_charging())) {
+ current += CURRENT_BACKLIGHT;
+ }
+#else
+ if (backlight_get_timeout() == 1) { /* LED always on */
+ current += CURRENT_BACKLIGHT;
+ }
+#endif
+
+ return(current);
+}
+
+
/* Check to see whether or not we've received an alarm in the last second */
#ifdef HAVE_ALARM_MOD
static void power_thread_rtc_process(void)
{
-
if (rtc_check_alarm_flag()) {
- rtc_enable_alarm(false);
+ rtc_enable_alarm(false);
}
}
#endif
@@ -457,24 +513,56 @@ static void power_thread_rtc_process(void)
* This function is called to do the relativly long sleep waits from within the
* main power_thread loop while at the same time servicing any other periodic
* functions in the power thread which need to be called at a faster periodic
- * rate than the slow periodic rate of the main power_thread loop
+ * rate than the slow periodic rate of the main power_thread loop.
+ *
+ * While we are waiting for the time to expire, we average the battery
+ * voltages.
*/
static void power_thread_sleep(int ticks)
{
+ int small_ticks;
#ifdef HAVE_CHARGING
+ unsigned int tmp;
+ bool charger_plugged;
+#endif
+
+#ifdef HAVE_CHARGING
+ charger_plugged = charger_inserted();
+#endif
while (ticks > 0) {
- int small_ticks = MIN(HZ/2, ticks);
+ small_ticks = MIN(HZ/2, ticks);
sleep(small_ticks);
ticks -= small_ticks;
+#ifdef HAVE_CHARGING
car_adapter_mode_processing();
+#endif
#ifdef HAVE_ALARM_MOD
- power_thread_rtc_process();
+ power_thread_rtc_process();
#endif
- }
-#else
- sleep(ticks); /* no fast-processing functions, sleep the whole time */
+
+ /*
+ * Do a digital exponential filter. We don't sample the battery if
+ * the disk is spinning unless we are in USB mode (the disk will most
+ * likely always be spinning in USB mode).
+ * If the charging voltage is greater than 0x3F0 charging isn't active
+ * and that voltage isn't valid.
+ */
+ if (!ata_disk_is_active() || usb_inserted()) {
+ avgbat = avgbat - (avgbat / BATT_AVE_SAMPLES) +
+ adc_read(ADC_UNREG_POWER);
+ /*
+ * battery_centivolts is the centivolt-scaled filtered battery value.
+ */
+ battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) * BATTERY_SCALE_FACTOR) / 10000;
+ }
+
+#ifdef HAVE_CHARGING
+ /* If the charger was plugged in, exit now so we can start charging */
+ if(!charger_plugged && charger_inserted())
+ return;
#endif
+ }
}
@@ -488,102 +576,60 @@ static void power_thread_sleep(int ticks)
static void power_thread(void)
{
int i;
- int avg, ok_samples, spin_samples;
- int current = 0;
+ short *phps, *phpd; /* power history rotation pointers */
#if CONFIG_BATTERY == BATT_LIION2200
int charging_current;
#endif
#ifdef HAVE_CHARGE_CTRL
- int delta;
- int charged_time = 0;
int charge_max_time_now = 0; /* max. charging duration, calculated at
beginning of charging */
- int charge_pause = 0; /* no charging pause at the beginning */
- int trickle_time = 0; /* how many minutes trickle charging already? */
#endif
+
+ /* initialize the voltages for the exponential filter */
+ avgbat = adc_read(ADC_UNREG_POWER) * BATT_AVE_SAMPLES;
+ battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) * BATTERY_SCALE_FACTOR) / 10000;
+
+#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
+ fd = -1;
+#endif
+
while (1)
{
- /* never read power while disk is spinning, unless in USB mode */
- if (ata_disk_is_active() && !usb_inserted()) {
-#ifdef HAVE_ALARM_MOD
- power_thread_rtc_process();
-#endif
- sleep(HZ);
- continue;
- }
-
- /* Make POWER_AVG measurements and calculate an average of that to
- * reduce the effect of backlights/disk spinning/other variation.
- */
- ok_samples = spin_samples = avg = 0;
- for (i = 0; i < POWER_AVG_N; i++) {
- if (ata_disk_is_active()) {
- if (!ok_samples) {
- /* if we don't have any good non-disk-spinning samples,
- * we take a sample anyway in case the disk is going
- * to spin all the time.
- */
- avg += adc_read(ADC_UNREG_POWER);
- spin_samples++;
- }
- } else {
- if (spin_samples) /* throw away disk-spinning samples */
- spin_samples = avg = 0;
- avg += adc_read(ADC_UNREG_POWER);
- ok_samples++;
- }
- power_thread_sleep(HZ*POWER_AVG_SLEEP);
- }
- avg = avg / ((ok_samples) ? ok_samples : spin_samples);
-
/* rotate the power history */
+ phpd = &power_history[POWER_HISTORY_LEN - 1];
+ phps = phpd - 1;
for (i = 0; i < POWER_HISTORY_LEN-1; i++)
- power_history[i] = power_history[i+1];
-
- /* insert new value in the end, in centivolts 8-) */
- power_history[POWER_HISTORY_LEN-1] =
- (avg * BATTERY_SCALE_FACTOR) / 10000;
+ *phpd-- = *phps--;
- /* update battery level every minute, ignoring first 15 minutes after
- start charge/decharge */
-#ifdef HAVE_CHARGE_CTRL
- if ((powermgmt_last_cycle_startstop_min > 25) || (charge_state > 1))
-#endif
- battery_level_update();
+ /* insert new value at the start, in centivolts 8-) */
+ power_history[0] = battery_centivolts;
+
+ /* update battery level every minute */
+ battery_level_update();
/* calculate estimated remaining running time */
- /* decharging: remaining running time */
- /* charging: remaining charging time */
+ /* discharging: remaining running time */
+ /* charging: remaining charging time */
-#ifdef HAVE_CHARGE_CTRL
- if (charge_state == 1)
- powermgmt_est_runningtime_min = (100 - battery_level()) *
- battery_capacity / 100 * 60 / CURRENT_CHARGING;
- else {
- current = usb_inserted() ? CURRENT_USB : CURRENT_NORMAL;
- if ((backlight_get_timeout() == 1) ||
- (charger_inserted() && backlight_get_on_when_charging()))
- /* LED always on or LED on when charger connected */
- current += CURRENT_BACKLIGHT;
- powermgmt_est_runningtime_min = battery_level() *
- battery_capacity / 100 * 60 / current;
-#if MEM == 8 /* assuming 192 kbps, the running time is 22% longer with 8MB */
- powermgmt_est_runningtime_min =
- powermgmt_est_runningtime_min * 122 / 100;
-#endif /* MEM == 8 */
- }
-#else
- current = usb_inserted() ? CURRENT_USB : CURRENT_NORMAL;
- if (backlight_get_timeout() == 1) /* LED always on */
- current += CURRENT_BACKLIGHT;
powermgmt_est_runningtime_min = battery_level() *
- battery_capacity / 100 * 60 / current;
+ battery_capacity / 100 * 60 / runcurrent();
#if MEM == 8 /* assuming 192 kbps, the running time is 22% longer with 8MB */
powermgmt_est_runningtime_min =
powermgmt_est_runningtime_min * 122 / 100;
#endif /* MEM == 8 */
-#endif /* HAVE_CHARGE_CONTROL */
+
+#ifdef HAVE_CHARGE_CTRL
+ /*
+ * If we are charging, the "runtime" is estimated time till the battery
+ * is recharged.
+ */
+ // TBD: use real charging current estimate
+ if (charge_state == CHARGING) {
+ powermgmt_est_runningtime_min = (100 - battery_level()) *
+ battery_capacity / 100 * 60 / (CURRENT_MAX_CHG - runcurrent());
+ }
+#endif /* HAVE_CHARGE_CTRL */
#if CONFIG_BATTERY == BATT_LIION2200
/* We use the information from the ADC_EXT_POWER ADC channel, which
@@ -595,262 +641,235 @@ static void power_thread(void)
if(charger_inserted()) {
charging_current = adc_read(ADC_EXT_POWER);
if(charging_current < 0x80) {
- charge_state = 2; /* Trickle */
+ charge_state = TRICKLE;
} else {
- charge_state = 1; /* Charging */
+ charge_state = CHARGING;
}
} else {
- charge_state = 0; /* Not charging */
+ charge_state = DISCHARGING;
}
-#else
+#endif /* # if CONFIG_BATTERY == BATT_LIION2200 */
+
#ifdef HAVE_CHARGE_CTRL
- if (charge_pause > 0)
- charge_pause--;
-
if (charger_inserted()) {
- if (charge_state == 1) {
- /* charger inserted and enabled */
- charged_time++;
- snprintf(power_message, POWER_MESSAGE_LEN,
- "Chg %dm max %dm", charged_time, charge_max_time_now);
-
- if (charged_time > charge_max_time_now) {
- DEBUGF("power: charged_time > charge_max_time_now, "
- "enough!\n");
- /* have charged too long and deltaV detection did not
- work! */
- powermgmt_last_cycle_level = battery_level();
+ /*
+ * Time to start charging again?
+ * 1) If we are currently discharging but trickle is enabled,
+ * the charger must have just been plugged in.
+ * 2) If our battery level falls below the restart level, charge!
+ */
+ if (((charge_state == DISCHARGING) && trickle_charge_enabled) ||
+ (battery_level() < charge_restart_level)) {
+
+ /*
+ * If the battery level is nearly charged, just trickle.
+ * If the battery is in between, top-off and then trickle.
+ */
+ if(battery_percent > charge_restart_level) {
+ powermgmt_last_cycle_level = battery_percent;
powermgmt_last_cycle_startstop_min = 0;
- charger_enable(false);
- snprintf(power_message, POWER_MESSAGE_LEN,
- "Chg tmout %d min", charge_max_time_now);
- /* disable charging for several hours from this point,
- just to be sure */
- charge_pause = CHARGE_PAUSE_LEN;
- /* no trickle charge here, because the charging cycle
- didn't end the right way */
- charge_state = 0; /* 0: decharging/charger off, 1: charge,
- 2: top-off, 3: trickle */
+ if(battery_percent >= 95) {
+ trickle_sec = START_TRICKLE_SEC;
+ charge_state = TRICKLE;
+ } else {
+ trickle_sec = START_TOPOFF_SEC;
+ charge_state = TOPOFF;
+ }
} else {
- if (charged_time > CHARGE_MIN_TIME) {
- /* have charged continuously over the minimum charging
- time, so we monitor for deltaV going
- negative. Multiply thingsby 100 to get more
- accuracy without floating point arithmetic.
- power_history[] contains centivolts so after
- multiplying by 100 the deltas are in tenths of
- millivolts (delta of 5 is 0.0005 V).
- */
- delta =
- ( power_history[POWER_HISTORY_LEN-1] * 100
- + power_history[POWER_HISTORY_LEN-2] * 100
- - power_history[POWER_HISTORY_LEN-1-
- CHARGE_END_NEGD+1] * 100
- - power_history[POWER_HISTORY_LEN-1-
- CHARGE_END_NEGD] * 100 )
- / CHARGE_END_NEGD / 2;
-
- if (delta < -100) { /* delta < -10 mV */
+ /*
+ * Start the charger full strength
+ */
+ i = CHARGE_MAX_TIME_1500 * battery_capacity / 1500;
+ charge_max_time_now =
+ i * (100 + 35 - battery_percent) / 100;
+ if (charge_max_time_now > i) {
+ charge_max_time_now = i;
+ }
+ snprintf(power_message, POWER_MESSAGE_LEN,
+ "ChgAt %d%% max %dm", battery_level(),
+ charge_max_time_now);
+
+ /* enable the charger after the max time calc is done,
+ because battery_level depends on if the charger is
+ on */
+ DEBUGF("power: charger inserted and battery"
+ " not full, enabling\n");
+ powermgmt_last_cycle_level = battery_percent;
+ powermgmt_last_cycle_startstop_min = 0;
+ trickle_sec = 60;
+ charge_state = CHARGING;
+ }
+ }
+ if (charge_state == CHARGING) {
+ /* charger inserted and enabled 100% of the time */
+ trickle_sec = 60; /* 100% on */
+
+ snprintf(power_message, POWER_MESSAGE_LEN,
+ "Chg %dm, max %dm", powermgmt_last_cycle_startstop_min,
+ charge_max_time_now);
+ /*
+ * Sum the deltas over the last X minutes so we can do our
+ * end-of-charge logic based on the battery level change.
+ */
+ long_delta = short_delta = 999999;
+ if (powermgmt_last_cycle_startstop_min > CHARGE_MIN_TIME) {
+ short_delta = power_history[0] -
+ power_history[CHARGE_END_NEGD - 1];
+ }
+ if (powermgmt_last_cycle_startstop_min > CHARGE_END_ZEROD) {
+ /*
+ * Scan the history: if we have a big delta in the middle of
+ * our history, the long term delta isn't a valid end-of-charge
+ * indicator.
+ */
+ long_delta = power_history[0] -
+ power_history[CHARGE_END_ZEROD - 1];
+ for(i = 0; i < CHARGE_END_ZEROD; i++) {
+ if(((power_history[i] - power_history[i+1]) > 5) ||
+ ((power_history[i] - power_history[i+1]) < -5)) {
+ long_delta = 888888;
+ break;
+ }
+ }
+ }
+
+ /*
+ * End of charge criteria (any qualify):
+ * 1) Charged a long time
+ * 2) DeltaV went negative for a short time
+ * 3) DeltaV was close to zero for a long time
+ * Note: short_delta and long_delta are centivolts
+ */
+ if ((powermgmt_last_cycle_startstop_min > charge_max_time_now) ||
+ (short_delta < -5) || (long_delta < 5))
+ {
+ if (powermgmt_last_cycle_startstop_min > charge_max_time_now) {
+ DEBUGF("power: powermgmt_last_cycle_startstop_min > charge_max_time_now, "
+ "enough!\n");
+ /* have charged too long and deltaV detection did not
+ work! */
+ snprintf(power_message, POWER_MESSAGE_LEN,
+ "Chg tmout %d min", charge_max_time_now);
+ } else {
+ if(short_delta < -5) {
DEBUGF("power: short-term negative"
" delta, enough!\n");
- powermgmt_last_cycle_level = battery_level();
- powermgmt_last_cycle_startstop_min = 0;
- charger_enable(false);
snprintf(power_message, POWER_MESSAGE_LEN,
- "end negd %d %dmin", delta, charged_time);
- /* disable charging for several hours from this
- point, just to be sure */
- charge_pause = CHARGE_PAUSE_LEN;
- /* enable trickle charging */
- if (trickle_charge_enabled) {
- trickle_sec = CURRENT_NORMAL * 60 /
- CURRENT_CHARGING;
- /* first guess, maybe consider if LED
- backlight is on, disk is active,... */
- trickle_time = 0;
- charge_state = 2; /* 0: decharging/charger
- off, 1: charge,
- 2: top-off, 3: trickle */
- } else {
- charge_state = 0; /* 0: decharging/charger
- off, 1: charge,
- 2: top-off, 3: trickle */
- }
+ "end negd %d %dmin", short_delta,
+ powermgmt_last_cycle_startstop_min);
} else {
- /* if we didn't disable the charger in the
- previous test, check for low positive delta */
- delta =
- ( power_history[POWER_HISTORY_LEN-1] * 100
- + power_history[POWER_HISTORY_LEN-2] * 100
- - power_history[POWER_HISTORY_LEN-1-
- CHARGE_END_ZEROD+1] * 100
- - power_history[POWER_HISTORY_LEN-1-
- CHARGE_END_ZEROD] * 100 )
- / CHARGE_END_ZEROD / 2;
-
- if (delta < 1) { /* delta < 0.1 mV */
- DEBUGF("power: long-term small "
- "positive delta, enough!\n");
- powermgmt_last_cycle_level = battery_level();
- powermgmt_last_cycle_startstop_min = 0;
- charger_enable(false);
- snprintf(power_message, POWER_MESSAGE_LEN,
- "end lowd %d %dmin",
- delta, charged_time);
- /* disable charging for several hours from
- this point, just to be sure */
- charge_pause = CHARGE_PAUSE_LEN;
- /* enable trickle charging */
- if (trickle_charge_enabled) {
- trickle_sec =
- CURRENT_NORMAL * 60 / CURRENT_CHARGING;
- /* first guess, maybe consider if LED
- backlight is on, disk is active,... */
- trickle_time = 0;
- charge_state = 2;
- /* 0: decharging/charger off, 1: charge,
- 2: top-off, 3: trickle */
- } else {
- charge_state = 0;
- /* 0: decharging/charger off, 1: charge,
- 2: top-off, 3: trickle */
- }
- }
+ DEBUGF("power: long-term small "
+ "positive delta, enough!\n");
+ snprintf(power_message, POWER_MESSAGE_LEN,
+ "end lowd %d %dmin", long_delta,
+ powermgmt_last_cycle_startstop_min);
}
}
+ /* Switch to trickle charging. We skip the top-off
+ since we've effectively done the top-off operation
+ already since we charged for the maximum full
+ charge time. For trickle charging, we use 0.05C */
+ powermgmt_last_cycle_level = battery_percent;
+ powermgmt_last_cycle_startstop_min = 0;
+ if (trickle_charge_enabled) {
+ trickle_sec = START_TRICKLE_SEC;
+ charge_state = TRICKLE;
+ } else {
+ /* If we don't trickle charge, we discharge */
+ trickle_sec = 0; /* off */
+ charge_state = DISCHARGING;
+ }
}
}
- else if (charge_state > 1) { /* top off or trickle? */
- /* adjust trickle charge time */
- if ( ((charge_state == 2) &&
- (power_history[POWER_HISTORY_LEN-1] > TOPOFF_VOLTAGE))
- || ((charge_state == 3) &&
- (power_history[POWER_HISTORY_LEN-1] >
- TRICKLE_VOLTAGE)) ) { /* charging too much */
- trickle_sec--;
- }
- else { /* charging too less */
- trickle_sec++;
- }
-
- if (trickle_sec > 24)
- trickle_sec = 24;
-
- if (trickle_sec < 1)
- trickle_sec = 1;
-
- /* charge the calculated amount of seconds */
- charger_enable(true);
- power_thread_sleep(HZ * trickle_sec);
- charger_enable(false);
-
- /* trickle charging long enough? */
-
- if (trickle_time++ > TRICKLE_MAX_TIME + TOPOFF_MAX_TIME) {
- trickle_sec = 0; /* show in debug menu that trickle is
- off */
- charge_state = 0; /* 0: decharging/charger off, 1: charge,
- 2: top-off, 3: trickle */
+ else if (charge_state > CHARGING) /* top off or trickle */
+ {
+ /* Time to switch from topoff to trickle? Note that we don't
+ * adjust trickle_sec: it will get adjusted down by the
+ * charge level adjustment in the loop and will drift down
+ * from the topoff level to the trickle level.
+ */
+ if ((charge_state == TOPOFF) &&
+ (powermgmt_last_cycle_startstop_min > TOPOFF_MAX_TIME))
+ {
+ powermgmt_last_cycle_level = battery_percent;
powermgmt_last_cycle_startstop_min = 0;
+ charge_state = TRICKLE;
}
- if ((charge_state == 2) &&
- (trickle_time > TOPOFF_MAX_TIME)) /* change state? */
- charge_state = 3; /* 0: decharging/charger off, 1: charge,
- 2: top-off, 3: trickle */
-
- } else { /* charge_state == 0 */
+ /* Adjust trickle charge time. I considered setting the level
+ * higher if the USB is plugged in, but it doesn't appear to
+ * be necessary and will generate more heat [gvb].
+ */
+ if(((charge_state == TOPOFF) && (battery_centivolts > TOPOFF_VOLTAGE)) ||
+ ((charge_state == TRICKLE) && (battery_centivolts > TRICKLE_VOLTAGE)))
+ { /* charging too much */
+ if(trickle_sec > 0)
+ trickle_sec--;
+ }
+ else { /* charging too little */
+ if(trickle_sec < 60)
+ trickle_sec++;
+ }
+ } else if (charge_state == DISCHARGING) {
+ trickle_sec = 0;
/* the charger is enabled here only in one case: if it was
turned on at boot time (power_init) */
/* turn it off now */
if (charger_enabled)
charger_enable(false);
}
-
- /* Start new charge cycle? This must be possible also in
- trickle/top-off, because when usb connected, */
- /* the trickle charge amount may not be enough */
-
- if ((charge_state == 0) || (charge_state > 1))
- /* if battery is not full, enable charging */
- /* make sure charging starts if 1%-lazyness in
- battery_level_update() is too slow */
- if ( (battery_level() < charge_restart_level)
- || (power_history[POWER_HISTORY_LEN-1] <
- BATTERY_LEVEL_DANGEROUS)) {
- if (charge_pause) {
- DEBUGF("power: batt level < restart level,"
- " but charge pause, not enabling\n");
- snprintf(power_message, POWER_MESSAGE_LEN,
- "chg pause %d min", charge_pause);
- } else {
- /* calculate max charge time depending on current
- battery level */
- /* take 35% more because some more energy is used for
- heating up the battery */
- i = CHARGE_MAX_TIME_1500 * battery_capacity / 1500;
- charge_max_time_now =
- i * (100 + 35 - battery_level()) / 100;
- if (charge_max_time_now > i) {
- charge_max_time_now = i;
- }
- snprintf(power_message, POWER_MESSAGE_LEN,
- "ChgAt %d%% max %dm", battery_level(),
- charge_max_time_now);
-
- /* enable the charger after the max time calc is done,
- because battery_level depends on if the charger is
- on */
- DEBUGF("power: charger inserted and battery"
- " not full, enabling\n");
- powermgmt_last_cycle_level = battery_level();
- powermgmt_last_cycle_startstop_min = 0;
- charged_time = 0;
-
- charger_enable(true);
- charge_state = 1; /* 0: decharging/charger off, 1:
- charge, 2: top-off, 3: trickle */
- /* clear the power history so that we don't use values
- before discharge for the long-term delta
- */
- for (i = 0; i < POWER_HISTORY_LEN-1; i++)
- power_history[i] =
- power_history[POWER_HISTORY_LEN-1];
- }
- }
-
} else {
- /* charger not inserted */
- if (charge_state > 0) {
+ if (charge_state != DISCHARGING) {
/* charger not inserted but was enabled */
DEBUGF("power: charger disconnected, disabling\n");
- powermgmt_last_cycle_level = battery_level();
+
+ charger_enable(false);
+ powermgmt_last_cycle_level = battery_percent;
powermgmt_last_cycle_startstop_min = 0;
- /* show in debug menu that trickle is off */
trickle_sec = 0;
- charger_enable(false);
- charge_state = 0; /* 0: decharging/charger off, 1: charge, 2:
- top-off, 3: trickle */
- snprintf(power_message, POWER_MESSAGE_LEN, "Charger disc");
+ charge_state = DISCHARGING;
+ snprintf(power_message, POWER_MESSAGE_LEN, "Charger: discharge");
}
- /* charger not inserted and disabled, so we're discharging */
}
powermgmt_last_cycle_startstop_min++;
#endif /* HAVE_CHARGE_CTRL*/
-#endif /* # if CONFIG_BATTERY == BATT_LIION2200 */
-
- /* sleep for roughly a minute */
+
+ /* sleep for a minute */
+
#ifdef HAVE_CHARGE_CTRL
- i = 60 - trickle_sec - POWER_AVG_N * POWER_AVG_SLEEP;
+ if(trickle_sec > 0) {
+ charger_enable(true);
+ power_thread_sleep(HZ * trickle_sec);
+ }
+ if(trickle_sec < 60)
+ charger_enable(false);
+ power_thread_sleep(HZ * (60 - trickle_sec));
#else
- i = 60 - POWER_AVG_N * POWER_AVG_SLEEP;
+ power_thread_sleep(HZ * 60);
#endif
- if (i > 0)
- power_thread_sleep(HZ*(i));
+#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
+ if((fd < 0) && !usb_inserted()) {
+ fd = open(DEBUG_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT);
+ snprintf(debug_message, DEBUG_MESSAGE_LEN,
+ "cycle_min, bat_centivolts, bat_percent, chgr, chg_state, trickle_sec\n");
+ write(fd, debug_message, strlen(debug_message));
+ fsync(fd);
+ } else if((fd >= 0) && !usb_inserted()) {
+ snprintf(debug_message, DEBUG_MESSAGE_LEN, "%d, %d, %d, %d, %d, %d\n",
+ powermgmt_last_cycle_startstop_min, battery_centivolts,
+ battery_percent, charger_inserted(), charge_state, trickle_sec);
+ write(fd, debug_message, strlen(debug_message));
+ fsync(fd);
+ } else if((fd >= 0) && usb_inserted()) {
+ /* NOTE: It is probably already TOO LATE to close the file */
+ close(fd);
+ fd = -1;
+ }
+#endif
handle_auto_poweroff();
}
}
@@ -862,31 +881,9 @@ void powermgmt_init(void)
/* init history to 0 */
memset(power_history, 0x00, sizeof(power_history));
-#if 0
- /* initialize the history with a single sample to prevent level
- flickering during the first minute of execution */
- power_history[POWER_HISTORY_LEN-1] =
- (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000;
- /* calculate the first battery level */
- battery_level_update();
- /* calculate the remaining time to that the info screen displays something
- useful */
- powermgmt_est_runningtime_min =
- battery_level() * battery_capacity / 100 * 60 / CURRENT_NORMAL;
-#if MEM == 8 /* assuming 192 kbps, the running time is 22% longer with 8MB */
- powermgmt_est_runningtime_min = powermgmt_est_runningtime_min * 122 / 100;
-#endif
-
-#ifdef HAVE_CHARGE_CTRL
- snprintf(power_message, POWER_MESSAGE_LEN, "Powermgmt started");
-
- /* if the battery is nearly empty, start charging immediately */
- if (power_history[POWER_HISTORY_LEN-1] < BATTERY_LEVEL_DANGEROUS)
- charger_enable(true);
-#endif
-#endif
-
+#ifdef HAVE_CHARGING
charger_power_is_on = charger_inserted();
+#endif
create_thread(power_thread, power_stack, sizeof(power_stack),
power_thread_name);
@@ -898,11 +895,17 @@ void powermgmt_init(void)
void shutdown_hw(void)
{
#ifndef SIMULATOR
+#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
+ if(fd > 0) {
+ close(fd);
+ fd = 0;
+ }
+#endif
mpeg_stop();
ata_flush();
ata_spindown(1);
while(ata_disk_is_active())
- sleep(HZ/10);
+ sleep(HZ/10);
mp3_shutdown();
#if CONFIG_KEYPAD == ONDIO_PAD