Difficulty: Intermediate | Time: 45 Minutes | Target Board: ESP32-S3-WROOM-1 (8MB Flash)

The Short Answer: Checking Arduino LittleFS Free Space Available

To check the Arduino LittleFS free space available on your microcontroller, you need to query the filesystem's total capacity and subtract the currently used bytes. In the Arduino ESP32 core, this is done using LittleFS.totalBytes() and LittleFS.usedBytes(). The difference between these two values gives you your exact free space in bytes.

This guide targets the ESP32-S3-WROOM-1 (DevKitC-1) with 8MB of onboard QSPI flash, running the Arduino ESP32 Core v3.x. The ESP32-S3 is the current standard for IoT edge logging, and LittleFS has entirely replaced the deprecated SPIFFS due to its power-loss resilience and wear-leveling efficiency. If you are building a datalogger or an OTA-updatable device, knowing your exact available LittleFS free space before attempting a write operation is the difference between a robust field device and a bricked boot-loop.

Bench Tip: Never assume your free space matches your physical flash chip size. The Arduino IDE partition table reserves space for the bootloader, the OTA partitions, and the filesystem metadata. An 8MB chip rarely yields 8MB of usable LittleFS space.

Hardware Spec Sheet & Pin Mapping

To make this debugging process visible without relying solely on the Serial Monitor, we will wire up a standard I2C OLED. This allows you to monitor the Arduino LittleFS free space available in real-time directly from the workbench.

Parts List

  • MCU: ESP32-S3 DevKitC-1 (Specifically the 8MB Flash / 2MB PSRAM variant, e.g., Espressif ESP32-S3-DevKitC-1-N8R2)
  • Display: 0.96-inch SSD1306 I2C OLED (128x64 resolution, 4-pin variant)
  • Wiring: 4x silicone jumper wires (22 AWG stranded)
  • Prototyping: Half-size solderless breadboard

Pin Mapping Table

SSD1306 OLED PinESP32-S3 DevKitC-1 GPIOFunctionNotes
GNDGNDGroundConnect to any common ground rail
VCC / 3V33V3PowerDo NOT use 5V; the SSD1306 logic is 3.3V tolerant but 5V degrades the OLED organic layer faster
SCLGPIO 9I2C ClockDefault I2C SCL for ESP32-S3 Arduino Core
SDAGPIO 8I2C DataDefault I2C SDA for ESP32-S3 Arduino Core

LittleFS Partition Sizing & Overhead Data

Before writing code, you must understand how the ESP32 allocates flash memory. The Arduino IDE uses a CSV-based partition table to divide the physical flash. Below is a data-dense breakdown of what your Arduino LittleFS free space available actually looks like under different partition schemes on an 8MB ESP32-S3 chip.

Partition Scheme (IDE Menu)Total FlashLittleFS AllocationUsable Free Space (Formatted)Metadata Overhead
Default 8M with spiffs (3MB APP/1.5MB FAT)8 MB1.4 MB~1.32 MB~80 KB (LittleFS block headers)
8M with FFat (4MB APP/4MB FAT)8 MB0 MB (Uses FAT)N/A (Not LittleFS)N/A
8M Partition (3MB APP, 1.5MB LittleFS)8 MB1.5 MB~1.41 MB~90 KB
Custom: 2MB APP, 6MB LittleFS8 MB6.0 MB~5.85 MB~150 KB (Scales with block count)

Source reference: Espressif ESP-IDF Partition Tables Documentation. Note that LittleFS metadata overhead scales non-linearly based on the block size (typically 4KB) and total partition size.

Complete Implementation: Real-Time Space Monitor

The following code initializes the I2C OLED, mounts the LittleFS partition, and continuously calculates and displays the Arduino LittleFS free space available. It includes robust error handling for filesystem mount failures.

Required Libraries: Install Adafruit SSD1306 and Adafruit GFX Library via the Arduino Library Manager. LittleFS is native to the ESP32 core.

#include <Arduino.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// --- Pin Definitions ---
#define PIN_I2C_SDA 8
#define PIN_I2C_SCL 9
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

// --- Object Initialization ---
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// --- Helper Function: Format Bytes ---
String formatBytes(size_t bytes) {
  if (bytes < 1024) return String(bytes) + " B";
  else if (bytes < (1024 * 1024)) return String(bytes / 1024.0, 2) + " KB";
  else return String(bytes / (1024.0 * 1024.0), 2) + " MB";
}

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow serial port to stabilize
  Serial.println("\n--- LittleFS Space Monitor Booting ---");

  // Initialize I2C with explicit pins for ESP32-S3
  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);

  // Initialize OLED
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("[FATAL] SSD1306 allocation failed. Check I2C wiring."));
    for(;;); // Halt execution
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Booting LittleFS...");
  display.display();

  // Mount LittleFS (format if mount fails on first boot)
  if(!LittleFS.begin(true)) {
    Serial.println("[E][LittleFS.cpp:92] begin(): Mount Failed");
    display.clearDisplay();
    display.setCursor(0,0);
    display.println("MOUNT FAILED!");
    display.println("Check Partition");
    display.display();
    return; // Halt further execution
  }

  Serial.println("LittleFS mounted successfully.");
}

