SD CardA SD card is a storage device which contains a microcontroller. We can communicate with this microcontroller using different interfaces. The one I will describe here is the SPI interface. The SPI interface is flexible with the timing and requires only four connections. There are also many shields for the Arduino with ready solutions to access a SD card.

One problem accessing a SD card from an Arduino board is the different voltage. SD cards working with a supply voltage between 2.7 and 3.6 volts. The outputs of the Arduino board provide 5 volts which will kill the SD card instantly (do not try this). You need a voltage converter to convert the 5V output/inputs to a suitable voltage range.

I am using the Adafruit data logger shield, where this is already done. The SD card is connected to pins 10, 11, 12 and 13 of the Arduino board.


You will find all the detail specification on the website of the SD card association. Look in the downloads section for “simplified specifications”.

Put the SD Card into SPI Mode

To put the SD card into SPI mode you have to wait until the card is properly powered on, set data in and chip select to high and send at least 74 clocks. The speed of the SPI line should be below 400kHz to keep backward compatibility with slower SD cards. Because the Arduino controller itself takes some time until the code starts, we do not actually have to wait for the SD card.

	/// Initialize the SD Card
	inline SDCard::Status initialize()
		// Keep the start time to detect time-outs.
		uint16_t startTime = static_cast<uint16_t>(millis());
		uint32_t argument = 0;
		uint8_t result = 0;
		uint32_t responseValue = 0;

		// Initialize the SPI library

The initialization starts with the declaration of some variables we need later. In the next step we configure the chip select pin (pin 10) as digital output. The method onlyChipSelectEnd() sets the chip select line to high, this means the chip is not selected. At the end we initialize the SPI library calling SPI.begin().

	/// Only chip select.
	inline void onlyChipSelectBegin()

	/// Only chip select.
	inline void onlyChipSelectEnd()

Despite this methods using direct port manipulation, they actually just put the chip select line to low (onlyChipSelectBegin()) or high (onlyChipSelectEnd()).

Back to the initialization function. In the next step we set the speed of the SPI line to 250kHz, for backward compatibility. We are doing this be starting a new transaction and setting the speed bit-order (MSBFIRST) and mode (SPI_MODE0).

		// Speed should be <400kHz for the initialization. spiSettings = SPISettings(250000, MSBFIRST, SPI_MODE0); SPI.beginTransaction(spiSettings); // Send >74 clocks to prepare the card.

Next we select the chip (onlyChipSelectBegin()) and send some clocks (actually 800 clocks), using the spiWait() method, which just sends 1 bits and discards any received value. After the initial clocks, we unselect the chip (onlyChipSelectEnd()) and send another 16 clocks using spiWait() just to pass some time.

