I am currently working on a MP3 alarm clock, basically a configurable alarm clock that plays MP3s as alarms. This project requires a large amount of memory to store the MP3 files and the calendar for the alarm, and I want to be able to easily load new music and settings to this device. I've decided that a microSD card is the perfect choice for this job.
In this tutorial, I will show you how to read and write to a SD card using an AVR microcontroller. There are some prerequisite knowledge you need to have before you can follow this tutorial:
Relevent Links:
MicroSD cards are cheap, but if you need a card reader, the surface-mounted holder would cost $4 + shipping, and although I can probably solder that, I still would like to do testing on a breadboard. Sparkfun does sell a breakout board for their microSD holder for $15 + shipping, but that's a little too expensive. I've decided to buy a 1 GB microSD card that came with the microSD to SD adapter for $10 at a local place, and solder pin headers to the adapter so I can easily insert it into a breadboard.
The disadvantage is that there is no mechanical switch that can tell your microcontroller whether or not a card is in place, also there's no springy push-to-eject mechanism. I can live without either, since the software can detect the card (through error handling) and pulling out a microSD card isn't that much of a pain to begin with.
MMC and SD cards have near identical interfaces, SD cards have two additional connectors which are not important to us. MMC and SD cards have a SPI interface which makes it very easy for hobbyists to interface with these cards with low cost equipment.
Connect the SD card to the microcontroller following this table and picture (draw your own schematics using this table since connections on different chips may vary).
| Pin on SD card | Function | Connect to Pin on AVR |
| MOSI (Master Out Slave In) | The master refers to the device that generates the clock (the microcontroller), the SD card is the slave. Data on this pin travels from the microcontroller to the SD card. Also known as "DI". | MOSI |
| MISO (Master In Slave Out) | Data on this pin travels from the SD card to the microcontroller. Also known as "DO". | MISO |
| SCK | Serial clock pin, also known as "CLK" | SCK |
| CS | Chip select, the SD card pays attention to the data traveling on the SPI bus when this pin is low, and ignores the data on the bus when this pin is high | Port B 3, but it can really be any pin since this one is software definable. You should use a pull-up resistor (I use a 10k and it works fine) on this pin. |
| GND | Vss, ground, 0V | Ground |
| 3.3V+ | Vcc, power supply pin, supply 3.3V to this pin to power the card. | Only connect to a 3.3V power supply |
Take special care because the SD card only runs on 3.3V and the SPI bus is not 5V tolerant. If you do not know how to properly level shift, make sure your ATmega644 is powered with only 3.3V and never 5V, and that your programmer is using 3.3V and not 5V. On most USB programmers, you can select whether or not the programmer powers the target, do not let the programmer power the target. If you're programmer use a buffer chip, make sure the buffer chip is powered with 3.3V and not 5V.
Link to a page about voltage level shifting
Wire up your AVR and SD card on the breadboard, wire up your programmer and serial port, I used a 3.3V LDO regulator to supply everything with 3.3V using the 5V from my USB port.
I've also found that hot-inserts (inserting the card while the power is on) can cause the AVR to reset and also mess up the USB to serial converter. I put a (rather large) 470uF capacitor between Vcc and GND and that solved the problem, it acts as a power reservoir during the sudden large current draw caused by the inserted card.
This is an example fuse bit setup for an ATmega644.
Go download FatFs and the sample projects, the link to the download is on the bottom of the page.
Once you've downloaded the core FatFs files, place ff.c ff.h diskio.c diskio.h integer.h into your own project folder, and include it in your project (just add them into the project in whatever IDE you are using).
Your project should look something like this:
Go edit diskio.c , you will notice that MMC card code is not actually included. Download the sample project files, and get mmc.c from the AVR sample project. Either replace diskio.c with mmc.c , or just copy and paste in the contents of mmc.c into diskio.c . At this point, you should be fully capable of doing any project yourself by reading the sample project.
You still need to customize mmc.c to your needs. The most important part is the SELECT() and DESELECT() macros, find where they are defined and edit it to match your electrical connections. Next, go find the power_on(), power_off(), and chk_power() functions. You might not need power_off() and chk_power() ever, but edit power_on() to match your electrical connections (enable outputs on all SPI pins, except make MISO an input, also make sure SS is an output).
Find disk_timerproc() inside mmc.c , if you do not have the ability to detect whether or not the card is inserted and whether or not the disk is write protected, then replace the entire function with this:
void disk_timerproc (void)
{
static BYTE pv;
BYTE n, s;
n = Timer1; /* 100Hz decrement timer */
if (n) Timer1 = --n;
n = Timer2;
if (n) Timer2 = --n;
s &= ~STA_NODISK;
s &= ~STA_PROTECT;
Stat = s;
}
As the code comments said, that function should be called every 10 milliseconds. You can use a timer interrupt to do this. If you do not want to use a timer interrupt, then go in mmc.c and replace every delay function which is dependant on the global variables "Timer1" and "Timer2" with your own delay function (such as the _delay_ms() function built into AVR-GCC).
There are places where "Timer1" and "Timer2" are used as timeouts. Go find code that looks like
while (Timer1 && function_name(parameters));and turn them into
for (int i = 0; i < 10 && function_name(parameters); i++) _delay_ms(10);which would implement an timeout of 100 milliseconds without calling disk_timerproc() every 10 milliseconds.
Note: The above occurs at several locations in mmc.c , and they all look different, it's up to you to find them all and replace them accordingly. Adjust the timeout according to what the original code needed.
There's also this bit of code that should be changed from
Timer1 = 20;
do { /* Wait for data packet in timeout of 200ms */
token = rcvr_spi();
} while ((token == 0xFF) && Timer1);
into
token = 0xFF;
for (int i = 0; i < 20 && token == 0xFF; i++)
{
token = rcvr_spi();
_delay_ms(10);
}
which implements a 200 millisecond timeout without calling disk_timerproc() every 10 milliseconds.
Go edit ff.h , and configure the definitions to your needs. Make sure you set "_FS_TINY" to 1 to make it work on an AVR chip.
To initialize the system, you must first initialize the disk by calling disk_initialize(0), then mounting the file system by calling f_mount(0, &FATFS_Obj) where FATFS_Obj is an instance of the FATFS data structure. This instance must be declared static. The initialization will look like this:
FRESULT f_err_code; static FATFS FATFS_Obj; disk_initialize(0); f_err_code = f_mount(0, &FATFS_Obj);
From the code above, if f_err_code is not equal to 0 or FR_OK, then there is a problem. Most FatFs functions will return an error code that is not equal to 0 when something goes wrong. You can use this fact to detect the presence of a card. Be creative with this fact, my MP3 player already supports hot card insert support.
Say I wanted to write "bar" and a integer that's equal to 5 as a decimal to the beginning of a file called "foo.txt" in the directory "moo".
FIL fil_obj; int variableName = 5; f_open(&fil_obj, "/moo/foo.txt", FA_WRITE); f_printf(&fil_obj, "bar %d", variableName); f_close(&fil_obj);
fil_obj is an instance of the FIL struct, which is a file handle. This file handle acts as an identifier so FatFs knows which file you want to work with, having multiple FIL structs lets you have multiple open files. f_printf works exactly like how it works in stdio.h. When opening a file, you specify the permissions, such as read or write, there are also permissions such as create the file if it doesn't exist. When you close the file, it tells the file table where the file ends so you don't end up with a corrupted disk.
Just by knowing the above code, you can now make a datalogger or something like that. Have fun.
Things can get more complicated, go read the documentation on the FatFs website