[prev in list] [next in list] [prev in thread] [next in thread] 

List:       rockbox
Subject:    [PATCH] Charger for the recorder
From:       Heikki Hannikainen <hessu () hes ! iki ! fi>
Date:       2002-07-30 13:05:30
Message-ID: Pine.LNX.4.44.0207301549460.2992-200000 () hes ! iki ! fi
[Download RAW message or body]

Hi,

  Here's my initial work on charging for the Recorders and long-term 
battery voltage statistics for all models. The patch is against the 
current CVS state. The players do not need this and can not make use of 
this as they have the charging done in hardware. This recorder code runs 
when rockbox runs, to get the Archos charging code reboot the device with 
the charger connected.

  Please keep in mind that this is alpha quality code and comes with no
warranty - if it breaks, you get to keep both pieces. Probably the worst
it can do is burn out your batteries. But keep on eye on the box while it
charges. I've only tested it for a couple times now.

  The algorithm is roughly described in the beginning of
firmware/powermgmt.c, it was designed on #rockbox yesterday. The
implementation does vary a bit from the specification - it calculates an
average delta voltage instead of amount of negative samples, this seems to 
be easier to tune.

  The code should probably cleaned up a bit, at least to provide a cleaner
API-sort of thing. And the 'battery full' condition detection parameters
(average length & limit values) must be tuned a little bit when we have
some experience on how the logic works out.

  Besides the charging logic, the patch adds a battery status viewer with 
three views: an automatically scaled voltage graph, voltage/status/delta 
display and a list of the last 7 1-minute deltas. Up/down keys switch 
between these.

  This is work in progress, don't expect it to work properly yet. But this 
should serve as a good starting point for experimenting.

  - Hessu


["20020730-charger.diff" (TEXT/PLAIN)]

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    <scroll speed & WPS display byte>
 0x10    0x24    <playlist options byte>
 0x11    0x25    <AVC byte>
-  
+
         <all unused space filled with 0xff>
 
   the geeky but useless statistics part:
 0x24    <total uptime in seconds: 32 bits uint, actually unused for now>
-  
+
 0x2a    <checksum 2 bytes: xor of 0x0-0x29>
 
 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) );



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic