-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathsdcard.py
More file actions
314 lines (282 loc) · 14 KB
/
sdcard.py
File metadata and controls
314 lines (282 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
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