Disable wifi when reading ADC2 voltage

This commit is contained in:
Thomas Farstrike
2025-11-24 16:32:41 +01:00
parent e5d7a11444
commit 28147fb312
5 changed files with 164 additions and 39 deletions
@@ -528,7 +528,7 @@ class UpdateDownloader:
except Exception as e:
result['error'] = str(e)
print(f"UpdateDownloader: Error during download: {e}")
print(f"UpdateDownloader: Error during download: {e}") # -113 when wifi disconnected
return result
+142 -29
View File
@@ -5,40 +5,153 @@ MAX_VOLTAGE = 4.15
adc = None
scale_factor = 0
adc_pin = None
# Cache to reduce WiFi interruptions (ADC2 requires WiFi to be disabled)
_cached_raw_adc = None
_last_read_time = 0
CACHE_DURATION_MS = 30000 # 30 seconds
def _is_adc2_pin(pin):
"""Check if pin is on ADC2 (ESP32-S3: GPIO11-20)."""
return 11 <= pin <= 20
# This gets called by (the device-specific) boot*.py
def init_adc(pinnr, sf):
global adc, scale_factor
try:
print(f"Initializing ADC pin {pinnr} with scale_factor {scale_factor}")
from machine import ADC, Pin # do this inside the try because it will fail on desktop
adc = ADC(Pin(pinnr))
# Set ADC to 11dB attenuation for 03.3V range (common for ESP32)
adc.atten(ADC.ATTN_11DB)
scale_factor = sf
except Exception as e:
print("Info: this platform has no ADC for measuring battery voltage")
"""
Initialize ADC for battery voltage monitoring.
def read_battery_voltage():
IMPORTANT for ESP32-S3: ADC2 (GPIO11-20) doesn't work when WiFi is active!
Use ADC1 pins (GPIO1-10) for battery monitoring if possible.
If using ADC2, WiFi will be temporarily disabled during readings.
Args:
pinnr: GPIO pin number
sf: Scale factor to convert raw ADC (0-4095) to battery voltage
"""
global adc, scale_factor, adc_pin
scale_factor = sf
adc_pin = pinnr
try:
print(f"Initializing ADC pin {pinnr} with scale_factor {sf}")
if _is_adc2_pin(pinnr):
print(f" WARNING: GPIO{pinnr} is on ADC2 - WiFi will be disabled during readings")
from machine import ADC, Pin
adc = ADC(Pin(pinnr))
adc.atten(ADC.ATTN_11DB) # 0-3.3V range
except Exception as e:
print(f"Info: this platform has no ADC for measuring battery voltage: {e}")
def read_raw_adc(force_refresh=False):
"""
Read raw ADC value (0-4095) with caching.
On ESP32-S3 with ADC2, WiFi is temporarily disabled during reading.
Raises RuntimeError if WifiService is busy (connecting/scanning) when using ADC2.
Args:
force_refresh: Bypass cache and force fresh reading
Returns:
float: Raw ADC value (0-4095)
Raises:
RuntimeError: If WifiService is busy (only when using ADC2)
"""
global _cached_raw_adc, _last_read_time
# Desktop mode - return random value
if not adc:
import random
random_voltage = random.randint(round(MIN_VOLTAGE*100),round(MAX_VOLTAGE*100)) / 100
#print(f"returning random voltage: {random_voltage}")
return random_voltage
# Read raw ADC value
total = 0
# Read multiple times to try to reduce variability.
# Reading 10 times takes around 3ms so it's fine...
for _ in range(10):
total = total + adc.read()
raw_value = total / 10
#print(f"read_battery_voltage raw_value: {raw_value}")
voltage = raw_value * scale_factor
# Clamp to 04.2V range for LiPo battery
voltage = max(0, min(voltage, MAX_VOLTAGE))
return voltage
return random.randint(1900, 2600) if scale_factor == 0 else random.randint(
int(MIN_VOLTAGE / scale_factor), int(MAX_VOLTAGE / scale_factor)
)
# Check cache
current_time = time.ticks_ms()
if not force_refresh and _cached_raw_adc is not None:
age = time.ticks_diff(current_time, _last_read_time)
if age < CACHE_DURATION_MS:
return _cached_raw_adc
# Check if this is an ADC2 pin (requires WiFi disable)
needs_wifi_disable = adc_pin is not None and _is_adc2_pin(adc_pin)
# Import WifiService only if needed
WifiService = None
if needs_wifi_disable:
try:
from mpos.net.wifi_service import WifiService
except ImportError:
pass
# Check if WiFi operations are in progress
if WifiService and WifiService.wifi_busy:
raise RuntimeError("Cannot read battery voltage: WifiService is busy")
# Disable WiFi for ADC2 reading
wifi_was_connected = False
if needs_wifi_disable and WifiService:
wifi_was_connected = WifiService.is_connected()
WifiService.wifi_busy = True
WifiService.disconnect()
time.sleep(0.05) # Brief delay for WiFi to fully disable
try:
# Read ADC (average of 10 samples)
total = sum(adc.read() for _ in range(10))
raw_value = total / 10.0
# Update cache
_cached_raw_adc = raw_value
_last_read_time = current_time
return raw_value
finally:
# Re-enable WiFi (only if we disabled it)
if needs_wifi_disable and WifiService:
WifiService.wifi_busy = False
if wifi_was_connected:
# Trigger reconnection in background thread
try:
import _thread
_thread.start_new_thread(WifiService.auto_connect, ())
except Exception as e:
print(f"battery_voltage: Failed to start reconnect thread: {e}")
def read_battery_voltage(force_refresh=False):
"""
Read battery voltage in volts.
Args:
force_refresh: Bypass cache and force fresh reading
Returns:
float: Battery voltage in volts (clamped to 0-MAX_VOLTAGE)
"""
raw = read_raw_adc(force_refresh)
voltage = raw * scale_factor
return max(0.0, min(voltage, MAX_VOLTAGE))
# Could be interesting to keep a "rolling average" of the percentage so that it doesn't fluctuate too quickly
def get_battery_percentage():
return (read_battery_voltage() - MIN_VOLTAGE) * 100 / (MAX_VOLTAGE - MIN_VOLTAGE)
"""
Get battery charge percentage.
Returns:
float: Battery percentage (0-100)
"""
voltage = read_battery_voltage()
percentage = (voltage - MIN_VOLTAGE) * 100.0 / (MAX_VOLTAGE - MIN_VOLTAGE)
return max(0.0, min(100.0, percentage))
def clear_cache():
"""Clear the battery voltage cache to force fresh reading on next call."""
global _cached_raw_adc, _last_read_time
_cached_raw_adc = None
_last_read_time = 0
@@ -258,8 +258,11 @@ indev.set_display(disp) # different from display
indev.enable(True) # NOQA
# Battery voltage ADC measuring
# NOTE: GPIO13 is on ADC2, which requires WiFi to be disabled during reading on ESP32-S3.
# battery_voltage.py handles this automatically: disables WiFi, reads ADC, reconnects WiFi.
# Readings are cached for 30 seconds to minimize WiFi interruptions.
import mpos.battery_voltage
mpos.battery_voltage.init_adc(13, 2 / 1000)
mpos.battery_voltage.init_adc(13, 3.3 * 2 / 4095)
import mpos.sdcard
mpos.sdcard.init(spi_bus, cs_pin=14)
+6 -1
View File
@@ -85,7 +85,12 @@ except Exception as e:
# print(f"boot_unix: code={event_code}") # target={event.get_target()}, user_data={event.get_user_data()}, param={event.get_param()}
#keyboard.add_event_cb(keyboard_cb, lv.EVENT.ALL, None)
print("boot_unix.py finished")
# Simulated battery voltage ADC measuring
import mpos.battery_voltage
mpos.battery_voltage.init_adc(999, (3.3 / 4095) * 2)
print("linux.py finished")
+11 -7
View File
@@ -11,7 +11,7 @@ NOTIFICATION_BAR_HEIGHT=24
CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough
WIFI_ICON_UPDATE_INTERVAL = 1500
BATTERY_ICON_UPDATE_INTERVAL = 5000
BATTERY_ICON_UPDATE_INTERVAL = 30000 # not too often, because on fri3d_2024, this briefly disables wifi
TEMPERATURE_UPDATE_INTERVAL = 2000
MEMFREE_UPDATE_INTERVAL = 5000 # not too frequent because there's a forced gc.collect() to give it a reliable value
@@ -92,9 +92,10 @@ def create_notification_bar():
temp_label = lv.label(notification_bar)
temp_label.set_text("00°C")
temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7) , 0)
memfree_label = lv.label(notification_bar)
memfree_label.set_text("")
memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0)
if False:
memfree_label = lv.label(notification_bar)
memfree_label.set_text("")
memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0)
#style = lv.style_t()
#style.init()
#style.set_text_font(lv.font_montserrat_8) # tiny font
@@ -134,7 +135,11 @@ def create_notification_bar():
print("Warning: could not check WLAN status:", str(e))
def update_battery_icon(timer=None):
percent = mpos.battery_voltage.get_battery_percentage()
try:
percent = mpos.battery_voltage.get_battery_percentage()
except Exception as e:
print(f"battery_voltage.get_battery_percentage got exception, not updating battery_icon: {e}")
return
if percent > 80: # 4.1V
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
elif percent > 60: # 4.0V
@@ -149,7 +154,6 @@ 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
@@ -182,7 +186,7 @@ def create_notification_bar():
lv.timer_create(update_time, CLOCK_UPDATE_INTERVAL, None)
lv.timer_create(update_temperature, TEMPERATURE_UPDATE_INTERVAL, None)
lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None)
#lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None)
lv.timer_create(update_wifi_icon, WIFI_ICON_UPDATE_INTERVAL, None)
lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None)