If you are connecting an SD memory card module to an Arduino for data logging, the direct answer is to use the hardware SPI bus: wire MOSI to pin 11, MISO to pin 12, SCK to pin 13, and Chip Select (CS) to pin 10 (or 4). Format your microSD card strictly to FAT32 using the official SD Association formatter, not your OS native tool. The code below targets the Arduino Uno R3 and Arduino Nano v3 (ATmega328P variants) using the standard SD.h library.

Project Difficulty Rating: Beginner-Intermediate (2/5)
Time to Complete: 20 minutes for wiring and basic logging; 45+ minutes if debugging SPI level-shifting issues.

Hardware Specifications and SPI Pin Mapping

The most common module on the market is the generic Catalexi-style Micro SD Card Adapter (often recognized by its red or blue PCB). While cheap, understanding its internal architecture is critical for long-term reliability. These modules typically include an AMS1117-3.3 LDO regulator to drop 5V to 3.3V for the card's VCC, but they handle logic-level shifting in wildly different ways depending on the manufacturing batch.

Table 1: Generic Micro SD Adapter Module Specifications
Parameter Specification / Value Engineering Notes
Operating Voltage 4.5V - 5.5V (Input) Onboard LDO drops to 3.3V for the card.
Logic Levels 3.3V (Tolerates 5V on some batches) SD spec requires 3.3V. 5V may degrade card over time.
Interface SPI (Mode 0 or Mode 3) Max clock speed typically 12-25 MHz on Arduino.
Max Capacity 32GB (SDHC) Standard SD.h struggles with SDXC (>32GB/exFAT).
File System FAT16 (≤2GB) or FAT32 (4GB-32GB) exFAT and NTFS will cause immediate initialization failure.

Because the Arduino Uno and Nano operate at 5V logic, you must map the SPI pins correctly. Note that the hardware SPI pins change if you upgrade to an Arduino Mega2560.

Table 2: SPI Pin Mapping by Board Variant
Module Pin Arduino Uno / Nano (ATmega328P) Arduino Mega2560 Function
VCC 5V 5V Powers the module's LDO regulator.
GND GND GND Common ground reference.
MOSI Pin 11 Pin 51 Master Out, Slave In (Data to card).
MISO Pin 12 Pin 50 Master In, Slave Out (Data from card).
SCK Pin 13 Pin 52 Serial Clock (Timing signal).
CS Pin 10 (or 4) Pin 53 (or 4) Chip Select (Must be OUTPUT in code).

Step-by-Step Wiring Procedure

Before wiring, format your microSD card. Do not use the default Windows or macOS format tool, as they often default to exFAT for cards 32GB and larger, or use non-standard cluster sizes. Download the official SD Memory Card Formatter from the SD Association. Select "Overwrite format" to clear any hidden partition tables that cause SD.begin() to hang.

  1. Power the Module: Connect the module's VCC pin to the Arduino's 5V pin, and GND to GND. Do not power the module from the 3.3V pin on the Uno/Nano; the onboard ATmega328P 3.3V regulator only supplies ~50mA, while an SD card can pull up to 200mA during write operations, causing brownouts.
  2. Connect Hardware SPI: Wire MOSI to 11, MISO to 12, and SCK to 13. Keep these wires under 6 inches (15 cm) long. SPI is highly susceptible to capacitance and crosstalk at high frequencies; long jumper wires will cause corrupted sectors.
  3. Set the Chip Select (CS): Connect the CS pin to Digital Pin 10. Crucial bench note: Even if you use Pin 4 for CS, Pin 10 must be configured as an OUTPUT in your code, or the ATmega328P hardware SPI controller will automatically drop into Slave mode and freeze.
  4. Address the 5V Logic Issue (Optional but Recommended): The cheap modules output 3.3V on MISO, which the 5V Arduino reads fine. However, the Arduino sends 5V on MOSI, SCK, and CS. While the SD card's internal ESD diodes usually clamp this without immediate failure, it violates the SD specification. For a permanent deployment, route these three lines through a CD4050 or TXB0104 logic level shifter.

Complete Data Logging Code (Arduino Uno/Nano)

This sketch targets the Arduino Uno R3 and Nano v3. It reads an analog sensor (simulated here via A0), timestamps it using millis(), and writes it to datalog.txt. It includes explicit error handling and uses file.flush() to ensure data is committed to the flash memory immediately, preventing file corruption if the Arduino loses power unexpectedly.


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

// PIN DEFINITIONS
// Hardware SPI pins (11, 12, 13) are handled automatically by the SPI library.
// We only need to define the Chip Select pin.
#define CHIP_SELECT_PIN 10
#define SENSOR_PIN A0

