From 2584896920724cd5e72caaf9b64c3ef81b45ee9f Mon Sep 17 00:00:00 2001 From: Jörg Hohensohn Date: Thu, 3 Mar 2005 07:25:43 +0000 Subject: More aggressive Recorder V1 charging (patch #1116884 from Jerry Van Baren) git-svn-id: svn://svn.rockbox.org/rockbox/trunk@6105 a1c6a512-1295-4272-9138-f99709370657 --- firmware/export/adc.h | 2 +- firmware/export/powermgmt.h | 39 ++- firmware/powermgmt.c | 819 ++++++++++++++++++++++---------------------- 3 files changed, 440 insertions(+), 420 deletions(-) (limited to 'firmware') diff --git a/firmware/export/adc.h b/firmware/export/adc.h index a18cb1995a..40bc3e108a 100644 --- a/firmware/export/adc.h +++ b/firmware/export/adc.h @@ -63,7 +63,7 @@ #define ADC_BUTTON_ROW2 5 /* Used for scanning the keys, different voltages for different keys */ #define ADC_UNREG_POWER 6 /* Battery voltage with a better scaling */ -#define ADC_EXT_POWER 7 /* The external power voltage, V=X*0.0148 */ +#define ADC_EXT_POWER 7 /* The external power voltage, 0v or 2.7v */ #endif diff --git a/firmware/export/powermgmt.h b/firmware/export/powermgmt.h index 922630097a..59f9465d49 100644 --- a/firmware/export/powermgmt.h +++ b/firmware/export/powermgmt.h @@ -48,13 +48,12 @@ #define BATTERY_RANGE (BATTERY_LEVEL_FULL - BATTERY_LEVEL_EMPTY) #define POWER_HISTORY_LEN 2*60 /* 2 hours of samples, one per minute */ -#define POWER_AVG_N 4 /* how many samples to take for each measurement */ -#define POWER_AVG_SLEEP 9 /* how long do we sleep between each measurement */ #define CHARGE_END_NEGD 6 /* stop when N minutes have passed with * avg delta being < -0.05 V */ #define CHARGE_END_ZEROD 50 /* stop when N minutes have passed with * avg delta being < 0.005 V */ + #ifndef SIMULATOR #ifdef HAVE_CHARGE_CTRL @@ -65,27 +64,40 @@ #define CHARGE_RESTART_HI 85 /* %: when to restart charging in 'charge' mode */ /* attention: if set too high, normal charging is started in trickle mode */ #define CHARGE_RESTART_LO 10 /* %: when to restart charging in 'discharge' mode */ -#define CHARGE_PAUSE_LEN 60 /* how many minutes to pause between charging cycles */ #define TOPOFF_MAX_TIME 90 /* After charging, go to top off charge. How long should top off charge be? */ #define TOPOFF_VOLTAGE 565 /* which voltage is best? (centivolts) */ #define TRICKLE_MAX_TIME 12*60 /* After top off charge, go to trickle charge. How long should trickle charge be? */ #define TRICKLE_VOLTAGE 545 /* which voltage is best? (centivolts) */ +#define START_TOPOFF_SEC 25 /* initial trickle_sec for topoff */ +#define START_TRICKLE_SEC 15 /* initial trickle_sec for trickle */ + extern char power_message[POWER_MESSAGE_LEN]; -extern char charge_restart_level; + +extern int long_delta; /* long term delta battery voltage */ +extern int short_delta; /* short term delta battery voltage */ extern int powermgmt_last_cycle_startstop_min; /* how many minutes ago was the charging started or stopped? */ extern int powermgmt_last_cycle_level; /* which level had the batteries at this time? */ -extern int battery_lazyness[20]; /* how does the battery react when plugging in/out the charger */ +void enable_deep_discharge(bool on); /* deep discharge the battery */ + void enable_trickle_charge(bool on); extern int trickle_sec; /* trickle charge: How many seconds per minute are we charging actually? */ #endif /* HAVE_CHARGE_CTRL */ -#if defined(HAVE_CHARGE_CTRL) || CONFIG_BATTERY == BATT_LIION2200 -extern int charge_state; /* tells what the charger is doing (for info display): 0: decharging/charger off, 1: charge, 2: top-off, 3: trickle */ -#endif +#if defined(HAVE_CHARGE_CTRL) || (CONFIG_BATTERY == BATT_LIION2200) +typedef enum { + DISCHARGING, + CHARGING, + TOPOFF, + TRICKLE +} charge_state_type; + +/* tells what the charger is doing */ +extern charge_state_type charge_state; +#endif /* defined(HAVE_CHARGE_CTRL) || (CONFIG_BATTERY == BATT_LIION2200) */ #ifdef HAVE_MMC /* Values for Ondio */ #define CURRENT_NORMAL 95 /* average, nearly proportional to 1/U */ @@ -95,9 +107,14 @@ extern int charge_state; /* tells what the charger is doing (for info di #define CURRENT_NORMAL 145 /* usual current in mA when using the AJB including some disk/backlight/... activity */ #define CURRENT_USB 500 /* usual current in mA in USB mode */ #define CURRENT_BACKLIGHT 30 /* additional current when backlight is always on */ -#define CURRENT_CHARGING 300 /* charging current */ -#endif +#define CURRENT_MIN_CHG 70 /* minimum charge current */ +#define MIN_CHG_V 8500 /* at 8.5v charger voltage get CURRENT_MIN_CHG */ +#define CURRENT_MAX_CHG 350 /* maximum charging current */ +#define MAX_CHG_V 10250 /* anything over 10.25v gives CURRENT_MAX_CHG */ +#endif /* HAVE_MMC */ + +extern unsigned int bat; /* filtered battery voltage, centivolts */ extern unsigned short power_history[POWER_HISTORY_LEN]; /* Start up power management thread */ @@ -114,7 +131,7 @@ bool battery_level_safe(void); void set_poweroff_timeout(int timeout); void set_battery_capacity(int capacity); /* set local battery capacity value */ -void set_battery_type(int type); /* set local battery type */ +void set_battery_type(int type); /* set local battery type */ void set_sleep_timer(int seconds); int get_sleep_timer(void); 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 -- cgit v1.2.3