NiusCam selects the correct built-in storage bus with the same board profile as the camera. The storage module supports capacity reporting, atomic JPEG writes, progress callbacks, sequential naming, and explicitly bounded recording.
| Board | Interface | NiusCam behavior |
|---|---|---|
| ESP32-S3-CAM N16R8 | SD_MMC | 1-bit mode with a 10 MHz initial request and bounded fallback |
| AI-Thinker ESP32-CAM | SD_MMC | 1-bit mode with a 10 MHz initial request and bounded fallback |
| XIAO ESP32-S3 Sense | SPI | Board-profile pins with 10 MHz, 4 MHz, then 1 MHz mount attempts |
The actual negotiated performance depends on the card, wiring, board, power, filesystem layout, and other active peripherals.
- Back up any needed files.
- Format supported media as FAT32 using the operating system or a reputable SD formatter.
- Use a normal partition table with one FAT32 data partition.
- Safely eject the card before inserting it into the unpowered board.
- Fully insert the card, then power or reset the board.
NiusCam deliberately does not format media. Formatting is destructive and is better performed where the user can confirm the selected drive.
Seeed specifies microSD cards up to 32 GB for the XIAO ESP32-S3 Sense expansion board. Its expansion board must be fully seated and the J3 SD/SPI bridge must be connected according to Seeed's XIAO ESP32-S3 Sense filesystem guide.
#include <NiusCam.h>
#include <NiusCamStorage.h>
using namespace NiusCam;
Camera camera;
Storage storage;
void setup() {
Serial.begin(115200);
Result result = camera.begin();
if (!result) {
Serial.println(result.message());
return;
}
result = storage.begin();
if (!result) {
Serial.println(result.message());
return;
}
StorageInfo card = storage.info();
Serial.print("Capacity: ");
Serial.println(card.capacityBytes);
Serial.print("Free: ");
Serial.println(card.freeBytes);
}storage.begin() uses the automatic board profile. For custom hardware, pass
the same explicit BoardProfile used by camera.begin().
Frame frame = camera.capture();
if (!frame) {
Serial.println("Capture failed");
return;
}
Result result = storage.saveJpeg(frame, "/photo.jpg", true);
if (!result) {
Serial.print("Save failed: ");
Serial.println(result.message());
}With atomic=true, NiusCam:
- Removes a stale
/photo.jpg.partif present. - Writes the complete frame in bounded chunks.
- Flushes and closes the temporary file.
- Removes an older target file if present.
- Renames the temporary file to
/photo.jpg.
This prevents a normal short write from being presented under the final name. It does not turn FAT into a journaled filesystem; sudden power loss can still require filesystem repair.
The source frame must use PixelFormat::Jpeg. NiusCam does not encode an RGB or
grayscale frame during storage.
Pass a callback to saveJpeg():
void showProgress(const TransferProgress &progress, void *) {
size_t percent = 0;
if (progress.totalBytes)
percent = progress.completedBytes * 100 / progress.totalBytes;
Serial.print("Write ");
Serial.print(percent);
Serial.print("% | ");
Serial.print(progress.completedBytes);
Serial.print('/');
Serial.print(progress.totalBytes);
Serial.print(" bytes | ");
Serial.print(progress.bytesPerSecond / 1024.0f, 1);
Serial.print(" KiB/s | ETA ");
Serial.print(progress.etaSeconds, 1);
Serial.println('s');
}
Result result =
storage.saveJpeg(frame, "/photo.jpg", true, showProgress);TransferProgress contains completed bytes, total bytes, elapsed milliseconds,
measured bytes per second, and estimated remaining seconds. The callback runs
inside the write; avoid delays or additional storage operations in it.
Small JPEGs may complete in one chunk, so their rate and ETA are only rough indicators. The bundled SaveToSD example prints a 20-character progress bar and clear completion/error messages.
uint32_t nextSequence = 0;
Frame frame = camera.capture();
Result result = storage.saveNextJpeg(
frame, "/photos", "CAM", &nextSequence);This creates /photos when needed, searches for the next unused name such as
CAM000000.jpg, and advances nextSequence after success. The search is
bounded below one million names.
Without a sequence pointer, the method searches from zero each call. Keeping a sequence variable is more efficient for repeated captures.
uint32_t saved = 0;
Result result = storage.recordJpegs(
camera, 100, 1000, "/recording", &saved);
Serial.print("Saved files: ");
Serial.println(saved);This attempts exactly 100 JPEG captures with one-second target intervals and stops on the first capture or storage error. It is intentionally bounded: it does not silently create an endless recorder, delete old files, or hide a full card.
recordJpegs() does not currently accept a progress callback. Applications
needing per-file UI, retry policy, retention, or cancellation should build a
loop from capture() and saveNextJpeg().
Storage methods return a portable Result. A reliable application should:
- Stop if
storage.begin()fails. - Check
storage.info().freeBytesbefore a long recording. - Check every save result.
- Report how many files completed before a failure.
- Call
storage.end()before removing media or repurposing its bus. - Remount and verify critical files after any power or media fault.
Example pattern:
Result result = storage.saveJpeg(frame, "/photo.jpg");
if (!result) {
Serial.print("Storage error: ");
Serial.print(result.message());
Serial.print("; native code: 0x");
Serial.println(result.native, HEX);
}Different failure layers imply different remedies:
| Symptom | Meaning | First checks |
|---|---|---|
No card response / CMD0 / GO_IDLE_STATE |
The card did not enter SPI/SD idle state; failure occurs before FAT mounting | Reseat or power-cycle card, inspect board/bridge connection, confirm CS/bus wiring and power |
| Card responds but mount fails | Media was contacted, but partition/filesystem initialization failed | Reformat FAT32, check partition table, test another card |
| Mount succeeds but writes fail | Filesystem, capacity, power, signal integrity, or card-health problem | Check free space, stable supply, lower bus frequency, remount and verify |
| Intermittent corruption | Unsafe removal, power loss, poor signal integrity, or failing media | Stop writes before removal, improve power/wiring, replace card, run host filesystem repair |
Reformatting cannot fix a card that never answers the bus reset command. See Troubleshooting for board-specific checks.
storage.native() returns the selected Arduino fs::FS* for operations not
provided by NiusCam. Do not retain or use that pointer after storage.end().
Mixing native and NiusCam operations concurrently requires application-level
synchronization.