File dataFile;

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

  Serial.print("Initializing SD card...");

  // CRITICAL: Pin 10 must be set as OUTPUT on Uno/Nano, even if using another pin for CS.
  // This prevents the ATmega328P SPI hardware from defaulting to Slave mode.
  pinMode(10, OUTPUT);
  pinMode(CHIP_SELECT_PIN, OUTPUT);

  // Attempt to initialize the SD card
  if (!SD.begin(CHIP_SELECT_PIN)) {
    Serial.println("initialization failed!");
    Serial.println("Check: 1) FAT32 format, 2) CS pin wiring, 3) 5V power supply.");
    while (1); // Halt execution to prevent infinite loop spam
  }
  
  Serial.println("initialization done.");
}

void loop() {
  // Read sensor data
  int sensorValue = analogRead(SENSOR_PIN);
  unsigned long timeStamp = millis();

  // Open the file. Note: only one file can be open at a time in the standard SD library.
  dataFile = SD.open("datalog.txt", FILE_WRITE);

  if (dataFile) {
    // Write CSV formatted data: Time(ms), SensorValue
    dataFile.print(timeStamp);
    dataFile.print(",");
    dataFile.println(sensorValue);
    
    // Flush forces the data out of the RAM buffer and onto the physical card.
    // This prevents data loss if power is cut before file.close() is called.
    dataFile.flush(); 
    
    // Print to serial for bench debugging
    Serial.print("Logged: ");
    Serial.print(timeStamp);
    Serial.print("ms, Value: ");
    Serial.println(sensorValue);
    
    dataFile.close();
  } else {
    Serial.println("error opening datalog.txt");
    // If this triggers, the card is likely full, write-protected, or corrupted.
  }

  // Log once per second
  delay(1000);
}

Debugging: Fixing Initialization and File Errors

When working with the SD memory card module on Arduino, you will inevitably hit the serial monitor and see the exact error string: initialization failed!. Less commonly, you will see error opening datalog.txt after a successful initialization. Here is the diagnostic decision path.

The First Three Things to Check When It Fails:
  1. Filesystem Format: Is the card formatted to FAT32 with a standard cluster size (usually 32KB)? Windows defaults to exFAT for 64GB+ cards, which the standard SD.h library cannot read. Use the SD Association Formatter.
  2. CS Pin Mismatch: Does the #define CHIP_SELECT_PIN in your code match the physical wire? Many tutorials use Pin 4, while standard Ethernet/SD shields use Pin 10. If they don't match, the SPI bus talks to the void.
  3. Power Rail Sag: Are you powering the Arduino via a weak USB hub? SD cards draw spikes of 150mA-200mA during sector writes. If the 5V rail drops below 4.5V during this spike, the card's internal controller resets, causing SD.begin() to fail.
Table 3: Ranked Causes for SD Module Errors
Exact Error String Most Likely Cause Fix / Measurement
initialization failed! exFAT or NTFS Filesystem Reformat to FAT32 using official SD Formatter tool.
initialization failed! Missing Pin 10 OUTPUT declaration Add pinMode(10, OUTPUT); in setup().
initialization failed! Wiring / SPI Bus Contention Check continuity on MISO/MOSI. Ensure no other SPI device has CS pulled LOW.
error opening datalog.txt Root Directory Limit Reached FAT32 root dir limits file count. Move files into a subfolder (e.g., SD.open("logs/data.txt")).
error opening datalog.txt Card Write-Protect Switch Check the physical microSD adapter sleeve for a locked switch.

A note on SPI bus sharing: If you are using an Ethernet shield (W5100/W5500) or an SPI LCD screen on the same Arduino, they share the MOSI, MISO, and SCK lines. You must ensure that the Chip Select pins for all inactive devices are pulled HIGH. If the Ethernet CS pin is left floating or LOW, it will intercept the SD card initialization commands, resulting in an immediate initialization failed! error.

Extending and Simplifying the Build

How to Extend: Adding Real-Time Timestamps

The millis() function resets every time the Arduino loses power, making it useless for long-term environmental logging. To extend this build, add a DS3231 I2C Real-Time Clock (RTC) module. The DS3231 uses the I2C bus (SDA to A4, SCL to A5 on the Uno/Nano), meaning it will not interfere with the SD card's SPI bus. You can use the RTCLib by Adafruit to pull the current date and time, formatting it into a standard ISO 8601 string before writing to the CSV file. Remember to install a CR2032 coin cell on the RTC to keep time during power outages.

How to Simplify: Switch to a 3.3V Native Microcontroller

If you are tired of worrying about 5V logic levels frying your microSD cards, or if you need to log data at high speeds (e.g., audio sampling or high-frequency vibration data), abandon the 5V Arduino Uno. Switch to a 3.3V native board like the Arduino Nano 33 IoT, the Adafruit Feather M4, or an ESP32 DevKit. Because these boards natively output 3.3V on their SPI pins, you can wire them directly to a bare microSD breakout board without any LDO regulators or logic level shifters. Furthermore, you can upgrade from the legacy SD.h library to the much faster SdFat library by Bill Greiman, which supports SDXC (exFAT) cards up to 2TB and offers significantly lower latency for file.flush() operations.