linrunner / TLP

TLP - Optimize Linux Laptop Battery Life
https://linrunner.de/tlp
GNU General Public License v2.0
2.51k stars 129 forks source link

Charge settings based on battery voltage? #722

Closed automorphism88 closed 5 months ago

automorphism88 commented 6 months ago

Is your feature request related to a problem? Please describe.

You often see advice like "don't charge your battery past 80% to minimize wear". However, this is at best an approximation, because what "80%" means varies between different batteries, and with the same battery over time, as its estimate of its capacity is recalibrated. For instance, smartphones tend to charge batteries to a higher voltage at "100%", to maximize battery life while minimizing battery size/weight, but this makes the battery last for fewer charge cycles.

What is actually relevant to the underlying lithium-ion battery chemistry involved is the voltage per cell. My research indicates that 3.92V per cell is a good level to charge Li-ion batteries to when they'll be plugged in for long durations. That's because the most significant way in which Li-ion batteries wear out starts at this voltage, and increases exponentially as voltage increases beyond 3.92V (roughly doubling with each 0.1V increase). Charging to a higher voltage gives you more capacity, so it's a tradeoff, but ideally you'd only charge to a higher voltage when you need that extra capacity.

On my (rooted) Android phone, it's possible to directly set the maximum charge voltage (in sysfs, ultimately with a daemon that serves the same role that tlp does for laptops). Unfortunately, this doesn't appear to be the case on my laptop (Thinkpad T480), because the batteries don't have the sysfs file to control maximum voltage which exists on my phone. All you can do is set the start and stop charging thresholds as a percentage. I think this is a hardware limitation, and Linux sysfs just gives access to whatever parameters are supported by the hardware. Some smartphones don't support this either.

The workaround is to use the setting that is available on my laptop, and which TLP supports, which is to set thresholds for starting and stopping charging based on the reported charge percentage. But the problem is that while the percentage charge reported by a battery has some relationship to the current battery voltage, that relationship is not constant. It's an estimate, which is updated over time as the battery is charged and discharged, and the battery recalibrates its internal estimates. So the reported charge percentage that corresponds to 3.92V/cell for a particular battery is not constant, and requires changing the TLP settings to maintain the desired voltage.

It's not just that the voltage is lower at the same estimated percentage charge when the battery is discharging (particularly under high load), higher if it's "not charging" (i.e., idle), and higher still if it's charging (since some voltage difference is required to push energy into the battery, with the difference being larger when the charge level is low and the battery charges faster). I account for this by measuring the voltage when the battery is idle ("not charging") when determining the relationship between charge percentage and voltage, and giving the voltage some time to "settle" after the battery becomes idle. The problem is that the relationship between the idle voltage ("not charging") and percentage charge reported changes over time, particularly after full charges/discharges (which presumably recalibrate the batteries' internal estimates of their capacity).

Describe the solution you'd like

A clear and concise description of:

My personal use case is that my laptop is plugged in most of the time, or unplugged for brief durations only, where I don't need anywhere near the full battery capacity, so I'd like to charge it to a level that minimizes wear (i.e., 3.92V/cell). On the rare occasions I need the full capacity of my battery (e.g. traveling), I will manually initiate a full charge (or at least, charge to a higher threshold than I would ordinarily use, e.g. 80% or 90%). This ensures that I only wear out my battery when I need to, and keeps it working like new.

I've been trying to estimate the best target charge percentage for each battery (my laptop has two) by tracking the voltage at different states of charge, so that when my laptop is plugged in and the batteries are idle, the voltage will be at or near 3.92V/cell. Unfortunately, this is a moving target, that requires adjustment over time to maintain the desired voltage. After carefully calibrating my tlp settings to reach the desired voltage, I'll check the voltage again later, and discover that it's either too low or too high. So I have to keep adjusting my tlp settings (particularly after a full charge cycle).

I'd like an option to set a target voltage (either per cell or overall - for my laptop, the batteries have three cells, so to calculate the voltage per cell from what is reported in sysfs, you have to divide by 3,000,000, as it's reported in microvolts), and for TLP to adjust the stop and start charging thresholds appropriately, by estimating the relationship between charge percentage reported by the battery and voltage per cell, and updating that estimate over time, in order to maintain the target voltage. It would also be possible to set a minimum and maximum value for the stop and start charging thresholds, and only allow adjustment within that range, in order to reach the target voltage. Similarly, you could set a minimum and maximum for the target voltage, so that adjustments to the stop/start charging percentages would only be triggered if the voltage was out of the range (probably with a narrower range for the calibration, and a larger range before a recalibration is triggered, so that you're not constantly calibrating right to the edge of the range and then recalibrating as soon as it changes a little).

For the calibration, I think it should be possible for tlp to check /sys/class/power_supply/BAT*/voltage_now periodically when a battery is "not charging" (triggered either by time elapsed since last calibration, or by logged changes in the batteries' charge state) and adjust the target charge percentage automatically. The recalibration could be triggered only when the laptop has been plugged in for some period of time, and interrupted/postponed if the laptop is unplugged.

If the voltage when idle is too high, you can write force-discharge to /sys/class/power_supply/BAT*/charge_behaviour to drain the battery a little. If the voltage when idle is too low, you can raise the charging threshold, and charge it a little. How much you charge or discharge the battery should depend on how far away the voltage is from the target (perhaps remembering how much the voltage changed with the last adjustment), and after each adjustment, the voltage could be checked again, after waiting a suitable period of time with the battery idle ("not charging") at that percentage, for the voltage to settle. The relationship between voltage and percentage charge is not linear, but is pretty close near the middle of the charge range (where the target voltage would likely be). The slope gets steeper (i.e. more change in voltage for a 1% change in state of charge) as you approach 0% or 100% charge.

Then, iterate until the voltage is close enough to the target, and the calibration is complete. Set the stop and start charging thresholds accordingly, until the next calibration. This more or less describes what I've been doing manually, and would like TLP to automate.

Describe alternatives you've considered

One workaround is to set the target charge percentage lower than you really want, in order to account for fluctuations. For instance, on my laptop, the percentage charge reported when the batteries are idle at 3.92V/cell is usually somewhere between 60% and 75% (higher for the smaller internal battery, which might or might not be related to the fact that it's more worn, as I replaced the larger removable battery when I bought this laptop used, but not the internal battery). So I could start charging at 50% and stop at 55%, and I'd probably never have to update that to keep my voltage/cell below 3.92V. But that would unnecessarily waste capacity. Also, maintaining batteries below 3.92V can increase wear in other ways.

Additional context

Add any other context and references about the feature request here:

The following shell script will report the current charge state and voltage/cell to 3 decimal places for each battery, assuming volts/cell can be found by dividing voltage_now by 3,000,000 (output can obviously be adjusted to taste, this is just an example):

#!/bin/sh -
for i in /sys/class/power_supply/BAT* ; do
    printf -- '%s (%s%%): %sV %s\n' "$(basename "$i")" "$(cat "$i/capacity")" "$(echo "scale=3; $(cat "$i/voltage_now")/3000000" | bc -l)" "$(cat "$i/status")"
done
linrunner commented 5 months ago

Hi, for good reason, TLP uses what the firmware offers, i.e. charge thresholds. Charge control belongs in the firmware, where it works permanently and not just when the laptop is on and an OS is running. If you want something else, you are free to implement it yourself. Good luck.