A file system is an organized way to save and read files from the microcontroller.
We already know how to save our Wi-Fi password in internal memory. But what if we want to store a temperature history for a whole year? Or if we want our device to serve large HTML files and images from a web server?
The internal memory of the ESP32 is limited and shares space with other partitions. For large volumes of data, we can use an SD card.
In nanoFramework, working with an SD is surprisingly similar to working with a USB flash drive in Windows: the system “mounts” it as a disk drive (usually D:\) and we use System.IO to read and write.
For this tutorial you need to install the NuGet package: nanoFramework.System.IO.FileSystem
Logical Units: Internal Storage and SD
Before connecting wires, let’s understand how nanoFramework organizes storage. Unlike Arduino (where you use different libraries like SPIFFS.h or SD.h), here everything is unified under the same System.IO umbrella.
The system assigns Drive Letters to the different physical storages:
I:\(Internal): This is the user partition on the flash when the target includes it. It is small and suitable for configuration.D:\,E:\… (External): These are removable devices, such as SD cards or USB memory sticks (on STM32 chips). They are huge (GBs), but may not be present if the user removes the card. Ideal for Logs and Assets.
Connection via SPI or SDMMC
To connect an SD card to the ESP32 we have two modes:
- SPI (1-bit): Uses 4 wires (MISO, MOSI, SCK, CS). It is slower, but compatible with all pins and boards. It is the most common in the maker world.
- SDMMC (4-bit): Uses a faster native protocol. It requires specific pins and is more complex to route on the board, but offers higher speed.
For this course, we will assume you are using a standard microSD card reader module connected via SPI.
- CS: GPIO 5 (common on VSPI)
- SCK: GPIO 18
- MISO: GPIO 19
- MOSI: GPIO 23
- VCC: the voltage specified by the module (the card works at 3.3V; some adapters have a built-in regulator)
- GND: GND
Detecting the Card with StorageEventManager
Unlike the internal memory I:\ which is always there, the SD card is removable. We can’t just try to write to D:\ right after booting, because the user might not have inserted the card yet, or the operating system might not have mounted it yet.
To manage this, nanoFramework offers us the static class StorageEventManager.
using System;
using System.IO;
using System.Diagnostics;
using nanoFramework.System.IO.FileSystem; // Required Namespace
public class Program
{
public static void Main()
{
// 1. Subscribe to storage events
StorageEventManager.RemovableDeviceInserted += StorageEventManager_RemovableDeviceInserted;
StorageEventManager.RemovableDeviceRemoved += StorageEventManager_RemovableDeviceRemoved;
Debug.WriteLine("System ready. Insert an SD card...");
// In a real program, we would do nothing here until the event fires.
// Note: If the card WAS already inserted at boot, the event will fire almost immediately
// after startup.
Thread.Sleep(Timeout.Infinite);
}
private static void StorageEventManager_RemovableDeviceInserted(object sender, RemovableDeviceEventArgs e)
{
Debug.WriteLine($"Device detected at path: {e.Path}!");
// e.Path is usually "D:\" or similar
// Now it is safe to write
WriteLog(e.Path);
}
private static void StorageEventManager_RemovableDeviceRemoved(object sender, RemovableDeviceEventArgs e)
{
Debug.WriteLine($"Device removed: {e.Path}");
// Here we should stop any ongoing writes to avoid corruption
}
}Hardware Configuration:
Depending on your board and firmware version (image), you may need to configure the SPI pins before the system recognizes the SD card.
On modern versions (nanoFramework.System.Device.Mmc), you may need to explicitly mount the card in code if your firmware does not have a default configuration. Check your specific board’s documentation.
Creating a CSV Datalogger
Let’s create the “Hello World” of industrial systems: a data logger. We will save a text line with the date and a simulated value to a .csv file each time the card is detected.
The CSV format is ideal because you can remove the card, insert it into your PC, and open it directly with Excel.
private static void WriteLog(string drive)
{
// Build the full path. E.g., "D:\datalog.csv"
string filePath = Path.Combine(drive, "datalog.csv");
try
{
// Simulate reading a sensor
double temperature = 24.5;
double humidity = 60.2;
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
// CSV format: Date,Temperature,Humidity
string line = $"{timestamp},{temperature},{humidity}\r\n";
// AppendAllText is wonderful:
// 1. If the file doesn't exist, it creates it.
// 2. If it exists, it appends the text (doesn't overwrite).
// 3. It opens, writes, and closes the file safely.
File.AppendAllText(filePath, line);
Debug.WriteLine($"Data saved to {filePath}");
// Let's read it to verify (only the last lines in a real scenario)
if (File.Exists(filePath))
{
Debug.WriteLine("Current file size: " + new FileInfo(filePath).Length + " bytes");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error writing to SD: {ex.Message}");
}
}Listing Files and Directories
System.IO is very powerful. We can explore what’s inside the card. This is useful if, for example, you want to find a specific configuration file or play all .mp3 files from a folder.
public static void ListContents(string path)
{
// Get directories
var directories = Directory.GetDirectories(path);
foreach (var dir in directories)
{
Debug.WriteLine($"[DIR] {dir}");
}
// Get files
var files = Directory.GetFiles(path);
foreach (var file in files)
{
Debug.WriteLine($"[FILE] {file}");
}
}Best Practices with SD Cards
Working with physical file systems has its risks:
- Data Corruption: If the power goes out or you remove the card WHILE it is being written (
File.AppendAllText), the file system (FAT32) can become corrupted.
- Solution: Try to keep files open for the shortest time possible.
AppendAllTextis good because it opens and closes quickly.
- FAT32: nanoFramework generally supports FAT16 and FAT32. Make sure to format your card as FAT32 (not exFAT or NTFS) on Windows before using it.
- Compatibility: Although it supports cards of 32GB or more, it is usually more stable for microcontrollers to use Class 10 cards from 4GB to 16GB.
With StorageEventManager and System.IO we can log data to an SD card and react to its insertion or removal. For a battery-powered device, we still need to reduce the time it stays active.