Elastic Sheep

Because elasticdog was already taken

Elastic Sheep header image 2

Reading an SD card with an ATMEGA168

January 12th, 2010 · 29 Comments · Uncategorized

In this small week-end project, I will connect a SD card to an ATMEGA168 and try to read data from it.

The SD card pin-out

The SD card can be talked to with three different transfer modes: 1-bit SD mode, 4-bit SD mode and SPI mode. According to Wikipedia, all cards must support all three modes except for micro SD where the SPI mode is optional. I will nonetheless try to read my micro SD with the SPI mode.

[Edit Correct pinout table added thanks to John Ulyate's comment]

Connecting the card to a circuit

I found a circuit example on the following page http://www.captain.at/electronic-atmega-mmc.php. The MCU is a 5V-powered ATMEGA16. The SD card is powered through a 3.3V regulator. The author used resistor dividers to connect the SD card inputs to the ATMEGA16 SPI pins.

I bought the same regulator and thought, why not instead try to power the ATMEGA168 directly with 3.3V thus avoiding the need for a voltage adaptation. The ATMEGA168 datasheet tells me that at 3.3V the maximum safe frequency is 16MHz so I can keep my current crystal.

I improvised an interface for a micro SD with a SD card adapter and a 7-pins header.

SD card adapter and pin header before soldering

I used some thin wire to avoid stressing the SD Card pads and added some hot glue to have a stronger header connector.

SD card adapter soldered to pin header

Here is the schematic of my version of the circuit and a picture of the resulting breadboard:

ATMEGA168 and SD card test circuit schematic

SD card and ATMEGA168 test circuit on breadboard

Understanding the SPI interface

SPI stands for Serial Peripheral Interface. This interface allows high speed synchronous transfers between a MCU and a peripheral or another MCU. The ATMEGA168 datasheet contains the following figure that highlights the powerful simplicity of this interface:

SPI master slave interconnection (from the ATMEGA168 datasheet)

Each side of the interface has a 8-bit shift register. The left side is the master and the right side is the slave. When the master initiates a transfer to the slave, its SPI clock signal triggers a bit-by-bit copy of each register to the other one. Sending a byte from the master always involves receiving a byte from the slave.

The master SPI clock dictates the speed of the transfer. For the ATMEGA168 the maximum speed is Fclk/2. So with a 16 MHz crystal, the maximum theoretical bandwidth is 8Mbit/s, or 1MByte/s. A regular card should support a clock of 25 MHz.

Here is some code adapted from the ATMEGA168 datasheet to initialize the SPI interface and do a transfer. A single function is enough to send or receive a byte or both.

void spi_init(void)
  /* Set MOSI and SCK output */
  /* Enable SPI, Master, set clock rate fck/128 */
  SPCR = _BV(SPE) | _BV(MSTR) | _BV(SPR0) | _BV(SPR1);
  SPSR = _BV(SPI2X);
uint8_t spi_send_receive_byte(uint8_t byte)
  /* Start transmission */
  SPDR = byte;
  /* Wait for transmission complete */
  while (!(SPSR & _BV(SPIF)));
  /* Read the received byte */
  byte = SPDR;
  return byte;

The interface can be tested with a loopback: by wiring the MOSI output to the MISO input, you can check that the received byte match the sent byte.

Implementing the MMC protocol

The protocol to use with the SPI interface is defined by Sandisk (ProdManualSDCardv1.9.pdf)

For a simple implementation, I started from the AN10406 LPC2000 application note (Accessing SD/MMC card using SPI on LPC2000). I adapted the mmc_init and mmc_read_block functions to be able to read the first block of a SD card.

The full program is downloadable at the end of the post. The serial port speed is set at 57600 bauds.

Here is the serial output of reading the sector 63 of my test SD card. It contains a FAT32 MBR with a descriptor for the only available partition “NO NAME”:

send CMD0
send dummy clocks
send CMD1
send dummy clocks
send CMD16
mmc_init end
read block 63
receive block
block received
0000: eb58904d53444f53352e300002082400 .X.MSDOS5.0...$.
0010: 0200000000f800003f00ff003f000000 ........?...?...
0020: 99933c001e0f00000000000002000000 ..<.............
0030: 01000600000000000000000000000000 ................
0040: 000029696561a84e4f204e414d452020 ..)iea.NO NAME  
0050: 2020464154333220202033c98ed1bcf4   FAT32   3.....
0060: 7b8ec18ed9bd007c884e028a5640b408 {......|.N..V@..
0070: cd137305b9ffff8af1660fb6c640660f ..s......f...@f.
0080: b6d180e23ff7e286cdc0ed0641660fb7 ....?.......Af..
0090: c966f7e1668946f8837e16007538837e .f..f.F..~..u8.~
00a0: 2a007732668b461c6683c00cbb0080b9 *.w2f.F.f.......
00b0: 0100e82b00e94803a0fa7db47d8bf0ac ...+..H...}.}...
00c0: 84c074173cff7409b40ebb0700cd10eb ..t.<.t.........
00d0: eea0fb7debe5a0f97debe098cd16cd19 ...}....}.......
00e0: 6660663b46f80f824a00666a00665006 f`f;F...J.fj.fP.
00f0: 53666810000100807e02000f852000b4 Sfh.....~.... ..
0100: 41bbaa558a5640cd130f821c0081fb55 A..U.V@........U
0110: aa0f851400f6c1010f840d00fe4602b4 .............F..
0120: 428a56408bf4cd13b0f9665866586658 B.V@......fXfXfX
0130: 6658eb2a6633d2660fb74e1866f7f1fe fX.*f3.f..N.f...
0140: c28aca668bd066c1ea10f7761a86d68a ...f..f....v....
0150: 56408ae8c0e4060accb80102cd136661 V@............fa
0160: 0f8254ff81c300026640490f8571ffc3 ..T.....f@I..q..
0170: 4e544c44522020202020200000000000 NTLDR      .....
0180: 00000000000000000000000000000000 ................
0190: 00000000000000000000000000000000 ................
01a0: 0000000000000000000000000d0a5265 ..............Re
01b0: 6d6f7665206469736b73206f72206f74 move disks or ot
01c0: 686572206d656469612eff0d0a446973 her media....Dis
01d0: 6b206572726f72ff0d0a507265737320 k error...Press 
01e0: 616e79206b657920746f207265737461 any key to resta
01f0: 72740d0a0000000000accbd8000055aa rt............U.

The sd-reader library

To read the data from the filesystem on the SD card you need the mmc_read_block primitive and an implementation of the FAT16 or FAT32 abstraction.

Because this is a week-end project, I changed gears and switched to an open source library to do the job. Roland Riegel has implemented a sd-reader library for the ATMEGA8 family. Its FAT16 implementation can fit in an ATMEGA168, but the FAT32 one requires the ROM size of an ATMEGA328.

You can download the latest source code on this page: http://www.roland-riegel.de/sd-reader/index.html.

No modifications are needed for the ATMEGA168. I just modified the configuration of the programmer to use an avrispmkii:

flash: $(HEX)
	avrdude -p $(MCU_AVRDUDE) -P usb -c avrispmkII -V -U flash:w:$(HEX)

To compile and flash:

make flash

The main program of the library provides a shell on the UART at 9600 bauds with some basic commands to manipulate the SD card file system (ls, cat, cd, write).

Formatting a FAT16 filesystem on a mac

I followed the instructions provided in this Adafruit forum entry: http://forums.adafruit.com/viewtopic.php?f=31&t=7108.

newfs_msdos -F 16 /dev/(SD Card device/device number) 

You can check in Disk Utility that the SD card has been correctly formatted with a FAT16 filesystem:

Disk Utility SD Card formatted as FAT16

Testing the library

My first tries with the write command failed inexpectedly. I was able to create a directory, touch a file, but unable to write data in it...

At the same time I stumbled on the OpenLog board from Sparkfun that is based on the very same library. By comparing the implementation with the reference one, I found the explanation for my failing write. The sd-reader library expects a pin telling if the lock switch of a card is enabled. I don't provide this facility and have to bypass it.

To do so I patched the library with the modifications from the OpenLog firmware (product page) in sd_raw_config.h:

// From Sparkfun OpenLog
//My 2 hour pitfall: If not using card detect or write protect, assign these values:
//#define configure_pin_available() DDRC &= ~(1 << DDC4)
//#define configure_pin_locked() DDRC &= ~(1 << DDC5)
#define configure_pin_available() //Do nothing
#define configure_pin_locked() //Do nothing
//#define get_pin_available() ((PINC >> PC4) & 0x01)
//#define get_pin_locked() ((PINC >> PC5) & 0x01)
#define get_pin_available() (0) //Emulate that the card is present
#define get_pin_locked() (1) //Emulate that the card is always unlocked

I also added a modification of my own to display the ASCII content of a file with the cat command in main.c:

/* Display printable characters */
for(uint8_t i = 0; i < size; ++i)
  if ((buffer[i] >= 32) && (buffer[i] <= 126))

With this modification, everything is working fine:

$ cu -s 9600 -l /dev/tty.usbserial
manuf:  0x03
oem:    SD
prod:   SU02G
rev:    80
serial: 0x201b858a
date:   8/7
size:   1938MB
copy:   1
wr.pr.: 0/0
format: 0
free:   2030075904/2032336896
> ls
._.Trashes                           4096
.Trashes/                            0
.fseventsd/                          0
.Spotlight-V100/                     0
> mkdir test
> cd test
> ls
./                                   0
../                                  0
> touch data
> write data 0
< line 1
< line 2
< line 3
> ls
./                                   0
../                                  0
data                                 18
> cat data
00000000: 6c 69 6e 65 20 31 6c 69 line 1li
00000008: 6e 65 20 32 6c 69 6e 65 ne 2line
00000010: 20 33 20 32 6c 69 6e 65  3

My patches to the sd-reader library are available at the end of the post.

A drawback: the required code size

After having understand how the SPI interface is working and how to dialog with an MMC card, I can see myself using the sd-reader library in a future project.

One drawback of the library is the size of code required to implement the FAT16 layer. 8kbytes is half the flash size of an ATMEGA168 without a flash loader.

layer code size static RAM usage
MMC/SD 2410 518
Partition 456 17
FAT16 7928 188

You also need half the size of the ATMEGA168 RAM for the MMC layer to be able to load blocks in memory.

If you need to save on flash space, an alternative is to only use raw accesses to the SD card blocks. The SD card can then be saw as a giant EEPROM where you read/write pages. The trade-off is that you will not be able to plug the card in a PC and expect to be able to read its content in the explorer/finder. It could only be read with commands issued through the MCU with a serial link for example.

For a serious application requiring FAT16 access from either MCU and PC, I would recommend to directly start a project with at least an ATMEGA328.

Source code

Tags: ··

29 Comments so far ↓

  • Olivier

    What about a SD card reader ?
    It’s cheap, light and you can easily put the card in a computer to retrieve some data.

  • Chris Hatton

    A most excellent and enlightening article, thanks. I learned about how the SPI bus works from this. Never mind the suggestion from Olivier, some people don’t seem to get the point of ‘doing something for yourself’.

  • The Chief Sheep

    Hi Chris, thanks for your comment, I’m pleased to see my explanations about SPI has been helpful to someone else.
    Don’t worry about Olivier. It is just a friend making fun of my posts ;)

  • Bjorn

    An idea I have seen on FPGA projects that access SD-card is to format the card in PC using for example FAT16 and then generate a large , empty, file. As the generated file is the first file in the file system all data will be consecutive on the flash so by identifying where the first byte of content data is located it should be possible to start writing with the uC at that position using raw-mode. Then the file can be examinded in the PC with an intact FAT16 filesystem but with data written by the uC :o )
    This might be a way of storing data in FAT16 without the implementation of FAT16 file system in the uC.
    What do you think of this idea?

  • Jens Willy Johannsen


    Does it run ok at 16 MHz at 3.3V? Because in my datasheet it says that “Maximum frequency … is a linear curve between 2.7V and 4.5V” going from 10 MHz to 20 MHz, which results in about 13.3 MHz at 3.3V.
    (ATmega168-20PU, section 28.3 Speed Grades).

    I’m trying to do pretty much the same as you and I would love to be able to keep my existing 16 MHz crystal.

  • The Chief Sheep

    Hi Jens,

    You’re right, a 16MHz crystal is out of the safe operating area of the ATMEGA168.

    There are two alternatives:

    1) Use a slower crystal, 8MHz or 12MHz for example.
    2) The cleaner way is to run the ATMEGA168 at 5V and add a 3.3V low dropout regulator to separately power the SD card. You also need voltage dividers on the signal going to the card (DATA_IN, SCLK and CS). Ladyada designed its Arduino waveshield this way.

    I had neither 8 MHz or 12MHz crystal and solution 2 requires much more components so when I did my experiments I kept the 16MHz crystal and lowered the main voltage to 3.3V. I didn’t encounter any problem with this setup but its not something I would suggest using for a clean design ;) .

  • Jens Willy Johannsen

    Thanks for the info, Sheep.
    I ended up using a 12 MHz crystal and everything works perfectly.

    My peripheral device (GPS) can handle 5V on VCC but serial comm runs on low-voltage TTL levels only. Which a 5V ATmega can’t understand.
    And since I didn’t want to clutter up the board with stuff for TTL LVTTL conversion I went out and got myself a couple of 12 MHz xtals.

  • John Ulyate

    Your microSD pin descriptions are wrong.

    microSD Memory Card – Pins Definition

    SD Mode

    1 DAT2 I/O/PP DATA Line [Bit 2]
    2 CD / DAT3 I/O/PP Card Detect / DATA Line [Bit 3]
    3 CMD PP Command / Response
    4 VDD S Supply voltage
    5 CLK I Clock
    6 VSS S Supply voltage ground
    7 DAT0 I/O/PP DATA Line [Bit 0]
    8 DAT1 I/O/PP DATA Line [Bit 1]

    SPI Mode
    1 RSV — Reserved (*)
    2 CS I CHIP Select(Negative true)
    3 DI I DATA IN
    4 VDD S Supply voltage
    5 SCLK I Clock
    6 VSS S Supply voltage ground
    8 RSV — Reserved (*)

  • John Ulyate


    The SD card pinout is correct, and the uSD to SD converter will convert correctly, but from your graphics it would appear that uS D(1) is the same as SD(1) which it is not. The correct translator values is the following: (SD Bus)
    (I destroyed a translator/converter to verify)

    SD, uSD, Description
    9, 1, DAT2
    1, 2, DAT3/CD
    2, 3, CMD
    4, 4, Vdd
    5, 5, CLK
    3&6, 6, Vss
    7, 7, DAT0
    8, 8, DAT1


  • The Chief Sheep

    Hi John,

    Thanks for the information. I didn’t cross-checked the uSD pinout and wrongly though it was aligned with SD pinout.
    I have updated my post with a separate uSD pinout table.

  • sunil

    Your post was very helpful for me to understand the SD SPI interface.May I know the reason why you have read the block 63 for the FAT32 MBR.I am new to the filesystem.I am also working on interfacing the SPI to SD memory card.I am able to get all the commands(CID,CSD,SCR) correctly.I tried to do a block read (the data token also has obtained) for the first 8 blocks and I could get the following data from in the first block and 0′s in all the next blocks.I know I am going wrong with my concepts .
    Can you please help me.


  • sunil

    The above data from the block 0 is missing some bytes.
    The following is the data from the first block(block0)
    Can any one please help me.

  • Kishore

    Hi Sunil,

    I am also getting the issue of yours, when ever i read a blcok i get same response as you said.

    If read to block 0, i get the all zeros and at the end few data along with the 0xaa55, if i read block 1 , one extra 0×00 adding 0xaa55 …

    Same way if i read block2 at the i am getting aa550000

    Same way if i read block3 at the end i am getting aa55000000

    i don’t understand, where i am going wrong

  • Al3x 10

    i think you touched the mbr of the card…correct me if i’m wrong but the aa55 is the MBR signature.

  • Nick Bansal

    Sorry I am not understanding all the components for this… Am i just being blind or is there components on the breadboard that aren’t on the schematic?…. im quite new to this so can i have some help on the matter please

  • The Chief Sheep

    The schematic illustrates the connection of the SD card to the SPI pins of the ATMEGA168.
    On the breadboard, the green PCB is a breakout board that provides an ISP connector to ease the programming of the ATMEGA168 (Older version of http://www.sparkfun.com/products/8508). The red led was only added to help me debug my programs.

  • Maryam

    thank you very much for your really helpful post. I have a project about SD card and I want to use your idea of powering the micro controller with 3.3V can I use this idea for ATMEGA16A?

  • The Chief Sheep

    Hi Maryam,
    It should work fine.
    You would only be limited by the maximum safe operating frequency of the ATMEGA16A. It is about 10.67MHz at 3.3V (see datasheet 27.3).

  • muhereza shafiki

    how can i build an mp3 micro sd player out of only transistors not ics

  • The Chief Sheep

    Hi Muhereza,
    It should be theoretically possible but you would need a really big bag of transistor!

  • harry

    How to Atmega 128 microcontroller read wav file using the code vision avr ?

  • The Chief Sheep

    Hi Harry,
    I don’t know much about the CodeVision IDE as I am using avr-gcc and avr-libc.
    I think you should be able to import the source code of my project into it without too much difficulty.
    But you may encounter syntax problems with the compiler.

  • Edson

    I would like to know if is it possible to build this SD card reader using a common PIC like 18F series, because the PIC here used is difficult to find.
    Please, give me some advice.

  • Badr A.

    A lot of thanks for the helpful posted. I’m really interested in what have posted. I have a project that need from me to write to a text file in the SD card using the same microcontoller ATMega168. I would like to know what I should change in the source you have posted.

  • The Chief Sheep

    Hi Badr,
    The sd-reader library has functions to write files on a FAT filesystem.
    To write a text file, you can use the fat_create_file, fat_write_file and fat_close_file functions.
    Manipulating the fat_dir_struct and fat_file_struct is not very intuitive but you can follow the example code in sd-reader main.c for the “write” command.

  • Matthew


    I’ve just bought a FlashAir wifi enabled SD card and was wondering if there is a way to power the SD card directly with a 3.3V battery just to enable the wifi so that I can transfer pictures to and from it without having to connect it to a camera.

  • The Chief Sheep

    Hi Matt,
    I think it should be possible.
    You can use the VDD and VSS1/VSS2 pins to respectively connect a 3.3V regulated power supply and ground to your SD card.
    Best regards,

Leave a Comment