Shop OBEX P1 Docs P2 Docs Learn Events
P2 uSD Card Driver — FAT32 Filesystem (spin2/pasm2) - Page 3 — Parallax Forums

P2 uSD Card Driver — FAT32 Filesystem (spin2/pasm2)

135

Comments

  • evanhevanh Posts: 17,160

    @"Stephen Moraco" said:
    @evanh, what is the current state with v1.3.0 vs. your older card? Is the logic working?

    I've done more reading of the spec and found the answer. Turns out there is a minimum of 1 byte gap and these cards are following the spec correctly. The timing diagram I posted above should have but doesn't specify the timing of relevance, Nrc. It is listed in the table but has it's own separate diagram:

    Sorry for not just uploading the PDF itself. It's one I found in a Web search that is not the free edition.

  • evanhevanh Posts: 17,160

    So adding a padding $FF byte at the end of the prior sequence, before rasing CS instead of after lowering CS, should also do the job.

  • evanhevanh Posts: 17,160
    edited 2026-03-08 12:50

    I suppose, to be fair, it's not clear, from the diagram at least, if Nrc holds true after CS rises. CS high could be considered a reset so then my previous interpretation would hold. It's a little fuzzy so not surprising different manufacturers might have interpreted different ways.

    Either way, it's clear the driver has to make the allowance for Nrc holding true irrespective of CS. And the best solution is to, like Flexspin's sdmm.c driver, combine it with the busy check.

  • evanhevanh Posts: 17,160
    edited 2026-03-10 04:32

    @"Stephen Moraco" said:
    @evanh, what is the current state with v1.3.0 vs. your older card? Is the logic working?

    Every time you post an update at the moment I have to redo a huge number of debug deletes so that the important parts can report anything. Otherwise I just get compile errors of too big debug.

    Here's the relevant Adata 16GB card:

    Cog0  INIT $0000_0000 $0000_0000 load
    Cog0  INIT $0000_0404 $0000_0000 load
    Cog0  clkfreq = 200_000_000, clkmode = $100_09FB
    Cog0    [start] Worker cog 1 started, lock 0
    Cog1  INIT $0000_0404 $0001_8DF2 load
    Cog1    [fs_worker] Starting on cog 1
    Cog1    [fs_worker] Pins initialized: CS=P39 MOSI=P34 MISO=P36 SCK=P35
    Cog1    [do_mount] Calling initCard()...
    Cog1      [initCard] Starting card init...
    Cog1      [initCard] Reference: SPI_SD_Implementation_Reference.md
    Cog1      [initCard] Step 1: Power-on delay (100ms)...
    Cog1      [initCard] Step 2: SPI config, bit_delay=bit_delay = 2_000 (~50kHz)
    Cog1      [initCard] Pins: CS=1 MOSI=1 MISO=1 SCK=0
    Cog1      [initCard] Step 3: Recovery flush (4096 clocks to clear stuck transfer)...
    Cog1      [initCard] MISO after recovery flush: 1 (should be 1)
    Cog1      [initCard] Step 3.5: Initializing smart pins (ManAtWork pattern)...
    Cog1      [initSPIPins] Setting up smart pins (ManAtWork pattern)...
    Cog1      [initSPIPins] Mode values:
    Cog1      [initSPIPins]   spi_clk_mode=$spi_clk_mode = $0000_004A (P_TRANSITION | P_OE, idle LOW)
    Cog1      [initSPIPins]   spi_tx_mode=$spi_tx_mode = $0100_0078 (P_SYNC_TX | P_OE | smartB)
    Cog1      [initSPIPins]   spi_rx_mode=$spi_rx_mode = $0700_003A (P_SYNC_RX | smartB)
    Cog1      [initSPIPins]   SCK=P35 MOSI=P34 MISO=P36
    Cog1      [initSPIPins]   SCK configured: P_TRANSITION | P_OE (awaiting applySPISpeed)
    Cog1      [initSPIPins]   MOSI configured: P_SYNC_TX | smartB (enabled)
    Cog1      [initSPIPins]   MISO configured: P_SYNC_RX | smartB (enabled)
    Cog1      [initSPIPins]   Event SE1 configured for SCK pin 35
    Cog1      [initSPIPins] Smart pins configured and enabled
    Cog1      [applySPISpeed] Target=400 kHz, Actual=400 kHz
    Cog1      [applySPISpeed] Half-period=250 clocks
    Cog1      [sp_transfer_8] pins: miso=36 sck=35 raw=$FF00_0000 final=$FF
    Cog1      [sp_transfer_8] pins: miso=36 sck=35 raw=$FFFF_0000 final=$FF
    Cog1      [sp_transfer_8] pins: miso=36 sck=35 raw=$FFFF_FF00 final=$FF
    Cog1      [sp_transfer_8] pins: miso=36 sck=35 raw=$FFFF_FFFF final=$FF
    Cog1      [sp_transfer_8] pins: miso=36 sck=35 raw=$FFFF_FFFF final=$FF
    Cog1      [initCard] Dummy clocks sent via smart pins
    Cog1      [initCard] Step 4: CMD0 (GO_IDLE_STATE)...
    Cog1        [cmd] CS before assert: 1
    Cog1        [cmd] CS after pinl: 0 MISO=0
    Cog1        [cmd] After CMD0 sent, MISO=0
    Cog1      [initCard] CMD0 response: $$1
    Cog1      [initCard] CMD0 OK - card in idle state
    Cog1      [initCard] Step 5: CMD8 (SEND_IF_COND, VHS=1, pattern=$AA)...
    Cog1      [initCard] CMD8 response (32-bit): $resp = $0000_01AA
    Cog1      [initCard] CMD8 echo valid ($1AA) -> Ver 2.0+ SD card
    Cog1      [initCard] Step 6: ACMD41 init loop (arg=$acmd41_arg = $4000_0000)...
    Cog1      [initCard] ACMD41 complete - card ready!
    Cog1      [initCard] Step 7: CMD58 (READ_OCR)...
    Cog1      [initCard] OCR: $resp = $C0FF_8000
    Cog1      [initCard]   Bit 31 (ready): ((Shr__(resp, 31)) & 0x1) = 1
    Cog1      [initCard]   Bit 30 (CCS):   ((Shr__(resp, OCR_CCS_SHIFT)) & 0x1) = 1
    Cog1      [initCard] Card type: SDHC/SDXC (block addressing)
    Cog1      [initCard] Step 8: Card identification and speed selection...
    Cog1    [identifyCard] Reading card registers...
    Cog1    [parseMfrId] Manufacturer ID=$$1D
    Cog1    [identifyCard] Detected PNY/AData card - using conservative timing
    Cog1    [parseTransSpeed] TRAN_SPEED=$$32 -> (speed_hz / 1000000) = 25 MHz
    Cog1    [parseTimeouts] SDHC/SDXC: read=100ms, write=500ms
    Cog1      [applySPISpeed] Target=20_000 kHz, Actual=20_000 kHz
    Cog1      [applySPISpeed] Half-period=5 clocks
    Cog1    [setOptimalSpeed] Limited to 20 MHz for slow card
    Cog1      [initCard] Dummy clocks sent, CS HIGH
    Cog1      [probeCmd13] pre=[$FF $FF $FF $FF $FF $FF]
    Cog1      [probeCmd13] cap=[$FF $00 $00 $FF $FF $FF $FF $FF] NCR=2
    Cog1      [probeCmd13] R1=$$00 STATUS=$$00
    Cog1      [probeCmd13] CMD13 OK (R1=$$00 STATUS=$$00)
    Cog1      [probeCmd23] SCR advertises CMD23 (CMD_SUPPORT=$$03) - verifying...
    Cog1      [probeCmd23] CMD23 REJECTED R1=$$04 - not supported in SPI mode
    Cog1      [initCard] === INIT SUCCESS ===
    Cog1    [do_mount] Reading MBR sector 0...
    Cog1    [do_mount] MBR type code: $B
    Cog1    [do_mount] FAT32 detected, VBR at sector vbr_sec = 2_048
    Cog1    [do_mount] Bytes/sector: vbrBytesPerSec(pVBR) = 512
    Cog1    [do_mount] Sectors/cluster: sec_per_clus = 64
    Cog1    [do_mount] Reserved sectors: reserved = 64
    Cog1    [do_mount] Number of FATs: vbrNumFats(pVBR) = 2
    Cog1    [do_mount] VBR volume label: (&vol_label) = "ADATA64    "
    Cog1    [do_mount] Root dir volume label: (&vol_label) = "ADATA64    "
    Cog1    [do_mount] FSInfo: free_count=fsi_free_count = 1_902_460 nxt_free=fsi_nxt_free = 210
    Cog1    [do_mount] SUCCESS
    Cog1    [do_mount] SUCCESS, mode=FILESYSTEM
    Cog0    [mount] Mount successful, mode=FILESYSTEM
    Cog0  Card mounted, volume: ADATA64    
    

    So the probeCmd13() is succeeding now. However, such a check is also defunct now that we know the why.

  • evanhevanh Posts: 17,160
    edited 2026-03-10 04:38

    @evanh said:
    Every time you post an update at the moment I have to redo a huge number of debug deletes so that the important parts can report anything. Otherwise I just get compile errors of too big debug.

    It kind of begs the question of how have you been getting any reporting yourself, Stephen?

  • RaymanRayman Posts: 16,208

    I am very supportive of this effort and appears off to great start.
    Keep it up @"Stephen Moraco"

    8.3 filenames is a serious limitation. But directory support helps a lot with that. Should be fine. FatFs can do long filenames but like triples the memory usage :(

    I’m crazy backed up in projects but will prioritize testing this…

    One minor thing to examine is support for RTC for file dating.

  • @evanh said:

    @evanh said:
    Every time you post an update at the moment I have to redo a huge number of debug deletes so that the important parts can report anything. Otherwise I just get compile errors of too big debug.

    It kind of begs the question of how have you been getting any reporting yourself, Stephen?

    Augh! That is painful. The latest release, v1.3.2, now has DEBUG_MASK and "CHANNEL"s for debug output. Set the mask to a new value, recompile, and only the new selected debug channels are compiled into your code. Should be much easier. This is all documented.

    I started with driver debug only, then eventually it became stable enough that I moved to disabling debug in the driver and pushing some critical state through the caller <-> backend COG interface and just reporting it via debug in the calling program. All the regression tests do this.

  • Stephen MoracoStephen Moraco Posts: 459
    edited 2026-03-10 07:05

    @Rayman said:
    One minor thing to examine is support for RTC for file dating.

    Useful idea. We need to brainstorm a bit about how to support this. Pretty complicated trying to support RTC devices. Pass a creation time through the file create/write method; maybe have a set date/time call for the driver to keep it updated, and use this for file creation/modification? Maybe let the app maintain the current date/time and pass a reference to the driver for use in file creation/modification? Any other ideas?

    FYI- (if my understanding is correct) our FAT32 directory entries can contain (all local date time, not utc):

    • Creation date/time (plus 1‑byte sub‑second field).
    • Last write/modify date/time.
    • Last access date (no time of day, date only).
  • evanhevanh Posts: 17,160

    Stephen,
    I just noticed you've made an incorrect note in the source comments about ManAtWork's clock polarity reference material. Clock mode 0 is not a necessity at all. SPI slave devices that accept clock mode 0 (CPOL=0 and CPHA=0) are also happy to accept clock mode 3 (CPOL=1 and CPHA=1). In fact bit-bashed master implementations on microcontrollers are rarely precisely mode 0 or mode 3 but somewhere in between. I regularly write routines that explicitly take advantage of this flexibility to allow higher clock rates to be used.

    Now, when it comes to the Prop2 boot pins, P58/P59/P60/P61, there is a good reason to always use CPOL=1, and therefore cmode 3. The same four pins are used for both the SD card slot and the soldered Flash EEPROM chip. The difference being SCLK and CS are swapped. But that means that a clock low on one is a chip-select on the other. Things are much less likely to get into a confused state between them if the clock idles high instead of low. Hence why ManAtWork did it the way he did.

  • evanhevanh Posts: 17,160

    @"Stephen Moraco" said:

    @evanh said:
    Every time you post an update at the moment I have to redo a huge number of debug deletes so that the important parts can report anything. Otherwise I just get compile errors of too big debug.

    Augh! That is painful. The latest release, v1.3.2, now has DEBUG_MASK and "CHANNEL"s for debug output. Set the mask to a new value, recompile, and only the new selected debug channels are compiled into your code. Should be much easier. This is all documented.

    DEBUG_MASK is working. Thank you!

  • roglohrogloh Posts: 6,311
    edited 2026-03-11 03:52

    @"Stephen Moraco" said:

    @Rayman said:
    One minor thing to examine is support for RTC for file dating.

    Useful idea. We need to brainstorm a bit about how to support this. Pretty complicated trying to support RTC devices. Pass a creation time through the file create/write method; maybe have a set date/time call for the driver to keep it updated, and use this for file creation/modification? Maybe let the app maintain the current date/time and pass a reference to the driver for use in file creation/modification? Any other ideas?

    FYI- (if my understanding is correct) our FAT32 directory entries can contain (all local date time, not utc):

    • Creation date/time (plus 1‑byte sub‑second field).
    • Last write/modify date/time.
    • Last access date (no time of day, date only).

    I think an API for getting time/date only from the RTC is the way to go. People have lots of different RTC devices and you don't want to try to code that part into the SD driver. The only need from the SD driver to a timing device is to access the time/date in some standardized format. Any typical i2c/SPI RTC devices or other timing method (GPS uart) could be then supported by the end users if they implement the API. Mainly requires a fallback when the device is not present and timing is unsupported. Using 1 JAN 1970 12:00am etc. Even just the P2 clock ticks since startup might be somehow partially usable at least as an offset for a given session.

  • ke4pjwke4pjw Posts: 1,308
    edited 2026-03-11 04:35

    Just a couple of thoughts. Wouldn't it be better to just pass the datetime? My implementation actually uses an NTP server to get the current datetime and sets an offset from the P2 boot time (getsec) to determine the current date time. (No RTC needed)

    If you simply pass the datetime (through whatever format is easiest for the SD driver) using a custom now() method could be used to pass datetime for creation and modifications regardless of where the time is actually derived (RTC,NTP,GPS,etc).

  • roglohrogloh Posts: 6,311
    edited 2026-03-11 07:21

    @ke4pjw said:
    Just a couple of thoughts. Wouldn't it be better to just pass the datetime? My implementation actually uses an NTP server to get the current datetime and sets an offset from the P2 boot time (getsec) to determine the current date time. (No RTC needed)

    Yes I meant that too - my post was poorly worded above perhaps. The simple API can get the date/time from ANY device, there should be no HW dependency and network time is also feasible. The User just implements the required API.

  • RaymanRayman Posts: 16,208

    Maybe with Spin2 you could pass some kind of method pointer to the driver for date/time?
    With a null pointer being default that wouldn't get called, or just return something default based on clock tics?

  • ke4pjwke4pjw Posts: 1,308
    edited 2026-03-12 02:57

    @"Stephen Moraco" Claude swapped out FSRW FAT32.spin2 with your driver for me easy peasy. I have not done a full regression test, but the config reads from the SD card and the web pages are served up. Simply amazing! I had to upload the most recent version of my code and a zip of your driver. I used this prompt:

    Would you be capable of changing the filesystem object ( FS ) that the Blitzen 24 uses to access the SD card to use IronSheeps's SD card driver located at https://github.com/ironsheep/P2-uSD-FAT32-FS

    Below is the log of the whole process. Simply amazing!

    https://claude.ai/share/9e4eab98-9b67-4f30-84f6-13e97af39707

    @Rayman this might be the easiest way to integrate the new driver into whatever code you were looking at changing.

  • RaymanRayman Posts: 16,208
    edited 2026-03-12 01:30

    I am looking forward to trying it out. Thanks.

    Saw seek in there and had flashback to where “seek” had some serious edge case flaws in other drivers…

    Maybe seeking past end of file was one, don’t really recall much…

  • evanhevanh Posts: 17,160

    Call it a filesystem rather than driver. The driver sits underneath and talks block level ops. There is no files at the driver level.

  • Stephen MoracoStephen Moraco Posts: 459
    edited 2026-03-12 20:43

    ok, here's my current thinking on uSD filesystem upgrades:

    • Add call to set date/time, cog track time from there until next date/time set. If it is set, file creations/modifications will be set. Last access deemed too costly (constantly rewriting dir. sectors), so not enabling at this time.
    • Add a non-blocking form of calls for all file operations. You check for a transfer complete. This is an alternative use model. Choose the one that best fits your application. Also, mix and match as your application needs.
    • Develop a means to use the dead time for the background cog. When not using the filesystem, use the cog as an overall application resource, but the filesystem takes priority. Studying patterns for implementing this now. We'll have discussions on this in this thread as I come up with ideas/approaches.

    As always, any improvements made here will also make it into the dual driver.

    Are there any more obvious needs?

  • RaymanRayman Posts: 16,208

    @"Stephen Moraco" So, now that you've explained that this driver is all in Spin2 with inline assembly, have a question... Could this be run from the main cog, without invoking a new cog?

    Wondering because sometimes the main cog isn't doing all that much and sometimes only need uSD driver occasionally, maybe start up only...

  • RaymanRayman Posts: 16,208

    BTW: Looked up FatFS notes on exFat and seems license is only needed for commercial products, at least according to them:
    https://elm-chan.org/fsw/ff/doc/appnote.html#exfat

  • ke4pjwke4pjw Posts: 1,308
    edited 2026-03-13 01:23

    I validated the following:

    • The uSD filesystem properly reads files.
    • The uSD filesystem properly deletes files.
    • The uSD filesystem properly writes files.
    • The uSD filesystem properly creates directories.
    • The uSD filesystem properly dismounts the card, leaving it in a state where the P2 can boot from the same SD card. Additionally, a couple of bugs that exist in FAT32.spin2 that do not properly update filesize and leave the filesystem in an inconsistent state when unexpectedly powered off do not occur with this object.
    • Windows does not want to perform a FSCK on the SD card after it is removed from the P2 when using this object.
    • My code transverses directories and the uSD filesystem properly transverses those directories.
    • File and Directory creations and modification dates are valid and have a date of January 27, 2009 06:00 AM (At least that is what is displayed on my machine in CDT)
    • The uSD filesystem appears to have slightly more performance.

    Blitzen 24 was converted to this object via CLaude without any code tweeking required by me. It just works and it works better than FAT32.spin2.

    Additional observation: Often with FAT32.spin I would get MD5 checksum errors of the files I download from Azure blob storage. I compare the MD5 of what Azure tells me with what was written to the SD card. I have not received a MD5 mismatch while testing.

    Well done @"Stephen Moraco" !!

  • evanhevanh Posts: 17,160

    @ke4pjw said:
    Additional observation: Often with FAT32.spin I would get MD5 checksum errors of the files I download from Azure blob storage. I compare the MD5 of what Azure tells me with what was written to the SD card. I have not received a MD5 mismatch while testing.

    What driver were you using? If it was sdspi_bashed2.spin2 then that would explain your problems. That driver is notable flaky at writing data to the SD card. I fixed up its most glaring issues and named it sdspi_bashed3.spin2 a year or two back.

  • ke4pjwke4pjw Posts: 1,308
    edited 2026-03-13 03:45

    I was using sdspi_with_audio.spin2. At the time it was the only one that worked reliably with PropTool. I had high hopes for the bashed drivers, but gave up. I did not try yours, but a free COG would be nice :)

  • evanhevanh Posts: 17,160
    edited 2026-03-13 07:41

    There is the option of libc.spin2. That provides many useful functions, including C file management, as one large spin2 object. And its cogless to boot. Only requirement is it needs to be compiled with Flexspin.

    EDIT: I just had a squiz at sdspi_with_audio.spin2 and, although not identical to sdspi_bashed2.spin2, it has the same flaws. Namely it just doesn't really do busy checks. So, yeah, it'll have the same issues.

  • @evanh said:
    EDIT: I just had a squiz at sdspi_with_audio.spin2 and, although not identical to sdspi_bashed2.spin2, it has the same flaws. Namely it just doesn't really do busy checks. So, yeah, it'll have the same issues.

    Should probably try backporting the bashed3 changes to that... (do you know where you posted it still? Otherwise need to go on a safari.)
    I built the with_audio driver for the Spin Hexagon P2 port. That only ever writes its 512 byte highscore file, so I guess that is why it mostly works. Getting it to reliably read was already a big deal in those early days. Though I do remember having bugs sometimes where the audio would not start playing correctly, maybe related...

  • ke4pjwke4pjw Posts: 1,308

    @"Stephen Moraco" I don't know which direction you are taking regarding the datetime for the object, but maybe this would be helpful. I use getsec + known unixbootime to know what the current time is based on a Unix timestamp. Below is a link to my NTP client. There are some useful Spin2 methods in there for displaying the datetime in human readable form based on Unix timestamp.

    https://forums.parallax.com/discussion/175966/p2-ntp-client

    Whatever epoch you decide on, I recommend using getsec() + known bootime epoc to determine current time. It is a free counter.

  • evanhevanh Posts: 17,160
    edited 2026-03-13 15:29

    @Wuerfel_21 said:

    @evanh said:
    EDIT: I just had a squiz at sdspi_with_audio.spin2 and, although not identical to sdspi_bashed2.spin2, it has the same flaws. Namely it just doesn't really do busy checks. So, yeah, it'll have the same issues.

    Should probably try backporting the bashed3 changes to that... (do you know where you posted it still? Otherwise need to go on a safari.)

    Long forgotten but I do have my working copy. It's dated Nov 2024 which nicely lines up with when the 4-bit SD mode driver was becoming stable and I was doing lots of speed tests then. I changed the init() interface at the last moment to make it cleaner when swapping between drivers. Easy to change back.

    EDIT: Found it based on the date and topic refresher - https://forums.parallax.com/discussion/comment/1563810/#Comment_1563810

  • Wuerfel_21Wuerfel_21 Posts: 5,824
    edited 2026-03-13 22:23

    @evanh said:
    Long forgotten but I do have my working copy. It's dated Nov 2024 which nicely lines up with when the 4-bit SD mode driver was becoming stable and I was doing lots of speed tests then. I changed the init() interface at the last moment to make it cleaner when swapping between drivers. Easy to change back.

    EDIT: Found it based on the date and topic refresher - https://forums.parallax.com/discussion/comment/1563810/#Comment_1563810

    oops, don't have bashed2 anymore (found it), either and the code structure is too different to see the problem immediately

    EDIT: So what you're saying is, the busy check should not be at the end of a write, but before issuing a command? Because the audio driver does have a ready check after a block write.
    And that is also how the P1 tinySDDA driver that it was to jump in for does it, which has been totally fine (though with caveat that Spin Hexagon still only ever writes 512 byte score files)

  • evanhevanh Posts: 17,160
    edited 2026-03-13 23:01

    @Wuerfel_21 said:
    EDIT: So what you're saying is, the busy check should not be at the end of a write, but before issuing a command? Because the audio driver does have a ready check after a block write.

    I didn't analyse if that was a vital config but pretty much yes. I've copied how sdmm.cc command sequence does it. That is the more efficient solution anyway since it exits from the driver while the card is known to be in a busy state. Only checking in if more action is requested.
    There is a second busy check after CMD24, but before any block data is sent, as well.

  • evanhevanh Posts: 17,160

    The principle being don't bother checking until you actually need to know.

Sign In or Register to comment.