You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Disable wifi when reading ADC2 voltage
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 0–3.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 0–4.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)
|
||||
|
||||
@@ -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 +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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user