You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
315 lines
14 KiB
Python
315 lines
14 KiB
Python
import os
|
|
import machine
|
|
import vfs
|
|
|
|
class SDCardManager:
|
|
def __init__(self, mode=None, spi_bus=None, cs_pin=None, cmd_pin=None, clk_pin=None,
|
|
d0_pin=None, d1_pin=None, d2_pin=None, d3_pin=None, slot=1, width=None, freq=20000000):
|
|
self._sdcard = None
|
|
self._mode = None
|
|
|
|
# Auto-detect mode: if SDIO pins provided, use SDIO; otherwise use SPI
|
|
if cmd_pin is not None or clk_pin is not None or d0_pin is not None:
|
|
self._mode = 'sdio'
|
|
else:
|
|
self._mode = 'spi'
|
|
|
|
# Allow explicit mode override only if explicitly provided (not default)
|
|
if mode is not None and mode in ('spi', 'sdio'):
|
|
self._mode = mode
|
|
|
|
print(f"SD card mode: {self._mode.upper()}")
|
|
|
|
if self._mode == 'spi':
|
|
self._init_spi(spi_bus, cs_pin)
|
|
elif self._mode == 'sdio':
|
|
self._init_sdio(cmd_pin, clk_pin, d0_pin, d1_pin, d2_pin, d3_pin, slot, width, freq)
|
|
|
|
def _init_spi(self, spi_bus, cs_pin):
|
|
"""Initialize SD card in SPI mode."""
|
|
if spi_bus is None or cs_pin is None:
|
|
print("ERROR: SPI mode requires spi_bus and cs_pin parameters")
|
|
print(" - Provide: init(spi_bus=machine.SPI(...), cs_pin=pin_number)")
|
|
return
|
|
|
|
try:
|
|
self._sdcard = machine.SDCard(spi_bus=spi_bus, cs=cs_pin)
|
|
self._sdcard.info()
|
|
print("SD card initialized successfully in SPI mode")
|
|
except Exception as e:
|
|
print(f"ERROR: Failed to initialize SD card in SPI mode: {e}")
|
|
print(" - Possible causes: Invalid SPI configuration, SD card not inserted, faulty wiring, or firmware issue")
|
|
print(f" - Check: SPI pins for the SPI bus, card insertion, VCC (3.3V/5V), GND")
|
|
print(" - Try: Hard reset ESP32, test with known-good SD card")
|
|
|
|
def _init_sdio(self, cmd_pin, clk_pin, d0_pin, d1_pin=None, d2_pin=None, d3_pin=None,
|
|
slot=1, width=None, freq=20000000):
|
|
"""Initialize SD card in SDIO mode."""
|
|
# Validate required SDIO parameters
|
|
if cmd_pin is None or clk_pin is None or d0_pin is None:
|
|
print("ERROR: SDIO mode requires cmd_pin, clk_pin, and d0_pin parameters")
|
|
print(" - Provide: init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, ...)")
|
|
return
|
|
|
|
# Auto-detect SDIO width based on provided data pins
|
|
# This happens BEFORE explicit width validation to allow user override
|
|
if width is None:
|
|
# Count how many data pins are provided
|
|
data_pins_provided = sum([
|
|
d0_pin is not None,
|
|
d1_pin is not None,
|
|
d2_pin is not None,
|
|
d3_pin is not None
|
|
])
|
|
|
|
if data_pins_provided == 1:
|
|
# Only d0_pin provided: use 1-bit mode
|
|
width = 1
|
|
print("INFO: Auto-detected SDIO width=1 (only d0_pin provided)")
|
|
elif data_pins_provided == 4:
|
|
# All four data pins provided: use 4-bit mode
|
|
width = 4
|
|
print("INFO: Auto-detected SDIO width=4 (all four data pins provided)")
|
|
else:
|
|
# Partial pins provided: this is an error
|
|
print(f"ERROR: Invalid SDIO pin configuration - {data_pins_provided} data pins provided")
|
|
print(" - For 1-bit mode: provide only d0_pin")
|
|
print(" - For 4-bit mode: provide all four pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
|
print(" - Or explicitly specify width parameter to override auto-detection")
|
|
return
|
|
|
|
# Validate width parameter
|
|
if width not in (1, 4):
|
|
print(f"ERROR: SDIO width must be 1 or 4, got {width}")
|
|
return
|
|
|
|
# Validate slot parameter
|
|
if slot not in (0, 1):
|
|
print(f"ERROR: SDIO slot must be 0 or 1, got {slot}")
|
|
return
|
|
|
|
# Validate that provided pins match the requested width
|
|
if width == 4:
|
|
if d1_pin is None or d2_pin is None or d3_pin is None:
|
|
print("ERROR: SDIO 4-bit mode requires all four data pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
|
print(" - Provide all four data pins for 4-bit mode")
|
|
print(" - Or use 1-bit mode with only d0_pin")
|
|
return
|
|
elif width == 1:
|
|
if d1_pin is not None or d2_pin is not None or d3_pin is not None:
|
|
print("ERROR: SDIO 1-bit mode should only have d0_pin, but extra pins were provided")
|
|
print(" - For 1-bit mode: provide only d0_pin")
|
|
print(" - For 4-bit mode: provide all four pins (d0_pin, d1_pin, d2_pin, d3_pin)")
|
|
return
|
|
|
|
try:
|
|
# For 4-bit mode, all data pins are required
|
|
if width == 4:
|
|
self._sdcard = machine.SDCard(
|
|
slot=slot,
|
|
cmd=cmd_pin,
|
|
clk=clk_pin,
|
|
data_pins=(d0_pin,d1_pin,d2_pin,d3_pin,),
|
|
width=width,
|
|
freq=freq
|
|
)
|
|
else: # 1-bit mode
|
|
self._sdcard = machine.SDCard(
|
|
slot=slot,
|
|
cmd=cmd_pin,
|
|
clk=clk_pin,
|
|
data_pins=(d0_pin,),
|
|
width=width,
|
|
freq=freq
|
|
)
|
|
|
|
self._sdcard.info()
|
|
print(f"SD card initialized successfully in SDIO mode (slot={slot}, width={width}-bit, freq={freq}Hz)")
|
|
except Exception as e:
|
|
print(f"ERROR: Failed to initialize SD card in SDIO mode: {e}")
|
|
print(" - Possible causes: Invalid SDIO pin configuration, SD card not inserted, faulty wiring, or firmware issue")
|
|
print(f" - Check: SDIO pins (CMD, CLK, D0-D3), card insertion, VCC (3.3V), GND")
|
|
print(" - Try: Hard reset ESP32, verify pin assignments, test with known-good SD card")
|
|
|
|
def _try_mount(self, mount_point):
|
|
try:
|
|
os.mount(self._sdcard, mount_point)
|
|
print(f"SD card mounted successfully at {mount_point}")
|
|
return True
|
|
except OSError as e:
|
|
import errno
|
|
if e.errno == errno.EPERM: # EPERM is 1, meaning already mounted
|
|
print(f"Got mount error {e} which means already mounted.")
|
|
return True
|
|
else:
|
|
print(f"WARNING: Failed to mount SD card at {mount_point}: {e}")
|
|
print(" - Possible causes: Unformatted SD card (needs FAT32), corrupted filesystem, or card removed")
|
|
print(f" - Check: SD card format, ensure card is inserted")
|
|
print(" - Try: Format card on PC, or proceed to auto-format if enabled")
|
|
return False
|
|
|
|
def _format(self, mount_point):
|
|
try:
|
|
print(f"Attempting to format SD card for {mount_point}...")
|
|
try:
|
|
os.umount(mount_point)
|
|
print(f" - Unmounted {mount_point} (if it was mounted)")
|
|
except OSError:
|
|
print(f" - No prior mount found for {mount_point}, proceeding with format")
|
|
vfs.VfsFat.mkfs(self._sdcard)
|
|
print("SD card formatted successfully as FAT32")
|
|
return True
|
|
except OSError as e:
|
|
print(f"ERROR: Failed to format SD card: {e}")
|
|
print(" - Possible causes: SD card not inserted, write-protected, incompatible, or hardware error")
|
|
print(f" - Check: Card insertion, write-protect switch, verify wiring of SPI bus.")
|
|
print(" - Try: Test with another SD card, reformat on PC, ensure VCC/GND correct")
|
|
return False
|
|
|
|
def mount_with_optional_format(self, mount_point):
|
|
if not self._sdcard:
|
|
print(f"ERROR: No SD card object initialized for mounting at {mount_point}")
|
|
print(" - Possible causes: SD card initialization failed in __init__")
|
|
print(" - Check: Review initialization errors above, verify SPI setup and hardware")
|
|
print(" - Try: Hard reset, check SPI pins and SD card")
|
|
return False
|
|
|
|
if not self._try_mount(mount_point):
|
|
print(f"INFO: Initial mount failed at {mount_point}, attempting to format...")
|
|
if self._format(mount_point):
|
|
if not self._try_mount(mount_point):
|
|
print(f"ERROR: Failed to mount SD card at {mount_point} even after formatting")
|
|
print(" - Possible causes: Persistent hardware issue, incompatible SD card, or firmware bug")
|
|
print(f" - Check: Wiring of SPI bus and card type.")
|
|
print(" - Try: Hard reset, test with different SD card, reflash firmware")
|
|
return False
|
|
else:
|
|
print(f"ERROR: Could not format SD card for {mount_point} - mount aborted")
|
|
print(" - See format error details above for troubleshooting")
|
|
return False
|
|
|
|
try:
|
|
contents = os.listdir(mount_point)
|
|
print(f"SD card contents at {mount_point}: {contents}")
|
|
return True
|
|
except OSError as e:
|
|
print(f"WARNING: Could not list SD card contents at {mount_point}: {e}")
|
|
print(" - Possible causes: Filesystem corruption, card removed, or VFS cache issue")
|
|
print(f" - Check: Ensure card is inserted, verify mount with is_mounted('{mount_point}')")
|
|
print(" - Try: Unmount and remount, or reformat card")
|
|
return False
|
|
|
|
def is_mounted(self, mount_point):
|
|
try:
|
|
mounted = mount_point in os.listdir('/') and not os.mkdir(f'{mount_point}/_tmp_test')
|
|
if mounted:
|
|
print(f"SD card is mounted at {mount_point}")
|
|
try:
|
|
os.rmdir(f'{mount_point}/_tmp_test')
|
|
except:
|
|
pass
|
|
else:
|
|
print(f"SD card is not mounted at {mount_point}")
|
|
print(" - Possible causes: Never mounted, unmounted manually, or card removed")
|
|
print(f" - Try: Call mount_with_optional_format('{mount_point}')")
|
|
return mounted
|
|
except OSError as e:
|
|
print(f"WARNING: Failed to check mount status at {mount_point}: {e}")
|
|
print(" - Possible causes: Card removed, invalid mount point, or filesystem error")
|
|
print(f" - Check: Ensure {mount_point} exists and card is inserted")
|
|
print(" - Try: Remount or reinsert card")
|
|
return False
|
|
|
|
def list(self, mount_point):
|
|
try:
|
|
contents = os.listdir(mount_point)
|
|
print(f"SD card contents at {mount_point}: {contents}")
|
|
return contents
|
|
except OSError as e:
|
|
print(f"WARNING: Failed to list contents at {mount_point}: {e}")
|
|
print(" - Possible causes: SD card not mounted, removed, or corrupted filesystem")
|
|
print(f" - Check: Run is_mounted('{mount_point}'), ensure card is inserted")
|
|
print(" - Try: Remount with mount_with_optional_format('{mount_point}')")
|
|
return []
|
|
|
|
# --- Singleton pattern ---
|
|
_manager = None
|
|
|
|
def init(mode=None, spi_bus=None, cs_pin=None, cmd_pin=None, clk_pin=None,
|
|
d0_pin=None, d1_pin=None, d2_pin=None, d3_pin=None, slot=1, width=None, freq=20000000):
|
|
"""
|
|
Initialize the global SD card manager.
|
|
|
|
SPI mode (default):
|
|
init(spi_bus=machine.SPI(...), cs_pin=pin_number)
|
|
|
|
SDIO mode with auto-detection:
|
|
init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, d1_pin=A, d2_pin=B, d3_pin=C, slot=1, freq=20000000)
|
|
|
|
SDIO width auto-detection:
|
|
- If only d0_pin is provided: width is auto-set to 1 (1-bit mode)
|
|
- If all four data pins (d0, d1, d2, d3) are provided: width is auto-set to 4 (4-bit mode)
|
|
- If width parameter is explicitly provided: that value is used (overrides auto-detection)
|
|
- If partial data pins are provided (e.g., only d0 and d1): raises an error
|
|
|
|
Auto-detection of mode:
|
|
If SDIO pins are provided, SDIO mode is used automatically.
|
|
"""
|
|
global _manager
|
|
if _manager is None:
|
|
_manager = SDCardManager(
|
|
mode=mode,
|
|
spi_bus=spi_bus,
|
|
cs_pin=cs_pin,
|
|
cmd_pin=cmd_pin,
|
|
clk_pin=clk_pin,
|
|
d0_pin=d0_pin,
|
|
d1_pin=d1_pin,
|
|
d2_pin=d2_pin,
|
|
d3_pin=d3_pin,
|
|
slot=slot,
|
|
width=width,
|
|
freq=freq
|
|
)
|
|
else:
|
|
print("WARNING: SDCardManager already initialized")
|
|
print(" - Use existing instance via get()")
|
|
return _manager
|
|
|
|
def get():
|
|
"""Get the global SD card manager instance."""
|
|
if _manager is None:
|
|
print("ERROR: SDCardManager not initialized")
|
|
print(" - Call init() with appropriate parameters first in lib/mpos/board/*.py")
|
|
print(" - SPI mode: init(spi_bus=machine.SPI(...), cs_pin=pin_number)")
|
|
print(" - SDIO mode: init(mode='sdio', cmd_pin=X, clk_pin=Y, d0_pin=Z, ...)")
|
|
return _manager
|
|
|
|
def get_mode():
|
|
"""Get the current SD card mode ('spi' or 'sdio')."""
|
|
mgr = get()
|
|
if mgr is None:
|
|
print("ERROR: Cannot get mode - SDCardManager not initialized")
|
|
return None
|
|
return mgr._mode
|
|
|
|
def mount(mount_point):
|
|
mgr = get()
|
|
if mgr is None:
|
|
print("ERROR: Cannot mount - SDCardManager not initialized")
|
|
print(" - Call init() with appropriate parameters first")
|
|
return False
|
|
return mgr.mount_with_optional_format(mount_point)
|
|
|
|
def mount_with_optional_format(mount_point):
|
|
mgr = get()
|
|
if mgr is None:
|
|
print("ERROR: Cannot mount with format - SDCardManager not initialized")
|
|
print(" - Call init() with appropriate parameters first")
|
|
return False
|
|
success = mgr.mount_with_optional_format(mount_point)
|
|
if not success:
|
|
print(f"ERROR: mount_with_format('{mount_point}') failed")
|
|
print(" - See detailed errors above for mount or format issues")
|
|
return success
|