Wednesday, July 13, 2022

SD card tutorial - Interfacing an SD card with a microcontroller over SPI (part 2 of 2)

This is part 2 of the tutorial on SD card specifications. In part 1 of the tutorial we made functions necessary for SPI communication and card initialization in SPI mode. At the end of this second part you should be able to read and write an SD card.

Contents

5. Reading/Writing Data Blocks

5.1 Block Length

5.2 CMD17 – Reading a Single Block

5.2.1 Read Errors

5.3 CMD24 – Writing a Single Block

6. Conclusion or Confusion

7. Links 


SD card tutorial - Interfacing an SD card with a microcontroller over SPI (part 2 of 2)


5. Reading/Writing Data Blocks

5.1 Block Length

Block length can be set in Standard Capacity SD cards using CMD16 (SET_BLOCKLEN) however for SDHC and SDXC cards, the block length is always set to 512 bytes. Since nowadays most if not all cards are of high capacity type, we will only consider the latter in this tutorial.

5.2 CMD17 – Reading a Single Block

The command for reading a single block is CMD17 – READ_SINGLE_BLOCK with the format shown below.

SD Card SPI - CMD17
 

SDHC and SDXC are block addressed and since a block has a set size of 512 bytes, an address of 0 (block 0) will read back bytes 0-511, address 1 will read back 512-1023 and so on. On the other hand, the Standard Capacity SD cards are byte addressed.

The flow of a single block read is shown in the diagram below:

SD Card SPI - Single Block Read

After sending CMD17 the card will respond with R1 followed by a data block suffixed with a CRC. To know when the data is actually starting a start token is send by the card right before the single data block. The start token is 0b11111110 (0xFE) as depicted in the next image.

SD Card SPI - Start Token

A function for reading a single block can be implemented like this:

uint8_t SD_Buffer[512 + 1]; // reserve 1 byte for the null
uint8_t SD_ResponseToken;

// CMD17 – READ_SINGLE_BLOCK
// For a 16MHz oscillator and SPI clock set to divide by 2. 
// Thus, to get the number of bytes we need to send over SPI to reach 100ms, we do 
// (0.1s * 16000000 MHz) / (2 * 8 bytes) = 100000
#define CMD17                   17
#define CMD17_CRC               0x00
#define SD_MAX_READ_ATTEMPTS    (0.1 * F_CPU) / (2 * 8)

/*______________________________________________________________________________________________
	Read a single block of data
				
	addr	32-bit address
					
	buff	a buffer of at least 512 bytes to store the data in
	
	token	0xFE – Successful read
		0×0X – Data error (Note that some cards don't return 
			an error token instead timeout will occur)
		0xFF – Timeout
_______________________________________________________________________________________________*/
uint8_t sd_read_single_block(uint32_t addr, uint8_t *buf){
	uint8_t res1, read = 0;
	uint32_t readAttempts;
	
	if(SD_CardType == SD_V1_SDSC) addr *= 512;

	// set token to none
	SD_ResponseToken = 0xFF;

	sd_assert_cs();
	sd_command(CMD17, addr, CMD17_CRC);

	// read R1
	res1 = sd_read_response1();

	// if response received from card
	if(res1 != 0xFF){
		// wait for a response token
		// the host should use 100ms timeout (minimum) for single and multiple read operations
		readAttempts = 0;

		while(++readAttempts != SD_MAX_READ_ATTEMPTS){
			if((read = SPI_ReceiveByte()) != 0xFF) break;
		}

		// if response token is 0xFE
		if(read == 0xFE){
			// read 512 byte block
			for(uint16_t i = 0; i < SD_BUFFER_SIZE; i++) *buf++ = SPI_ReceiveByte();
			
			// add null to the end
			*buf = 0;

			// read and discard 16-bit CRC
			SPI_ReceiveByte();
			SPI_ReceiveByte();
		}

		// set token to card response
		SD_ResponseToken = read;
	}

	sd_deassert_cs();
	return res1;
}

