This part will utilize the previously made FAT library with a few tweaks – it turned out that some byte and word-sized values needed explicit casting to unsigned long so that the calculations worked as they should. The new library and all code shown here can be found from the updated project zip.

Initializing the SD card automatically

Instead of manually pressing 1, 2, and 3, we’ll now write a single function to initialize the SD card to SPI mode. For standard SD (not high capacity SDHC) cards, it’s enough to:

Send clock pulses for 80 cycles (“read” 10 bytes)

Send command 0x40 (it should return 1)

Send command 0x41 until it returns 0 (it returns 1 while the card is busy)

Reading data from SD card

Reading data from SD cards (as well as writing it) is done in discrete blocks. The read command is given the offset of data to be read, and it returns X bytes, where X is the size of transfer block set during initialization. For normal SD cards, block size can be set to smaller than the 512 used in the initialization code, but for higher capacity SDHC and SDXC cards the block size is always 512 bytes, so I decided to use that so there would be less code changes in case I wanted to support SDHC cards later on.

Because I wanted to support systems that have only 128 or 256 bytes of SRAM (if you remember from earlier parts, the FAT library uses a 32 byte buffer), reading all 512 bytes into memory is not possible. Instead, we capture a small window of the data. For example, to read bytes 32-63 (zero-based indexing, so 32 bytes starting from 33rd byte) from a 512 byte sector, we issue a read command for the whole sector, but discard the first 32 bytes, then capture the next 32 bytes, and then again skip the remaining 448 bytes plus 2 CRC bytes. Here’s the read command:

The read command (0x51) is much like all other SD commands and it takes a 32-bit address – we use the sector number and multiply it by 512 (sector<<9). Note that SDHC cards use sector addressing and not byte addressing, so this would not be needed for SDHC. After sending the command, SD card responds with “0” to indicate the command has been received, and then it sends 0xFF until data is ready, at which point it sends 0xFE, then the 512 bytes of data and finally 2 bytes of checksum. Pretty straightforward!

Providing FAT library disk functions

As you probably recall, we now need to provide FAT library two functions to navigate around the SD card: fat16_seek() and fat16_read(). Now that we have our flexible SD_read() function, it’s really just a matter of keeping two pointers, current SD sector (sd_sector) and offset within that (sd_offset), which we just set when the library wants to seek, and pass to SD_read when the FAT library wants to read bytes (and increment the pointers afterwards). Without further ado, here are the wrapper functions:

Note that the read function can be very simple because we know the reads never cross sector boundaries – the FAT library reads 32 bytes at a time, and 512 is a multiple of that. If that was not true, the read function would need some additional logic to deal with it.

Wrapping it all up

We now have all the helper functions we need. We have the UART. We speak through the SPI. We command the SD card. We understand the FAT. Here’s a simple main function to enjoy our accomplishments through reading a file called README.TXT and displaying it over UART:

That’s all! You can now read SD cards with your ATmega. And best of all, you should also understand every nook and cranny of the code used to do that. Pretty cool! If you have a ATmega with 1 kB of SRAM, you could quite easily implement SD writing, or write some nice code for navigating the SD card directories. Or maybe you’d like to upgrade the library to support FAT32 or SDHC cards. The choice is yours!

Thanks for reading the tutorial, I hope you found it useful. Remember to subscribe to the feed for more tutorials and interesting projects in the future. And of course, all feedback is very welcome, too!

Published by

Joonas Pihlajamaa

Coding since 1990 in Basic, C/C++, Perl, Java, PHP, Ruby and Python, to name a few. Also interested in math, movies, anime, and the occasional slashdot now and then. Oh, and I also have a real life, but lets not talk about it!
View all posts by Joonas Pihlajamaa

Hmm, there is a link to part 2 in the very beginning in this article, although a bit hidden. It would be nice to have some kind of automatic “table of contents” for the full tutorial set, but so far I have just linked every post to the previous part.

Anyway, now that you reminded me, I’ll also add links to the next part to the end of each part.

Hi,it is very fruitful tutorial.As to me I have a problem with the writing single block(512 byte)of data to SD card.I initialized SD card (CMD0,0x95)(CMD1,0xFF) and set the block length(CMD16,0xff) successfully.
Below you can see the result.

The problem is that when i send ‘Single Block Write’ Command to SD card I get an 0x00 response.(i know it is OK!) after I send data token which is 0xFE and send 512 bytes chunks of data and send two bytes CRC
consecutively. I cannot get any response whether data is accepted or not.
Here is below you can see the result.

CMD 58 FF 00 FF FF FF FF FF FF FF FF.

And below you can find my shortened code.

response=send_SDCommand(WRITE_SINGLE_BLOCK,startBlock<<9,0xFF);

if(response!=0)
{
return response;

}

SD_CS_ASSERT();// CS Low
//????????????what is going on here
SPI_transmit(0xFE); data token. we have to send it before.
for(i=0;i<512;i++)

SPI_transmit('A');

SPI_transmit(0xFF); // \
SPI_transmit(0xFF); // \ 16 bit dummy CRC

//Every data block written to the card is acknowledged by a data response token. It isone byte long and has the following format
// XXX0SSS1 // S= STATUS bits(3)
//if S's 010= Data accepted.
//if S's 101’—Data rejected due to a CRC error.
response=SPI_transmit(0xFF); //110’—Data Rejected due to a Write Error.
if((response & 0x1F)!=0x05)

You seem to have done quite a good job on the writing part, congratulations for that! Unfortunately I haven’t tried writing SD cards myself, so can’t help you much. Maybe some microcontroller forum would have people who could help?

Only thing that springs to my mind that you might want to check is that if one needs to send clock pulses to the card afterwards so it can do processing, maybe if you send some bogus data after write command, you get a response after a while? It’s been a year since I tinkered with SD and SPI so I’m most likely very off in this one, though…

Hi. Thank you for this tutorial it has helped a lot. I have a problem though. In fuction sd_init the sentence for(i=0; i<10 && SD_command(0x40, 0x00000000, 0x95, 8) != 1; i++)
does not compile since SD_command is void. We get R1 written in buffer. I allways get the response: FF01FFFFFFFFFFFF Is that correct and what should function SD_command return?
I apologise if I sound stupid, but I am very new to this…

No problem. The only issue is that in 30 months, I’ve also mostly forgotten everything about this. The SD_command is covered in previous part of the tutorial, you might want to try that one first, part 3 also covers at least partially the supposed output.

I also wrote a SD card driver – mainly for openlog, but I have a few variations. The lib is at https://github.com/tz1/sparkfun/tree/master/fat32lib – but there’s also “hyperlog” that adds an external SPI RAM to the mix since part of the SD card spec is it can take up to 250mS to do housekeeping, so you would lose data at higher speeds.

I might’ve originally included some links to FAT32, but basically you’ll need to read the FAT32 specs yourself and see what the difference is, I haven’t done that myself. Quick google on “fat32 specification” gave for example this page:

The sd_command is a void function so how can it return any value
You have used the return value from the sd_command function in the for loop in the sd_init function.
Please tell me any solution..
Thanks a lott

Very useful tutorial for beginners. I was just searching for this king of elaborate stuff. I plan to do it on MSP430 chip.
I will start with RAW read writes and later switch over to FAT32 for readability in a PC.

I am trying to implement this using an ATmega 2560. I changed the relevant parameters in fat88.c to make it compatible with my device.
However, to run this project as is, what files from your zip would I use? If I use all of them my compiler throws errors since the same functions are defined in multiple files/repeated.