SD Card SPI Interface: Initialization, Commands, and Responses
SD cards are Flash-based storage modules widely used in embedded systems for logging runtime data and storing configuration. They expose two host interfaces: the native SD bus and the simpler SPI bus. This guide focuses on SPI mode, command framing, response handling, and a compact C driver demonstrating card initialization and block I/O.
Fundamentals
- SD vs. MMC: While similar, MMC and SD have diffferent initialization sequences in SPI mode (notably CMD1 for MMC vs. ACMD41 for SD). Many hosts only support SD.
- SD vs. TF (microSD): microSD uses the same protocol but has fewer pins than a full-size SD card, typically omitting one of the additional ground (VSS) pins.
- Filesystems: The card stores raw blocks; filesystems (FAT, exFAT, etc.) are layered on top by the host and are not part of the card itself.
- Controller features: The card internally manages wear leveling, bad blocks, ECC, power and clock control, and security logic, transparent to the host in SPI mode.
Pins and Electrical Notes
- Full-size SD (SD mode): CLK, CMD, DAT0–DAT3, VDD, VSSx; microSD reduces pin count and typically has a single VSS.
- SPI wiring to SD:
- SCLK ↔ CLK
- MOSI ↔ CMD (DI)
- MISO ↔ DAT0 (DO)
- CS ↔ DAT3 (pull-up can be used for card detect in SD mode)
- If your PCB uses SD mode but leaves some data lines unused, pull them up to prevent floating lines from causing extra current and instability.
Commonly Readable Card Registers
- OCR: Operation Conditions Register (voltage and CCS, via CMD58)
- CID: Card Identification (manufacturer info, via CMD10)
- CSD: Card-Specific Data (capacity/timing, via CMD9)
- SCR: SD Configuration Register (capabilities/features, via ACMD51)
- SD Status: Extended status (via ACMD13)
Command Frame on SPI
An SPI command is 6 bytes, clocked MSB first:
- Byte 1: 0b01iiiiii (i = command index). Practically, send (0x40 | cmd).
- Bytes 2–5: 32-bit argument, MSB first.
- Byte 6: CRC7 in the upper 7 bits, LSB is always 1.
Notes on CRC in SPI mode:
- CRC is disabled by default in SPI after reset, so only CMD0 needs a valid CRC (0x95). Many hosts also provide a proper CRC for CMD8 (0x87) for better interoperability.
- CMD59 can enable/disable CRC in SPI mode if needed.
SPI-Mode Command Summary
- CMD0: Go to idle state
- CMD1: Initialize (MMC); SD cards typically use ACMD41 instead
- CMD8: Send interface condition (check 2.0 support and voltage)
- CMD9: Read CSD
- CMD10: Read CID
- CMD12: Stop transmission (multi-block read)
- CMD13: Read card status
- CMD16: Set block length (512 for SPI use)
- CMD17: Read single block
- CMD18: Read multiple blocks (terminate with CMD12)
- CMD24: Write single block
- CMD25: Write multiple blocks (terminate with a stop token)
- CMD27: Program CSD
- CMD28/CMD29: Set/clear write protection (address-group based)
- CMD30: Send write protection status
- CMD32/CMD33: Set first/last block address for erase range
- CMD38: Erase selected blocks
- CMD42: Lock/unlock
- CMD55: Next command is application-specific (ACMD)
- CMD56: Application-specific R/W data block
- CMD58: Read OCR (check CCS for SDHC/SDXC)
- CMD59: CRC on/off in SPI
Application (preceded by CMD55):
- ACMD13: Read SD status
- ACMD22: Number of written blocks (32-bit + CRC)
- ACMD23: Set pre-erase block count (speeds up multi-block write)
- ACMD41: SD initialization (HCS bit for SDHC/SDXC)
- ACMD42: Connect/disconnect 50 kΩ pull-up on CD/DAT3
- ACMD51: Read SCR
Response Types (SPI)
- R1: 1 byte. Bit-encoded status (Idle, Erase Reset, Illegal Command, CRC Error, Erase Sequence Error, Address Error, Parameter Error)
- R1b: R1 + busy time (card pulls MISO low until ready)
- R2: 2-byte status (extended flags)
- R3: OCR (R1 + 32-bit OCR), e.g., for CMD58
- R7: Interface condition echo (R1 + 32-bit data), e.g., for CMD8
Initialization Flow (SPI)
- Configure MCU SPI and GPIO (CS output high, SPI slow ≤400 kHz initially).
- Supply clock with CS high for atleast 74 cycles (send ≥80 dummy clocks).
- Issue CMD0 with CRC 0x95 until R1 indicates Idle (0x01).
- Issue CMD8 with argument 0x1AA to probe SD v2.0 and voltage; check R7 echo.
- SD v2.x path:
- Loop: CMD55; ACMD41 with HCS=1 until R1 == 0x00.
- Issue CMD58; read OCR and CCS to distinguish SDHC/SDXC.
- SD v1.x/MMC path:
- Try CMD55; ACMD41 without HCS; if it never goes ready, fall back to MMC with CMD1 until ready.
- For non-SDHC cards, set block length to 512 with CMD16.
- Increase SPI clock (e.g., to several MHz, within card limits) after initialization.
Reference Implementation: Minimal SPI SD Driver (C)
sdspi.h
#ifndef SDSPI_H
#define SDSPI_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Card type flags
#define SDSPI_CARD_NONE 0x00
#define SDSPI_CARD_MMC 0x01
#define SDSPI_CARD_SDSC 0x02 // SD v1/v2, byte addressing
#define SDSPI_CARD_SDHC 0x04 // SDHC/SDXC, block addressing
// SPI-mode command indices
#define CMD0 0x00
#define CMD1 0x01
#define CMD8 0x08
#define CMD9 0x09
#define CMD10 0x0A
#define CMD12 0x0C
#define CMD13 0x0D
#define CMD16 0x10
#define CMD17 0x11
#define CMD18 0x12
#define CMD23 0x17
#define CMD24 0x18
#define CMD25 0x19
#define CMD55 0x37
#define CMD58 0x3A
#define CMD59 0x3B
// Data tokens
#define TOKEN_DATA_START 0xFE
#define TOKEN_WRITE_MULTI 0xFC
#define TOKEN_WRITE_STOP 0xFD
// R1 status bits
#define R1_IDLE 0x01
#define R1_ERASE_RESET 0x02
#define R1_ILLEGAL_CMD 0x04
#define R1_COM_CRC_ERR 0x08
#define R1_ERASE_SEQ_ERR 0x10
#define R1_ADDR_ERR 0x20
#define R1_PARAM_ERR 0x40
// API
int sdspi_init(void);
int sdspi_read_cid(uint8_t cid[16]);
int sdspi_read_csd(uint8_t csd[16]);
uint32_t sdspi_sector_count(void);
int sdspi_read_blocks(uint32_t lba, uint8_t *buf, uint32_t count);
int sdspi_write_blocks(uint32_t lba, const uint8_t *buf, uint32_t count);
#ifdef __cplusplus
}
#endif
#endif // SDSPI_H
sdspi.c
#include "sdspi.h"
// Board-specific SPI hooks (provide implementations elsewhere)
static void spi_set_slow(void); // ≤400 kHz
static void spi_set_fast(void); // up to card limit (e.g., 25 MHz)
static uint8_t spi_xfer(uint8_t x); // full-duplex transfer
static void cs_assert(void); // drive CS low
static void cs_deassert(void); // drive CS high
static void delay_short(void); // small delay (e.g., a few hundred µs)
// Internal state
static uint8_t g_card_type = SDSPI_CARD_NONE;
static void clock_dummy_bytes(unsigned n)
{
while (n--) (void)spi_xfer(0xFF);
}
static int wait_ready(uint32_t ticks)
{
while (ticks--) {
if (spi_xfer(0xFF) == 0xFF) return 0;
}
return -1;
}
static void deselect_bus(void)
{
cs_deassert();
spi_xfer(0xFF); // one extra 8 clocks
}
static int select_card(void)
{
cs_assert();
return wait_ready(0xFFFFFF) == 0 ? 0 : -1;
}
static uint8_t send_cmd_raw(uint8_t cmd, uint32_t arg, uint8_t crc)
{
uint8_t r1;
deselect_bus();
if (select_card() != 0) return 0xFF;
spi_xfer(0x40 | cmd);
spi_xfer((uint8_t)(arg >> 24));
spi_xfer((uint8_t)(arg >> 16));
spi_xfer((uint8_t)(arg >> 8));
spi_xfer((uint8_t)(arg));
spi_xfer(crc);
if (cmd == CMD12) spi_xfer(0xFF); // skip a stuff byte after CMD12
for (int i = 0; i < 32; ++i) {
r1 = spi_xfer(0xFF);
if ((r1 & 0x80) == 0) break;
}
return r1;
}
static uint8_t send_cmd(uint8_t cmd, uint32_t arg)
{
uint8_t crc = 0x01;
if (cmd == CMD0) crc = 0x95; // required when CRC is off
else if (cmd == CMD8) crc = 0x87; // valid CRC for 0x000001AA
return send_cmd_raw(cmd, arg, crc);
}
static uint8_t send_acmd(uint8_t acmd, uint32_t arg)
{
uint8_t r1 = send_cmd(CMD55, 0);
if (r1 > 1) return r1; // CMD55 failed
return send_cmd(acmd, arg);
}
static int read_data_block(uint8_t *buf, size_t n)
{
// Wait for data token
uint32_t t = 0xFFFFFF;
uint8_t token;
do {
token = spi_xfer(0xFF);
} while (token == 0xFF && --t);
if (token != TOKEN_DATA_START) return -1;
for (size_t i = 0; i < n; ++i) buf[i] = spi_xfer(0xFF);
// discard CRC
spi_xfer(0xFF); spi_xfer(0xFF);
return 0;
}
static int write_data_block(const uint8_t *buf, uint8_t token)
{
if (wait_ready(0xFFFFFF) != 0) return -1;
spi_xfer(token);
if (token != TOKEN_WRITE_STOP) {
for (size_t i = 0; i < 512; ++i) spi_xfer(buf[i]);
// dummy CRC
spi_xfer(0xFF); spi_xfer(0xFF);
uint8_t resp = spi_xfer(0xFF) & 0x1F;
if (resp != 0x05) return -2; // data rejected
}
return 0;
}
int sdspi_init(void)
{
uint8_t r1;
uint8_t r7[4];
// Hardware/SPI low-speed setup
cs_deassert();
spi_set_slow();
// ≥ 74 clocks with CS high
clock_dummy_bytes(10);
// Go to idle
for (int i = 0; i < 20; ++i) {
r1 = send_cmd(CMD0, 0);
if (r1 == R1_IDLE) break;
}
if (r1 != R1_IDLE) {
deselect_bus();
return -1;
}
g_card_type = SDSPI_CARD_NONE;
// Probe v2.0 support
r1 = send_cmd(CMD8, 0x1AA);
if (r1 == R1_IDLE) {
for (int i = 0; i < 4; ++i) r7[i] = spi_xfer(0xFF);
if (r7[2] == 0x01 && r7[3] == 0xAA) {
// ACMD41 with HCS until ready
uint32_t retry = 0xFFFF;
do {
r1 = send_acmd(CMD41, 0x40000000UL);
} while (r1 && --retry);
if (retry && send_cmd(CMD58, 0) == 0) {
uint8_t ocr[4];
for (int i = 0; i < 4; ++i) ocr[i] = spi_xfer(0xFF);
g_card_type = (ocr[0] & 0x40) ? SDSPI_CARD_SDHC : SDSPI_CARD_SDSC;
}
}
}
// v1.x or MMC fallback
if (g_card_type == SDSPI_CARD_NONE) {
r1 = send_acmd(CMD41, 0);
if (r1 <= 1) {
uint32_t retry = 0xFFFF;
do {
r1 = send_acmd(CMD41, 0);
} while (r1 && --retry);
g_card_type = SDSPI_CARD_SDSC;
} else {
uint32_t retry = 0xFFFF;
do {
r1 = send_cmd(CMD1, 0);
} while (r1 && --retry);
if (r1 == 0) g_card_type = SDSPI_CARD_MMC;
}
if (g_card_type != SDSPI_CARD_NONE) {
if (send_cmd(CMD16, 512) != 0) g_card_type = SDSPI_CARD_NONE;
}
}
deselect_bus();
spi_set_fast();
return (g_card_type == SDSPI_CARD_NONE) ? -2 : 0;
}
int sdspi_read_cid(uint8_t cid[16])
{
if (!cid) return -1;
uint8_t r1 = send_cmd(CMD10, 0);
int rc = -2;
if (r1 == 0) rc = read_data_block(cid, 16);
deselect_bus();
return rc;
}
int sdspi_read_csd(uint8_t csd[16])
{
if (!csd) return -1;
uint8_t r1 = send_cmd(CMD9, 0);
int rc = -2;
if (r1 == 0) rc = read_data_block(csd, 16);
deselect_bus();
return rc;
}
static uint32_t parse_capacity_from_csd(const uint8_t csd[16])
{
// Returns number of 512-byte sectors, 0 on error
if (!csd) return 0;
if ((csd[0] & 0xC0) == 0x40) {
// CSD v2.0 (SDHC/SDXC)
uint16_t csize = ((uint16_t)csd[7] & 0x3F) << 16;
csize |= ((uint16_t)csd[8]) << 8;
csize |= csd[9];
return ((uint32_t)csize + 1) << 10; // (C_SIZE+1)*1024 blocks
} else {
// CSD v1.0 (SDSC/MMC)
uint16_t csize = ((uint16_t)(csd[6] & 0x03) << 10) | ((uint16_t)csd[7] << 2) | (csd[8] >> 6);
uint8_t read_bl_len = csd[5] & 0x0F;
uint16_t c_size_mult = ((uint16_t)((csd[9] & 0x03) << 1) | ((csd[10] & 0x80) >> 7));
uint32_t block_len = 1UL << read_bl_len;
uint32_t mult = 1UL << (c_size_mult + 2);
uint32_t blocks = (uint32_t)(csize + 1) * mult;
uint64_t capacity = (uint64_t)blocks * block_len; // bytes
return (uint32_t)(capacity / 512ULL);
}
}
uint32_t sdspi_sector_count(void)
{
uint8_t csd[16];
if (sdspi_read_csd(csd) != 0) return 0;
return parse_capacity_from_csd(csd);
}
int sdspi_read_blocks(uint32_t lba, uint8_t *buf, uint32_t count)
{
if (!buf || count == 0) return -1;
uint8_t r1;
uint32_t addr = lba;
if (g_card_type != SDSPI_CARD_SDHC) addr <<= 9; // byte address for SDSC/MMC
if (count == 1) {
r1 = send_cmd(CMD17, addr);
int rc = -2;
if (r1 == 0) rc = read_data_block(buf, 512);
deselect_bus();
return rc;
}
r1 = send_cmd(CMD18, addr);
if (r1 != 0) {
deselect_bus();
return -2;
}
for (uint32_t i = 0; i < count; ++i) {
if (read_data_block(buf + 512 * i, 512) != 0) {
break;
}
}
send_cmd(CMD12, 0); // stop transmission
deselect_bus();
return 0;
}
int sdspi_write_blocks(uint32_t lba, const uint8_t *buf, uint32_t count)
{
if (!buf || count == 0) return -1;
uint8_t r1;
uint32_t addr = lba;
if (g_card_type != SDSPI_CARD_SDHC) addr <<= 9;
if (count == 1) {
r1 = send_cmd(CMD24, addr);
int rc = -2;
if (r1 == 0) rc = write_data_block(buf, TOKEN_DATA_START);
deselect_bus();
return rc;
}
// Pre-erase hint for SD (not MMC)
if (g_card_type != SDSPI_CARD_MMC) {
send_acmd(CMD23, count);
}
r1 = send_cmd(CMD25, addr);
if (r1 != 0) {
deselect_bus();
return -2;
}
for (uint32_t i = 0; i < count; ++i) {
if (write_data_block(buf + 512 * i, TOKEN_WRITE_MULTI) != 0) {
deselect_bus();
return -3;
}
}
// Stop transmission
(void)write_data_block(NULL, TOKEN_WRITE_STOP);
deselect_bus();
return 0;
}
Board binding stubs (example skeleton; implement per MCU):
// Example placeholders — implement with your SPI/GPIO HAL
static void spi_set_slow(void) { /* set SPI prescaler for ≤400 kHz */ }
static void spi_set_fast(void) { /* set SPI prescaler for high-speed */ }
static uint8_t spi_xfer(uint8_t x) { /* transfer byte */ return 0xFF; }
static void cs_assert(void) { /* CS low */ }
static void cs_deassert(void) { /* CS high */ }
static void delay_short(void) { /* small delay */ }
Behavior notes:
- For SDSC/MMC, addresses in CMD17/24 are byte addresses; for SDHC/SDXC, they are block-based (512-byte units).
- When CRC is disabled in SPI mode, include valid CRC for CMD0 (0x95). Many hosts also use a valid CRC for CMD8 (0x87). Enable/disable CRC with CMD59 if required by your application.
- After initialization, the block length should be 512 bytes (CMD16) for non-high-capacity cards. High-capacity cards always use 512-byte blocks.
- Always provide extra clocks after deselecting to allow the card to finish internal operations.