The standard Arduino micro SD card module communicates over the SPI bus and is the most common way to add persistent datalogging to a 5V microcontroller project. Out of the box, the ubiquitous blue or green "Catalex-style" adapter modules require a 5V VCC supply but route 3.3V logic to the SD card via an onboard LDO and level-shifting resistors. If your code compiles but the serial monitor spits out an initialization error, the issue is almost always a filesystem format mismatch, a floating Chip Select pin, or an ATmega328P SPI hardware quirk.

Module Specs and SD Card Compatibility Matrix

Before wiring anything, you must match your physical SD card to the limitations of the Arduino SD.h library and the SPI bus speed. The standard library does not support exFAT, which is the default format for cards 64GB and larger on modern Windows and macOS systems.

Card Type Capacity Range Required Filesystem SPI Clock Limit SD.h Compatibility
SDSC (Standard) 1MB – 2GB FAT16 12.5 MHz (Max) Native Support
SDHC (High Capacity) 4GB – 32GB FAT32 25 MHz (Max) Native Support (Best Choice)
SDXC (Extended) 64GB – 2TB exFAT (Default) 50 MHz (UHS-I) Fails (Requires reformat to FAT32)
microSDXC (A1/A2) 64GB+ exFAT / FAT32 50 MHz+ Fails (App class cards cause SPI timeouts)
Bench Tip: Stick to a name-brand 8GB or 16GB microSDHC card (like SanDisk Ultra or Samsung EVO). Avoid "A1" or "A2" app-performance cards for SPI datalogging; their internal controllers are optimized for random 4K reads/writes over SDIO, and they frequently introduce latency spikes that cause SPI buffer overruns on an 8-bit AVR.

Hardware BOM and SPI Pin Mapping

This build targets the Arduino Uno R3 (ATmega328P). The wiring uses the hardware SPI pins for MOSI, MISO, and SCK to ensure maximum throughput, while dedicating a separate digital pin for Chip Select (CS).

Parts List

  • MCU: Arduino Uno R3 (or genuine clone with ATmega16U2 USB bridge)
  • Module: Catalex-style MicroSD Card Adapter (look for the AMS1117-3.3 LDO on the back)
  • Media: 16GB microSDHC Class 10 card (formatted to FAT32)
  • Wiring: 6x Male-to-Female or Male-to-Male Dupont jumper wires

SPI Pin Mapping Table

MicroSD Module Pin Arduino Uno R3 Pin Function / Notes
GND GND Common ground reference
VCC 5V Powers the module's onboard 3.3V LDO
MISO D12 Master In, Slave Out (Data to Arduino)
MOSI D11 Master Out, Slave In (Data to SD Card)
SCK D13 Serial Clock (driven by Arduino)
CS D4 Chip Select (Active LOW, user-definable)

Debugging "Initialization Failed" Errors

If your serial monitor prints initialization failed! or Card failed, or not present, do not immediately assume the module is dead. The SD.begin() function returns a boolean 0 (false) when the SPI handshake fails. Here are the first three things to check on the bench, ranked by probability.

1. The Filesystem Format (exFAT vs FAT32)

If you bought a 64GB or larger card, your computer formatted it as exFAT. The standard Arduino SD library only parses FAT16 and FAT32 partition tables. The Fix: Download the official SD Memory Card Formatter from the SD Association. Do not use the built-in Windows/macOS format tool. If the card is >32GB, you may need a third-party tool like GUIFormat to force FAT32, though switching to a smaller 16GB card is the faster bench fix.

2. The Hardware SS Pin Quirk (Pin 10 on Uno)

On the ATmega328P, the hardware SPI peripheral dictates that the dedicated SS pin (Digital Pin 10 on the Uno) must be configured as an OUTPUT. If Pin 10 is left as an INPUT and gets pulled LOW by noise or a stray wire, the ATmega automatically drops out of SPI Master mode and becomes an SPI Slave. The SD card will never respond. The Fix: Add pinMode(10, OUTPUT); in your setup() block, even if you are using Pin 4 for your actual Chip Select line.

3. Logic Level Overvoltage and MISO Pull-ups

Cheap clone modules often omit the 74LVC125A level-shifter IC, relying instead on simple resistor dividers. This can result in the MISO line failing to pull all the way up to 3.3V, causing the Arduino to read corrupted CRC bytes during the initial ACMD41 handshake. The Fix: Measure the MISO pin with a multimeter while the card is idle; it should sit near 3.3V. If it floats around 1.5V, enable the internal pull-up on Pin 12 in your code: pinMode(12, INPUT_PULLUP);.

