Home
Last Update: May 27th, 2010 (Fixed reversed VSS and VDD pins, also added nicer syntax highlighting)

FatFs explained for AVRs and SD cards

FatFs Homepage

What can I do with a Fat32 filesystem and an AVR?
An SD card without a filesystem is simply an array of memory bits that you can write to. If you wrote random data to an SD card and plugged the card into a computer you wouldn't be able to do anything with it. Computers expect a way to be able to quickly see what files exist, what directories they are in, how big each file is, and where they start in memory. This information is stored in a file allocation table, and this table has a specifc way of being created and updated. The FatFs library handles this for you and lets you interact with it on a much higher level, with functions like f_write, f_open, f_read, etc. The files and data you read and write to using an avr are also readable and writeable to and from any computer. This guide will show you how to setup the FatFs library on an avr so it can read and write to a Fat32 file system. Note, the code for Fat32 functionality is very big, and uses considerable memory, you'll need a chip with lots of resources. Benchmarks for AVRs are located here. Ask yourself if you're sure you need a Fat32 system, maybe a huge array of bits is all you need. If so, refer to another tutorial for SD cards without the Fat32 layer, you'll save a huge amount of code and ram space, but a computer won't be able to read it. There is an excellent page here.(Unless you write more code to teach the computer how, but that's way out of scope here.)
I decided to write this page because the FatFs library is a really great library, except that it supports so many devices it's impossible for the official FatFs site to document every single one of them. It's extremely important to understand that the FatFs ONLY provides functions to interface and create a FAT file system, it provides no functions that are actually related to SD cards or even the atmel AVRs. The official site includes samples that deal with AVRs and SD cards, and I'll use them here, but there is no detailed tutorial included with those samples. I had to fiddle with other tutorials and bits of code I found around the net before I was able to fully understand the library. Another problem is that most tutorials (all the ones I read) deal with very specific projects with very specific AVR models and provide few clues as to how to setup this library from scratch for any avr.
This page will never refer to a specific AVR model and will never refer to a specific application, it is a step by step guide through all the essential wiring and FatFs setup for SD cards.
Generic hardware setup
SD cards use SPI to transfer data. There is also "native mode" for SD cards, the advantages are few and I hear it's far more complicated, and so it is ignored here. MMC cards apparently work in almost the same way as SD cards, I've never tried it but I'm almost sure with some slight wiring differences the code and procedure here should work just fine. SD cards work only at 3.3V, that means you'll either need a level converter (this is the one I used) or you'll have to power your AVR at 3.3V. It is also important to have a general idea of how the SPI interface to the SD works as well. This page has nothing to do with the FAT filesystem, it only explains how to put bytes of data with no organization onto the chip.
Once you have selected an AVR and have an SD card it's then time to connect it. If you don't have a breakout board for an SD card, here are some tricks that I found that work well. One is to solder some jumpers right onto the pins.
If you don't want to do that, I also had success cutting short wire, and curving it into a half circle, and putting one end into a breadboard and having the other end press into the breadboard. After placing a few of these in a row it's possible to slide the SD card under them. Another trick, this time from hack-a-day, it's really awesome.
Next we have to wire the SD card to the AVR spi port. This picture labels the pins on an SD card, to the names of the pins on the AVR.
TODO: Add a pic of my jumper pin soldering.
There is also a good description on this wiki-based pinout site. http://www.allpinouts.org/index.php/Secure_Digital_(SD)_Card The select pin doesn't exist on the AVR, but I labeled it that way since the pin is only used to select the SD card on the spi bus. If that didn't make sense to you read the section below, if you're familiar with SPI, skip it.

SPI basics
The SPI port is able to connect to as many devices as you'd like, so long as you only talk to one at a time. The select pin on each device on the spi bus needs to be held high, and to communicate to a device the select pin on that device is pulled low. This way all the devices know to ignore the data on the spi bus, and only the low device reads it. Since the spi port is used by the ISP it's important to make sure the SD card remains unselected (select pin high) as the device is being programmed. The SS (slave select) pin on the AVR I used was held high during programming, so that was a good pin for me. It should also be possible to power down the SD card, but I've never tried it personally.

