Files
2026-02-10 11:25:45 +01:00

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