Fri3d Camp 2024 Badge: improve battery monitor calibration

This commit is contained in:
Thomas Farstrike
2025-11-24 22:07:18 +01:00
parent 54b0aa04ac
commit 60b68efd79
5 changed files with 46 additions and 19 deletions
+19 -15
View File
@@ -4,7 +4,7 @@ MIN_VOLTAGE = 3.15
MAX_VOLTAGE = 4.15
adc = None
scale_factor = 0
conversion_func = None # Conversion function: ADC value -> voltage
adc_pin = None
# Cache to reduce WiFi interruptions (ADC2 requires WiFi to be disabled)
@@ -19,7 +19,7 @@ def _is_adc2_pin(pin):
return 11 <= pin <= 20
def init_adc(pinnr, sf):
def init_adc(pinnr, adc_to_voltage_func):
"""
Initialize ADC for battery voltage monitoring.
@@ -29,13 +29,16 @@ def init_adc(pinnr, sf):
Args:
pinnr: GPIO pin number
sf: Scale factor to convert raw ADC (0-4095) to battery voltage
adc_to_voltage_func: Conversion function that takes raw ADC value (0-4095)
and returns battery voltage in volts
"""
global adc, scale_factor, adc_pin
scale_factor = sf
global adc, conversion_func, adc_pin
conversion_func = adc_to_voltage_func
adc_pin = pinnr
try:
print(f"Initializing ADC pin {pinnr} with scale_factor {sf}")
print(f"Initializing ADC pin {pinnr} with conversion function")
if _is_adc2_pin(pinnr):
print(f" WARNING: GPIO{pinnr} is on ADC2 - WiFi will be disabled during readings")
from machine import ADC, Pin
@@ -44,6 +47,9 @@ def init_adc(pinnr, sf):
except Exception as e:
print(f"Info: this platform has no ADC for measuring battery voltage: {e}")
initial_adc_value = read_raw_adc()
print("Reading ADC at init to fill cache: {initial_adc_value} => {read_battery_voltage(raw_adc_value=initial_adc_value)}V => {get_battery_percentage(raw_adc_value=initial_adc_value)}%")
def read_raw_adc(force_refresh=False):
"""
@@ -63,12 +69,10 @@ def read_raw_adc(force_refresh=False):
"""
global _cached_raw_adc, _last_read_time
# Desktop mode - return random value
# Desktop mode - return random value in typical ADC range
if not adc:
import random
return random.randint(1900, 2600) if scale_factor == 0 else random.randint(
int(MIN_VOLTAGE / scale_factor), int(MAX_VOLTAGE / scale_factor)
)
return random.randint(1900, 2600)
# Check if this is an ADC2 pin (requires WiFi disable)
needs_wifi_disable = adc_pin is not None and _is_adc2_pin(adc_pin)
@@ -115,7 +119,7 @@ def read_raw_adc(force_refresh=False):
WifiService.temporarily_enable(was_connected)
def read_battery_voltage(force_refresh=False):
def read_battery_voltage(force_refresh=False, raw_adc_value=None):
"""
Read battery voltage in volts.
@@ -125,19 +129,19 @@ def read_battery_voltage(force_refresh=False):
Returns:
float: Battery voltage in volts (clamped to 0-MAX_VOLTAGE)
"""
raw = read_raw_adc(force_refresh)
voltage = raw * scale_factor
raw = raw_adc_value if raw_adc_value else read_raw_adc(force_refresh)
voltage = conversion_func(raw) if conversion_func else 0.0
return max(0.0, min(voltage, MAX_VOLTAGE))
def get_battery_percentage():
def get_battery_percentage(raw_adc_value=None):
"""
Get battery charge percentage.
Returns:
float: Battery percentage (0-100)
"""
voltage = read_battery_voltage()
voltage = read_battery_voltage(raw_adc_value=raw_adc_value)
percentage = (voltage - MIN_VOLTAGE) * 100.0 / (MAX_VOLTAGE - MIN_VOLTAGE)
return max(0.0, min(100.0, percentage))
@@ -275,9 +275,14 @@ best fit on battery power:
2269 is 3.831
"""
def adc_to_voltage(adc_value):
"""
Convert raw ADC value to battery voltage using calibrated linear function.
Calibration data shows linear relationship: voltage = -0.0016237 * adc + 8.2035
This is ~10x more accurate than simple scaling (error ~0.01V vs ~0.1V).
"""
return (-0.0016237 * adc_value + 8.2035)
#mpos.battery_voltage.init_adc(13, adc_to_voltage)
mpos.battery_voltage.init_adc(13, 1/616) # simple scaling has an error of ~0.01V vs the adc_to_voltage() method
mpos.battery_voltage.init_adc(13, adc_to_voltage)
import mpos.sdcard
mpos.sdcard.init(spi_bus, cs_pin=14)
+6 -1
View File
@@ -88,7 +88,12 @@ except Exception as e:
# Simulated battery voltage ADC measuring
import mpos.battery_voltage
mpos.battery_voltage.init_adc(999, (3.3 / 4095) * 2)
def adc_to_voltage(adc_value):
"""Convert simulated ADC value to voltage."""
return adc_value * (3.3 / 4095) * 2
mpos.battery_voltage.init_adc(999, adc_to_voltage)
print("linux.py finished")
@@ -81,7 +81,19 @@ mpos.ui.main_display.set_rotation(lv.DISPLAY_ROTATION._90) # must be done after
# Battery voltage ADC measuring
import mpos.battery_voltage
mpos.battery_voltage.init_adc(5, 262 / 100000)
def adc_to_voltage(adc_value):
"""
Convert raw ADC value to battery voltage.
Currently uses simple linear scaling: voltage = adc * 0.00262
This could be improved with calibration data similar to Fri3d board.
To calibrate: measure actual battery voltages and corresponding ADC readings,
then fit a linear or polynomial function.
"""
return adc_value * 0.00262
mpos.battery_voltage.init_adc(5, adc_to_voltage)
# On the Waveshare ESP32-S3-Touch-LCD-2, the camera is hard-wired to power on,
# so it needs a software power off to prevent it from staying hot all the time and quickly draining the battery.
@@ -154,6 +154,7 @@ def create_notification_bar():
# Percentage is not shown for now:
#battery_label.set_text(f"{round(percent)}%")
#battery_label.remove_flag(lv.obj.FLAG.HIDDEN)
update_battery_icon() # run it immediately instead of waiting for the timer
def update_wifi_icon(timer):
from mpos.net.wifi_service import WifiService