I had to put small (68 ohm) resistors between the ISP and the SD card to help isolate the AVR while programming. 68 ohms worked well at 3.3V programming. Sometimes (10% of the time) it would fail to program, if this becomes annoying I was able to disconnect one of MOSI or MISO and have it work 100% of the time. No need to disconnect the entire card.
The Purple lines goto the ISP


Setting up FatFs
Download the latest version of the FatFs library. I used this version:
link
You'll also need some files out of the samples download. Again, I used this version:
link
Here is a list of files and what their functions are:

From the FatFs library: From the sample archive:
This is a large archive containing samples for various hardware. The only sample we're interested in is the avr sample. We're going to steal the diskio code from it. This section describes, step by step, how to use these files and get them working together and in your specific device and application.
First, take (from the non-sample archive) diskio.h, ff.h, ff.c and integer.h and put them in a new project folder. I'll call this folder base from now on.
Next, take the mmc.c file and rename it to diskio.c and move it into the base folder.
The last thing is to create a main file (I call it main.c) and include ff.h to use the library. You'll also need to link everything together, this is toolchain specific, but here is what my command line compile lines look like...
avr-gcc -I. -I/usr/avr/include -mmcu=atmega168 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=main.lst -c main.c -o main.o
main.c:3: warning: function declaration isn't a prototype
avr-gcc -I. -I/usr/avr/include -mmcu=atmega168 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=diskio.lst -c diskio.c -o diskio.o
diskio.c:602: warning: function declaration isn't a prototype
avr-gcc -I. -I/usr/avr/include -mmcu=atmega168 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=ff.lst -c ff.c -o ff.o
avr-gcc -Wl,-Map,myproject.out.map -mmcu=atmega168 -lm -o myproject.out main.o diskio.o ff.o
This is where the tricky parts begins, if you've already tried to compile those files you'll notice there aren't any compile errors. I'm going to explain each one and how to get around it.

Here is the list of errors I got while compiling the diskio.c file from the application.
avr-gcc -I. -I/usr/avr/include -mmcu=atmega8 -Os -fpack-struct -fshort-enums -funsigned-bitfields -funsigned-char -Wall -Wstrict-prototypes -Wa,-ahlms=diskio.lst -c diskio.c -o diskio.o
diskio.c: In function 'power_on':,
diskio.c:129: error: 'PORTE' undeclared (first use in this function)
diskio.c:129: error: (Each undeclared identifier is reported only once
diskio.c:129: error: for each function it appears in.)
diskio.c: In function 'power_off':
diskio.c:147: error: 'PORTE' undeclared (first use in this function)
diskio.c: In function 'chk_power':
diskio.c:154: error: 'PORTE' undeclared (first use in this function)
diskio.c: In function 'disk_initialize':
diskio.c:309: error: 'CT_SD2' undeclared (first use in this function)
diskio.c:309: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c:314: error: 'CT_SD1' undeclared (first use in this function)
diskio.c:316: error: 'CT_MMC' undeclared (first use in this function)
diskio.c: In function 'disk_read':
diskio.c:366: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c: In function 'disk_write':
diskio.c:405: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c:413: error: 'CT_SDC' undeclared (first use in this function)
diskio.c: In function 'disk_ioctl':
diskio.c:500: error: 'CT_SD2' undeclared (first use in this function)
diskio.c:511: error: 'CT_SD1' undeclared (first use in this function)
make: *** [diskio.o] Error 1


Let's take a look at the first PORTE error. I compiled mine for an atmega8, normally you would never actually do that because the mega8 doesn't have enough space for the library, but the 168 does. To get this code to work with a microcontroller that doesn't have a Port E, (like the atmega168) we need to be able to re-write parts of the diskio.c file.
The PORTE is used in power_on.

/*-----------------------------------------------------------------------*/
/* Power Control (Platform dependent) */
/*-----------------------------------------------------------------------*/
/* When the target system does not support socket power control, there */
/* is nothing to do in these functions and chk_power always returns 1. */

static
void power_on (void)
{
  PORTE &= ~0x80; /* Socket power ON */
  for (Timer1 = 3; Timer1; ); /* Wait for 30ms */
  PORTB = 0b10110101; /* Enable drivers */
  DDRB = 0b11000111;
  SPCR = 0b01010000; /* Initialize SPI port (Mode 0) */
  SPSR = 0b00000001;
}