The function needs two arguments: a 32-bit data address and a pointer to a buffer of at least 512 bytes to store the received data. Here the buffer is called SD_Buffer. The response token will be stored in the 1-byte global variable called SD_ResponseToken. When no error occurred the response token will be 0xFE otherwise it will hold the token error discussed in the next section.

After sending the read command, the response R1 is checked if is not 0xFF meaning the card responded and then we poll the card until we receive a token or timeout occurs.

According to physical spec at section 4.6.2.1:

The host should use 100ms timeout (minimum) for single and multiple read operations

To implement this timeout we can’t simply use a blocking function such as delay_ms() and a timer would be too expensive. Instead we could count the number of bytes sent over SPI and the number depends on the frequency at which the microcontroller operates and the SPI speed. The formula is already implemented in the given code above. For a 16MHz oscillator and SPI clock set to divide by 2 to get the number of bytes to send over SPI to reach 100ms, we do:

(0.1s * 16000000 MHz) / (2 * 8 bytes) = 100000

If the response token equals 0xFE then the 512 bytes data block is put into the main buffer. The 2 bytes CRC must be read even if not used.

5.2.1 Read Errors

If an error occurs when the card tries to retrieve the requested data, it will send a data error token instead of a data start token. This flow is shown in the diagram below.

SD Card SPI - Read Data Error

The format for the data error token is shown below.

SD Card SPI - Data Error Token

Error: A general or an unknown error occurred during the operation.

CC Error: Internal card controller error.

Card ECC Failed: Card internal ECC (error correction) was applied but failed to correct the data.

Out of Range: data address argument is out of range.

The following defines can be used to check the error bits:

// Data Error Token
#define SD_TOKEN_OOR(X)		X & 0b00001000	// Data Out Of Range
#define SD_TOKEN_CECC(X)	X & 0b00000100	// Card ECC Failed
#define SD_TOKEN_CC(X)		X & 0b00000010	// CC Error
#define SD_TOKEN_ERROR(X)	X & 0b00000001

If the read function return a response R1 with an error, the data error token can be checked for more details on the cause of error(s). Not every card will return an error token instead they will timeout. If no error occurs R1 will be 0.

5.3 CMD24 – Writing a Single Block

The command for writing a single block is CMD24 – WRITE_BLOCK.

SD Card SPI - CMD24

The flow for a single block write is shown below.

SD Card SPI - Single Block Write

First send the write block command, wait for a response (R1), then send a start block token (0xFE) followed by 512 bytes of data to be written. After this we will wait for new type of token from the card: a data response token.

Data Response Token

If the card accepts the data the response token will be 0bxxx00101. The card will then send busy tokens (0×00) until it has finished writing the data.

Here is the function for writing a single block:

