Learning Goals 5 min
- Wire a microSD card module to the UNO over SPI (4 data wires + power) and explain at a high level what SPI is.
- Initialise the card with
SD.begin(CS_PIN), open a file withSD.open(...), write lines withfile.println(...), and close cleanly. - Inspect the resulting file by removing the card and plugging it into your laptop. Confirm the text actually got written.
Warm-Up 10 min
EEPROM gave you 1 KB. An SD card gives you 4 GB, or 16 GB, or 128 GB. That's 4 million to 100 million times more storage. With that much room you can log a sensor reading every second for years.
What SPI is, in one paragraph
SPI (Serial Peripheral Interface) is a 4-wire communication protocol Arduinos use to talk to lots of chips: SD cards, OLED displays, shift registers, accelerometers. Four signals: MOSI (master sends data to slave), MISO (slave sends data back), SCK (clock), CS (which slave we're talking to). The Arduino is the "master"; the SD card is the "slave". We'll cover SPI properly in L3-20; today we just use the SD library which hides the protocol.
New Concept · Wiring + library use 25 min
Pinout — UNO + microSD module
| SD module | UNO pin | SPI role |
|---|---|---|
| VCC | +5 V | — |
| GND | GND | — |
| MISO | D12 | data from card to Arduino |
| MOSI | D11 | data from Arduino to card |
| SCK | D13 | clock |
| CS | D10 | chip select (we picked it; any free pin works) |
Three of the four pins (MISO 12, MOSI 11, SCK 13) are hardware-fixed — those are the UNO's SPI hardware pins, you can't move them. CS is your choice — D10 is the conventional default. Six wires total counting power.
Card format
The SD library only reads FAT16 or FAT32 formatted cards. New cards usually arrive FAT32. If the library can't see your card, use your computer to reformat it: right-click → Format → FAT32. Don't use exFAT or NTFS.
The minimum-viable SD sketch
#include <SPI.h>
#include <SD.h>
const int CS = 10;
void setup() {
Serial.begin(9600);
while (!Serial) ;
Serial.print("Initialising SD card... ");
if (!SD.begin(CS)) {
Serial.println("FAILED.");
return;
}
Serial.println("OK.");
// Open or create a file in write mode
File f = SD.open("hello.txt", FILE_WRITE);
if (f) {
f.println("Hello from Arduino!");
f.close();
Serial.println("Wrote hello.txt.");
} else {
Serial.println("Could not open file.");
}
}
void loop() {}Four library calls:
SD.begin(CS)— initialise the SD library and the card. Returns true on success.SD.open(name, mode)— open a file.FILE_WRITEopens for appending (creates if missing). Returns aFileobject.f.println(string)— write a line. Same API as Serial.println.f.close()— flush and close. Always close, or your write may not actually land on the card.
FILE_WRITE vs FILE_READ
FILE_WRITEopens for appending — new content goes to the end of an existing file (or creates a fresh one).FILE_READopens for reading from the start. Usefile.read()orfile.readStringUntil('\n').
There's no "overwrite from scratch" mode in this library. To start fresh, delete the file first: SD.remove("hello.txt");
Worked Example · Hello world to SD 25 min
Step 1 — format the card
Plug the microSD into a card reader on your laptop. Right-click → Format → FAT32. Choose a small allocation unit (default is fine). Confirm. Takes a few seconds.
Step 2 — wiring
Connect the SD module as per the pin table above. Insert the formatted card.
Step 3 — upload the minimum sketch
Use the sketch from the New Concept section. Open Serial Monitor at 9600. Expected output:
Initialising SD card... OK. Wrote hello.txt.
If you see FAILED, double-check: card is FAT32, CS pin matches the wiring, all 4 SPI wires are connected correctly.
Step 4 — verify on your computer
Power off the Arduino. Eject and remove the SD card. Plug into your laptop. Open the card's file browser. You should see HELLO.TXT (8.3 names — old FAT limitation; long filenames work but are mangled).
Open the file. Inside:
Hello from Arduino!
One line. Reinsert the card into the Arduino, power on, and the sketch will run again — appending a second line. Now the file reads:
Hello from Arduino! Hello from Arduino!
Every boot adds one more line. That's persistence on the gigabyte scale.
Step 5 — sketch a real log
Modify the sketch to log a timestamp + pot reading every second:
#include <SPI.h>
#include <SD.h>
const int CS = 10;
const int POT = A0;
File log;
unsigned long lastLog = 0;
void setup() {
Serial.begin(9600);
if (!SD.begin(CS)) { Serial.println("SD fail"); return; }
log = SD.open("data.csv", FILE_WRITE);
if (log) {
log.println("seconds,pot");
log.close();
}
}
void loop() {
if (millis() - lastLog < 1000) return;
lastLog = millis();
log = SD.open("data.csv", FILE_WRITE);
if (log) {
log.print(millis() / 1000);
log.print(",");
log.println(analogRead(POT));
log.close();
}
}Notice the pattern: open, write one line, close. For high-rate loggers this is wasteful (a lot of open overhead). For 1 Hz it's fine and means a power loss costs at most 1 line of data.
Step 6 — pull the card after a minute
Let the logger run for a minute or so. Pop the card. Open data.csv on your laptop:
seconds,pot 1,512 2,512 3,481 4,237 5,189 ...
One row per second, header on top. Drop into Excel → Insert Chart → line chart. Your first real persistent data logger. Cluster G's payoff in one sketch.
Try It Yourself 15 min
Goal: Use SD.remove("data.csv") at the top of setup to ensure a fresh file every boot. Compare with the appending behaviour.
Hint
SD.remove("data.csv");Add as the first line after SD.begin. Now each run produces a single-session file. Useful for "test then download" workflows.
Goal: Use FILE_READ to open data.csv and print its first 20 lines to Serial. Useful for debugging without removing the card.
Hint
File f = SD.open("data.csv", FILE_READ);
if (f) {
for (int i = 0; i < 20 && f.available(); i++) {
Serial.println(f.readStringUntil('\n'));
}
f.close();
}f.available() returns the number of bytes left to read. readStringUntil('\n') grabs one line at a time.
Goal: Log to a NEW filename every boot — log001.csv, log002.csv, log003.csv. Use EEPROM (from L02-39) to remember the next log number.
Hint
#include <EEPROM.h>
const int LOG_NUM_ADDR = 100;
unsigned int logNum;
EEPROM.get(LOG_NUM_ADDR, logNum);
if (logNum == 0xFFFF) logNum = 0;
logNum++;
EEPROM.put(LOG_NUM_ADDR, logNum);
char filename[16];
sprintf(filename, "log%03u.csv", logNum);
Serial.print("Writing to "); Serial.println(filename);
log = SD.open(filename, FILE_WRITE);Combines the two persistence mechanisms: EEPROM remembers which file number we're on; SD card holds the actual logged data. A real product would do exactly this.
Mini-Challenge · Multi-channel logger 15 min
Take any 2–3 sensors you've used in L2 (LDR, pot, DHT11, HC-SR04, thermistor — your pick) and log them all to one CSV file. One row per sample interval, multiple columns, header on top.
Spec:
- Pick a sample interval that's appropriate for your slowest sensor (DHT11 = 2 s minimum).
- Header row:
seconds,sensor1,sensor2,sensor3 - One data row per sample. Use
sprintf+ a singleprintlnfor tidy formatting. - Handle invalid readings (NaN, -1) by writing an empty cell (e.g.
10,27.3,,512) so the CSV stays parseable. - Log for at least 5 minutes. Pop the card. Chart in spreadsheet.
It's done when:
- The CSV opens in Excel/Sheets without errors and has correct column count on every row.
- The chart from the data tells a coherent story (e.g. temperature stable, light dropping as you blocked the window, pot turned at one point).
- Pulling power mid-log loses at most 1–2 rows (not the whole file).
- You can describe in one sentence what each column represents.
Recap 5 min
An SD card module + the SD library + 6 wires gives your Arduino virtually unlimited persistent storage. The API mirrors Serial closely — file.println, file.print, and the file appears on a regular FAT-formatted microSD card you can pop into any computer. The big trade-offs are RAM (SD library eats ~500 bytes), speed (writes are slow), and the need to close() for data to actually land on disk. Combined with EEPROM (small, fast, on-chip) you now have the full Arduino persistence story. Tomorrow we use all of this to ship the proper Sensor Data Logger — Cluster G's capstone.
- SD card
- The removable memory card found in cameras, phones, IoT devices. Sizes from 4 GB to 1 TB. Mounts as a regular flash filesystem on a computer.
- microSD
- The physically smaller form factor (15 × 11 mm vs full-size 32 × 24 mm). Same protocol; an adapter lets a microSD fit a full-size slot.
- FAT32
- The filesystem format the Arduino SD library understands. Older but universally supported. Most blank cards arrive in this format.
- SPI
- Serial Peripheral Interface — the 4-wire protocol the Arduino uses to talk to the SD card (and many other chips). Covered in depth in L03-20.
- MOSI / MISO / SCK / CS
- The four SPI wires. MOSI = master to slave data; MISO = slave to master data; SCK = clock; CS = chip select. On UNO: D11, D12, D13, and your-choice-of-pin.
SD.begin(CS)- Initialises the SD library and the card hardware. Returns true on success. Call once in
setup(). SD.open(name, mode)- Opens or creates a file.
FILE_WRITEappends;FILE_READreads. Returns aFileobject that's falsy on failure. file.close()- Flushes buffered data to the card and releases the file handle. Always call after writing. Skipping it = silent data loss on power-cycle.
- 8.3 filenames
- The classic FAT short-filename limit: up to 8 chars + a 3-char extension (
data.csv,log001.txt). The SD library supports long names too but it's safer to stick with 8.3. - Card removal
- Always power down the Arduino before pulling the card to avoid corrupting the filesystem. (Phones & cameras have a software "eject" for the same reason.)
Homework 5 min
Test SD reliability. Run a logger overnight (8+ hours) that writes one line per second. Check the resulting file the next morning:
- How many lines did you expect? (8 × 3600 = ~28 800)
- How many did you get? Was it close?
- Open the file in a text editor and scan for any obviously corrupted lines (missing comma, garbage characters, truncation).
- Did the Arduino still work the next morning? (Long runs can crash from RAM exhaustion if your sketch leaks.)
Bring back next class:
- Your overnight log file (a screenshot of the row count or the first + last 5 lines).
- Your answers to the four questions above.
- Your
hw-l02-41.inosketch. - Tomorrow we polish this into the Sensor Data Logger capstone — properly timestamped, multi-sensor, error-handled.