This function exists because someone may want to be able to turn the SD card on and off from the microcontroller software. Note that in the power on function the bit is cleared on PORTE, and although it is not shown here it is set in the power off function. This confused me at first, I expected the pin to be high in the power on and low in power off, but then I realised that the absolute max current draw for a GPIO pin on megas is around 40mA. SD cards run at at least 50mA, and the standard rates says 200mA is the max they can use. The reason that the PORTE is inverted is because it's actually controlling a switch that provides external power to the device. The nice thing about using a GPIO pin for power is that you have control over when the SD card is running. The SD card itself has a microcontroller and takes current whenever a voltage is applied across it. The for loop in the power_on function is a little more complicated and will be explained in the next section. Lets skip to the SPI and DDRB lines. The sample was written for an atmega64 and PORTB on the mega64 is the SPI port. If you refer to the SPI section in the mega64 datasheet you'll see that these lines enable SPI in mode 0. If that confuses you, you should take a moment to familiarize yourself with SPI on AVRs before continuing. If you decide to not have software control over the power to the SD card then you can comment out the first two lines of the power on function. Either way you'll also need to double check the power_off and chk_power functions. Once power_on is implemented for your application you need to do the opposite in power_off, and update chk_power. If you don't have software control over the power to the SD card then chk_power should always just return one. Now we're only left with these errors:
diskio.c: In function 'disk_initialize':
diskio.c:309: error: 'CT_SD2' undeclared (first use in this function)
diskio.c:309: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c:314: error: 'CT_SD1' undeclared (first use in this function)
diskio.c:316: error: 'CT_MMC' undeclared (first use in this function)
diskio.c: In function 'disk_read':
diskio.c:366: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c: In function 'disk_write':
diskio.c:405: error: 'CT_BLOCK' undeclared (first use in this function)
diskio.c:413: error: 'CT_SDC' undeclared (first use in this function)
diskio.c: In function 'disk_ioctl':
diskio.c:500: error: 'CT_SD2' undeclared (first use in this function)
diskio.c:511: error: 'CT_SD1' undeclared (first use in this function)
make: *** [diskio.o] Error 1


They are just a few defines we didn't move over when we pulled out the diskio.c function from the AVR sample. The defines are here, and I got them from the diskio.h file from the same AVR sample. Copy and paste them into the diskio.h file you're using.
/* Card type flags (CardType) */

#define CT_MMC 0x01
#define CT_SD1 0x02
#define CT_SD2 0x04
#define CT_SDC (CT_SD1|CT_SD2)
#define CT_BLOCK 0x08

Those code changes were enough to get my project to compile. If you run into any more errors please leave a comment in the comment section at the bottom of this page.
There are some linker errors to take care of however.
ff.o: In function `f_mkdir':
ff.c:(.text+0x3006): undefined reference to `get_fattime'
ff.o: In function `f_sync':
ff.c:(.text+0x326e): undefined reference to `get_fattime'
ff.o: In function `f_open':
ff.c:(.text+0x3418): undefined reference to `get_fattime'
The FAT filesystem keeps track of when files were created and modified. Keeping track of time is an application dependent task so we need to implement the time function. I don't have a realtime clock so I just returned 0 from the function. The date was bogus, but the fatfs library doesn't care. Leave a comment with an actual implementation of the function if you have one.
I added this into diskio.c

DWORD get_fattime () {
  return 0;
}
At this point my project compiled, but would not run. The reason is explained in the next section.

DiskIO timing
The diskio.c file communicates with the SD card and if something fails then it needs to know how long it has been waiting for a response from the card. If the card doesn't respond in a few milliseconds then the function fails. The diskio.c file has a function called void disk_timerproc() and it has to be called every 10ms the function is responsible for constantly decrementing the Timer1 and Timer2 variables. These are not to be confused with the AVR timers, these are actual variables and are set to positive integers in the diskio.c file and the disk_timerproc decrements them so that the diskio.c functions have a way of keeping time. This section briefly outlines how to setup an AVR timer interrupt that is called every 10ms, it is application and device specific, so I won't go into too much detail. This setup is for the mega168.
ISR(TIMER0_COMPA_vect) {  /* should be called every 10ms */
  disk_timerproc();
}

