I really like the Sandisk and Kingston ones we sell
Yes, set it explicitly rather than trusting a default:
- Format tool: use the official SD Association formatter, full format, not Windows quick format.
- Allocation unit size: go bigger than the default the formatter picks for your card size — 32KB or 64KB. The reason this matters directly for your bug: the number of FAT cluster-chain entries that have to be walked/freed on
truncate()/remove() scales with cluster count, not file size. A 100MB file at 4KB clusters is ~25,600 entries to process; at 32KB clusters it’s ~3,200 — roughly 8x less work for the exact operation that’s failing. This is very likely why 40MB works and 50/100MB don’t: you’re bumping into a time/entry-count threshold, not a hard size limit.
- Filesystem: must be FAT16/FAT32 — this firmware is pinned to SdFat v1.1.4 (per the header comment), which does not understand exFAT.
- Card type/size: stick to SDHC (4–32GB), not SDXC. Old SdFat versions have spotty SDXC support. For brand, industrial-grade cards (SanDisk Industrial, Kingston Industrial, etc.) are worth it here — consumer cards can have unpredictable latency spikes from background wear-leveling/garbage collection, and that’s exactly the kind of variability that would push a marginal-timing operation over the edge intermittently (which matches your “sometimes get
<, sometimes don’t” behavior).
What your new logs are telling us
This part is more concerning than the original report:
112Error file.open112Error file.open112<112Error file.open...
1 and 2 are the boot-sequence prints from setup() (1 = serial up, 2 = SD initialized). Seeing 1, 2 repeat over and over — not just one Error file.open followed by the infinite blink loop — means the device is resetting itself repeatedly, not just sitting in blinkError(). blinkError() never returns, so under normal firmware behavior you’d only ever see one 112Error file.open and then silence except the LED. Repeated boot sequences mean something external is resetting OpenLog: most likely your host MCU is toggling the OpenLog reset line because it’s waiting for < and timing out, then retrying. Worth confirming — is your MCU’s init routine cycling the reset pin? If it is, that reset is landing mid-operation every time, which would also explain things getting progressively worse (a reset in the middle of a FAT truncate/free-chain operation can leave that specific file’s cluster chain in a worse, half-freed state than before).
Then with your own IDE-compiled build:
11121211211211211211211212112
No “Error file.open” text at all now — just repeated 1,1,2 with no error message ever printing. That’s a harder failure than a graceful open()/remove() returning false — it looks like a crash or reset happening before the code even reaches the point of printing the error, which is consistent with something like a stack overflow or brownout on the tight 2KB SRAM of the ATmega328 while it’s mid-walk through a large/fragmented cluster chain.
One thing to rule out here specifically: when you compiled from the Arduino IDE, did you pin SdFat to the exact same v1.1.4 referenced in the source comments? If Library Manager pulled a newer SdFat (2.x is a full rewrite with a different API/class structure), you could be running meaningfully different internals than the shipped v4.3 binary even though the .ino looks the same. That would explain why your build behaves worse than the precompiled binary rather than the same.
On your patch making it fail at remove() instead of open()
That’s actually useful diagnostic info: it tells me the bottleneck isn’t the O_TRUNC logic specifically, it’s the underlying cluster-chain deallocation itself, which both truncate() and remove() have to do. My earlier suggestion just moved where in that same expensive operation the reset lands.
Given the card has been rotating through the same 100 file slots repeatedly, it’s plausible free space has become fragmented over time — a “100MB” file’s clusters can end up scattered across the card after many rotate cycles, meaning the chain isn’t a handful of contiguous FAT entries but potentially thousands of scattered ones, each requiring effectively random-access reads on SPI (slow, no burst). That’s consistent with 40MB working, 50/100MB not.
Suggested next steps, in order of information value
- Disconnect the MCU reset line entirely and test standalone with a UART terminal, so nothing external can interrupt an in-progress operation. Give it real time (30+ seconds) before assuming it’s hung — see if it eventually completes or truly locks up.
- Pin SdFat to exactly v1.1.4 for your IDE build and recompile, to make it an apples-to-apples comparison with the shipped binary.
- Reformat the test card with a large allocation unit (32 or 64KB) and re-run your
remove()-then-open() patch — if that resolves it, that confirms the cluster-count/fragmentation theory directly.
- If it’s still failing standalone with a fresh, non-fragmented card, that points away from fragmentation and more toward a hard timing/memory ceiling in v1.1.4’s chain-walking code on the 328’s limited RAM — at that point it becomes a firmware-level fix (possibly bounding how much work is done per
loop() iteration, or moving to a newer SdFat with better large-file handling) rather than a format/config workaround.