micropython-tarjetas-sd-datalogging

How to Use microSD Cards with MicroPython

  • 5 min

A microSD card is an external and removable storage medium that we can use from MicroPython.

The internal filesystem (which we saw in the previous chapter) is fantastic, but it has two major limitations: it’s small (a few megabytes) and it’s difficult to extract (you need tools to download the files).

If we are building a weather station, a GPS tracker, or a black box for a vehicle, we need real space. We need Gigabytes.

The standard solution in the industry is to use a microSD Card. They are cheap, reliable, and best of all, we can take the card out, plug it into the PC, and open the .csv file directly in Excel.

Hardware: microSD module and SPI

SD cards can operate in two modes: SDIO (faster, but complex and requires specific pins) and SPI (slower, but works on any microcontroller).

In MicroPython, we will almost always use SPI mode.

We need a microSD card reader module.

  • VCC: 5V or 3.3V (Depends on the module. Many blue modules have a regulator and accept 5V. If you use a simple “breakout” adapter, it operates strictly at 3.3V).
  • GND: Ground.
  • SCK, MOSI, MISO: SPI bus pins.
  • CS (Chip Select): The pin to activate the card.

Logic Voltage SD cards operate strictly at 3.3V. If you use a 5V Arduino, you would burn the card. But since we are using ESP32 or Raspberry Pi Pico (which are 3.3V), we can connect the data pins (MOSI, MISO, SCK, CS) directly without worry.

The sdcard.py driver

Unlike the OLED screen driver or the OneWire bus, the driver for SD cards is usually not pre-installed in the base MicroPython firmware (to save space).

We have to install it. It is the official driver maintained by MicroPython.

If you have WiFi configured (via mip):

import mip
mip.install("sdcard")
Copied!

If not, download the sdcard.py file from the official MicroPython repository and upload it to the root of your board using Thonny.

Mounting the filesystem

Here comes the key concept: Mounting.

The operating system doesn’t know what’s inside the SD card. We have to tell it: “Hey, take this physical device and make its files appear inside the /sd folder”.

This is done with the os module and the VfsFat class (Virtual File System FAT).

Initialization and mounting

We are going to connect an SD card to an ESP32 using the standard SPI bus (VSPI).

import machine
import os
import sdcard

# 1. SPI Configuration (Hardware SPI)
# Pins for ESP32 (VSPI): SCK=18, MOSI=23, MISO=19
# CS Pin (Chip Select): We can use any, e.g., GPIO 5
cs = machine.Pin(5, machine.Pin.OUT)
spi = machine.SPI(2, baudrate=1000000, polarity=0, phase=0, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19))

# 2. Initialize the SD card
try:
    sd = sdcard.SDCard(spi, cs)
    
    # 3. Mount the filesystem
    # The first argument is the block device (the card)
    # The second is the path where we want to see it ('/sd')
    vfs = os.VfsFat(sd)
    os.mount(vfs, "/sd")
    
    print("SD card mounted successfully at /sd")
    
    # List to verify
    print("Files on the SD:", os.listdir("/sd"))

except OSError as e:
    print("Error mounting the SD:", e)
Copied!

If you try to mount the card and it is already mounted (for example, if you run the script twice without restarting), it will give an error. It’s good practice to check for this or catch the exception.

Saving measurements to the SD card

Once mounted, using it is as easy as using internal memory. The only difference is that the file path must start with /sd/.

Let’s create a Datalogger that saves simulated data in CSV format (Comma Separated Values), Excel’s favorite format.

import time
import random

# File path on the card
FILENAME = "/sd/log_data.csv"

def write_header():
    # Check if the file exists so we don't delete the header if we restart
    try:
        os.stat(FILENAME)
        print("File already exists. Appending data...")
    except OSError:
        print("Creating new file with header...")
        with open(FILENAME, "w") as f:
            f.write("Timestamp,Temperature,Humidity\n")

def log_data(temp, hum):
    t = time.localtime()
    timestamp = "{:02d}/{:02d}/{} {:02d}:{:02d}:{:02d}".format(t[2], t[1], t[0], t[3], t[4], t[5])
    
    # CSV format: date, data1, data2
    line = "{},{},{}\n".format(timestamp, temp, hum)
    
    # IMPORTANT: Mode 'a' (Append) to add to the end
    with open(FILENAME, "a") as f:
        f.write(line)
    
    print("Saved:", line.strip())

# --- Main Program ---
write_header()

while True:
    # Simulate sensor readings
    temperature = random.randint(20, 30)
    humidity = random.randint(40, 60)
    
    log_data(temperature, humidity)
    
    # It is important to sync/save periodically.
    # The 'with open' statement already takes care of closing and saving (flush) on each iteration.
    
    time.sleep(5)
Copied!

Best practices with SD cards

Working with SDs has its risks. Here is some veteran advice:

  1. FAT32 Format: Make sure your card is formatted in FAT16 or FAT32. Very new or large cards (64GB+) usually come in exFAT, which MicroPython does NOT support by default. Use 32GB cards or smaller, or reformat them to FAT32.
  2. The blinking LED: Some SD modules have an LED that blinks during writes. Never remove power while this LED is active, or you will corrupt the filesystem.
  3. File closing: Notice that in the example we use with open(...). This ensures the file is closed (close()) immediately after writing. If you leave the file open and there is a power loss, you will lose the data that was in the RAM buffer and hadn’t been written to the card yet.
  4. Power consumption: SD cards consume a fair amount (can have peaks of 100mA during writes). Make sure your power supply is stable.

Unmounting the card

If you want to eject the card via software (so it’s safe to remove it), we use umount.

os.umount("/sd")
Copied!

With this, we no longer have a memory limit. We can save temperature logs every second for years without filling the card. The limit is now your imagination (and the battery)!