micropython-sistema-archivos-littlefs-fat-os

File System in MicroPython: LittleFS and FAT

  • 5 min

The file system in MicroPython is a layer for saving and reading files in the microcontroller’s memory.

One of the great powers of MicroPython is that it does not treat flash memory as a block of raw bytes, but implements a complete file system.

This means we can create files like data.txt, config.json, or folders like /logs exactly as we would on our computer. This data is persistent: if you disconnect the power, it will still be there when you turn the board back on.

But not all file systems are the same. In the world of microcontrollers, there are two main contenders: FAT and LittleFS.

FAT and LittleFS

Depending on the firmware you have installed and your board (ESP32 or Raspberry Pi Pico), you will find one or the other.

FAT (File Allocation Table)

It’s the classic one. The same used by old pendrives.

  • Advantage: It is compatible with Windows/Mac/Linux. That’s why, when you connect a Raspberry Pi Pico via USB, it appears as a disk drive and you can drag files into it.
  • Disadvantage: It is fragile. If the power goes out just while you are writing a file, the file system is very likely to become corrupted and you will lose data.
  • Disadvantage: It does not handle flash memory wear well.

LittleFS (Little Fail-Safe)

It is a modern system specifically designed for microcontrollers and Flash memories.

  • Advantage: It is power-failure safe. If the power cuts out while you are writing, you may lose that last piece of data, but the file system will not break.
  • Advantage: It has Wear Leveling. It distributes writes across the entire memory so that the same sectors don’t always wear out, prolonging the life of your board.
  • Disadvantage: It is not directly visible as a USB drive on your PC. You need tools like Thonny, mpremote, or rshell to upload and download files.

Our recommendation For rapid development and prototyping, FAT is very convenient. For final products or devices that will be continuously logging data (dataloggers), LittleFS is mandatory for safety. The ESP32 uses LittleFS by default in modern versions of MicroPython.

The os module: a file explorer

To manage files and folders from code, we use the standard os module.

Listing and navigating

import os

# List files in the current directory
print(os.listdir())
# Output: ['boot.py', 'main.py', 'lib']

# Get file information (stat)
# Returns a tuple: (mode, ino, dev, nlink, uid, gid, size, time...)
info = os.stat('boot.py')
print(f"Size of boot.py: {info[6]} bytes")
Copied!

Creating folders and deleting

# Create a directory
try:
    os.mkdir('/data')
except OSError:
    print("The folder already existed")

# Delete a file
# os.remove('old_file.txt')

# Delete a folder (must be empty)
# os.rmdir('/data')
Copied!

Reading and writing files

The syntax is identical to standard Python on PC. We use the open() function together with the with block (recommended to ensure the file is closed properly).

Opening modes

  • 'w' (Write): Creates the file. If it already exists, erases its content and starts from scratch.
  • 'a' (Append): Adds content to the end of the file without deleting previous content.
  • 'r' (Read): Read-only.

Writing a data log

Let’s simulate a system that saves the temperature each time it runs.

import time
import random

def save_log(data):
    # Use 'a' mode to append a new line
    with open('log.txt', 'a') as f:
        timestamp = time.time()
        line = f"{timestamp},{data}\n"
        f.write(line)
    print("Data saved.")

# Simulate saving 5 data points
for _ in range(5):
    temp = random.randint(20, 30)
    save_log(temp)
    time.sleep(1)
Copied!

If you run this multiple times, you will see that the log.txt file keeps growing.

Reading a configuration

It is very common to have a file to save configurations (WiFi SSID, tokens, timeouts) so they are not hardcoded in the code.

def read_config():
    try:
        with open('log.txt', 'r') as f:
            content = f.read()
            print("--- File Content ---")
            print(content)
            print("-------------------")
    except OSError:
        print("The file does not exist.")

read_config()
Copied!

Reading line by line

If the file is very large (e.g., a multi-megabyte log), you should not use f.read() because it will try to load the entire file into RAM and give you a MemoryError.

The correct approach is to read line by line:

with open('log.txt', 'r') as f:
    for line in f:
        print("Processing line:", line.strip())
Copied!

JSON for storing structured data

Saving plain text with commas (CSV) is fine, but for complex configurations it is better to use JSON. MicroPython has the ujson (or json) module for this.

import json

config = {
    "wifi_ssid": "MyHouse",
    "wifi_pass": "123456",
    "read_interval": 60,
    "calibration": [1.0, 0.5, 0.1]
}

# 1. Save dictionary to JSON file
with open('config.json', 'w') as f:
    json.dump(config, f)

# 2. Load dictionary from JSON file
with open('config.json', 'r') as f:
    loaded_config = json.load(f)

print(loaded_config['wifi_ssid']) # Prints "MyHouse"
Copied!

Common errors

  1. OSError: 28 (ENOSPC): “No space left on device”. You have filled the Flash memory. You need to delete old files.
  2. Corruption in FAT: On Raspberry Pi Pico, if you disconnect the USB while the code is writing a file, it may appear as corrupted in Windows.
  3. Worn out Flash: Although it is difficult to reach the limit, Flash memories have a limited number of write cycles (approx. 100,000 cycles). Do not write to files inside a fast loop (e.g., every 10ms). If you need speed, save to RAM and dump to disk every hour.

With this, we now have the ability to create devices that “remember” things. Whether it’s the WiFi password or the temperature history of the last week.