int main() {

  TIMSK0 |= 1 << OCIE0A;  /* enable interrupt for timer match a */
  OCR0A = 78;  /* 10 ms interrupt at 8MHz */
  TCCR0B |= (1 << CS02) | (1 << CS00);  //speed = F_CPU/1024
  power_timer0_enable();

  sei();

}
From the mega168 manual:
"When the OCIE0A bit is written to one, and the I-bit in the Status Register is set, the Timer/Counter0 Compare Match A interrupt is enabled. The corresponding interrupt is executed if a Compare Match in Timer/Counter0 occurs, i.e., when the OCF0A bit is set in the Timer/Counter 0 Interrupt Flag Register - TIFR0."
This puts timer A into compare mode. Every time the counter is incremented, the AVR automatically compares the current timer value to the value in the OCR0A register. If the values match then the TIMER0_COMPA_vect interrupt is called. Setting CS02 and CS00 in TCCR0B sets the timer clock scalar to 1024, meaning that every time the clock source ticks 1024 the clock value is incremented. In my case I left it to the default, (the CPU clock) and it was running at 8MHz. That means there was a 1/8000000 second delay between every clock tick. After 1024 ticks the timer is incremented. That means 1024/8000000 seconds pass every increment. 78*1024/8000000 = 0.009984, or about 10mS, so that explains the 78 going into the OCR0A register. If the CPU was running at 20MHz, then X*1024/20000000 = 0.01, solve for X. (195.3125)

Using the library
Now that you understand how the library works and have the diskio code setup, you're ready to use the high level function you want. The first thing you have to do is mount the drive. Our diskio code only supports one drive and one partition. The first thing to do is mount it. Here is some code I grabbed out of an old project.
FATFS FileSystemObject;

if(f_mount(0, &FileSystemObject)!=FR_OK) {
  //flag error
 }

DSTATUS driveStatus = disk_initialize(0);

if(driveStatus & STA_NOINIT ||
   driveStatus & STA_NODISK ||
   driveStatus & STA_PROTECT
   ) {
  //flag error.
 }

  /*  Sometimes you may want to format the disk.
    if(f_mkfs(0,0,0)!=FR_OK) {
    //error
  }
  */

FIL logFile;
//works
if(f_open(&logFile, "/GpsLog.txt", FA_READ | FA_WRITE | FA_OPEN_ALWAYS)!=FR_OK) {
  //flag error
 }

unsigned int bytesWritten;
f_write(&logFile, "New log opened!\n", 16, &bytesWritten);
//Flush the write buffer with f_sync(&logFile);

//Close and unmount. 
f_close(&logFile);
f_mount(0,0);

Note that I called the GpsLog.txt file "/GpsLog.txt", unless you enable chdir you need to specify the absolute path. See function docs and config options.
Documentation for all of these functions are in the official FatFs library. There is one pitfall here, if you try to format a disk with the f_mkfs function it won't compile because there are configuration options you have to go through.
They are located in the ff.h file and there are descriptive comments for each.
E-mailed comments
I got some comments about this page via e-mail. Pasting useful snippets here:

--------------------------------
On another note, this site also uses FatFS for a project:

http://www.mit.edu/~vona/dne/dne.html
The interesting thing about this site is that he enabled the card detect feature for microSD cards. This is done by adding a pull-down resistor to the CS line (~1Mohm) and then reading the voltage level on that pin. A high value indicates the card is present - low implies no card. Most microSD sockets lack both card_detect and card_locked signals so the method used by FatFS does not work. The modified mmc.c file is linked from his site.

William
--------------------------------

Comments

Derek Robertson
04 Nov 2011, 05:24
Willaim,

Many thanks... had several attempt (An evening every few weeks) at getting this implimented on an AVR with Imagecraft ICC avr, but wasn't making much progress.

Within a few hours of finding your article I was up and running. Tip for other ICC avr users... you'll have problems with __flash/const. I left all the low level code alone and created wrapper functons at the top level i.e.

// Same as f_open, but the path can be a __flash sting
FRESULT cf_open (FIL *fp,__flash XCHAR *path,BYTE mode)
{
cstrcpy(sd_string,path);
f_open(fp,sd_string,mode);
}
lamer
27 Aug 2011, 06:54
I want to use variable filename.
For examble! 1.txt, 2.txt, ... , 1000.txt
How can I do?
Zsolti
10 Apr 2011, 15:26
Hi Chris!

I have a problem.
The code doesn't work for me. :(
The SPI isn't setted up, how to programming and where? in diskio.c or main.c
And can you give some help to setting up the pins

