From 3157e1395674a930c74e2ef4cc4ce78dffea8569 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Wed, 24 Dec 2008 16:58:41 +0000 Subject: Simplify powermgmt thread loops so it calls functions turn (no more power_thread_sleep). Do other target-friendly simplifications, generic battery switch handling and split sim-specific code. Whoever can, please verify charging on the Archos Recorder (due to change in the charger duty cycle code). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19579 a1c6a512-1295-4272-9138-f99709370657 --- .../target/sh/archos/recorder/powermgmt-recorder.c | 437 ++++++++++++++++++++- 1 file changed, 436 insertions(+), 1 deletion(-) (limited to 'firmware/target/sh/archos/recorder/powermgmt-recorder.c') diff --git a/firmware/target/sh/archos/recorder/powermgmt-recorder.c b/firmware/target/sh/archos/recorder/powermgmt-recorder.c index 6de5cc8037..7b1842016c 100644 --- a/firmware/target/sh/archos/recorder/powermgmt-recorder.c +++ b/firmware/target/sh/archos/recorder/powermgmt-recorder.c @@ -19,9 +19,13 @@ * KIND, either express or implied. * ****************************************************************************/ - #include "config.h" +#include "system.h" +#include +#include "debug.h" +#include "storage.h" #include "adc.h" +#include "power.h" #include "powermgmt.h" const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = @@ -60,3 +64,434 @@ unsigned int battery_adc_voltage(void) { return (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) >> 10; } + +/** Charger control **/ +#ifdef CHARGING_DEBUG_FILE +#include "file.h" +#define DEBUG_FILE_NAME "/powermgmt.csv" +#define DEBUG_MESSAGE_LEN 133 +static char debug_message[DEBUG_MESSAGE_LEN]; +static int fd = -1; /* write debug information to this file */ +static int wrcount = 0; +#endif /* CHARGING_DEBUG_FILE */ + +/* + * For a complete description of the charging algorithm read + * docs/CHARGING_ALGORITHM. + */ +int long_delta; /* long term delta battery voltage */ +int short_delta; /* short term delta battery voltage */ +bool disk_activity_last_cycle = false; /* flag set to aid charger time + * calculation */ +char power_message[POWER_MESSAGE_LEN] = ""; /* message that's shown in + debug menu */ + /* percentage at which charging + starts */ +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? */ +int trickle_sec = 0; /* how many seconds should the + charger be enabled per + minute for trickle + charging? */ +int pid_p = 0; /* PID proportional term */ +int pid_i = 0; /* PID integral term */ + +static unsigned int target_voltage = TRICKLE_VOLTAGE; /* desired topoff/trickle + * voltage level */ +static int charge_max_time_idle = 0; /* max. charging duration, calculated at + * beginning of charging */ +static int charge_max_time_now = 0; /* max. charging duration including + * hdd activity */ +static int minutes_disk_activity = 0; /* count minutes of hdd use during + * charging */ +static int last_disk_activity = CHARGE_END_LONGD + 1; /* last hdd use x mins ago */ + +#ifdef CHARGING_DEBUG_FILE +static void debug_file_close(void) +{ + if (fd >= 0) { + close(fd); + fd = -1; + } +} + +static void debug_file_log(void) +{ + if (usb_inserted()) { + /* It is probably too late to close the file but we can try... */ + debug_file_close(); + } + else if (fd < 0) { + fd = open(DEBUG_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT); + + if (fd >= 0) { + snprintf(debug_message, DEBUG_MESSAGE_LEN, + "cycle_min, bat_millivolts, bat_percent, chgr_state" + " ,charge_state, pid_p, pid_i, trickle_sec\n"); + write(fd, debug_message, strlen(debug_message)); + wrcount = 99; /* force a flush */ + } + } + else { + snprintf(debug_message, DEBUG_MESSAGE_LEN, + "%d, %d, %d, %d, %d, %d, %d, %d\n", + powermgmt_last_cycle_startstop_min, battery_voltage(), + battery_level(), charger_input_state, charge_state, + pid_p, pid_i, trickle_sec); + write(fd, debug_message, strlen(debug_message)); + wrcount++; + } +} + +static void debug_file_sync(void) +{ + /* + * If we have a lot of pending writes or if the disk is spining, + * fsync the debug log file. + */ + if (wrcount > 10 || (wrcount > 0 && storage_disk_is_active())) { + if (fd >= 0) + fsync(fd); + + wrcount = 0; + } +} +#else /* !CHARGING_DEBUG_FILE */ +#define debug_file_close() +#define debug_file_log() +#define debug_file_sync() +#endif /* CHARGING_DEBUG_FILE */ + +/* + * Do tasks that should be done every step. + */ +static void do_frequent_tasks(void) +{ + if (storage_disk_is_active()) { + /* flag hdd use for charging calculation */ + disk_activity_last_cycle = true; + } + + debug_file_sync(); +} + +/* + * The charger was just plugged in. If the battery level is + * nearly charged, just trickle. If the battery is low, start + * a full charge cycle. If the battery level is in between, + * top-off and then trickle. + */ +static void charger_plugged(void) +{ + int battery_percent = battery_level(); + + pid_p = 0; + pid_i = 0; + powermgmt_last_cycle_level = battery_percent; + powermgmt_last_cycle_startstop_min = 0; + + snprintf(power_message, POWER_MESSAGE_LEN, "Charger plugged in"); + + if (battery_percent > START_TOPOFF_CHG) { + + if (battery_percent >= START_TRICKLE_CHG) { + charge_state = TRICKLE; + target_voltage = TRICKLE_VOLTAGE; + } + else { + charge_state = TOPOFF; + target_voltage = TOPOFF_VOLTAGE; + } + } + else { + /* + * Start the charger full strength + */ + int i = CHARGE_MAX_MIN_1500 * get_battery_capacity() / 1500; + charge_max_time_idle = i * (100 + 35 - battery_percent) / 100; + + if (charge_max_time_idle > i) + charge_max_time_idle = i; + + charge_max_time_now = charge_max_time_idle; + + snprintf(power_message, POWER_MESSAGE_LEN, + "ChgAt %d%% max %dm", battery_percent, + 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, charging\n"); + trickle_sec = 60; + long_delta = short_delta = 999999; + charge_state = CHARGING; + } +} + +/* + * The charger was just unplugged. + */ +static void charger_unplugged(void) +{ + DEBUGF("power: charger disconnected, disabling\n"); + + charger_enable(false); + powermgmt_last_cycle_level = battery_level(); + powermgmt_last_cycle_startstop_min = 0; + trickle_sec = 0; + pid_p = 0; + pid_i = 0; + charge_state = DISCHARGING; + snprintf(power_message, POWER_MESSAGE_LEN, "Charger: discharge"); +} + +static void charging_step(void) +{ + int i; + + /* alter charge time max length with extra disk use */ + if (disk_activity_last_cycle) { + minutes_disk_activity++; + charge_max_time_now = charge_max_time_idle + + minutes_disk_activity*2 / 5; + disk_activity_last_cycle = false; + last_disk_activity = 0; + } + else { + last_disk_activity++; + } + + /* + * Check the delta voltage over the last X minutes so we can do + * our end-of-charge logic based on the battery level change + * (no longer use minimum time as logic for charge end has 50 + * minutes minimum charge built in). + */ + if (powermgmt_last_cycle_startstop_min > CHARGE_END_SHORTD) { + short_delta = power_history[0] - + power_history[CHARGE_END_SHORTD - 1]; + } + + if (powermgmt_last_cycle_startstop_min > CHARGE_END_LONGD) { + /* + * Scan the history: the points where measurement is taken need to + * be fairly static. Check prior to short delta 'area'. Also only + * check first and last 10 cycles (delta in middle OK). + */ + long_delta = power_history[0] - + power_history[CHARGE_END_LONGD - 1]; + + for (i = CHARGE_END_SHORTD; i < CHARGE_END_SHORTD + 10; i++) + { + if ((power_history[i] - power_history[i+1]) > 50 || + (power_history[i] - power_history[i+1]) < -50) { + long_delta = 777777; + break; + } + } + + for (i = CHARGE_END_LONGD - 11; i < CHARGE_END_LONGD - 1 ; i++) + { + if ((power_history[i] - power_history[i+1]) > 50 || + (power_history[i] - power_history[i+1]) < -50) { + long_delta = 888888; + break; + } + } + } + + snprintf(power_message, POWER_MESSAGE_LEN, + "Chg %dm, max %dm", powermgmt_last_cycle_startstop_min, + charge_max_time_now); + + /* + * End of charge criteria (any qualify): + * 1) Charged a long time + * 2) DeltaV went negative for a short time ( & long delta static) + * 3) DeltaV was negative over a longer period (no disk use only) + * + * Note: short_delta and long_delta are millivolts + */ + if (powermgmt_last_cycle_startstop_min >= charge_max_time_now || + (short_delta <= -50 && long_delta < 50) || + (long_delta < -20 && last_disk_activity > CHARGE_END_LONGD)) { + + int battery_percent = battery_level(); + + 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); + /* + * 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. + */ + powermgmt_last_cycle_level = battery_percent; + powermgmt_last_cycle_startstop_min = 0; + charge_state = TRICKLE; + + /* + * Set trickle charge target to a relative voltage instead + * of an arbitrary value - the fully charged voltage may + * vary according to ambient temp, battery condition etc. + * Trickle target is -0.15v from full voltage acheived. + * Topup target is -0.05v from full voltage. + */ + target_voltage = power_history[0] - 150; + + } + else { + if(short_delta <= -5) { + DEBUGF("power: short-term negative" + " delta, enough!\n"); + snprintf(power_message, POWER_MESSAGE_LEN, + "end negd %d %dmin", short_delta, + powermgmt_last_cycle_startstop_min); + target_voltage = power_history[CHARGE_END_SHORTD - 1] - 50; + } + else { + 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); + target_voltage = power_history[CHARGE_END_LONGD - 1] - 50; + } + + /* + * Switch to top-off charging. + */ + powermgmt_last_cycle_level = battery_percent; + powermgmt_last_cycle_startstop_min = 0; + charge_state = TOPOFF; + } + } +} + +static void topoff_trickle_step(void) +{ + unsigned int millivolts; + + /* + *Time to switch from topoff to trickle? + */ + if (charge_state == TOPOFF && + powermgmt_last_cycle_startstop_min > TOPOFF_MAX_MIN) { + + powermgmt_last_cycle_level = battery_level(); + powermgmt_last_cycle_startstop_min = 0; + charge_state = TRICKLE; + target_voltage = target_voltage - 100; + } + /* + * Adjust trickle charge time (proportional and integral terms). + * Note: 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]. + */ + millivolts = battery_voltage(); + + pid_p = ((signed)target_voltage - (signed)millivolts) / 5; + if (pid_p <= PID_DEADZONE && pid_p >= -PID_DEADZONE) + pid_p = 0; + + if ((unsigned)millivolts < target_voltage) { + if (pid_i < 60) + pid_i++; /* limit so it doesn't "wind up" */ + } + else { + if (pid_i > 0) + pid_i--; /* limit so it doesn't "wind up" */ + } + + trickle_sec = pid_p + pid_i; + + if (trickle_sec > 60) + trickle_sec = 60; + + if (trickle_sec < 0) + trickle_sec = 0; +} + +void charging_algorithm_step(void) +{ + static int pwm_counter = 0; /* PWM total cycle in steps */ + static int pwm_duty = 0; /* PWM duty cycle in steps */ + + switch (charger_input_state) + { + case CHARGER_PLUGGED: + charger_plugged(); + break; + + case CHARGER_UNPLUGGED: + charger_unplugged(); + break; + + case CHARGER: + case NO_CHARGER: + do_frequent_tasks(); + + if (pwm_counter > 0) { + if (pwm_duty > 0 && --pwm_duty <= 0) + charger_enable(false); /* Duty cycle expired */ + + if (--pwm_counter > 0) + return; + + /* PWM cycle is complete */ + powermgmt_last_cycle_startstop_min++; + debug_file_log(); + } + break; + } + + switch (charge_state) + { + case CHARGING: + charging_step(); + break; + + case TOPOFF: + case TRICKLE: + topoff_trickle_step(); + break; + + case DISCHARGING: + default: + break; + } + + /* If 100%, ensure pwm_on never expires and briefly disables the + * charger. */ + pwm_duty = (trickle_sec < 60) ? trickle_sec*2 : 0; + pwm_counter = 60*2; + charger_enable(trickle_sec > 0); +} + +#ifdef CHARGING_DEBUG_FILE +void charging_algorithm_close(void) +{ + debug_file_close(); +} +#endif /* CHARGING_DEBUG_FILE */ + +/* Returns true if the unit is charging the batteries. */ +bool charging_state(void) +{ + return charge_state == CHARGING; +} -- cgit v1.2.3