Connecting an Arduino and SD card requires routing the SPI bus (MOSI, MISO, SCK) and a dedicated Chip Select (CS) pin. If you are using a 5V board like the Arduino Uno R3, you must use a breakout module with built-in logic level shifters and a 3.3V LDO regulator. Feeding 5V logic directly into a bare SD card socket will permanently brick the card's internal controller.
This guide provides the exact hardware decision path, SPI pin mappings, and fully compilable C++ code to get your data logger running, followed by a debugging matrix for the most common failure modes.
The Verdict: Which Arduino and SD Card Module Should You Buy?
Not all SD modules are created equal. The $1.50 generic blue "Catalex" modules found on Amazon lack proper level shifting and often fail on 5V Arduinos. Use this decision tree to select the right hardware for your specific logging requirements.
| Project Scenario | Recommended Module | Why This Pick? |
|---|---|---|
| Default / Slow Logging (1Hz) Standard sensor logging on a 5V Arduino Uno/Nano. |
Adafruit MicroSD Breakout (ID: 254) ~$7.50 |
Includes a proper 3.3V LDO and MOSFET-based level shifters. Protects your SD card from 5V logic spikes. |
| Time-Stamped Logging Need real-time clock (RTC) data alongside sensor readings. |
Adafruit Data Logger Shield (ID: 1141) ~$15.95 |
Combines a DS1307 RTC and SD socket on a single shield. Routes CS to pin 10 and RTC I2C to A4/A5 automatically. |
| High-Frequency / Audio Logging >10kHz waveforms or WAV audio files. |
Teensy 4.1 with built-in SDIO ~$39.95 (Board) |
SPI is too slow for high-frequency data. Teensy 4.1 uses a dedicated 4-bit SDIO bus for massive throughput. |
SD.h library does not support the exFAT file system used on larger cards.
Hardware Spec Sheet and SPI Pin Mapping
The following build targets the Arduino Uno R3 (ATmega328P) and the Adafruit MicroSD Breakout (ID: 254). The SPI bus on the Uno is hardwired to specific pins. You cannot move MOSI, MISO, or SCK to arbitrary digital pins without resorting to slow software bit-banging.
Parts List
- Microcontroller: Arduino Uno R3 (or genuine clone with ATmega16U2 USB chip)
- SD Module: Adafruit MicroSD Card Breakout Board (Product ID: 254)
- Storage: SanDisk Ultra 32GB microSDHC (UHS-I, Class 10)
- Wiring: 22 AWG solid core jumper wires
SPI Pin Mapping Table
| SD Breakout Pin | Arduino Uno R3 Pin | Function / Notes |
|---|---|---|
| 5V / VCC | 5V | Powers the onboard LDO and level shifters. |
| GND | GND | Common ground reference. |
| MOSI | D11 | Master Out Slave In (Data from Uno to SD). |
| MISO | D12 | Master In Slave Out (Data from SD to Uno). |
| SCK / CLK | D13 | Serial Clock (Timing signal). |
| CS / CD | D10 | Chip Select. Can be any pin, but D10 is standard for Uno. |
Compilable Data Logging Code (Target: Arduino Uno R3)
This code uses the built-in SD.h library. It includes explicit pin definitions, serial error handling, and a basic analog read loop. Copy and paste this directly into the Arduino IDE (2.x or 1.8.x).
#include <SD.h>
#include <SPI.h>
// Explicit Pin Definitions for Arduino Uno R3
#define SD_CHIP_SELECT_PIN 10
#define SPI_MOSI_PIN 11
#define SPI_MISO_PIN 12
#define SPI_SCK_PIN 13
// Analog pin for sensor reading
#define SENSOR_PIN A0
File dataFile;
const char* logFileName = "log.txt"; // Must be 8.3 format!
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect (needed for native USB boards)
}
Serial.print("Initializing SD card...");
// Initialize the SD card via SPI
if (!SD.begin(SD_CHIP_SELECT_PIN)) {
Serial.println("initialization failed!");
// Halt execution to prevent infinite serial spam
while (1);
}
Serial.println("initialization done.");
// Create or append to the file
dataFile = SD.open(logFileName, FILE_WRITE);
if (dataFile) {
dataFile.println("Timestamp, SensorValue");
dataFile.close();
} else {
Serial.print("error opening ");
Serial.println(logFileName);
}
}
void loop() {
int sensorValue = analogRead(SENSOR_PIN);
unsigned long timeStamp = millis();
// Open file in append mode
dataFile = SD.open(logFileName, FILE_WRITE);
if (dataFile) {
dataFile.print(timeStamp);
dataFile.print(",");
dataFile.println(sensorValue);
dataFile.close(); // Always close to flush the buffer and update FAT table
Serial.print("Logged: ");
Serial.println(sensorValue);
} else {
Serial.print("error opening ");
Serial.println(logFileName);
}
// Wait 1 second before next reading
delay(1000);
}
Debugging: Exact Error Strings and the First Three Checks
SD card failures on the workbench almost always come down to file system formatting, naming conventions, or wiring faults. If your serial monitor throws an error, follow this decision path.
Error 1: initialization failed!
Ranked Causes:
- Wrong File System: The card is formatted as exFAT or NTFS. The standard
SD.hlibrary only supports FAT16 and FAT32. - Wiring Fault: MISO and MOSI are swapped, or the CS pin is floating.
- Logic Level Mismatch: You are using a cheap 3.3V module on a 5V Uno without level shifters, and the SD card's internal controller has locked up or burned out.
Error 2: error opening datalog.txt
Ranked Causes:
- 8.3 Filename Limit: The
SD.hlibrary strictly enforces the DOS 8.3 naming convention.datalog_2026.txtwill fail.log.txtordata.csvwill pass. - Directory Missing: You are trying to write to a subfolder (e.g.,
/logs/data.txt) but the/logsdirectory does not exist on the card. - Card Full or Locked: The physical write-protect switch on the microSD adapter is engaged, or the cluster allocation is full.
- Format correctly: Download the official SD Memory Card Formatter from the SD Association. Do not use the native Windows/macOS format tool, as they often misalign the partition boundaries for flash memory.
- Verify 8.3 Names: Ensure your filename is max 8 characters, plus a 3-character extension.
- Check SPI Continuity: Use a multimeter in continuity mode to verify D11 goes to MOSI, D12 goes to MISO, and D13 goes to SCK. Do not trust color-coded jumper wires.
Extending the Build: High-Speed Logging and 3.3V Logic
Once you have basic logging working, you will likely hit the limits of the standard SD.h library. Here is how to extend or simplify your build based on your project's evolution.
How to Simplify: Drop the String Formatting
If you are logging to a CSV, avoid using String objects or sprintf in your loop. The Arduino Uno only has 2KB of SRAM. Fragmenting the heap with String concatenation will eventually cause the SD buffer to fail silently. Use multiple File.print() calls separated by commas, exactly as shown in the code block above.
How to Extend: Move to SdFat for SDXC and Speed
If your project requires 64GB+ SDXC cards, exFAT support, or faster write speeds to prevent buffer overruns, abandon SD.h and use Bill Greiman's SdFat library. It is significantly more memory-efficient and supports modern file systems.
Migrating to 3.3V Boards (ESP32 / Arduino Nano 33 IoT)
If you switch from the 5V Uno R3 to a native 3.3V board like the ESP32, you no longer need the Adafruit 254 with its level shifters. You can use a bare SD socket breakout. However, the ESP32 uses different SPI pins by default. For an ESP32 DevKit V1, map your wiring to the VSPI bus: MOSI to GPIO 23, MISO to GPIO 19, SCK to GPIO 18, and CS to GPIO 5. Always consult the specific Arduino SD library reference for your target board's default SPI header mappings before soldering.






