You are on page 1of 13

Simple FAT and SD Tutorial Part 4

In the previous parts of this tutorial, we have built both an FAT library as well as a test program
to communicate with the SD card. Now it’s time to wrap it up with final parts of code to read a
file from SD card and print it out.
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:

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


2. Send command 0x40 (it should return 1)
3. Send command 0x41 until it returns 0 (it returns 1 while the card is busy)
4. Send command 0x50 to set read block size (as well as write block size)
Here’s the code to do just that (sd_sector and sd_pos will be used shortly):

unsigned long sd_sector;


unsigned short sd_pos;

char SD_init() {
char i;

// ]r:10
CS_DISABLE();
for(i=0; i<10; i++) // idle for 1 bytes / 80 clocks
SPI_write(0xFF);
// [0x40 0x00 0x00 0x00 0x00 0x95 r:8] until we get "1"
for(i=0; i<10 && SD_command(0x40, 0x00000000, 0x95, 8) != 1; i++)
_delay_ms(100);

if(i == 10) // card did not respond to initialization


return -1;

// CMD1 until card comes out of idle, but maximum of 10 times


for(i=0; i<10 && SD_command(0x41, 0x00000000, 0xFF, 8) != 0; i++)
_delay_ms(100);

if(i == 10) // card did not come out of idle


return -2;

// SET_BLOCKLEN to 512
SD_command(0x50, 0x00000200, 0xFF, 8);

sd_sector = sd_pos = 0;

return 0;
}

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:

// TODO: This function will not exit gracefully if SD card does not do what it should
void SD_read(unsigned long sector, unsigned short offset, unsigned char * buffer,
unsigned short len) {
unsigned short i, pos = 0;

CS_ENABLE();
SPI_write(0x51);
SPI_write(sector>>15); // sector*512 >> 24
SPI_write(sector>>7); // sector*512 >> 16
SPI_write(sector<<1); // sector*512 >> 8
SPI_write(0); // sector*512
SPI_write(0xFF);

for(i=0; i<10 && SPI_write(0xFF) != 0x00; i++) {} // wait for 0

for(i=0; i<10 && SPI_write(0xFF) != 0xFE; i++) {} // wait for data start

for(i=0; i<offset; i++) // "skip" bytes


SPI_write(0xFF);

for(i=0; i<len; i++) // read len bytes


buffer[i] = SPI_write(0xFF);

for(i+=offset; i<512; i++) // "skip" again


SPI_write(0xFF);

// skip checksum
SPI_write(0xFF);
SPI_write(0xFF);

CS_DISABLE();
}
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:
void fat16_seek(unsigned long offset) {
sd_sector = offset >> 9;
sd_pos = offset & 511;
}

char fat16_read(unsigned char bytes) {


SD_read(sd_sector, sd_pos, fat16_buffer, bytes);
sd_pos+=(unsigned short)bytes;

if(sd_pos == 512) {
sd_pos = 0;
sd_sector++;
}

return bytes;
}
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:
int main(int argc, char *argv[]) {
char i, ret;
short offset = 0x1B0;

USARTInit(64); // 20 MHz / (16 * 19200 baud) - 1 = 64.104x

SPI_init();

uwrite_str("Start\r\n");

if(ret = SD_init()) {
uwrite_str("SD err: ");
uwrite_hex(ret);
return -1;
}

if(ret = fat16_init()) {
uwrite_str("FAT err: ");
uwrite_hex(ret);
return -1;
}

if(ret = fat16_open_file("README ", "TXT")) {


uwrite_str("Open: ");
uwrite_hex(ret);
return -1;
}

while(fat16_state.file_left) {
ret = fat16_read_file(FAT16_BUFFER_SIZE);
for(i=0; i<ret; i++)
USARTWriteChar(fat16_buffer[i]);
}

return 0;
}
Here’s the output from our glorious test program:

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
Posted onApril 27, 2012AuthorJoonas PihlajamaaCategoriesElectronicsTagsatmega88, fat, sd,sdhc, tutorial, uart

33 thoughts on “Simple FAT and SD Tutorial Part 4”

1. Pingback: Logic analysis with Bus Pirate » Code and Life

2. Jacksays:
May 17, 2012 at 20:07

Hi – A link to Part 2 seems to be missing. (I found it anyway, but presumably


a snipe hunt is’nt what you intended…

Useful stuff – Thanks for putting it up.

Jack
REPLY

1. jokkebksays:
May 17, 2012 at 22:57

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.
REPLY

3. Pingback: World’s Simplest Logic Analyzer for $5 » Code and Life

4. David Cooksays:
May 28, 2012 at 07:38
Thank you very much, Mr. Pihlajamaa, for this wonderful tutorial.

You save beginners a lot of time by helping them past the sticking points.

Dave Cook
REPLY

5. stephensays:
September 27, 2012 at 09:11

good lesson
REPLY

6. nyoman yudisays:
December 12, 2012 at 03:23

Thanks … finally i made it work after 2 days


i tweak your example and made attiny2313 play wav
the video is here http://www.youtube.com/watch?v=N-ZO4oiwpXY
once again …THANK YOU VERY MUCH
REPLY

1. jokkebksays:
December 27, 2012 at 15:27

You’re welcome. Very cool to see such a project! I haven’t yet tried to output any audio with
MCU projects, your example makes me want to try it out. :)
REPLY

