I've seen various blog and forum posts, tutorials and application notes about accessing SD cards with microcontrollers using SPI, but I struggled a lot at different points when following them.

In my following answer, I tried to sum up an initialization sequence in more detail for anyone else struggling and also give some advice that I wish I knew in advance.


1 Answer 1


General advice

  • SDUC (ultra capacity, > 2 TB) cards don't support SPI according to the specifications.

  • There are several libraries that significantly simplify accessing the SD card, using a proper file system so that the files can be read easily with a file explorer on the computer. If you can, I would suggest stopping here and giving them a try, because you probably don't need to deal with low-level things:

  • If you can't use the libraries for some reasons, you can also initialize the SD card on your own (which is what I describe here) and write to the SD card blocks/sectors directly without a (proper) file system or with your own one. However, the disadvantage is that you can't simply read the data with a file explorer. Instead, you can use tools like HxD or the dd command on Linux (Windows versions are available as well) to read/copy the raw data. (For example, I had a real-time microcontroller system that only has small periodic time slots in which I can perform SD card logging tasks and start/evaluate SPI sequences. All the libraries I've seen are not real-time capable and can take quite some time to execute, at least from my understanding, so here we are.)

  • Although it is possible to share the SPI bus with SD card and other slaves (e.g. only using different chip select (CS) lines), I would not recommend it - it saves you some trouble to use separate SPI buses.

  • If you share the SPI bus with other slaves, always send another "dummy" byte (or more) with the CS line inactive (high) after each communication to the SD card before accessing other slaves. Otherwise, the SD card will keep the DO/MISO ((master in slave out)) output active and cause two slaves to drive/short-circuit the MISO line! So: CS low, exchange data with SD card, CS high, send dummy data (e.g. one byte 0xFF), then you can communicate with other slaves. (I solved it by sending the data to a "dummy" slave with the CS pin of this dummy/virtual slave unconnected - that way the microcontroller can deal with the CS lines by itself.) Also, start without using any other slaves until the initialization sequence works well, then you may re-enable the other slaves (after the initialization or even during initialization).

  • Use large buffer capacitors placed right at the SD card (e.g. 100 µF + 22 µF + 1 µF). The SD card will draw significant currents when plugging in, which might cause a brown-out for both the SD card and other components. EDIT: I used ceramic capacitors (3x 22 µF, 1x 10 µF, 1x 1 µF, 1x 100 nF) close to the SD card and separated from the main supply rail by a 1 Ω resistor and measured a transient voltage drop from 3.30 V to 3.22 V for about 100 µs when inserting the card, which is fine.

  • It might be beneficial to be able to power-cycle the SD card, e.g. to cut off its supply and enable it again. If the card "falls asleep", it won't wake up again unless you cut the power to reset it or you physically re-insert it: SD Card Reset Issue

  • Be aware that data loss may occur when the power fails during writing or erasing cycles. This might also happen when you think you don't write data since the SD card controller might resort data internally: How do I protect SD card against unexpected power failures?

    There seem to be power-failure tolerant SD cards / industrial SD cards that claim to be power-failure resilient according to this thread. I didn't test them, but you might want to look at them for important data.

Specification, Tutorials, Application Notes

  • Most important, the specification itself: SD Specification Part 1 Physical Layer (Simplified Specification), current version 8.00. You need to read parts of it to survive (at least Chapter 7: SPI Mode, the rest is not needed much for SPI because SPI only supports basic commands and functionality) and I will refer to it often.

Might have a look into these as well:

If you're stuck or want to read more:


In general, you communicate with the SD card via commands (see Command format in the specifications) which the SPI master sends to the SD card (SPI slave). The command is 48 bit (6 byte) long: 1 byte command, 4 byte argument (often 0, so 0x00000000), 1 byte of CRC7 + end bit.

  • 1st byte: 0b01xxxxxx, where xxxxxx is the binary representation of the command index, so CMD13 or ACMD13 => 001101
  • "ACMDxx" = application-specific command. Send CMD55 to tell the SD card, that the next command is an ACMD.
  • command, as well as argument(s) (4 bytes), are described in the specifications in Detailed Command Description
  • last byte: (CRC7 << 1) | 1 - by default only required for CMD8 (and can be pre-computed in this case) but I'd recommend always using it. See code at the end.

The SD card always replies with a response, described in chapter "7.3.2 Responses". However, the response may come anywhere between 0 and 8 bytes after your 48 bit message. So either you send single bytes and check each byte for the beginning of the response or you always transmit at least 19 bytes (6 for command, max. 8 to wait, 5 byte for response R3/R7) and find the beginning of the response later (useful when you use a DMA which runs in the background - of course, you can also read more bytes if that helps you when implementing a DMA routine, the card will just output a few more 0xFF's. As far as I remember, some cards will even repeat the response if you transceive a lot more bytes). In my case, 16 bytes were always enough (tested three cards: Kingston 32 GB, SanDisk 16 GB and Intenso 8 GB), but probably 19 are safer.

During the initialization sequence, the maximum allowed clock frequency is 400 kHz (100 - 400 kHz allowed). After initialization (when the clock is not in the "idle state" anymore), the clock speed can be increased to 25 MHz (for SD cards, for MMC: 20 MHz?).

"Idle" or "in idle state" doesn't mean idle in the form of "the card is ready", but more or less the opposite of what some would intuitively think: The card is brought into the "idle state" at startup - you need to initialize the card, so it reaches the running/data-transfer mode. The "idle" bit will be LOW once it is initialized and ready to transfer data, so when the card is ready to use, it is not "in_idle_state" anymore.

The initialization sequence

(Look at figures 7-1 or 7-2 in the specifications while reading for a better understanding.)

  1. As soon as you detect the SD card (e.g. using the card detect circuit), you need to wait at least 1 ms after the VDD of the card is settled ("device shall be ready to accept the first command within 1ms from detecting VDD min." --> Power Up Time of Card), so maybe try 2 ms as a start.

  2. Send at least 74 clocks with CS high, e.g. send 10-16 bytes of 0xFF to a dummy slave on the same SPI bus or send to SD card with CS manually pulled high. (The clock speed may be higher here as well but maybe start with 400 kHz to be on the safe side.)

(From here on, use CS normally, i.e. set active/LOW when transceiving data, set inactive/HIGH afterward.)

  1. Send CMD0 (go to idle state) 0x40 00 00 00 00 95 until you receive a valid response (which is 0x01). If you receive 0xFF, you need to transceive more data (response comes up to 8 bytes later) or resend CMD0 until you get a response. If you receive 0x09, that means you sent the command with the wrong CRC7 code (which is also ok except for CMD8, where a correct CRC is required). For any other response starting with a 0 bit, see " Format R1".

  2. Send CMD8 (interface condition), e.g. 0x48 00 00 01 AA 87 (see "4.3.13 Send Interface Condition Command (CMD8)"). The '1' in the argument states that we support 2.7-3.6 V, the 'AA' can be anything - it is a check pattern which you will receive again by the card and use to check if the connection and supply are good. Adjust the CRC7 if you change it. The reply is described in " Format R7" and "4.9.6 R7 (Card interface condition)". If the card's response starts with 0x05 (illegal cmd) or 0x0D (+CRC error), it is likely that you have an old V1.X SD Card (<= 2 GB) that doesn't support the command. Otherwise the response should be 0x01 .. .. .1 AA (. = 4 bits of don't care, 1 = voltage range 2.7-3.6 V, AA = our check pattern). If this worked (or the command is not supported), continue.

  3. Send CMD58 (read OCR) 7A 00 00 00 00 FD. The response will be R3 (" Format R3" and "5.1 OCR register") and should be something like 0x01 OCR[31..0]. Check if the voltage range allowed by the card (bits 15-23) is ok for your application. In my case, I only check if (OCR & 0x00380000) == 0x00380000, which means that bits 19-21 are HIGH, so the card supports at least 3.1 - 3.4 V. If this fits, continue (ignore the other bits for now). If it doesn't, the specifications say you shouldn't access the card anymore.

  4. Send CMD55 (to signalize the next command is an application-specific command, so ACMDxx), i.e. 0x77 00 00 00 00 65. Response should be 0x01 (or 0x00).

  5. Send ACMD41 (send operation condition). Set HCS to 1 (argument: 0x40000000) to support SDHC/SDXC cards (otherwise you would only support standard capacity cards <= 2 GB): 0x69 40 00 00 00 77. The response should be either 0x01 (which means we are still in "idle" state and need to repeat steps 6-7 (send CMD55+ACMD41) or 0x00, which means that the SD card left the "idle" state and is ready to operate. Only continue with step 8 if you receive 0x00.

  6. Send CMD58 again (read OCR) 7A 00 00 00 00 FD. This time the "Card power up status bit" should be 1. If it is, the Card capacity status (CCS) is valid. If the CCS bit is 1, we have a SDHC (2-32 GB) or SDXC (32GB-2TB) card. If it is 0, we have an SDSC card (<= 2 GB).

  7. You may now speed up the SPI bus from 400 kHz to up to 25 MHz (SD) or 20 MHz (MMC) and use the card for data transfers.

  8. It might be useful to read out some of the registers: SCR, CSD and CID (you can also do this as a test to evaluate if your read functions work before writing something to the card). CSD is probably most important because you can calculate the usable data size of the SD card. See "5.3 CSD register", "5.2 CID register" and "5.6 SCR register".

That's it!

In case you want to use DMA to communicate with the SD card, this can be a bit tricky: For the basic commands, you only need <= 19 bytes. For reading & writing, in addition to the commands, you need at least 1 start byte + 512 bytes for the actual data block + 2 CRC16 bytes but for reading, you also need some delay in advance before the SD card bothers to start sending data. Maybe you found a better solution, but I solved it by using three DMAs:

  • A) 128 bit/16 byte SPI TX/RX DMA for commands (might be advisable to use 19 or 20 bytes though)
  • B) SPI TX DMA for continuous data transfer with a variable transmission length
  • C) SPI RX DMA for continuous data transfer with a variable transmission length

For initialization and basic commands only use DMA A.

For reading/writing to data sectors or CSD/CID registers:

  • Use DMA A to transmit/receive the command/response. If the response is ok:
  • adjust the data transfer transmission size off DMA B/C depending on the command (e.g., for reading the CSD and CID registers, transceiving 128 bytes should be fine while for regular, single block read/write operations from/to the data sectors, I use 920 bytes), then start DMA B/C. Note that part of the read data can already start in DMA A, if the SD card replies quickly.

Other useful things

  • 2
    \$\begingroup\$ I've (partially) implemented a (very simple) SD-card reader in a more object-oriented style a few years ago. It's decently documented, maybe it can help someone: github.com/florianhumblot/Phat \$\endgroup\$ Dec 29, 2021 at 8:00
  • 1
    \$\begingroup\$ Kudos for doing this. We need comprehensive answers on Stack Exchange. \$\endgroup\$ Dec 29, 2021 at 17:11
  • 1
    \$\begingroup\$ Excellent answer! I wish I could upvote this more than once. \$\endgroup\$
    – TimB
    Dec 29, 2021 at 18:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.