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 0×40 (it should return 1)
  3. Send command 0×41 until it returns 0 (it returns 1 while the card is busy)
  4. Send command 0×50 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 (0×51) 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!

13 Comments or trackbacks to Simple FAT and SD Tutorial Part 4

Jack:
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

jokkebk says:
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

David Cook:
May 28, 2012 at 7: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

stephen:
September 27, 2012 at 9:11

good lesson

reply

nyoman yudi:
December 12, 2012 at 3: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

jokkebk says:
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

Gokay:
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,0×95)(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 0×00 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)!=0×05)

{
SD_CS_DEASSERT();
rs232_Stream("0×05 errorrrrrr…");
return 1;

}

Thank you !

reply

jokkebk says:
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

ganesh:
January 9, 2013 at 17:40

thanks very useful work

reply

Ahmed Eldamarawy:
April 30, 2013 at 2: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

jokkebk says:
June 9, 2013 at 15:49

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

reply

Leave a Reply

Your e-mail address will not be published.


+ three = 11

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>