// CMD24 – WRITE_BLOCK
// For a 16MHz oscillator and SPI clock set to divide by 2.
// Thus, to get the number of bytes we need to send over SPI to reach 250ms, we do
// (0.25s * 16000000 MHz) / (2 * 8 bytes) = 250000
#define CMD24                   24
#define CMD24_CRC               0×00
#define SD_MAX_WRITE_ATTEMPTS   (0.25 * F_CPU) / (2 * 8)
/*______________________________________________________________________________________________
	Write a single block of data
				
	addr	32-bit address
					
	buff	512 bytes of data to write from
	
	token	0×00 – busy timeout
		0×05 – data accepted
		0xFF – response timeout
_______________________________________________________________________________________________*/
uint8_t sd_write_single_block(uint32_t addr, uint8_t *buf){
	uint8_t res1;
	uint32_t readAttempts;
	
	if(SD_CardType == SD_V1_SDSC) addr *= 512;

	// set token to none
	SD_ResponseToken = 0xFF;

	sd_assert_cs();
	sd_command(CMD24, addr, CMD24_CRC);

	// read response
	res1 = sd_read_response1();

	// if no error
	if(res1 == 0){
		// send start token
		SPI_SendByte(0xFE);

		// write buffer to card
		for(uint16_t i = 0; i < SD_BUFFER_SIZE; i++) SPI_SendByte(buf[i]);

		// wait for a response (timeout = 250ms)
		// maximum timeout is defined as 250 ms for all write operations
		readAttempts = 0;
		while(++readAttempts < SD_MAX_WRITE_ATTEMPTS){
			if((res1 = SPI_ReceiveByte()) != 0xFF) break;
		}

		// if data accepted
		if((res1 & 0x1F) == 0x05){
			// set token to data accepted
			SD_ResponseToken = 0x05;

			// wait for write to finish (timeout = 250ms)
			readAttempts = 0;
			while(SPI_ReceiveByte() == 0x00){
				if(++readAttempts > SD_MAX_WRITE_ATTEMPTS){
					SD_ResponseToken = 0x00;
					break;
				}
			}
		}
	}

	sd_deassert_cs();
	return res1;
}

If after sending the write command 24 the response R1 is not an error we send the start token and then start transmitting the data in the buffer and then wait for the card to send a data response token.

Data accepted tokens are 0bxxx00101 (where x is a don't care), so we mask the upper three bits and see if it equals 0b00000101 (0×05). Finally we wait for the card to finish writing the data. The timeout is calculated like we did in the write function except this time the recommended timeout is 250ms instead of 100ms.

According to physical spec at section 4.6.2.2:

Maximum length of busy is defined as 250 ms for all write operations

There are 4 cases with this function that we should check after calling it:

  • R1 != 0×00 → Error writing block (parse R1 for details)

  • R1 == 0×00 and token == 0×05 → Success

  • R1 == 0×00 and token == 0×00 → Busy Signal timeout

  • R1 == 0×00 and token == 0×FF → No response after R1

At this point we now have all the necessary functions to initiate the SD card in SPI mode and read/write single data blocks.

 

6. Conclusion or Confusion

If you don’t understand it completely that’s normal; sometimes it takes reading different resources to clear things out.

In this tutorial I have covered the main aspects of the SD card specifications but if you want to know what other functionality they have, such as CRC and card encryption, take a look at the SD specifications manual linked below. I also recommend reading the one from SanDisk Secure Digital that I personally found easier to follow. I have included the PDFs as a download link because they initially didn’t have bookmarks so I added most of them to be able to navigate easier using a PDF reader.

Let us know if this was useful, or if you have any questions leave them in the comment section below.

You can also download this tutorial in a PDF format at the link below. If you find it useful, consider a small donation at the link provided.

SD card tutorial - Interfacing an SD card with a microcontroller over SPI, v1.0, 2022.pdf

 

7. Links

 

2 comments:

  1. could you also provide sample code you used to write data into the SD card?

    ReplyDelete
    Replies
    1. Sure. Here is an example on how to use the "sd_write_single_block" function to write data to sd card. This assumes that a block consists of 512 bytes. Although this is usually the case, some cards could have a block defined as 1024 bytes for example.

      // 512 bytes of data to be written on a single block
      // could be populated in a for loop
      uint8_t data[512];

      // SD card block address
      uint32_t block_address = 0;

      // Write on block 0 (from byte 0 to byte 511 on the card)
      sd_write_single_block(block_address, data);

      // Increment address by one block
      block_address++;

      // Write on block 1 (from byte 512 to byte 1024 on the card)
      sd_write_single_block(block_address, data);

      But without a file system, the card is used like a huge EEPROM memory. To keep track on where the data is, you need a file system such as FAT16 or FAT32. If you need a library that integrates both an SD card driver and a file system you can find it here: https://www.programming-electronics-diy.xyz/2022/07/sd-memory-card-library-for-avr.html

      Delete