summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/debug_menu.c24
-rw-r--r--firmware/export/powermgmt.h10
-rw-r--r--firmware/powermgmt.c750
3 files changed, 411 insertions, 373 deletions
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 1d42cf8312..9d5028af7c 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -1247,9 +1247,10 @@ bool view_battery(void)
1247 lcd_puts(0, 3, buf); 1247 lcd_puts(0, 3, buf);
1248#endif 1248#endif
1249#ifdef HAVE_CHARGE_CTRL 1249#ifdef HAVE_CHARGE_CTRL
1250 snprintf(buf, 30, "Charging: %s", 1250 snprintf(buf, 30, "Chgr: %s %s",
1251 charger_enabled ? "yes" : "no"); 1251 charger_inserted() ? "present" : "absent",
1252 lcd_puts(0, 4, buf); 1252 charger_enabled ? "on" : "off");
1253 lcd_puts(0, 3, buf);
1253 snprintf(buf, 30, "short delta: %d", short_delta); 1254 snprintf(buf, 30, "short delta: %d", short_delta);
1254 lcd_puts(0, 5, buf); 1255 lcd_puts(0, 5, buf);
1255 snprintf(buf, 30, "long delta: %d", long_delta); 1256 snprintf(buf, 30, "long delta: %d", long_delta);
@@ -1271,7 +1272,7 @@ bool view_battery(void)
1271 } 1272 }
1272 break; 1273 break;
1273 1274
1274 case 3: /* remeining time estimation: */ 1275 case 3: /* remaining time estimation: */
1275 lcd_clear_display(); 1276 lcd_clear_display();
1276 1277
1277#ifdef HAVE_CHARGE_CTRL 1278#ifdef HAVE_CHARGE_CTRL
@@ -1283,23 +1284,24 @@ bool view_battery(void)
1283 1284
1284 snprintf(buf, 30, "Lvl@cyc st: %d%%", powermgmt_last_cycle_level); 1285 snprintf(buf, 30, "Lvl@cyc st: %d%%", powermgmt_last_cycle_level);
1285 lcd_puts(0, 2, buf); 1286 lcd_puts(0, 2, buf);
1287
1288 snprintf(buf, 30, "P=%2d I=%2d", pid_p, pid_i);
1289 lcd_puts(0, 3, buf);
1290
1291 snprintf(buf, 30, "Trickle sec: %d/60", trickle_sec);
1292 lcd_puts(0, 4, buf);
1286#endif 1293#endif
1287 1294
1288 snprintf(buf, 30, "Last PwrHist: %d.%02d V", 1295 snprintf(buf, 30, "Last PwrHist: %d.%02d V",
1289 power_history[0] / 100, 1296 power_history[0] / 100,
1290 power_history[0] % 100); 1297 power_history[0] % 100);
1291 lcd_puts(0, 3, buf);
1292
1293 snprintf(buf, 30, "battery level: %d%%", battery_level());
1294 lcd_puts(0, 5, buf); 1298 lcd_puts(0, 5, buf);
1295 1299
1296 snprintf(buf, 30, "Est. remain: %d m", battery_time()); 1300 snprintf(buf, 30, "battery level: %d%%", battery_level());
1297 lcd_puts(0, 6, buf); 1301 lcd_puts(0, 6, buf);
1298 1302
1299#ifdef HAVE_CHARGE_CTRL 1303 snprintf(buf, 30, "Est. remain: %d m", battery_time());
1300 snprintf(buf, 30, "Trickle sec: %d/60", trickle_sec);
1301 lcd_puts(0, 7, buf); 1304 lcd_puts(0, 7, buf);
1302#endif
1303 break; 1305 break;
1304 } 1306 }
1305 1307
diff --git a/firmware/export/powermgmt.h b/firmware/export/powermgmt.h
index 3bccb0f59f..ccfdede9f9 100644
--- a/firmware/export/powermgmt.h
+++ b/firmware/export/powermgmt.h
@@ -57,12 +57,13 @@
57#ifndef SIMULATOR 57#ifndef SIMULATOR
58 58
59#ifdef HAVE_CHARGE_CTRL 59#ifdef HAVE_CHARGE_CTRL
60#define START_TOPOFF_CHG 85 /* Battery % to start at top-off */
61#define START_TRICKLE_CHG 95 /* Battery % to start at trickle */
62
60#define POWER_MESSAGE_LEN 32 /* power thread status message */ 63#define POWER_MESSAGE_LEN 32 /* power thread status message */
61#define CHARGE_MAX_TIME_1500 450 /* minutes: maximum charging time for 1500 mAh batteries */ 64#define CHARGE_MAX_TIME_1500 450 /* minutes: maximum charging time for 1500 mAh batteries */
62 /* actual max time depends also on BATTERY_CAPACITY! */ 65 /* actual max time depends also on BATTERY_CAPACITY! */
63#define CHARGE_MIN_TIME 10 /* minutes: minimum charging time */ 66#define CHARGE_MIN_TIME 10 /* minutes: minimum charging time */
64#define CHARGE_RESTART 85 /* %: when to restart charging in 'charge' mode */
65 /* attention: if set too high, normal charging is started in trickle mode */
66#define TOPOFF_MAX_TIME 90 /* After charging, go to top off charge. How long should top off charge be? */ 67#define TOPOFF_MAX_TIME 90 /* After charging, go to top off charge. How long should top off charge be? */
67#define TOPOFF_VOLTAGE 565 /* which voltage is best? (centivolts) */ 68#define TOPOFF_VOLTAGE 565 /* which voltage is best? (centivolts) */
68#define TRICKLE_MAX_TIME 12*60 /* After top off charge, go to trickle charge. How long should trickle charge be? */ 69#define TRICKLE_MAX_TIME 12*60 /* After top off charge, go to trickle charge. How long should trickle charge be? */
@@ -71,6 +72,9 @@
71#define START_TOPOFF_SEC 25 /* initial trickle_sec for topoff */ 72#define START_TOPOFF_SEC 25 /* initial trickle_sec for topoff */
72#define START_TRICKLE_SEC 15 /* initial trickle_sec for trickle */ 73#define START_TRICKLE_SEC 15 /* initial trickle_sec for trickle */
73 74
75#define PID_PCONST 2 /* PID proportional constant */
76#define PID_DEADZONE 2 /* PID proportional deadzone */
77
74extern char power_message[POWER_MESSAGE_LEN]; 78extern char power_message[POWER_MESSAGE_LEN];
75 79
76extern int long_delta; /* long term delta battery voltage */ 80extern int long_delta; /* long term delta battery voltage */
@@ -79,6 +83,8 @@ extern int short_delta; /* short term delta battery voltage */
79extern int powermgmt_last_cycle_startstop_min; /* how many minutes ago was the charging started or stopped? */ 83extern int powermgmt_last_cycle_startstop_min; /* how many minutes ago was the charging started or stopped? */
80extern int powermgmt_last_cycle_level; /* which level had the batteries at this time? */ 84extern int powermgmt_last_cycle_level; /* which level had the batteries at this time? */
81 85
86extern int pid_p; /* PID proportional term */
87extern int pid_i; /* PID integral term */
82extern int trickle_sec; /* trickle charge: How many seconds per minute are we charging actually? */ 88extern int trickle_sec; /* trickle charge: How many seconds per minute are we charging actually? */
83 89
84#endif /* HAVE_CHARGE_CTRL */ 90#endif /* HAVE_CHARGE_CTRL */
diff --git a/firmware/powermgmt.c b/firmware/powermgmt.c
index dd2233efb1..0366896bb4 100644
--- a/firmware/powermgmt.c
+++ b/firmware/powermgmt.c
@@ -57,17 +57,11 @@
57static char debug_message[DEBUG_MESSAGE_LEN]; 57static char debug_message[DEBUG_MESSAGE_LEN];
58#define DEBUG_STACK ((0x1000)/sizeof(long)) 58#define DEBUG_STACK ((0x1000)/sizeof(long))
59static int fd; /* write debug information to this file */ 59static int fd; /* write debug information to this file */
60static int wrcount;
60#else 61#else
61#define DEBUG_STACK 0 62#define DEBUG_STACK 0
62#endif 63#endif
63 64
64long last_event_tick;
65
66void reset_poweroff_timer(void)
67{
68 last_event_tick = current_tick;
69}
70
71#ifdef SIMULATOR /***********************************************************/ 65#ifdef SIMULATOR /***********************************************************/
72 66
73int battery_level(void) 67int battery_level(void)
@@ -125,24 +119,38 @@ static const short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] =
125#endif 119#endif
126}; 120};
127 121
128static int battery_type = 0; 122#ifdef HAVE_CHARGING
123/* voltages (centivolt) of 0%, 10%, ... 100% when charging enabled */
124static const short percent_to_volt_charge[11] =
125{
126 /* values guessed, see
127 http://www.seattlerobotics.org/encoder/200210/LiIon2.pdf until someone
128 measures voltages over a charging cycle */
129 476, 544, 551, 556, 561, 564, 566, 576, 582, 584, 585 /* NiMH */
130};
131#endif /* HAVE_CHARGING */
129 132
130#if defined(HAVE_CHARGE_CTRL) || CONFIG_BATTERY == BATT_LIION2200 133#if defined(HAVE_CHARGE_CTRL) || CONFIG_BATTERY == BATT_LIION2200
131charge_state_type charge_state; /* charging mode */ 134charge_state_type charge_state; /* charging mode */
132int charge_timer; /* charge timer (minutes, dec to zero) */
133#endif 135#endif
134 136
135#ifdef HAVE_CHARGING 137#ifdef HAVE_CHARGING
136/* Flag that the charger has been plugged in */ 138/*
137static bool charger_was_inserted = false; /* for power off logic */ 139 * Flag that the charger has been plugged in/removed: this is set for exactly
138static bool charger_power_is_on; /* for car adapter mode */ 140 * one time through the power loop when the charger has been plugged in.
139#endif 141 */
142static enum {
143 NO_CHARGER,
144 CHARGER_UNPLUGGED, /* transient state */
145 CHARGER_PLUGGED, /* transient state */
146 CHARGER
147} charger_input_state;
140 148
141/* Power history: power_history[0] is the newest sample */ 149static bool waiting_to_resume_play = false;
142unsigned short power_history[POWER_HISTORY_LEN]; 150static long play_resume_time;
151#endif
143 152
144#ifdef HAVE_CHARGE_CTRL 153#ifdef HAVE_CHARGE_CTRL
145
146int long_delta; /* long term delta battery voltage */ 154int long_delta; /* long term delta battery voltage */
147int short_delta; /* short term delta battery voltage */ 155int short_delta; /* short term delta battery voltage */
148 156
@@ -155,52 +163,56 @@ int powermgmt_last_cycle_startstop_min = 0; /* how many minutes ago was the
155 stopped? */ 163 stopped? */
156int powermgmt_last_cycle_level = 0; /* which level had the 164int powermgmt_last_cycle_level = 0; /* which level had the
157 batteries at this time? */ 165 batteries at this time? */
158int trickle_sec = 0; /* how many seconds should the 166int trickle_sec = 0; /* how many seconds should the
159 charger be enabled per 167 charger be enabled per
160 minute for trickle 168 minute for trickle
161 charging? */ 169 charging? */
162/* voltages (centivolt) of 0%, 10%, ... 100% when charging enabled */ 170int pid_p = 0; /* PID proportional term */
163static const short percent_to_volt_charge[11] = 171int pid_i = 0; /* PID integral term */
164{ 172#endif /* HAVE_CHARGE_CTRL */
165 /* values guessed, see
166 http://www.seattlerobotics.org/encoder/200210/LiIon2.pdf until someone
167 measures voltages over a charging cycle */
168 476, 544, 551, 556, 561, 564, 566, 576, 582, 584, 585 /* NiMH */
169};
170
171#endif /* HAVE_CHARGE_CTRL || CONFIG_BATTERY == BATT_LIION2200 */
172 173
173/* 174/*
174 * Average battery voltage and charger voltage, filtered via a digital 175 * Average battery voltage and charger voltage, filtered via a digital
175 * exponential filter. 176 * exponential filter.
176 */ 177 */
177unsigned int battery_centivolts;/* filtered battery voltage, centvolts */ 178static unsigned int battery_centivolts;/* filtered battery voltage, centvolts */
178static unsigned int avgbat; /* average battery voltage */ 179static unsigned int avgbat; /* average battery voltage (filtering) */
179#define BATT_AVE_SAMPLES 32 /* filter constant / @ 2Hz sample rate */ 180#define BATT_AVE_SAMPLES 32 /* filter constant / @ 2Hz sample rate */
180 181
181int battery_capacity = BATTERY_CAPACITY_MIN; /* only a default value */
182
183/* battery level (0-100%) of this minute, updated once per minute */ 182/* battery level (0-100%) of this minute, updated once per minute */
184static int battery_percent = -1; 183static int battery_percent = -1;
184static int battery_capacity = BATTERY_CAPACITY_MIN; /* default value, mAH */
185static int battery_type = 0;
186
187/* Power history: power_history[0] is the newest sample */
188unsigned short power_history[POWER_HISTORY_LEN];
185 189
186static bool car_adapter_mode_enabled = false; 190static bool car_adapter_mode_enabled = false;
187 191
188static char power_stack[DEFAULT_STACK_SIZE + DEBUG_STACK]; 192static char power_stack[DEFAULT_STACK_SIZE + DEBUG_STACK];
189static const char power_thread_name[] = "power"; 193static const char power_thread_name[] = "power";
190 194
191static int poweroff_timeout = 0; 195static int poweroff_timeout = 0;
192static long last_charge_time = 0; 196static int powermgmt_est_runningtime_min = -1;
193int powermgmt_est_runningtime_min = -1;
194 197
195static bool sleeptimer_active = false; 198static bool sleeptimer_active = false;
196static unsigned long sleeptimer_endtick; 199static long sleeptimer_endtick;
200
201static long last_event_tick;
202
203static void battery_level_update(void); /* forward declaration */
204
205void reset_poweroff_timer(void)
206{
207 last_event_tick = current_tick;
208}
197 209
198#if BATTERY_TYPES_COUNT > 1 210#if BATTERY_TYPES_COUNT > 1
199void set_battery_type(int type) 211void set_battery_type(int type)
200{ 212{
201 if (type != battery_type) { 213 if (type != battery_type) {
202 battery_type = type; 214 battery_type = type;
203 battery_percent = -1; /* reset on type change */ 215 battery_level_update(); /* recalculate the battery level */
204 } 216 }
205} 217}
206#endif 218#endif
@@ -219,79 +231,6 @@ int battery_time(void)
219 return powermgmt_est_runningtime_min; 231 return powermgmt_est_runningtime_min;
220} 232}
221 233
222/* look into the percent_to_volt_* table and get a realistic battery level
223 percentage */
224int voltage_to_percent(int voltage, const short* table)
225{
226 if (voltage <= table[0])
227 return 0;
228 else
229 if (voltage >= table[10])
230 return 100;
231 else {
232 /* search nearest value */
233 int i = 0;
234 while ((i < 10) && (table[i+1] < voltage))
235 i++;
236 /* interpolate linear between the smaller and greater value */
237 return i * 10 /* 10th */
238 + (voltage - table[i]) *
239 10 / (table[i+1] - table[i]); /* 1th */
240 }
241}
242
243/* update battery level, called only once per minute */
244void battery_level_update(void)
245{
246 int level;
247
248 level = battery_centivolts;
249 if(level > BATTERY_LEVEL_FULL)
250 level = BATTERY_LEVEL_FULL;
251
252 if(level < BATTERY_LEVEL_EMPTY)
253 level = BATTERY_LEVEL_EMPTY;
254
255#ifdef HAVE_CHARGE_CTRL
256 if (charge_state == DISCHARGING) {
257 level = voltage_to_percent(level,
258 percent_to_volt_discharge[battery_type]);
259 }
260 else if (charge_state == CHARGING) {
261 level = voltage_to_percent(level, percent_to_volt_charge);
262 }
263 else {/* in trickle charge, the battery is by definition 100% full */
264 battery_percent = level = 100;
265 }
266#else
267 /* always use the discharge table */
268 level = voltage_to_percent(level,
269 percent_to_volt_discharge[battery_type]);
270#endif
271
272#ifndef HAVE_MMC /* this adjustment is only needed for HD based */
273 if (battery_percent == -1) { /* first run of this procedure */
274 /* The battery voltage is usually a little lower directly after
275 turning on, because the disk was used heavily. Raise it by 5. % */
276 battery_percent = (level > 95) ? 100 : level + 5;
277 }
278 else
279#endif
280 {
281 /* the level is allowed to be -1 of the last value when usb not
282 connected and to be -3 of the last value when usb is connected */
283 if (usb_inserted()) {
284 if (level < battery_percent - 3)
285 level = battery_percent - 3;
286 }
287 else {
288 if (level < battery_percent - 1)
289 level = battery_percent - 1;
290 }
291 battery_percent = level;
292 }
293}
294
295/* Returns battery level in percent */ 234/* Returns battery level in percent */
296int battery_level(void) 235int battery_level(void)
297{ 236{
@@ -316,11 +255,11 @@ void set_poweroff_timeout(int timeout)
316void set_sleep_timer(int seconds) 255void set_sleep_timer(int seconds)
317{ 256{
318 if(seconds) { 257 if(seconds) {
319 sleeptimer_active = true; 258 sleeptimer_active = true;
320 sleeptimer_endtick = current_tick + seconds * HZ; 259 sleeptimer_endtick = current_tick + seconds * HZ;
321 } 260 }
322 else { 261 else {
323 sleeptimer_active = false; 262 sleeptimer_active = false;
324 sleeptimer_endtick = 0; 263 sleeptimer_endtick = 0;
325 } 264 }
326} 265}
@@ -333,31 +272,82 @@ int get_sleep_timer(void)
333 return 0; 272 return 0;
334} 273}
335 274
336/* We shut off in the following cases: 275/* look into the percent_to_volt_* table and get a realistic battery level
337 1) The unit is idle, not playing music 276 percentage */
338 2) The unit is playing music, but is paused 277static int voltage_to_percent(int voltage, const short* table)
278{
279 if (voltage <= table[0])
280 return 0;
281 else
282 if (voltage >= table[10])
283 return 100;
284 else {
285 /* search nearest value */
286 int i = 0;
287 while ((i < 10) && (table[i+1] < voltage))
288 i++;
289 /* interpolate linear between the smaller and greater value */
290 return (i * 10) /* Tens digit, 10% per entry */
291 + (((voltage - table[i]) * 10)
292 / (table[i+1] - table[i])); /* Ones digit: interpolated */
293 }
294}
339 295
340 We do not shut off in the following cases: 296/* update battery level, called once per minute */
341 1) The USB is connected 297static void battery_level_update(void)
342 2) The charger is connected 298{
343 3) We are recording, or recording with pause 299 int level;
344*/ 300
301#ifdef HAVE_CHARGE_CTRL
302 if (charge_state == DISCHARGING) {
303 level = voltage_to_percent(battery_centivolts,
304 percent_to_volt_discharge[battery_type]);
305 }
306 else if (charge_state == CHARGING) {
307 level = voltage_to_percent(battery_centivolts, percent_to_volt_charge);
308 }
309 else { /* in trickle charge, the battery is by definition 100% full */
310 level = 100;
311 }
312#else
313 /* always use the discharge table */
314 level = voltage_to_percent(battery_centivolts,
315 percent_to_volt_discharge[battery_type]);
316#endif
317
318#ifndef HAVE_MMC /* this adjustment is only needed for HD based */
319 if (battery_percent == -1) { /* first run of this procedure */
320 /* The battery voltage is usually a little lower directly after
321 turning on, because the disk was used heavily. Raise it by 5. % */
322 level = (level > 95) ? 100 : level + 5;
323 }
324#endif
325 battery_percent = level;
326}
327
328/*
329 * We shut off in the following cases:
330 * 1) The unit is idle, not playing music
331 * 2) The unit is playing music, but is paused
332 *
333 * We do not shut off in the following cases:
334 * 1) The USB is connected
335 * 2) The charger is connected
336 * 3) We are recording, or recording with pause
337 */
345static void handle_auto_poweroff(void) 338static void handle_auto_poweroff(void)
346{ 339{
347 long timeout = poweroff_idle_timeout_value[poweroff_timeout]*60*HZ; 340 long timeout = poweroff_idle_timeout_value[poweroff_timeout]*60*HZ;
348 int mpeg_stat = mpeg_status(); 341 int mpeg_stat = mpeg_status();
349#ifdef HAVE_CHARGING
350 bool charger_is_inserted = charger_inserted();
351#endif
352 342
353#ifdef HAVE_CHARGING 343#ifdef HAVE_CHARGING
354 /* The was_inserted thing prevents the unit to shut down immediately 344 /*
355 when the charger is extracted */ 345 * Inhibit shutdown as long as the charger is plugged in. If it is
356 if(charger_is_inserted || charger_was_inserted) 346 * unplugged, wait for a timeout period and then shut down.
357 { 347 */
358 last_charge_time = current_tick; 348 if(charger_input_state == CHARGER) {
349 last_event_tick = current_tick;
359 } 350 }
360 charger_was_inserted = charger_is_inserted;
361#endif 351#endif
362 352
363 if(timeout && 353 if(timeout &&
@@ -369,9 +359,8 @@ static void handle_auto_poweroff(void)
369 ((mpeg_stat == (MPEG_STATUS_PLAY | MPEG_STATUS_PAUSE)) && 359 ((mpeg_stat == (MPEG_STATUS_PLAY | MPEG_STATUS_PAUSE)) &&
370 !sleeptimer_active))) 360 !sleeptimer_active)))
371 { 361 {
372 if(TIME_AFTER(current_tick, last_event_tick + timeout) && 362 if(TIME_AFTER(current_tick, last_event_tick + timeout) &&
373 TIME_AFTER(current_tick, last_disk_activity + timeout) && 363 TIME_AFTER(current_tick, last_disk_activity + timeout))
374 TIME_AFTER(current_tick, last_charge_time + timeout))
375 { 364 {
376 shutdown_hw(); 365 shutdown_hw();
377 } 366 }
@@ -385,7 +374,8 @@ static void handle_auto_poweroff(void)
385 { 374 {
386 mpeg_stop(); 375 mpeg_stop();
387#ifdef HAVE_CHARGING 376#ifdef HAVE_CHARGING
388 if(charger_is_inserted) 377 if((charger_input_state == CHARGER) ||
378 (charger_input_state == CHARGER_PLUGGED))
389 { 379 {
390 DEBUGF("Sleep timer timeout. Stopping...\n"); 380 DEBUGF("Sleep timer timeout. Stopping...\n");
391 set_sleep_timer(0); 381 set_sleep_timer(0);
@@ -413,10 +403,7 @@ void set_car_adapter_mode(bool setting)
413 403
414#ifdef HAVE_CHARGING 404#ifdef HAVE_CHARGING
415static void car_adapter_mode_processing(void) 405static void car_adapter_mode_processing(void)
416{ 406{
417 static bool waiting_to_resume_play = false;
418 static long play_resume_time;
419
420 if (car_adapter_mode_enabled) { 407 if (car_adapter_mode_enabled) {
421 408
422 if (waiting_to_resume_play) { 409 if (waiting_to_resume_play) {
@@ -426,34 +413,23 @@ static void car_adapter_mode_processing(void)
426 } 413 }
427 waiting_to_resume_play = false; 414 waiting_to_resume_play = false;
428 } 415 }
429 } 416 } else {
430 else { 417 if (charger_input_state == CHARGER_UNPLUGGED) {
431 if (charger_power_is_on) { 418 /*
432 419 * Just got unplugged, pause if playing
433 /* if external power was turned off */ 420 */
434 if (!charger_inserted()) { 421 if ((mpeg_status() & MPEG_STATUS_PLAY) &&
435 422 !(mpeg_status() & MPEG_STATUS_PAUSE)) {
436 charger_power_is_on = false; 423 mpeg_pause();
437
438 /* if playing */
439 if ((mpeg_status() & MPEG_STATUS_PLAY) &&
440 !(mpeg_status() & MPEG_STATUS_PAUSE)) {
441 mpeg_pause();
442 }
443 } 424 }
444 } 425 } else if(charger_input_state == CHARGER_PLUGGED) {
445 else { 426 /*
446 /* if external power was turned on */ 427 * Just got plugged in, delay & resume if we were playing
447 if (charger_inserted()) { 428 */
448 429 if (mpeg_status() & MPEG_STATUS_PAUSE) {
449 charger_power_is_on = true; 430 /* delay resume a bit while the engine is cranking */
450 431 play_resume_time = current_tick + HZ*5;
451 /* if paused */ 432 waiting_to_resume_play = true;
452 if (mpeg_status() & MPEG_STATUS_PAUSE) {
453 /* delay resume a bit while the engine is cranking */
454 play_resume_time = current_tick + HZ*5;
455 waiting_to_resume_play = true;
456 }
457 } 433 }
458 } 434 }
459 } 435 }
@@ -509,14 +485,43 @@ static void power_thread_rtc_process(void)
509static void power_thread_sleep(int ticks) 485static void power_thread_sleep(int ticks)
510{ 486{
511 int small_ticks; 487 int small_ticks;
512#ifdef HAVE_CHARGING 488
513 bool charger_plugged; 489 while (ticks > 0) {
514#endif
515 490
516#ifdef HAVE_CHARGING 491#ifdef HAVE_CHARGING
517 charger_plugged = charger_inserted(); 492 /*
493 * Detect charger plugged/unplugged transitions. On a plugged or
494 * unplugged event, we return immediately, run once through the main
495 * loop (including the subroutines), and end up back here where we
496 * transition to the appropriate steady state charger on/off state.
497 */
498 if(charger_inserted()) {
499 switch(charger_input_state) {
500 case NO_CHARGER:
501 case CHARGER_UNPLUGGED:
502 charger_input_state = CHARGER_PLUGGED;
503 return;
504 case CHARGER_PLUGGED:
505 charger_input_state = CHARGER;
506 break;
507 case CHARGER:
508 break;
509 }
510 } else { /* charger not inserted */
511 switch(charger_input_state) {
512 case NO_CHARGER:
513 break;
514 case CHARGER_UNPLUGGED:
515 charger_input_state = NO_CHARGER;
516 break;
517 case CHARGER_PLUGGED:
518 case CHARGER:
519 charger_input_state = CHARGER_UNPLUGGED;
520 return;
521 }
522 }
518#endif 523#endif
519 while (ticks > 0) { 524
520 small_ticks = MIN(HZ/2, ticks); 525 small_ticks = MIN(HZ/2, ticks);
521 sleep(small_ticks); 526 sleep(small_ticks);
522 ticks -= small_ticks; 527 ticks -= small_ticks;
@@ -532,8 +537,6 @@ static void power_thread_sleep(int ticks)
532 * Do a digital exponential filter. We don't sample the battery if 537 * Do a digital exponential filter. We don't sample the battery if
533 * the disk is spinning unless we are in USB mode (the disk will most 538 * the disk is spinning unless we are in USB mode (the disk will most
534 * likely always be spinning in USB mode). 539 * likely always be spinning in USB mode).
535 * If the charging voltage is greater than 0x3F0 charging isn't active
536 * and that voltage isn't valid.
537 */ 540 */
538 if (!ata_disk_is_active() || usb_inserted()) { 541 if (!ata_disk_is_active() || usb_inserted()) {
539 avgbat = avgbat - (avgbat / BATT_AVE_SAMPLES) + 542 avgbat = avgbat - (avgbat / BATT_AVE_SAMPLES) +
@@ -541,13 +544,19 @@ static void power_thread_sleep(int ticks)
541 /* 544 /*
542 * battery_centivolts is the centivolt-scaled filtered battery value. 545 * battery_centivolts is the centivolt-scaled filtered battery value.
543 */ 546 */
544 battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) * BATTERY_SCALE_FACTOR) / 10000; 547 battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) *
548 BATTERY_SCALE_FACTOR) / 10000;
549 }
550#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
551 /*
552 * If we have a lot of pending writes or if the disk is spining,
553 * fsync the debug log file.
554 */
555 if((wrcount > 10) ||
556 ((wrcount > 0) && ata_disk_is_active())) {
557 fsync(fd);
558 wrcount = 0;
545 } 559 }
546
547#ifdef HAVE_CHARGING
548 /* If the charger was plugged in, exit now so we can start charging */
549 if(!charger_plugged && charger_inserted())
550 return;
551#endif 560#endif
552 } 561 }
553} 562}
@@ -568,17 +577,19 @@ static void power_thread(void)
568 int charging_current; 577 int charging_current;
569#endif 578#endif
570#ifdef HAVE_CHARGE_CTRL 579#ifdef HAVE_CHARGE_CTRL
571 int charge_max_time_now = 0; /* max. charging duration, calculated at 580 unsigned int target_voltage; /* desired topoff/trickle voltage level */
572 beginning of charging */ 581 int charge_max_time_now = 0; /* max. charging duration, calculated at
582 beginning of charging */
573#endif 583#endif
574 584
575 /* initialize the voltages for the exponential filter */ 585 /* initialize the voltages for the exponential filter */
576 586
577 avgbat = adc_read(ADC_UNREG_POWER) * BATT_AVE_SAMPLES; 587 avgbat = adc_read(ADC_UNREG_POWER) * BATT_AVE_SAMPLES;
578 battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) * BATTERY_SCALE_FACTOR) / 10000; 588 battery_centivolts = ((avgbat / BATT_AVE_SAMPLES) * BATTERY_SCALE_FACTOR) / 10000;
579 589
580#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL) 590#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
581 fd = -1; 591 fd = -1;
592 wrcount = 0;
582#endif 593#endif
583 594
584 while (1) 595 while (1)
@@ -611,7 +622,6 @@ static void power_thread(void)
611 * If we are charging, the "runtime" is estimated time till the battery 622 * If we are charging, the "runtime" is estimated time till the battery
612 * is recharged. 623 * is recharged.
613 */ 624 */
614 // TBD: use real charging current estimate
615 if (charge_state == CHARGING) { 625 if (charge_state == CHARGING) {
616 powermgmt_est_runningtime_min = (100 - battery_level()) * 626 powermgmt_est_runningtime_min = (100 - battery_level()) *
617 battery_capacity / 100 * 60 / (CURRENT_MAX_CHG - runcurrent()); 627 battery_capacity / 100 * 60 / (CURRENT_MAX_CHG - runcurrent());
@@ -638,185 +648,200 @@ static void power_thread(void)
638#endif /* # if CONFIG_BATTERY == BATT_LIION2200 */ 648#endif /* # if CONFIG_BATTERY == BATT_LIION2200 */
639 649
640#ifdef HAVE_CHARGE_CTRL 650#ifdef HAVE_CHARGE_CTRL
641 651 if (charger_input_state == CHARGER_PLUGGED) {
642 if (charger_inserted()) { 652 pid_p = 0;
653 pid_i = 0;
654 snprintf(power_message, POWER_MESSAGE_LEN, "Charger plugged in");
643 /* 655 /*
644 * Time to start charging again? 656 * The charger was just plugged in. If the battery level is
645 * 1) If we are currently discharging but trickle is enabled, 657 * nearly charged, just trickle. If the battery is low, start
646 * the charger must have just been plugged in. 658 * a full charge cycle. If the battery level is in between,
647 * 2) If our battery level falls below the restart level, charge! 659 * top-off and then trickle.
648 */ 660 */
649 if ((charge_state == DISCHARGING) || 661 if(battery_percent > START_TOPOFF_CHG) {
650 (battery_level() < CHARGE_RESTART)) { 662 powermgmt_last_cycle_level = battery_percent;
651 663 powermgmt_last_cycle_startstop_min = 0;
664 if(battery_percent >= START_TRICKLE_CHG) {
665 charge_state = TRICKLE;
666 } else {
667 charge_state = TOPOFF;
668 }
669 } else {
652 /* 670 /*
653 * If the battery level is nearly charged, just trickle. 671 * Start the charger full strength
654 * If the battery is in between, top-off and then trickle.
655 */ 672 */
656 if(battery_percent > CHARGE_RESTART) { 673 i = CHARGE_MAX_TIME_1500 * battery_capacity / 1500;
657 powermgmt_last_cycle_level = battery_percent; 674 charge_max_time_now =
658 powermgmt_last_cycle_startstop_min = 0; 675 i * (100 + 35 - battery_percent) / 100;
659 if(battery_percent >= 95) { 676 if (charge_max_time_now > i) {
660 trickle_sec = START_TRICKLE_SEC; 677 charge_max_time_now = i;
661 charge_state = TRICKLE;
662 } else {
663 trickle_sec = START_TOPOFF_SEC;
664 charge_state = TOPOFF;
665 }
666 } else {
667 /*
668 * Start the charger full strength
669 */
670 i = CHARGE_MAX_TIME_1500 * battery_capacity / 1500;
671 charge_max_time_now =
672 i * (100 + 35 - battery_percent) / 100;
673 if (charge_max_time_now > i) {
674 charge_max_time_now = i;
675 }
676 snprintf(power_message, POWER_MESSAGE_LEN,
677 "ChgAt %d%% max %dm", battery_level(),
678 charge_max_time_now);
679
680 /* enable the charger after the max time calc is done,
681 because battery_level depends on if the charger is
682 on */
683 DEBUGF("power: charger inserted and battery"
684 " not full, enabling\n");
685 powermgmt_last_cycle_level = battery_percent;
686 powermgmt_last_cycle_startstop_min = 0;
687 trickle_sec = 60;
688 charge_state = CHARGING;
689 } 678 }
690 }
691 if (charge_state == CHARGING) {
692 /* charger inserted and enabled 100% of the time */
693 trickle_sec = 60; /* 100% on */
694
695 snprintf(power_message, POWER_MESSAGE_LEN, 679 snprintf(power_message, POWER_MESSAGE_LEN,
696 "Chg %dm, max %dm", powermgmt_last_cycle_startstop_min, 680 "ChgAt %d%% max %dm", battery_level(),
697 charge_max_time_now); 681 charge_max_time_now);
698 /* 682
699 * Sum the deltas over the last X minutes so we can do our 683 /* enable the charger after the max time calc is done,
700 * end-of-charge logic based on the battery level change. 684 because battery_level depends on if the charger is
701 */ 685 on */
702 long_delta = short_delta = 999999; 686 DEBUGF("power: charger inserted and battery"
703 if (powermgmt_last_cycle_startstop_min > CHARGE_MIN_TIME) { 687 " not full, charging\n");
704 short_delta = power_history[0] - 688 powermgmt_last_cycle_level = battery_percent;
705 power_history[CHARGE_END_NEGD - 1]; 689 powermgmt_last_cycle_startstop_min = 0;
706 } 690 trickle_sec = 60;
707 if (powermgmt_last_cycle_startstop_min > CHARGE_END_ZEROD) { 691 long_delta = short_delta = 999999;
708 /* 692 charge_state = CHARGING;
709 * Scan the history: if we have a big delta in the middle of 693 }
710 * our history, the long term delta isn't a valid end-of-charge 694 }
711 * indicator. 695 if (charge_state == CHARGING) {
712 */ 696 snprintf(power_message, POWER_MESSAGE_LEN,
713 long_delta = power_history[0] - 697 "Chg %dm, max %dm", powermgmt_last_cycle_startstop_min,
714 power_history[CHARGE_END_ZEROD - 1]; 698 charge_max_time_now);
715 for(i = 0; i < CHARGE_END_ZEROD; i++) { 699 /*
716 if(((power_history[i] - power_history[i+1]) > 5) || 700 * Check the delta voltage over the last X minutes so we can do
717 ((power_history[i] - power_history[i+1]) < -5)) { 701 * our end-of-charge logic based on the battery level change.
718 long_delta = 888888; 702 */
719 break; 703 if (powermgmt_last_cycle_startstop_min > CHARGE_MIN_TIME) {
720 } 704 short_delta = power_history[0] -
705 power_history[CHARGE_END_NEGD - 1];
706 }
707 if (powermgmt_last_cycle_startstop_min > CHARGE_END_ZEROD) {
708 /*
709 * Scan the history: if we have a big delta in the middle of
710 * our history, the long term delta isn't a valid end-of-charge
711 * indicator.
712 */
713 long_delta = power_history[0] -
714 power_history[CHARGE_END_ZEROD - 1];
715 for(i = 0; i < CHARGE_END_ZEROD; i++) {
716 if(((power_history[i] - power_history[i+1]) > 5) ||
717 ((power_history[i] - power_history[i+1]) < -5)) {
718 long_delta = 888888;
719 break;
721 } 720 }
722 } 721 }
722 }
723 723
724 /* 724 /*
725 * End of charge criteria (any qualify): 725 * End of charge criteria (any qualify):
726 * 1) Charged a long time 726 * 1) Charged a long time
727 * 2) DeltaV went negative for a short time 727 * 2) DeltaV went negative for a short time
728 * 3) DeltaV was close to zero for a long time 728 * 3) DeltaV was close to zero for a long time
729 * Note: short_delta and long_delta are centivolts 729 * Note: short_delta and long_delta are centivolts
730 */ 730 */
731 if ((powermgmt_last_cycle_startstop_min > charge_max_time_now) || 731 if ((powermgmt_last_cycle_startstop_min > charge_max_time_now) ||
732 (short_delta < -5) || (long_delta < 5)) 732 (short_delta <= -5) || (long_delta < 5))
733 { 733 {
734 if (powermgmt_last_cycle_startstop_min > charge_max_time_now) { 734 if (powermgmt_last_cycle_startstop_min > charge_max_time_now) {
735 DEBUGF("power: powermgmt_last_cycle_startstop_min > charge_max_time_now, " 735 DEBUGF("power: powermgmt_last_cycle_startstop_min > charge_max_time_now, "
736 "enough!\n"); 736 "enough!\n");
737 /* have charged too long and deltaV detection did not 737 /* have charged too long and deltaV detection did not
738 work! */ 738 work! */
739 snprintf(power_message, POWER_MESSAGE_LEN,
740 "Chg tmout %d min", charge_max_time_now);
741 /*
742 * Switch to trickle charging. We skip the top-off
743 * since we've effectively done the top-off operation
744 * already since we charged for the maximum full
745 * charge time.
746 */
747 powermgmt_last_cycle_level = battery_percent;
748 powermgmt_last_cycle_startstop_min = 0;
749 charge_state = TRICKLE;
750 } else {
751 if(short_delta <= -5) {
752 DEBUGF("power: short-term negative"
753 " delta, enough!\n");
739 snprintf(power_message, POWER_MESSAGE_LEN, 754 snprintf(power_message, POWER_MESSAGE_LEN,
740 "Chg tmout %d min", charge_max_time_now); 755 "end negd %d %dmin", short_delta,
756 powermgmt_last_cycle_startstop_min);
741 } else { 757 } else {
742 if(short_delta < -5) { 758 DEBUGF("power: long-term small "
743 DEBUGF("power: short-term negative" 759 "positive delta, enough!\n");
744 " delta, enough!\n"); 760 snprintf(power_message, POWER_MESSAGE_LEN,
745 snprintf(power_message, POWER_MESSAGE_LEN, 761 "end lowd %d %dmin", long_delta,
746 "end negd %d %dmin", short_delta, 762 powermgmt_last_cycle_startstop_min);
747 powermgmt_last_cycle_startstop_min);
748 } else {
749 DEBUGF("power: long-term small "
750 "positive delta, enough!\n");
751 snprintf(power_message, POWER_MESSAGE_LEN,
752 "end lowd %d %dmin", long_delta,
753 powermgmt_last_cycle_startstop_min);
754 }
755 } 763 }
756 /* Switch to trickle charging. We skip the top-off 764 /*
757 since we've effectively done the top-off operation 765 * Switch to top-off charging.
758 already since we charged for the maximum full 766 */
759 charge time. For trickle charging, we use 0.05C */
760 powermgmt_last_cycle_level = battery_percent; 767 powermgmt_last_cycle_level = battery_percent;
761 powermgmt_last_cycle_startstop_min = 0; 768 powermgmt_last_cycle_startstop_min = 0;
762 769 charge_state = TOPOFF;
763 trickle_sec = START_TRICKLE_SEC;
764 charge_state = TRICKLE;
765 } 770 }
766 } 771 }
767 else if (charge_state > CHARGING) /* top off or trickle */ 772 }
773 else if (charge_state > CHARGING) /* top off or trickle */
774 {
775 /* Time to switch from topoff to trickle?
776 */
777 if ((charge_state == TOPOFF) &&
778 (powermgmt_last_cycle_startstop_min > TOPOFF_MAX_TIME))
768 { 779 {
769 /* Time to switch from topoff to trickle? Note that we don't 780 powermgmt_last_cycle_level = battery_percent;
770 * adjust trickle_sec: it will get adjusted down by the 781 powermgmt_last_cycle_startstop_min = 0;
771 * charge level adjustment in the loop and will drift down 782 charge_state = TRICKLE;
772 * from the topoff level to the trickle level. 783 }
773 */ 784 /*
774 if ((charge_state == TOPOFF) && 785 * Adjust trickle charge time (proportional and integral terms).
775 (powermgmt_last_cycle_startstop_min > TOPOFF_MAX_TIME)) 786 * Note: I considered setting the level higher if the USB is
776 { 787 * plugged in, but it doesn't appear to be necessary and will
777 powermgmt_last_cycle_level = battery_percent; 788 * generate more heat [gvb].
778 powermgmt_last_cycle_startstop_min = 0; 789 */
779 charge_state = TRICKLE; 790 if(charge_state == TOPOFF)
780 } 791 target_voltage = TOPOFF_VOLTAGE;
781 792 else
782 /* Adjust trickle charge time. I considered setting the level 793 target_voltage = TRICKLE_VOLTAGE;
783 * higher if the USB is plugged in, but it doesn't appear to 794
784 * be necessary and will generate more heat [gvb]. 795 pid_p = target_voltage - battery_centivolts;
785 */ 796 if((pid_p > PID_DEADZONE) || (pid_p < -PID_DEADZONE))
786 if(((charge_state == TOPOFF) && (battery_centivolts > TOPOFF_VOLTAGE)) || 797 pid_p = pid_p * PID_PCONST;
787 ((charge_state == TRICKLE) && (battery_centivolts > TRICKLE_VOLTAGE))) 798 else
788 { /* charging too much */ 799 pid_p = 0;
789 if(trickle_sec > 0) 800 if(battery_centivolts < target_voltage) {
790 trickle_sec--; 801 if(pid_i < 60) {
802 pid_i++; /* limit so it doesn't "wind up" */
791 } 803 }
792 else { /* charging too little */ 804 } else {
793 if(trickle_sec < 60) 805 if(pid_i > 0) {
794 trickle_sec++; 806 pid_i--; /* limit so it doesn't "wind up" */
795 } 807 }
808 }
809
810 trickle_sec = pid_p + pid_i;
796 811
797 } else if (charge_state == DISCHARGING) { 812 if(trickle_sec > 60) {
813 trickle_sec = 60;
814 }
815 if(trickle_sec < 0) {
798 trickle_sec = 0; 816 trickle_sec = 0;
799 /* the charger is enabled here only in one case: if it was
800 turned on at boot time (power_init) */
801 /* turn it off now */
802 if (charger_enabled)
803 charger_enable(false);
804 } 817 }
805 } else {
806 if (charge_state != DISCHARGING) {
807 /* charger not inserted but was enabled */
808 DEBUGF("power: charger disconnected, disabling\n");
809 818
819 } else if (charge_state == DISCHARGING) {
820 trickle_sec = 0;
821 /*
822 * The charger is enabled here only in one case: if it was
823 * turned on at boot time (power_init). Turn it off now.
824 */
825 if (charger_enabled)
810 charger_enable(false); 826 charger_enable(false);
811 powermgmt_last_cycle_level = battery_percent;
812 powermgmt_last_cycle_startstop_min = 0;
813 trickle_sec = 0;
814 charge_state = DISCHARGING;
815 snprintf(power_message, POWER_MESSAGE_LEN, "Charger: discharge");
816 }
817 } 827 }
818 powermgmt_last_cycle_startstop_min++; 828
819 829 if (charger_input_state == CHARGER_UNPLUGGED) {
830 /*
831 * The charger was just unplugged.
832 */
833 DEBUGF("power: charger disconnected, disabling\n");
834
835 charger_enable(false);
836 powermgmt_last_cycle_level = battery_percent;
837 powermgmt_last_cycle_startstop_min = 0;
838 trickle_sec = 0;
839 pid_p = 0;
840 pid_i = 0;
841 charge_state = DISCHARGING;
842 snprintf(power_message, POWER_MESSAGE_LEN, "Charger: discharge");
843 }
844
820#endif /* HAVE_CHARGE_CTRL*/ 845#endif /* HAVE_CHARGE_CTRL*/
821 846
822 /* sleep for a minute */ 847 /* sleep for a minute */
@@ -834,25 +859,36 @@ static void power_thread(void)
834#endif 859#endif
835 860
836#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL) 861#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
837 if((fd < 0) && !usb_inserted()) { 862 if(usb_inserted()) {
838 fd = open(DEBUG_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT); 863 if(fd >= 0) {
839 snprintf(debug_message, DEBUG_MESSAGE_LEN, 864 /* It is probably too late to close the file but we can try... */
840 "cycle_min, bat_centivolts, bat_percent, chgr, chg_state, trickle_sec\n"); 865 close(fd);
841 write(fd, debug_message, strlen(debug_message)); 866 fd = -1;
842 fsync(fd); 867 }
843 } else if((fd >= 0) && !usb_inserted()) { 868 } else {
844 snprintf(debug_message, DEBUG_MESSAGE_LEN, "%d, %d, %d, %d, %d, %d\n", 869 if(fd < 0) {
845 powermgmt_last_cycle_startstop_min, battery_centivolts, 870 fd = open(DEBUG_FILE_NAME, O_WRONLY | O_APPEND | O_CREAT);
846 battery_percent, charger_inserted(), charge_state, trickle_sec); 871 if(fd >= 0) {
847 write(fd, debug_message, strlen(debug_message)); 872 snprintf(debug_message, DEBUG_MESSAGE_LEN,
848 fsync(fd); 873 "cycle_min, bat_centivolts, bat_percent, chgr_state, charge_state, pid_p, pid_i, trickle_sec\n");
849 } else if((fd >= 0) && usb_inserted()) { 874 write(fd, debug_message, strlen(debug_message));
850 /* NOTE: It is probably already TOO LATE to close the file */ 875 wrcount = 99; /* force a flush */
851 close(fd); 876 }
852 fd = -1; 877 }
878 if(fd >= 0) {
879 snprintf(debug_message, DEBUG_MESSAGE_LEN, "%d, %d, %d, %d, %d, %d, %d, %d\n",
880 powermgmt_last_cycle_startstop_min, battery_centivolts,
881 battery_percent, charger_input_state, charge_state, pid_p, pid_i, trickle_sec);
882 write(fd, debug_message, strlen(debug_message));
883 wrcount++;
884 }
853 } 885 }
854#endif 886#endif
855 handle_auto_poweroff(); 887 handle_auto_poweroff();
888
889#ifdef HAVE_CHARGE_CTRL
890 powermgmt_last_cycle_startstop_min++;
891#endif
856 } 892 }
857} 893}
858 894
@@ -862,10 +898,6 @@ void powermgmt_init(void)
862 898
863 /* init history to 0 */ 899 /* init history to 0 */
864 memset(power_history, 0x00, sizeof(power_history)); 900 memset(power_history, 0x00, sizeof(power_history));
865
866#ifdef HAVE_CHARGING
867 charger_power_is_on = charger_inserted();
868#endif
869 901
870 create_thread(power_thread, power_stack, sizeof(power_stack), 902 create_thread(power_thread, power_stack, sizeof(power_stack),
871 power_thread_name); 903 power_thread_name);
@@ -876,11 +908,10 @@ void powermgmt_init(void)
876/* Various hardware housekeeping tasks relating to shutting down the jukebox */ 908/* Various hardware housekeeping tasks relating to shutting down the jukebox */
877void shutdown_hw(void) 909void shutdown_hw(void)
878{ 910{
879#ifndef SIMULATOR
880#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL) 911#if defined(DEBUG_FILE) && defined(HAVE_CHARGE_CTRL)
881 if(fd > 0) { 912 if(fd >= 0) {
882 close(fd); 913 close(fd);
883 fd = 0; 914 fd = -1;
884 } 915 }
885#endif 916#endif
886 mpeg_stop(); 917 mpeg_stop();
@@ -899,5 +930,4 @@ void shutdown_hw(void)
899 lcd_set_contrast(0); 930 lcd_set_contrast(0);
900#endif 931#endif
901 power_off(); 932 power_off();
902#endif
903} 933}