Skip to content

Latest commit

 

History

History
217 lines (165 loc) · 7.13 KB

File metadata and controls

217 lines (165 loc) · 7.13 KB

microSD storage

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.

Supported storage paths

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.

Prepare the card

  1. Back up any needed files.
  2. Format supported media as FAT32 using the operating system or a reputable SD formatter.
  3. Use a normal partition table with one FAT32 data partition.
  4. Safely eject the card before inserting it into the unpowered board.
  5. 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.

Mount and inspect storage

#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().

Save one JPEG atomically

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:

  1. Removes a stale /photo.jpg.part if present.
  2. Writes the complete frame in bounded chunks.
  3. Flushes and closes the temporary file.
  4. Removes an older target file if present.
  5. 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.

Progress, speed, and ETA

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.

Sequential filenames

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.

Bounded recording

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().

Error handling

Storage methods return a portable Result. A reliable application should:

  1. Stop if storage.begin() fails.
  2. Check storage.info().freeBytes before a long recording.
  3. Check every save result.
  4. Report how many files completed before a failure.
  5. Call storage.end() before removing media or repurposing its bus.
  6. 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);
}

Diagnosing mount failures

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.

Native filesystem access

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.