I use atmega32.
Help me.

Rgds
Zsolt
Chris
21 Jul 2010, 15:23
Hey Tony,
Looks like your project build setup isn't configured properly. You'd have to give information about how files are compiled and linked.
Tony Peng
21 Jul 2010, 12:30
Why am I getting these errors?
I can't even use f_mount...

http://mibpaste.com/IUoRv9
Pim
10 Jun 2010, 09:07

Hello,

Everything went well. After completing your tutorial and adding the get_fattime function I got these linker errors.

What should I do??

C:\projects\src\test\default/../ff.c:845: undefined reference to `ff_wtoupper'

C:\projects\src\test\default/../ff.c:1300: undefined reference to `ff_convert'

I really need this to work and log some information on my sd via an Atmega
Avrith Doug
16 Apr 2010, 19:01
Hello,

I'm trying to figure out exactly how much memory space FatFS takes on a MCU? Went on the project's website, but it's still not clear to me...

Can you clarify?

Thanks
Powerman
07 Mar 2010, 16:10
Forgot to say THANK YOU William!

Your code and explanation are greatly appreciated!

Cheers!
Trevor
Powerman
07 Mar 2010, 16:06
I have this code "modified" and working with SDHC cards. I'm using an ATMega128 running at 16MHz.
1. replace OCR0A = 78 with OCR0A = 156
2. replace Timer = 100 with Timer = 200 in disk_initialize()

I use a transistor to control the power to the card. This is controlled by PB7. 0=Power to Card, 1=No Power to Card.

I have tested using a 4GB SDHC, and a 16GB SDHC. Both cards work well. One issue however is that you cannot delete the file often times while in Windows reading the card. The only way to remove it is to format the card. Writing over the existing file from the micro controller's perspective seems to work fine.
uthenb
03 Oct 2009, 14:14
Thanks Chris for the reply and share current information on this page.
Chris
03 Oct 2009, 11:31
This page doesn't support SDHC (high capacity)
You can use the fatfs library to put a FAT32 filesystem on those cards, but you'd need to rewrite the diskio code. I don't have any information on how the SDHC io differs from the normal SD cards.
uthenb
03 Oct 2009, 10:43
Hi Chris.
Code FatFs supported SDHC or not?

Please reply thanks.
http://www.10logic.com.
William Douglas
29 Sep 2009, 22:06
Did you get the two function calls, disk_initialize() and f_mount(), in the wrong order?

From the documentation, it appears that the disk must be initialized before the f_mount function can be called. In fact, it states that calling the initialize function when the drive is registered to a work area can cause serious problems.
Graham Harris
27 Aug 2009, 15:43
Hi Chris,

Yes the switch is not locked and I can read and write to the SD via my PC. Now your code to trap errors has shown that the return from disk_initialize is 0x06 which is none of the failure values mentioned in the header file. Now I'm puzzled as to the purpose of the power on code fragment and was wondering if this maybe the issue. BTW I'm using the ATMega128.

Rgds
Graham
Chris
27 Aug 2009, 13:19
Hi Graham Harris,
>One comment : are you sure the vdd and gnd pinouts are correct? I ask as the pinouts I've seen are as follows.
I'll double check it myself by building a circuit, then I'll take a picture and post it. I used an image off another site and edited it a bit (shhh..) so I guess it could be wrong.

>f_mount returns FR_OK but when I run f_open I get FR_WRITE_PROTECTED and cannot see where I'm going wrong.

Double check to make sure that the little switch thing on the side is in the "read/write" mode and not the "read only" mode. Mine isn't labeled so I had to try both. I'll update the page to reflect this pitfall. The switch is tiny and easy to miss.

Good luck.
Graham Harris
27 Aug 2009, 11:01
Hi

Excellent document well done.

One comment : are you sure the vdd and gnd pinouts are correct? I ask as the pinouts I've seen are as follows.

pin 4 Vdd
pin 3&6 GND

The rest agree.

Also I was wondering if you came across this issue as I'm debugged out at the moment.

Disk_initialise is sucessful
f_mount returns FR_OK but when I run f_open I get FR_WRITE_PROTECTED and cannot see where I'm going wrong.
*Name:
Email:
Notify me about new comments on this page
Hide my email
*Text:
 
Powered by Scriptsmill Comments Script