2020-03-22 16:40:18 +01:00
|
|
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
# Copyright (C) 2020 Daniel Thompson
|
|
|
|
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Generic lithium ion battery driver
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
"""
|
2021-06-19 09:22:20 +02:00
|
|
|
import micropython
|
2022-09-20 11:06:02 +02:00
|
|
|
# don't remove Pin import even though your IDE tells you it's unused, otherwise
|
|
|
|
# micropython can't load your battery level.
|
2020-01-30 22:46:35 +01:00
|
|
|
from machine import Pin, ADC
|
|
|
|
|
|
|
|
class Battery(object):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Generic lithium ion battery driver.
|
|
|
|
|
|
|
|
.. automethod:: __init__
|
|
|
|
"""
|
|
|
|
|
2020-01-30 22:46:35 +01:00
|
|
|
def __init__(self, battery, charging, power=None):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Specify the pins used to provide battery status.
|
|
|
|
|
|
|
|
:param Pin battery: The ADC-capable pin that can be used to measure
|
|
|
|
battery voltage.
|
|
|
|
:param Pin charging: A pin (or Signal) that reports the charger status.
|
|
|
|
:param Pin power: A pin (or Signal) that reports whether the device
|
|
|
|
has external power, defaults to None (which means
|
|
|
|
use the charging pin for power reporting too).
|
|
|
|
"""
|
2020-01-30 22:46:35 +01:00
|
|
|
self._battery = ADC(battery)
|
|
|
|
self._charging = charging
|
|
|
|
self._power = power
|
2022-03-16 15:55:27 +01:00
|
|
|
self._cache = set()
|
2020-01-30 22:46:35 +01:00
|
|
|
|
2021-06-19 09:22:20 +02:00
|
|
|
@micropython.native
|
2020-01-30 22:46:35 +01:00
|
|
|
def charging(self):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Get the charging state of the battery.
|
|
|
|
|
|
|
|
:returns: True if the battery is charging, False otherwise.
|
|
|
|
"""
|
2022-03-16 15:55:27 +01:00
|
|
|
return self._charging.value()
|
2020-01-30 22:46:35 +01:00
|
|
|
|
|
|
|
def power(self):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Check whether the device has external power.
|
|
|
|
|
|
|
|
:returns: True if the device has an external power source, False
|
|
|
|
otherwise.
|
|
|
|
"""
|
2020-01-30 22:46:35 +01:00
|
|
|
if self._power:
|
|
|
|
return self._power.value()
|
|
|
|
return self._charging.value()
|
|
|
|
|
|
|
|
def voltage_mv(self):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Read the battery voltage.
|
|
|
|
|
|
|
|
Assumes a 50/50 voltage divider and a 3.3v power supply
|
|
|
|
|
2022-03-16 15:55:27 +01:00
|
|
|
The last values is kept in a cache and only the minium cached value is
|
|
|
|
shown to the user, this is to avoid the battery level
|
|
|
|
going up and down because of the lack of precision of the mv.
|
|
|
|
Note that this will underestimate battery level.
|
|
|
|
|
2020-05-14 22:31:22 +02:00
|
|
|
:returns: Battery voltage, in millivolts.
|
|
|
|
"""
|
2020-01-30 22:46:35 +01:00
|
|
|
raw = self._battery.read_u16()
|
2022-03-16 15:55:27 +01:00
|
|
|
mv = (2 * 3300 * raw) // 65535
|
|
|
|
|
|
|
|
if mv not in self._cache:
|
|
|
|
self._cache.add(mv)
|
|
|
|
while len(self._cache) > 2:
|
|
|
|
self._cache.remove(max(self._cache))
|
|
|
|
return min(self._cache)
|
2020-01-30 22:46:35 +01:00
|
|
|
|
|
|
|
def level(self):
|
2020-05-14 22:31:22 +02:00
|
|
|
"""Estimate the battery level.
|
|
|
|
|
|
|
|
The current the estimation approach is extremely simple. It is assumes
|
|
|
|
the discharge from 4v to 3.5v is roughly linear and 4v is 100% and
|
|
|
|
that 3.5v is 5%. Below 3.5v the voltage will start to drop pretty
|
2022-03-16 15:55:27 +01:00
|
|
|
sharply so we will drop from 5% to 0% pretty fast... but we'll
|
2020-05-14 22:31:22 +02:00
|
|
|
live with that for now.
|
|
|
|
|
|
|
|
:returns: Estimate battery level in percent.
|
|
|
|
"""
|
2020-01-30 22:46:35 +01:00
|
|
|
mv = self.voltage_mv()
|
|
|
|
level = ((19 * mv) // 100) - 660
|
2022-03-16 15:55:27 +01:00
|
|
|
level = min(100, max(0, level))
|
2020-01-30 22:46:35 +01:00
|
|
|
return level
|