Professional Documents
Culture Documents
Finally back! Sorry for taking a while, but getting the SD cards to work required quite a bit of
research! In this part of the tutorial, we’ll start talking with the SD card. Once you get that
working, adding the FAT library developed in the previous part will be easy.
This tutorial will most likely be the most challenging covered in my blog so far, so beware:
You may need to do some troubleshooting on this one! If you encounter problems, I
recommend you to ask any questions at the AVR Freaks tutorial forum topic, so more people
might be able to help you than just me!
The 3.3V UART I covered just a while ago will form the basis for this project. Only change we
will be doing is to replace ATtiny2313 with ATmega88. This is because SPI, SD and FAT code
will eat up almost 3 kB of program memory, and the ATtiny chips with that much program
memory and separate RX/TX and SPI pins are not that common, while ATmega88 is readily
available (48 and 168 work as well, of course). To accommodate the new chip, the following
hardware changes are made to the ATtiny2313 version:
ATmega88 will require two ground connections
In addition to VCC, also AVCC for analog circuitry needs to be wired to VCC
Additional capacitor between AVCC and GND is recommended (I used 10 uF)
Programming header MOSI/MISO/SCK are also in different place
I assume you are by now capable of wiring the ATmega correctly, but just to make things
easier, I attached a pinout diagram with the RXD/TXD wires marked with green (they go to
MAX3232 or similar RS232 circuit), VCC/GND marked with red/blue, crystal pins with
yellow and SPI/programming pins with black. These are all pins that need to be connected in
this project.
Also, the UART facilities of ATmega family differ a bit from ATtiny2313, so our helper
methods needs tweaking:
Once you have means to plug the SD card to breadboard, you need to connect it to ATmega88.
You can see the pinout above. The GND and 3.3V pins are easy, just connect them to the
relevant power rails in the breadboard (remember: SD cards will likely be damaged if you
operate them at 5V). MISO, MOSI and SCK go to the matching ATmega88 pins. CS (Circuit
Select) is connected to the SS (Slave Select, essentially another name for the same thing) pin.
Unmarked pads are not connected to anything. See the full size article photo for an example SD
card wired to breadboard – white wire is MISO, yellow is SCK, green is MOSI and red is
CS/SS.
The SD card connected to ATmega88 SPI pins sometimes interferes with MCU
programming – if you get checksum errors with AVRdude, disconnect SD card, flash, and
connect the SD card again!
If you want to learn more about SPI (and I recommend at least cursory look), I recommend you
to read the Wikipedia article on SPI. There’s some extra details like clock polarization and
phase that need to be the same for both master and slave, but knowing that the “out of the box”
setup of ATmega SPI hardware works with SD cards, we don’t need to consider them here.
The BusPirate syntax seen above is rather simple: “[” means that the CS line is activated
(pulled low) and “]” means the opposite (deactived / pulled high). A hex in SPI mode means
that hex is sent to the slave over SPI. “r” means a byte is read from the slave (while sending a
0xFF, because SPI operations always read and write at the same time). “:8” means the
command before it is repeated eight times, and it can be used for both read and write
operations. So the “Go to SPI mode” command actually does the following:
If you want to learn more about the SD protocol, there’s a simplified specification
sheet available from SD Association without signing an NDA.
#define CS (1<<PB2)
#define MOSI (1<<PB3)
#define MISO (1<<PB4)
#define SCK (1<<PB5)
#define CS_DDR DDRB
#define CS_ENABLE() (PORTB &= ~CS)
#define CS_DISABLE() (PORTB |= CS)
void SPI_init() {
CS_DDR |= CS; // SD card circuit select as output
DDRB |= MOSI + SCK; // MOSI and SCK as outputs
PORTB |= MISO; // pullup in MISO, might not be needed
CS_ENABLE();
SPI_write(cmd);
SPI_write(arg>>24);
SPI_write(arg>>16);
SPI_write(arg>>8);
SPI_write(arg);
SPI_write(crc);
CS_DISABLE();
// print out read bytes
}
Note that the “& 255” part after arg>>16 etc. can be omitted because SPI_write takes a
byte argument and any bits above the bottom 8 are truncated.
I added the above fragments of code to the USART test, and added a few printout commands
(uwrite_str for printing out strings over RS-232 and uwrite_hex for bytes in hex
format). I thought it would be nice to interact with the program manually for a change, so the
main loop just waits for “1”, “2” or “3” to be typed into terminal, and executes one of the
commands I mentioned above:
while(1) {
switch(USARTReadChar()) {
case '1':
SD_command(0x40, 0x00000000, 0x95, 8);
break;
case '2':
SD_command(0x41, 0x00000000, 0xFF, 8);
break;
case '3':
SD_command(0x50, 0x00000200, 0xFF, 8);
break;
}
}
I tried each of the commands – 1, 2, and 3 a few times. Here’s the output – if you check against
the BusPirate tutorial I linked above, we can see that it’s working! Download
the spi88.cyourself and flash it to try it out yourself:
As an excercise, you could already try adding a new function for the fourth command that will
read a sector from SD card. For this, you’ll just need to write the command 0x51 (read data)
with argument that is the data offset (start with 0 to dump the first sector) and CRC of 0xFF
(SPI mode does not check CRC so we always send 0xFF after setting the SPI mode…). After
that, instead of just reading 8 bytes, you read until you get 0x00 from SD card to indicate
command has been processed, and then wait for 0xFE to indicate data start. Then you just loop
for 512 bytes, printing them out as you go, and read additional two bytes (CRC).
In the next part of the tutorial, we’ll do this and more: Combine the FAT library to our SD
reading code to get a full-fledged SD reading library!
Finally, special thanks to Mark for his donation that I used to cover part of the BusPirate
purchase costs – this tutorial would’ve likely taken quite a while longer without the debugging
help I had from that tool!
Proceed to the next part of this tutorial
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 25, 2012AuthorJoonas PihlajamaaCategoriesElectronicsTagsatmega88, sd, sd card,sd
pinout, spi, tutorial, uart
1. Martha Rodriguezsays:
April 27, 2012 at 16:54
The MBR is also shared by boot code. For non-system disks, this commonly includes a short
program that displays an error if the user tries to boot from it. The message is part of that – not
that you would usually try to boot your computer from a SD card, but… :)
REPLY
1. Martha Rodriguezsays:
April 27, 2012 at 17:22
ok thanks. But, how I do view partition table entry, that is around 0x1BE if in that address show
this message. What am I doing bad?
REPLY
2. Martha Rodriguezsays:
April 27, 2012 at 17:48
Hi, My problem using HxD is that the block in my sd between 0x0000 and 0x 001FF is the
same in your file text.img between 0x10200 and 0x102FF. so I can`t partition table entry. What
can I do?
thanks.
REPLY
1. Martha Rodriguezsays:
April 27, 2012 at 18:12
ok, I just the problem, my sd doesn’t MBR, and in sector 0 I see the FAT16 for single partition.
REPLY
1. jokkebksays:
April 27, 2012 at 18:23
Yeah, some SD cards are initialized that way. You can then just skip the MBR reading
completely. :)
REPLY
Great tutorial..did help me learn a lot about FAT file system. I am trying to test the SPI code
that you have provided on this page, but the result I am getting from serial port is all FF in
comparison to yours FF 01 FF. I have checked connections of my circuit but I am unable to get
my head around the response I am getting back.
REPLY
1. Joonas Pihlajamaasays:
December 9, 2013 at 23:52
I have vague recollection that sometimes when SD initialization did not “go through” it would
not return that 01 at all. Unfortunately I don’t remember what was wrong then, maybe I needed
to send some additional clocks, or acknowledge the sent bytes, or something. :-d
REPLY
6. nuttakornsays:
September 19, 2013 at 10:17
i want the code write txt. to sd card with avr(atmega128) or write pattern
best regard
REPLY
7. Ricksays:
February 6, 2014 at 06:28
SPI_transmit(cmd);
SPI_transmit(arg>>24);
SPI_transmit(arg>>16);
SPI_transmit(arg>>8);
SPI_transmit(arg);
SPI_transmit(crc);
SD_CS_DEASSERT;
1. Joonas Pihlajamaasays:
February 6, 2014 at 10:28
Strange. Does your USART work outside of that function? You could try adding some debug
USART sends to the SD code so you can see if SD writing hangs at some point – the first thing
that popped to my mind was that maybe SD isn’t acknowledging the data and the code hangs
there.
REPLY
1. Ricksays:
February 6, 2014 at 12:45
Yes Usart is working, I tested on the first time program running, I used ATMEGA128
main :
void spi_init(void)
{
SPCR = _BV(SPE)|_BV(MSTR)|_BV(SPR1)|_BV(SPR0);
SPSR &= ~_BV(SPI2X);
}
return SPDR;
}
int main (void)
{
unsigned char option;
port_init();
spi_init();
usart_init(BAUD_PRESCALE);
usart_pstr("USART READY!");
while(1)
{
option = usart_receive();
switch(option)
{
case '1':
usart_pstr("1 command");
SD_command(0x40, 0x00000000, 0x95, 8);
break;
case '2':
SD_command(0x41, 0x00000000, 0xFF, 8);
usart_pstr("2 command");
break;
case '3':
SD_command(0x50, 0x00000200, 0xFF, 8);
usart_pstr("3 command");
break;
}
}
}
REPLY
1. Joonas Pihlajamaasays:
February 6, 2014 at 13:43
The approach sounds solid. Unfortunately I’ve forgotten most everything about SPI and SD
card communication since I wrote the tutorial two years ago, so you’ll probably have as much
luck as I with debugging the code. :( Maybe folks at AVRfreaks or similar electronics forum
might have something helpful.
REPLY
8. sergsays:
May 1, 2014 at 12:13
Hi, is it mandatory to read 8 bytes after sending SD command “r:8”? or do I just need to wait
for 0x01?
REPLY
1. Joonas Pihlajamaasays:
May 28, 2014 at 11:08
If I remember correctly, the SPI protocol works in a way that master device sends the clock, so
reading 8 bytes is required, otherwise the SD card won’t receive the clock cycles it is
expecting.
REPLY
9. Dsesesays:
August 25, 2014 at 09:55
CMD 40 FF 01 FF FF FF FF FF FF
CMD 41 FF 01 FF FF FF FF FF FF
CMD 41 FF 00 FF FF FF FF FF FF
CMD 50 FF 00 FF FF FF FF FF FF
FAT err: FF
1. Joonas Pihlajamaasays:
August 25, 2014 at 13:34
Unfortunately my SD knowledge has largely evaporated in the past two years. However, I do
remember I had most problems getting the SPI to work properly, so most likely candidate
would be some problem with that (timings, levels, connections, contact with card..).
If you have a Bus pirate or similar, you could first try to get the card to respond to manual
commands, that’s what worked for me.
REPLY
1. Dsesesays:
August 25, 2014 at 18:38
Hi Joonas,
1. Joonas Pihlajamaasays:
August 25, 2014 at 20:52
You might need to adjust the USARTinit magic value (UBRR or whatever) to match the new
baud rate too. USART is easier to debug though, as you can quickly see when you can or
cannot communicate over serial terminal with the device.
REPLY
1. Dsesesays:
August 26, 2014 at 09:52
i tried it, but it still didnt work output is still the same. do you think micro sd card will also
work on your code? i dont have SD card.
2. Joonas Pihlajamaasays:
August 26, 2014 at 11:29
10. Dsesesays:
August 26, 2014 at 15:46
Hi Joonas,
i actually do everything. such as fortmating the microsd to Fat16. and think your codes is
working. because when i remove the card. i get this “SD err”. then when put again the micro sd
card. i get it working. but it returns to Fat: err.. hmmm very confusing.
what is wrong then
REPLY
11. Pingback: Code and Life – Simple FAT and SD Tutorial Part 4 | kmestore DIY
12. QianFansays:
March 27, 2015 at 08:24
Hello:
I very like this web,it’s font,it’s color,and everything! Serials artical introduced me to learn SD
card and FAT format.
Sorry about my poor English,coming from a non-english county!
QianFan
Thank you!
REPLY
13. Julianosays:
May 3, 2015 at 18:11
Hello Friend! First I want to say thanks for the tutorial! I’m learning a lot …
I’m using an Atmega8 and a mini SD card with adapter, it will be stored some data from
sensors installed in the microcontroller to … I’m encountering an error FAT16 boot, more
precisely, the function “fat16_init ()” is launching an error: “” FAT err: FF “. I checked the
code, and in the file “fat16. c” the function “fat16_init ()” contains a sentry that keeps the
system startup file, in the following code snippet:
If (i == 4) {
None of the partitions were FAT16
return FAT16_ERR_NO_PARTITION_FOUND;
}
Thanks buddy!
REPLY
1. Joonas Pihlajamaasays:
May 3, 2015 at 22:52
With larger cards, Windows might format them FAT32. Also, some cards have partitions and
some don’t. No concrete advice unfortunately from here, I’d look at the card data with a hex
editor like HxD and compare it against the FAT specs to see what is different.
You might also copy the raw data from SD card to disk image and run tests on the computer, so
you can separate possible SD reading issues and FAT issues from each other.
REPLY
14. Andrzejsays:
November 23, 2015 at 01:47
Hello,
Regards.
Andrzej
REPLY
1. Joonas Pihlajamaasays:
November 23, 2015 at 12:42
Thanks! It’s been so long since I wrote this so I’m just going to take your word for it, I don’t
know how CS became CD and high became low…
REPLY
15. Dinushasays:
January 24, 2016 at 09:55
This is great article. I am using atmega32 and rus at 8MHz . I change the code according that.
But when i input 1,2,3 to putty it doesn’t give any output. why? please help me.
#include
#define CS (1<<PINB4)
#define MOSI (1<<PINB5)
#define MISO (1<<PINB6)
#define SCK (1<<PINB7)
#define CS_DDR DDRB
#define CS_ENABLE() (PORTB &= ~CS)
#define CS_DISABLE() (PORTB |= CS)
void SPI_init() {
CS_DDR |= CS; // SD card circuit select as output
DDRB |= MOSI + SCK; // MOSI and SCK as outputs
PORTB |= MISO; // pull up in MISO, might not be needed
CS_DISABLE();
uwrite_str("\r\n");
}
// ]r:10
CS_DISABLE();
for(i=0; i<10; i++) // idle for 1 bytes / 80 clocks
SPI_write(0xFF);
while(1) {
switch(USARTReadChar()) {
case '1':
SD_command(0x40, 0x00000000, 0x95, 8);
break;
case '2':
SD_command(0x41, 0x00000000, 0xFF, 8);
break;
case '3':
SD_command(0x50, 0x00000200, 0xFF, 8);
break;
}
}
return 0;
}
REPLY
1. Joonas Pihlajamaasays:
January 24, 2016 at 18:57
Thanks! Hard to say why the code does not work, unfortunately I cannot help in questions
(otherwise I’d be spending all my free time on that), so I suggest asking an electronics forum of
some kind.
What does the UCSRA & (1<>4) & 15) >4 type of code do? Looks really strange, or did
WordPress change something?
REPLY