Before we continue with the initialization, I will explain the simple SPI helper functions I use in the code everywhere. There are four functions:

	/// Send a byte over the SPI bus
	inline void spiSend(uint8_t value)

	/// Receive a byte from the SPI bus.
	inline uint8_t spiReceive()
		return SPI.transfer(0xff);

	/// Skip a number of bytes from the SPI bus.
	inline void spiSkip(uint8_t count) {
		for (uint8_t i = 0; i < count; ++i) {

	/// Wait while sending clocks on the SPI bus
	inline void spiWait(uint8_t count)
		for (uint8_t i = 0; i < count; ++i) {

spiSend() sends a single byte over the SPI line to the SD card. spiReceive() receives a single byte over the SPI line from the SD card. Actually a SPI works always in both directions, while a bit is sent using the data out line, in the same time a bit is received using the data in line. Theoretically a device could simultaneously send and receive data using a SPI. Practically the SD card does either receive or send data, but normally not simultaneously.

spiSkip() Just ignores a number of bytes. They are received but not used. At least the spiWait() does exactly the same as spiSkip(). Basically I just forgot to merge this two methods into one. šŸ™‚

At this point, the SD card should have entered the SPI mode. The SPI mode is a little bit simpler than the native operating mode. For example checksums are usually just ignored, which makes everything simpler.

The SPI Mode

The principle to send a command is always the same. You send a byte with the command number, where the 6th and 7th bit are set to 0 and 1. It is followed by four bytes which is called the “argument” of the command. Usually it is interpreted as 32bit value. At the end of the command is the CRC for the command.

Now you have to wait for zero to eight bytes, until you receive the first byte of the response. There are commands with more than one byte of response, but most commands respond with one single byte. This response has the 7th bit always set to 0.

The chip select has to be kept low while sending the command, argument, checksum and until the response is received. If data is read, you have to keep the chip select low while receiving or sending the data. This is especially important if you have multiple devices on the same SPI line. You can only interrupt reading or writing between whole blocks. Even if you set chip select to high, most SD card will interpret the clock signal and you will get unexpected results.

Format of a Command

SD Card Commands

List of Relevant Commands

This is a list of commands which are relevant for my software implementation. You find all other commands, for example to write data to the SD card, in the specification.

Command ID Argument Response
Go Idle State CMD0 0 R1
Send If Cond CMD8 R7
Stop Transmission CMD12 0 R1
Set Block Length CMD16 Length R1
Read Single Block CMD17 Block R1
Read Multiple Blocks CMD18 Start Block R1
Application Command CMD37 0 R1
Read OCR CMD58 R3

There are also a set of “application” commands. They are sent by first sending the “application command” with the ID 37, then a second command with the ID of the application command itself. I use only the application command 41 (ACMD41), which sends and receives the capacity of the SD card.

Send a Command

For sending commands to the SD card, I first declare two enumerations. The first enumeration defines the different return types.

	/// The response type.
	enum ResponseType : uint16_t {
		ResponseMask = 0x00c0, ///< The mask for the responses.
		Response1    = 0 << 6, ///< One byte response.
		Response3    = 1 << 6, ///< One byte plus 32bit value
		Response7    = 1 << 6, ///< One byte plus 32bit value (supported voltage)

If the : uint16_t is unknown to you, this was added some time ago to the c++ standard to declare the integer type to which the enumeration value is converted, and how a enumeration value is stored.

I use a 16 bit value for the enumeration, but only use bit 6 and 7 to define the three different results. The reason why I use a 16bit integer is the enumeration of the commands shown below.

	/// The command to send.
	enum Command : uint16_t {
		Cmd_GoIdleState          = 0 | Response1, ///< Go idle state.
		Cmd_SendIfCond           = 8 | Response7, ///< Verify SD Memory Card interface operating condition.
		Cmd_StopTransmission     = 12 | Response1, ///< Stop reading blocks.
		Cmd_SetBlockLenght       = 16 | Response1, ///< Set the block length
		Cmd_ReadSingleBlock      = 17 | Response1, ///< Read one block.
		Cmd_ReadMultiBlock       = 18 | Response1, ///< Read multiple blocks.
		Cmd_ApplicationCommand   = 37 | Response1, ///< Escape for application specific command.
		Cmd_ReadOCR              = 58 | Response3, ///< Retrieve the OCR register.
		ACmd_Flag                = 0x100, ///< The flag for app commands.
		ACmd_SendOpCond          = 41 | ACmd_Flag | Response1, ///< Sends host capacity support information and activates the card's initialization process.

In the command enumeration I combine the result codes into the command codes. Actually I could also simple define constant variables for all the the commands and codes, but the enumeration is type checked by the compiler which adds extra safety. There is also a flag ACmd_Flag to mark the application commands.

The sendCommand() method sends a command to the SD card and also reads the response.

	/// Send a command to the SD card synchronous.
	/// @param command The command to send.
	/// @param argument A pointer to the 4 byte buffer for the argument.
	/// @param responseValue A ponter to the variable where to save the 32bit value for R3/R7 responses. 0 = ignore.
	inline uint8_t sendCommand(Command command, uint32_t argument, uint32_t *responseValue = 0)
		uint8_t result;
		uint8_t responseValues[4];
		// Check if this is an app command
		if ((command & ACmd_Flag) != 0) {
			// For app commands, send a command 55 first.
			spiSend(55 | 0x40);
			for (uint8_t i = 0; ((result = spiReceive()) & 0x80) && i < 0x10; ++i); // now send the app command. } // Send the command. spiSend((command & 0x3f) | 0x40); spiSend(argument >> 24);
		spiSend(argument >> 16);
		spiSend(argument >> 8);
		uint8_t crc = 0xff;
		if (command == Cmd_GoIdleState) {
			crc = 0x95;
		} else if (command == Cmd_SendIfCond) {
			crc = 0x87;
		// There can be 0-8 no command return values until the return is received.
		for (uint8_t i = 0; ((result = spiReceive()) & 0x80) && i < 0x10; ++i);
		const uint8_t response = (command & ResponseMask);
		if (response != Response1) {
			// Read the next 4 bytes
			for (uint8_t i = 0; i < 4; ++i) {
				responseValues[i] = spiReceive();
			// If there is a pointer, assign the response value (MSB).
			if (responseValue != 0) {
				*responseValue =
					(static_cast<uint32_t>(responseValues[0]) << 24) |
					(static_cast<uint32_t>(responseValues[1]) << 16) |
					(static_cast<uint32_t>(responseValues[2]) << 8) |
		return result;

About the CRC in SPI Mode

The CRC codes are in most cases ignored, except for the “go idle state” command and for the “send if cond” command where (for unknown reasons)[1] the CRC has to be correct . Because both commands have a fixed argument of 0, the CRC is precalculated and is sent for these commands. For all other commands, a fake CRC of 0xFF is sent (bit 0 have always to be 1).

Wait Until the Card is Ready

Before a command can be send, the application should check if a 0xFF can be read, which indicates the card is ready. There are two methods to implement this. The first method waits until the card is ready for a command. You can pass a timeout in milliseconds as argument, if the card is ready in time, the method returns true. If the time runs out you get false.

	/// Wait until the chip isn't busy anymore
	inline bool waitUntilReady(uint16_t timeoutMillis) {
		uint16_t startTime = millis();
		do {
			const uint8_t result = spiReceive();
			if (result == 0xff) {
				return true;
		} while ((static_cast<uint16_t>(millis()) - startTime) < timeoutMillis);
		return false;

There is a second method, which combines the sendCommand and waitUntilReady command. To make sure the SD card is ready before the command is sent.

	/// Wait until the card is ready, then send the command.
	/// Same parameters as sendCommand().
	inline uint8_t waitAndSendCommand(Command command, uint32_t argument, uint32_t *responseValue = 0)
		return sendCommand(command, argument, responseValue);

As you can see, the result of the waitUntilReady is ignored, because the sendCommand will anyway fail in this case.

The Response

Depending in the command, there are different responses. The specification is calling them R1, R3 or R7. There are more responses, but these are the most important. See the following illustration how the responses look like:

SD Card Responses

The R1 response is just one byte. The 7th bit is set to 0 to set it apart from the “busy” response were the 7th bit is set to 1. All other bits, except bit 0 indicate an error.

The R3 response starts like the R1 response with the status byte. It is followed by four bytes with the current operation condition register (OCR).

The R7 response is a special one. It is only used in combination with the “send if cond” command CMD8. You send a condition to the card, which responds with the same condition if it is supported. You also send a “check pattern” in the lowest 8 bits, which the card responds to indicate a success.

Initialisation Continued

Let us continue with the initialize() method. At the current stage the SD card is in SPI mode. Now we can send commands to the card. As first command we send the “go idle state” command, which start the initialisation process of the card. This can take some time.

		// Send the CMD0
		while (waitAndSendCommand(Cmd_GoIdleState, 0) != R1_IdleState) {
			if ((static_cast<uint16_t>(millis())-startTime) > initTimeout) {
				error = SDCard::Error_TimeOut;
				goto initFail;

The second command we send is the command “send if cond”, which is only supported with newer cards (SD card version 2). We have to send special argument 0x000001AA and a correct CRC. The 0x_____1__ part of the argument checks if the voltage of 2.7ā€“3.6V is accepted. The 0x______AA part is a random check pattern. This could be any value.

If the command is supported and the voltage range is accepted, the card responds with the actual voltage range and our check pattern (0xAA). I my implementation I just compare the check pattern I sent.

		// Try to send CMD8 to check SD Card version.
		result = waitAndSendCommand(Cmd_SendIfCond, 0x01aa, &responseValue);
		if ((result & R1_IllegalCommand) != 0) {
			cardType = CardTypeSD1;
		} else {
			if ((responseValue & 0x000000ff) != 0x000000aa) {
				error = SDCard::Error_SendIfCondFailed;
				goto initFail;
			cardType = CardTypeSD2;

For newer cards, we send the application command “send op cond” with bit 30 set. This is the HCS flag which enables fast transfer rates. By getting this bit to 1, we indicate that we support SDHC and SDCX cards.

		// Send the ACMD41 to initialize the card.
		if (cardType == CardTypeSD2) {
			argument = 0x40000000; // Enable HCS Flag
		} else {
			argument = 0x00000000;
		while (waitAndSendCommand(ACmd_SendOpCond, argument) != R1_ReadyState) {
			if ((static_cast<uint16_t>(millis())-startTime) > initTimeout) {
				error = SDCard::Error_TimeOut;
				goto initFail;

Next, I check if the card is a SDHC card. This is done with the “read OCR” command. Each SD card has an “operation condition register” short OCR, which contains interesting flags about the current condition of the SD card ā€“ and can also be used to check the type of the card. Actually I do this for fun, there is no real use for the check. When I implemented the initialisation, I followed the flow diagram in the specification.

		// Check if we have a SDHC card
		if (cardType == CardTypeSD2) {
			if (waitAndSendCommand(Cmd_ReadOCR, 0, &responseValue) != R1_ReadyState) {
				error = SDCard::Error_ReadOCRFailed;
				goto initFail;
			// Check "Card Capacity Status (CCS)", bit 30 which is only valid
			// if the "Card power up status bit", bit 31 is set.
			if ((responseValue & 0xc0000000) != 0) {
				cardType = CardTypeSDHC;

On success, I set the block size to 512 byte. Newer cards have to block size fixed to 512 bytes. The set block size command is ignored. I set the block size to support old cards. Personally I do not own such old SD cards, so I could never test if the code is working with non SDHC cards or even SD cards version 1.

		// Set the block size to 512byte.
		if (waitAndSendCommand(Cmd_SetBlockLenght, blockSize) != R1_ReadyState) {
			error = SDCard::Error_SetBlockLengthFailed;
			goto initFail;


On success, I set the chip select high, and end the SPI transaction. The rest of the method sets the SPI settings which are used for further SPI transactions (if enabled), debug outputs and error handling. I also set SPI.usingInterrupt(255), because I used the same code from timer interrupts.

		// now rise the clock speed to maximum.
		spiSettings = SPISettings(32000000, MSBFIRST, SPI_MODE0);

		// Debug output
		SDC_DEBUG_PRINT(String(F("Card type ")));
		switch (cardType) {
			case CardTypeSD1: SDC_DEBUG_PRINTLN(String(F("SD1"))); break;
			case CardTypeSD2: SDC_DEBUG_PRINTLN(String(F("SD2"))); break;
			case CardTypeSDHC: SDC_DEBUG_PRINTLN(String(F("SDHC"))); break;

		// Make sure we can use SPI in the timer interrupt.
		SPI.usingInterrupt(255); // Using from timer.
		return SDCard::StatusReady;

		return SDCard::StatusError;

This is everything you need to initialise the SD card and prepare it to read from it.

Continue with: Accessing the SD Card (Part 3)

1. As martin pointed out in a comment. The CRC codes for the initial commands are required to avoid an accidental initialisation of the SD card from static or other currents.