Robust SPI Datalogger Code (Arduino Uno R3)

The following code is fully compilable, targets the Uno R3, and includes explicit pin definitions and error handling for both the initialization phase and the file-write phase. It logs a simulated sensor read (using analogRead) alongside a millis() timestamp every second.

#include <SPI.h>
#include <SD.h>

// --- PIN DEFINITIONS ---
// Explicitly define hardware SPI and Chip Select pins
#define PIN_CS      4   // Chip Select (can be any digital pin)
#define PIN_MOSI    11  // Hardware SPI MOSI
#define PIN_MISO    12  // Hardware SPI MISO
#define PIN_SCK     13  // Hardware SPI Clock
#define PIN_HW_SS   10  // ATmega328P Hardware SS (Must be OUTPUT)

// --- FILENAME ---
// 8.3 filename format required by FAT32
const char* logFile = "datalog.txt";
File myFile;

void setup() {
  Serial.begin(9600);
  while (!Serial) { ; } // Wait for serial port to connect (needed for native USB)

  Serial.println(F("Initializing SD card..."));

  // CRITICAL: Hardware SS must be set to OUTPUT for SPI Master mode
  pinMode(PIN_HW_SS, OUTPUT);
  digitalWrite(PIN_HW_SS, HIGH); // Deselect hardware SS

  // Optional: Help weak MISO lines on cheap clone modules
  pinMode(PIN_MISO, INPUT_PULLUP);

  // Initialize the SD library
  if (!SD.begin(PIN_CS)) {
    Serial.println(F("initialization failed!"));
    Serial.println(F("Check: 1) FAT32 format, 2) Wiring, 3) Pin 10 OUTPUT"));
    // Halt execution to prevent endless serial spam
    while (1); 
  }
  
  Serial.println(F("initialization done."));

  // Open the file for appending. If it doesn't exist, it creates it.
  myFile = SD.open(logFile, FILE_WRITE);
  if (!myFile) {
    Serial.print(F("error opening " ));
    Serial.println(logFile);
    while (1);
  }
  
  // Write a header row
  myFile.println(F("Timestamp_ms,Analog_A0_Raw"));
  myFile.flush(); // Force write to physical card
  myFile.close();
}

void loop() {
  // Read a simulated sensor value
  int sensorValue = analogRead(A0);
  unsigned long currentTime = millis();

  // Re-open file for appending
  myFile = SD.open(logFile, FILE_WRITE);
  if (myFile) {
    myFile.print(currentTime);
    myFile.print(",");
    myFile.println(sensorValue);
    
    // Flush ensures data is written to the FAT table immediately.
    // Without this, a sudden power loss corrupts the file.
    myFile.flush(); 
    myFile.close();
  } else {
    Serial.print(F("error opening " ));
    Serial.println(logFile);
  }

  // Datalogging interval
  delay(1000);
}
Power Loss Corruption: Notice the myFile.flush() command in the loop. The SD library buffers writes in the Arduino's SRAM. If you pull the USB cable while data is in the buffer, the FAT32 directory table will corrupt, rendering the card unreadable by your PC until you run a disk repair utility. Always flush before closing.

Extending to 3.3V Boards and Simplifying the Build

The Catalex module is designed for 5V Arduinos. If you migrate this project to a 3.3V board like the ESP32 DevKit V1 or a Teensy 4.0, you face a different problem: feeding 3.3V into the module's VCC pin often results in a voltage drop across the AMS1117 LDO, leaving the SD card with only ~2.8V. This causes brownouts during heavy write cycles.

How to Extend (ESP32 / 3.3V Migration)

For 3.3V microcontrollers, bypass the cheap adapter module entirely. Buy a dedicated Adafruit MicroSD Card Breakout Board (Product ID 254) or a SparkFun level-shifting module. These boards feature proper MOSFET-based bidirectional level shifters (like the BSS138) and direct 3.3V routing. When wiring to an ESP32, remember that the ESP32 uses the VSPI bus by default: MOSI is GPIO 23, MISO is GPIO 19, SCK is GPIO 18, and CS is typically GPIO 5.

How to Simplify (Switching to SdFat)

If your project requires high-speed logging (e.g., capturing accelerometer data at 500Hz), the standard SD.h library will bottleneck because it opens and closes the file cluster chain on every write. To simplify and accelerate the build, switch to Bill Greiman's SdFat library. SdFat allows you to pre-allocate contiguous clusters on the SD card, eliminating FAT table lookups during the loop and guaranteeing microsecond-level write latencies without dropping samples.