7. Gokaysays:
December 31, 2012 at 22:45

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.

CMD 40 FF 01 FF FF FF FF FF FF FF FF
CMD 41 FC 07 FF FF FF FF FF FF FF FF
CMD 41 FC 07 FF FF FF FF FF FF FF FF
CMD 41 FF 01 FF FF FF FF FF FF FF FF
CMD 41 FF 00 FF FF FF FF FF FF FF FF
CMD 50 FF 00 FF FF FF FF FF FF FF FF

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)

{
SD_CS_DEASSERT();
rs232_Stream("0x05 errorrrrrr…");
return 1;
}

Thank you !
REPLY

1. jokkebksays:
April 20, 2013 at 17:05

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…
REPLY

8. ganeshsays:
January 9, 2013 at 17:40

thanks very useful work


REPLY

9. Ahmed Eldamarawysays:
April 30, 2013 at 02:32

thank you for the nice and organized presentation that you made for all of us.
i hope you can also explain how to write to on FAT16 SDs.
REPLY

1. jokkebksays:
June 9, 2013 at 15:49

Thanks! I’ll probably write a post on FAT writing in the summer months, stay tuned. :)
REPLY

10. andrazsays:
August 25, 2014 at 19:15
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…
REPLY

1. Joonas Pihlajamaasays:
August 25, 2014 at 20:53

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.
REPLY

1. andrazsays:
August 26, 2014 at 12:24

I understand. I have read the previous part and SD_command is declared as void so it doesn’t
return anything.
REPLY

1. Joonas Pihlajamaasays:
August 26, 2014 at 12:56

Ah sorry, my bad. The fat88.c in the updated project zip (link early on in this part 4) contains a
version that returns unsigned char.
REPLY

1. andrazsays:
August 27, 2014 at 19:28

OK great. Thank you.

11. tzsays:
January 29, 2015 at 01:57

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.
REPLY

12. Omkarsays:
September 1, 2015 at 15:31

Please give me the link for part1 and part 2


Also if this code is modified for pic16 wud it work the same?
REPLY

1. Joonas Pihlajamaasays:
September 1, 2015 at 19:41

You can find the link to first part in the beginning of this article:

http://codeandlife.com/2012/04/02/simple-fat-and-sd-tutorial-part-1/
In the end of part 1, there is a link to part 2, and so on. :)

Pic16 should work the same, unless its voltages are different from an AVR chip.
REPLY

1. Omkarsays:
September 4, 2015 at 13:51

Please could u tell me the modifications for a fat32 sd card


REPLY

1. Joonas Pihlajamaasays:
September 6, 2015 at 20:11

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:

https://www.pjrc.com/tech/8051/ide/fat32.html
REPLY

13. Omkarsays:
September 5, 2015 at 14:39

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
REPLY

1. Joonas Pihlajamaasays:
September 6, 2015 at 20:10

Hi! Good question. I took a look into my source files and there is a version of SD_command in
fat88.c which does indeed return a value. So you should check that out. :)
REPLY

1. Omkarsays:
September 9, 2015 at 14:48

You have used the fat16_read function in which there is a variable fat_buffer but it is not
defined anywhere.
So where it is defined?
Thank You.
REPLY

14. Pingback: Simple FAT and SD Tutorial Part 3 | Code and Life
15. Pingback: Picoscope 2208B MSO Review – Code and Life

16. Jsays:
June 22, 2017 at 15:19

One simple question. It irritaes me, maybe i got something wrong, but why are you defining a
“short offset = 0x1B0;” in the main method, that is not used at all?
REPLY

1. Joonas Pihlajamaasays:
June 23, 2017 at 12:58

Probably leftover from some earlier iteration of my code. You can (try to) ignore it. :)
REPLY
17. Vinodsays:
April 11, 2018 at 16:15

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.

Thanks a lot.
Vinod
REPLY

18. Sheza Asays:


June 28, 2018 at 21:16

Hello,

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.
REPLY

1. Joonas Pihlajamaasays:
June 28, 2018 at 23:37

You can look at the Makefile inside the zip, it contains instructions which files make up which
.hex file (meant for compiling the project from command line):

fat88.elf: fat88.o fat16.o

This means fat88.elf (intermediate file to get the .hex) depends on fat88.o and fat16.o, both of
which the make utility can compile from corresponding .c files. So fat88.hex uses fat88.c and
fat16.c.

Similarly, test_spi.hex can be compiled from test_spi.c and fat16.c

It’s a long time since I made this, so no guarantees if that will work on a recent Atmel Studio
without errors, sorry…

You might also like