void loop() {
  // Query filesystem metrics
  size_t totalBytes = LittleFS.totalBytes();
  size_t usedBytes = LittleFS.usedBytes();
  size_t freeBytes = totalBytes - usedBytes;

  // Output to Serial
  Serial.printf("Total: %u | Used: %u | Free: %u\n", totalBytes, usedBytes, freeBytes);

  // Output to OLED
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(1);
  display.println("== LittleFS Stats ==");
  display.println("");
  
  display.print("Total: ");
  display.println(formatBytes(totalBytes));
  
  display.print("Used:  ");
  display.println(formatBytes(usedBytes));
  
  display.print("Free:  ");
  display.setTextColor(freeBytes < 50000 ? SSD1306_WHITE : SSD1306_WHITE); // Invert if low space
  display.println(formatBytes(freeBytes));
  
  // Draw a simple usage bar
  int barWidth = (usedBytes * 100) / totalBytes; // Percentage used
  display.drawRect(0, 50, 128, 10, SSD1306_WHITE);
  display.fillRect(2, 52, (barWidth * 124) / 100, 6, SSD1306_WHITE);
  
  display.display();

  delay(2000); // Update every 2 seconds to prevent OLED burn-in
}

Troubleshooting: Space Errors & Mount Failures

When working with flash memory, the most common roadblock is the filesystem refusing to mount. If your Serial Monitor outputs the exact error string [E][LittleFS.cpp:92] begin(): Mount Failed (note: the line number :92 may shift slightly depending on your exact ESP32 core version, e.g., :86 in older v2.x cores), your code will halt or fail to write data.

The First Three Things to Check When It Fails

  1. Verify the Partition Scheme: Go to Tools > Partition Scheme in the Arduino IDE. If you selected 'No OTA (2MB APP/2MB SPIFFS)' but your code calls LittleFS, it might mount an empty or incompatible partition. Ensure your scheme explicitly supports the filesystem type you are invoking, or use a custom partitions.csv file.
  2. Flash Size Mismatch: If you bought an ESP32-S3 with 8MB flash but the IDE is set to 'ESP32 Family Device' defaulting to 4MB, the partition table will attempt to write outside the recognized boundary, corrupting the LittleFS header. Always select the exact board variant (e.g., Tools > Flash Size > 8MB).
  3. Missing Filesystem Upload: LittleFS does not magically format itself with your data files unless you pass true to LittleFS.begin(true). If you are trying to read pre-existing files uploaded via the Arduino LittleFS Data Upload plugin, ensure the plugin is installed and you actually clicked Tools > ESP32 LittleFS Data Upload before running the sketch.

Ranked Causes for 'Out of Space' Write Failures

If the filesystem mounts, but file.print() fails silently or returns 0 bytes written, check these ranked causes:

  • Cause 1: Fragmentation & Wear-Leveling Reserves. LittleFS keeps a reserve of empty blocks for garbage collection. If your 'free space' reads 12KB, you might not actually be able to write a 12KB file. Always keep a 20% buffer.
  • Cause 2: Unclosed File Handles. Failing to call file.close() leaves data in the RAM cache. If the ESP32 resets before the cache flushes to flash, the space is marked 'used' but the data is lost, creating phantom usage.
  • Cause 3: Directory Bloat. LittleFS handles deep directory trees poorly compared to FAT32. Flattening your file structure reduces metadata overhead and reclaims usable bytes.

Extending and Simplifying the Build

Depending on your project phase, you may need to strip this down or scale it up.

How to Simplify

If you are in the early prototyping phase and don't want to wire an OLED, strip out all Wire.h and Adafruit_SSD1306 references. Rely entirely on Serial.printf(). This reduces the compiled binary size by roughly 45KB, which is critical if you are squeezing code into a minimal 1.4MB APP partition.

How to Extend

For a production datalogger, extend this build by implementing Log Rotation. Instead of just displaying the Arduino LittleFS free space available, add a conditional check in your logging function:

if ((LittleFS.totalBytes() - LittleFS.usedBytes()) < 50000) {
  LittleFS.remove("/logs/oldest_log.csv");
  Serial.println("Low space: Purged oldest log.");
}

Additionally, integrate the ArduinoOTA library. OTA updates require a free temporary partition or sufficient contiguous LittleFS space to download the new binary before flashing. Monitoring your free space ensures your OTA routine doesn't fail mid-download, which is the primary cause of field-deployed ESP32s bricking themselves during remote updates.

For deeper filesystem mechanics, refer to the LittleFS Project GitHub Repository for design notes on wear-leveling and power-fail safety algorithms.