diff -urN --exclude=CVS --exclude=*~ orig/firmware/drivers/adc.h firmware/drivers/adc.h --- orig/firmware/drivers/adc.h Mon Jul 29 15:56:22 2002 +++ firmware/drivers/adc.h Tue Jul 30 14:25:28 2002 @@ -40,6 +40,8 @@ #define BATTERY_SCALE_FACTOR 6546 #endif +#define EXT_SCALE_FACTOR 14800 + unsigned short adc_read(int channel); void adc_init(void); diff -urN --exclude=CVS --exclude=*~ orig/firmware/drivers/power.c firmware/drivers/power.c --- orig/firmware/drivers/power.c Mon Jul 29 15:54:57 2002 +++ firmware/drivers/power.c Tue Jul 30 14:25:28 2002 @@ -22,12 +22,14 @@ #include "adc.h" #include "power.h" +bool charger_enabled = 0; + #ifndef SIMULATOR bool charger_inserted(void) { #ifdef ARCHOS_RECORDER - return adc_read(ADC_EXT_POWER) > 0x200; + return adc_read(ADC_EXT_POWER) > 0x100; #else return (PADR & 1) == 0; #endif @@ -59,10 +61,13 @@ void charger_enable(bool on) { #ifdef ARCHOS_RECORDER - if(on) + if(on) { PBDR &= ~0x20; - else + charger_enabled = 1; + } else { PBDR |= 0x20; + charger_enabled = 0; + } #else on = on; #endif diff -urN --exclude=CVS --exclude=*~ orig/firmware/drivers/power.h firmware/drivers/power.h --- orig/firmware/drivers/power.h Sun Jul 28 18:17:24 2002 +++ firmware/drivers/power.h Tue Jul 30 14:25:28 2002 @@ -26,6 +26,8 @@ #define BATTERY_RANGE (BATTERY_LEVEL_FULL - BATTERY_LEVEL_EMPTY) +extern bool charger_enabled; + bool charger_inserted(void); void charger_enable(bool on); void ide_power_enable(bool on); diff -urN --exclude=CVS --exclude=*~ orig/firmware/powermgmt.c firmware/powermgmt.c --- orig/firmware/powermgmt.c Thu Jan 1 02:00:00 1970 +++ firmware/powermgmt.c Tue Jul 30 15:44:19 2002 @@ -0,0 +1,168 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Heikki Hannikainen + * + * 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. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "sh7034.h" +#include "kernel.h" +#include "thread.h" +#include "system.h" +#include "debug.h" +#include "panic.h" +#include "adc.h" +#include "string.h" +#include "power.h" +#include "powermgmt.h" + +#ifndef SIMULATOR + +static char power_stack[DEFAULT_STACK_SIZE]; +static char power_thread_name[] = "power"; + +unsigned short power_history[POWER_HISTORY_LEN]; +#ifdef ARCHOS_RECORDER +char charge_restart_level = CHARGE_RESTART_HI; +#endif + +/* + * This power thread maintains a history of battery voltage + * and should, in the future, enable charging when it's needed + * and power is available, and disable it when the battery is full. + * Battery 'fullness' can be determined by the voltage drop, see: + * + * http://www.nimhbattery.com/nimhbattery-faq.htm questions 3 & 4 + * http://www.powerpacks-uk.com/Charging%20NiMh%20Batteries.htm + * http://www.angelfire.com/electronic/hayles/charge1.html (soft start idea) + * http://www.powerstream.com/NiMH.htm (discouraging) + * http://www.panasonic.com/industrial/battery/oem/images/pdf/nimhchar.pdf + * http://www.duracell.com/oem/Pdf/others/nimh_5.pdf (discharging) + * http://www.duracell.com/oem/Pdf/others/nimh_6.pdf (charging) + * + * Charging logic which we're starting with (by Linus, Hes, Bagder): + * + * 1) max 16 hrs charge time (just in negative delta detection fails) + * 2) Stop at negative delta of 5 mins + * 3) Stop at 15 mins of zero-delta or below + * 4) minimum of 15 mins charge time before 2) is applied + * 5) after end of charging, wait for charge go down 80% + * before charging again if in 'no-use overnight charging mode' + * and down to 10% if in 'fixed-location mains-powered usage mode' + */ + +static void power_thread(void) +{ + int i; + int avg; +#ifdef ARCHOS_RECORDER + int delta; + int charged_time = 0; +#endif + + while (1) + { + DEBUGF("power_thread woke up\n"); + /* make POWER_AVG measurements and calculate an average of that to + * reduce the effect of backlights/disk spinning/other variation + */ + avg = 0; + for (i = 0; i < POWER_AVG; i++) { + avg += adc_read(ADC_UNREG_POWER); + sleep(2); + } + avg = avg / POWER_AVG; + + /* rotate the power history */ + for (i = 0; i < POWER_HISTORY_LEN-1; i++) + power_history[i] = power_history[i+1]; + + /* insert new value in the end, in decivolts 8-) */ + power_history[POWER_HISTORY_LEN-1] = (avg * BATTERY_SCALE_FACTOR) / 10000; + +#ifdef ARCHOS_RECORDER + if (charger_inserted()) { + if (charger_enabled) { + /* charger inserted and enabled */ + charged_time++; + if (charged_time > CHARGE_MAX_TIME) { + DEBUGF("power: charged_time > CHARGE_MAX_TIME, enough!\n"); + /* have charged too long and deltaV detection did not work! */ + charger_enable(false); + /* Perhaps we should disable charging for several + hours from this point, just to be sure. */ + } else { + if (charged_time > CHARGE_MIN_TIME) { + /* have charged continuously over the minimum charging time, + * so we monitor for deltaV going negative. Multiply things + * by 100 to get more accuracy without floating point arithmetic. + * power_history[] contains decivolts. + */ + delta = 0; + for (i = 0; i < CHARGE_END_NEGD; i++) + delta += power_history[POWER_HISTORY_LEN-1-i]*100 - power_history[POWER_HISTORY_LEN-1-i-1]*100; + delta = delta / CHARGE_END_NEGD; + + if (delta < -3000) { /* delta < -0.3 V */ + DEBUGF("power: short-term negative delta, enough!\n"); + charger_enable(false); + } else { + /* if we didn't disable the charger in the previous test, check for low positive delta */ + delta = 0; + for (i = 0; i < CHARGE_END_ZEROD; i++) + delta += power_history[POWER_HISTORY_LEN-1-i]*100 - power_history[POWER_HISTORY_LEN-1-i-1]*100; + delta = delta / CHARGE_END_ZEROD; + + if (delta <= 40) { /* delta of <= 0.004 V */ + DEBUGF("power: long-term small positive delta, enough!\n"); + charger_enable(false); + } + } + } + } + } else { + /* charged inserted but not enabled */ + /* if battery is not full, enable charging */ + if (battery_level() < charge_restart_level) { + DEBUGF("power: charger inserted and battery not full, enabling\n"); + charger_enable(true); + charged_time = 0; + } + } + } else { + /* charger not inserted */ + if (charger_enabled) { + /* charger not inserted but was enabled */ + DEBUGF("power: charger disconnected, disabling\n"); + charger_enable(false); + } + /* charger not inserted and disabled, so we're discharging */ + } +#endif /* ARCHOS_RECORDER*/ + + /* sleep for roughly a minute */ + sleep(HZ*(60-POWER_AVG*2)); + } +} + +void power_init(void) +{ + /* init history to 0 */ + memset(power_history, 0x00, sizeof(power_history)); + + create_thread(power_thread, power_stack, sizeof(power_stack), power_thread_name); +} + +#endif diff -urN --exclude=CVS --exclude=*~ orig/firmware/powermgmt.h firmware/powermgmt.h --- orig/firmware/powermgmt.h Thu Jan 1 02:00:00 1970 +++ firmware/powermgmt.h Tue Jul 30 15:42:38 2002 @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Heikki Hannikainen + * + * 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. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _POWERMGMT_H_ +#define _POWERMGMT_H_ + +#ifndef SIMULATOR + +#define POWER_HISTORY_LEN 2*60 /* 2 hours of samples, one per minute */ +#define POWER_AVG 3 /* how many samples to take for each measurement */ + +#ifdef ARCHOS_RECORDER +#define CHARGE_MAX_TIME 16*60 /* minutes: maximum charging time */ +#define CHARGE_MIN_TIME 10 /* minutes: minimum charging time */ +#define CHARGE_END_NEGD 6 /* stop when N minutes have passed with + * avg delta being < -0.3 V */ +#define CHARGE_END_ZEROD 20 /* stop when N minutes have passed with + * avg delta being < 0.005 V */ +#define CHARGE_RESTART_HI 90 /* %: when to restart charging in 'charge' mode */ +#define CHARGE_RESTART_LO 10 /* %: when to restart charging in 'discharge' mode */ + +extern char charge_restart_level; +#endif + +extern unsigned short power_history[POWER_HISTORY_LEN]; + +void power_init(void); + +#endif + +#endif diff -urN --exclude=CVS --exclude=*~ orig/apps/debug_menu.c apps/debug_menu.c --- orig/apps/debug_menu.c Tue Jul 23 18:14:02 2002 +++ apps/debug_menu.c Tue Jul 30 14:25:29 2002 @@ -33,6 +33,8 @@ #include "rtc.h" #include "debug.h" #include "thread.h" +#include "powermgmt.h" +#include "system.h" /*---------------------------------------------------*/ /* SPECIAL DEBUG STUFF */ @@ -154,7 +156,7 @@ snprintf(buf, 32, "AN3: %03x AN7: %03x", adc_read(3), adc_read(7)); lcd_puts(0, 5, buf); - battery_voltage = (adc_read(6) * BATTERY_SCALE_FACTOR) / 10000; + battery_voltage = (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000; batt_int = battery_voltage / 100; batt_frac = battery_voltage % 100; @@ -255,7 +257,7 @@ } lcd_puts(0, 0, buf); - battery_voltage = (adc_read(6) * BATTERY_SCALE_FACTOR) / 10000; + battery_voltage = (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000; batt_int = battery_voltage / 100; batt_frac = battery_voltage % 100; @@ -426,6 +428,128 @@ } } } + +/* + * view_battery() shows a automatically scaled graph of the battery voltage + * over time. Usable for estimating battery life / charging rate. + * The power_history array is updated in power_thread of powermgmt.c. + */ + +#define BAT_FIRST_VAL MAX(POWER_HISTORY_LEN - LCD_WIDTH - 1, 0) +#define BAT_YSPACE (LCD_HEIGHT - 20) + +void view_battery(void) +{ + int view = 0; + int i, x, y; + int maxv, minv; + char buf[32]; + + while(1) + { + switch (view) { + case 0: /* voltage history graph */ + /* Find maximum and minimum voltage for scaling */ + maxv = minv = 0; + for (i = BAT_FIRST_VAL; i < POWER_HISTORY_LEN; i++) { + if (power_history[i] > maxv) + maxv = power_history[i]; + if ((minv == 0) || ((power_history[i]) && (power_history[i] < minv)) ) + minv = power_history[i]; + } + + if (minv < 1) + minv = 1; + if (maxv < 2) + maxv = 2; + + lcd_clear_display(); + lcd_puts(0, 0, "Battery voltage:"); + snprintf(buf, 30, "scale %d.%02d-%d.%02d V", minv / 100, minv % 100, maxv / 100, maxv % 100); + lcd_puts(0, 1, buf); + + x = 0; + for (i = BAT_FIRST_VAL+1; i < POWER_HISTORY_LEN; i++) { + y = (power_history[i] - minv) * BAT_YSPACE / (maxv - minv); + lcd_clearline(x, LCD_HEIGHT-1, x, 20); + lcd_drawline(x, LCD_HEIGHT-1, x, MIN(MAX(LCD_HEIGHT-1 - y, 20), LCD_HEIGHT-1)); + x++; + } + + break; + + case 1: /* status: */ + lcd_clear_display(); + lcd_puts(0, 0, "Power status:"); + + y = (adc_read(ADC_UNREG_POWER) * BATTERY_SCALE_FACTOR) / 10000; + snprintf(buf, 30, "Battery: %d.%02d V", y / 100, y % 100); + lcd_puts(0, 1, buf); + y = (adc_read(ADC_EXT_POWER) * EXT_SCALE_FACTOR) / 10000; + snprintf(buf, 30, "External: %d.%02d V", y / 100, y % 100); + lcd_puts(0, 2, buf); + snprintf(buf, 30, "Charger: %s", charger_inserted() ? "present" : "absent"); + lcd_puts(0, 3, buf); + snprintf(buf, 30, "Charging: %s", charger_enabled ? "yes" : "no"); + lcd_puts(0, 4, buf); + + y = 0; + for (i = 0; i < CHARGE_END_NEGD; i++) + y += power_history[POWER_HISTORY_LEN-1-i]*100 - power_history[POWER_HISTORY_LEN-1-i-1]*100; + y = y / CHARGE_END_NEGD; + + snprintf(buf, 30, "short delta: %d", y); + lcd_puts(0, 5, buf); + + y = 0; + for (i = 0; i < CHARGE_END_ZEROD; i++) + y += power_history[POWER_HISTORY_LEN-1-i]*100 - power_history[POWER_HISTORY_LEN-1-i-1]*100; + y = y / CHARGE_END_ZEROD; + + snprintf(buf, 30, "long delta: %d", y); + lcd_puts(0, 6, buf); + break; + + case 2: /* voltage deltas: */ + lcd_clear_display(); + lcd_puts(0, 0, "Voltage deltas:"); + + for (i = 0; i <= 6; i++) { + y = power_history[POWER_HISTORY_LEN-1-i] - power_history[POWER_HISTORY_LEN-1-i-1]; + snprintf(buf, 30, "-%d min: %s%d.%02d V", i, + (y < 0) ? "-" : "", + ((y < 0) ? y * -1 : y) / 100, ((y < 0) ? y * -1 : y ) % 100); + lcd_puts(0, i+1, buf); + } + break; + } + + lcd_update(); + sleep(HZ/2); + + switch(button_get(false)) + { + case BUTTON_F1: + charger_enable(charger_enabled?false:true); + break; + + case BUTTON_UP: + if (view) + view--; + break; + + case BUTTON_DOWN: + if (view < 2) + view++; + break; + + case BUTTON_LEFT: + case BUTTON_OFF: + return; + } + } +} + #endif void debug_menu(void) @@ -443,6 +567,7 @@ { "View MAS regs", dbg_mas }, #ifdef ARCHOS_RECORDER { "View MAS codec", dbg_mas_codec }, + { "View battery", view_battery }, #endif }; diff -urN --exclude=CVS --exclude=*~ orig/apps/main.c apps/main.c --- orig/apps/main.c Sun Jul 28 18:56:58 2002 +++ apps/main.c Tue Jul 30 15:45:42 2002 @@ -30,6 +30,7 @@ #include "menu.h" #include "system.h" #include "usb.h" +#include "powermgmt.h" #include "adc.h" #include "i2c.h" #ifndef SIMULATOR @@ -153,6 +154,8 @@ status_init(); usb_start_monitoring(); + + power_init(); } int main(void) diff -urN --exclude=CVS --exclude=*~ orig/apps/settings.c apps/settings.c --- orig/apps/settings.c Tue Jul 30 10:56:16 2002 +++ apps/settings.c Tue Jul 30 15:45:40 2002 @@ -32,6 +32,7 @@ #include "ata.h" #include "power.h" #include "backlight.h" +#include "powermgmt.h" struct user_settings global_settings; @@ -57,12 +58,12 @@ 0x0f 0x23 0x10 0x24 0x11 0x25 - + the geeky but useless statistics part: 0x24 - + 0x2a Config memory is reset to 0xff and initialized with 'factory defaults' if @@ -265,7 +266,8 @@ rtc_config_block[0xe] = (unsigned char) ((global_settings.playlist_shuffle & 1) | ((global_settings.mp3filter & 1) << 1) | - ((global_settings.sort_case & 1) << 2)); + ((global_settings.sort_case & 1) << 2) | + ((global_settings.discharge & 1) << 3)); rtc_config_block[0xf] = (unsigned char) ((global_settings.scroll_speed << 3) | @@ -332,8 +334,9 @@ global_settings.playlist_shuffle = rtc_config_block[0xe] & 1; global_settings.mp3filter = (rtc_config_block[0xe] >> 1) & 1; global_settings.sort_case = (rtc_config_block[0xe] >> 2) & 1; + global_settings.discharge = (rtc_config_block[0xe] >> 3) & 1; } - + c = rtc_config_block[0xf] >> 3; if (c != 31) global_settings.scroll_speed = c; @@ -350,6 +353,9 @@ } lcd_scroll_speed(global_settings.scroll_speed); backlight_time(global_settings.backlight); +#ifdef ARCHOS_RECORDER + charge_restart_level = global_settings.discharge ? CHARGE_RESTART_LO : CHARGE_RESTART_HI; +#endif } /* @@ -373,6 +379,7 @@ global_settings.mp3filter = true; global_settings.sort_case = false; global_settings.playlist_shuffle = false; + global_settings.discharge = 0; global_settings.total_uptime = 0; global_settings.scroll_speed = 8; } diff -urN --exclude=CVS --exclude=*~ orig/apps/settings.h apps/settings.h --- orig/apps/settings.h Sun Jul 28 19:09:44 2002 +++ apps/settings.h Tue Jul 30 14:25:29 2002 @@ -45,6 +45,7 @@ int contrast; /* lcd contrast: 0-100 0=low 100=high */ int poweroff; /* power off timer: 0-100 0=never:each 1% = 60 secs */ int backlight; /* backlight off timer: 0-100 0=never:each 1% = 10 secs */ + bool discharge; /* maintain charge of at least: false = 90%, true = 10% */ /* resume settings */ diff -urN --exclude=CVS --exclude=*~ orig/apps/settings_menu.c apps/settings_menu.c --- orig/apps/settings_menu.c Mon Jul 22 19:39:17 2002 +++ apps/settings_menu.c Tue Jul 30 14:25:29 2002 @@ -32,6 +32,7 @@ #include "settings_menu.h" #include "backlight.h" #include "playlist.h" /* for playlist_shuffle */ +#include "powermgmt.h" static void shuffle(void) { @@ -67,6 +68,14 @@ set_option("[WPS display]", &global_settings.wps_display, names, 3 ); } +#ifdef ARCHOS_RECORDER +static void deep_discharge(void) +{ + set_bool( "[Deep discharge]", &global_settings.discharge ); + charge_restart_level = global_settings.discharge ? CHARGE_RESTART_LO : CHARGE_RESTART_HI; +} +#endif + void settings_menu(void) { int m; @@ -76,7 +85,10 @@ { "Sort mode", sort_case }, { "Backlight Timer", backlight_timer }, { "Scroll speed", scroll_speed }, - { "While Playing", wps_set }, + { "While Playing", wps_set }, +#ifdef ARCHOS_RECORDER + { "Deep discharge", deep_discharge }, +#endif }; m=menu_init( items, sizeof items / sizeof(struct menu_items) );