ATmega library for SD cards
(Last modified
21 Aug 11)
This code provides a generic set of routines for accessing an SD
card from an AVR device. The routines are target-independent;
they do not know about or depend on any hardware characteristics of the
target design. All of the target-dependent hardware accesses are
done in the host code (your program). Functions needed by the
library code are made available through a special routine called
sd_register(). Your code must invoke sd_register() with a pointer
to a structure containing pointers to the hardware-specific functions
needed. This may sound complicated but it is actually
straightforward. I've provided a demo program and examples below
that show
how to use the library.
This code is based on work done by Jesper Hansen
<jesper@redegg.net> and published on the web. See the
sdcard.h and sddemo.c files for details on derivation.
(21 Aug 2011) I
discovered several errors in my original sdcard code. I have
updated the .zip file below with new versions of the associated
files. This version supports only V1 SD cards (< 4GB
capacity). It should allow you to run multiple devices on the
same SPI bus as the SD card, but I have not tested that yet.
I believe you should be able to implement such a feature by writing the
associated select() and deselect() routines so each uses the correct
SPI control and status settings for the associated device, saving and
restoring those registers when appropriate. For example, the select()
routine for your SD card would save a copy of the SPCR and SPSR
registers, update those registers with values needed for communications
with the SD card, then enable the SD card. Similarly, the
deselect() routine would overwrite the SPCR and SPSR registers
with the values previously saved by the select() routine. In this
way, exchanging data with the SD card would not corrupt settings for
other SPI devices sharing the same bus.
Using the library
To use the library, download the .zip file below and unzip it in a
suitable folder. I keep all of my library object files in
c:\projects\library and all of my common header files in
c:\projects\include; feel free to adjust these locations as appropriate.
Open an AVRStudio4 project and assign an appropriate device to your
project. The sddemo project below uses an ATmega328p so you can
use that device if you want to rebuild the sddemo project without
change. Use the Projects/Configuration Options window to assign
the operating frequency of your target hardware (F_CPU); my sample
project used 8.0 MHz. Also set the paths to your Include
directory and your Library directory.
Note that my sddemo project uses a custom UART library, which is not
(yet) available. You can either create your own UART routines and
hook them into STDIN, STDOUT, and STDERR (check the AVR Freaks site for examples) or you can
just comment out any code that refers to the UART. This will
result in a project that won't be very useful, since you won't be able
to interact with the SD card, but you will be able to compile and link,
confirming that your library is properly installed.
Build your project and download it into your target device. Hook
up a serial terminal and start TeraTerm Pro or other comm program; set
for 38400, 8N1. Reset the target and you should see a short
display providing the block length, capacity in sectors, and capacity
in bytes of your card. A simple menu of commands allows you to
display selected sectors, read the CSD registers, and erase a given
sector (write to 0xff).
Customizing the code
The library's sd_register() routine accepts a pointer to a structure
holding three or four callback functions. Three of these
functions are required; they are select(), deselect(), and
xchg(). Respectively, these enable the SD card, disable the SD
card, and exchange a byte of data with the SD card. Your code
must provide these three hardware-dependent functions so the library
can interact with your SD card.
A fourth function, power() is optional and can be used to apply/remove
power to the SD card. If your hardware does not support such a
function, simply pass 0 as the pointer to the power() function when
invoking sd_register().
Since everything that is hardware-dependent lives in your host code,
you can control which I/O lines talk to the SD card. You can even
use the library to support accessing multiple SD cards, should you
need, for example, an A: drive and a B: drive on your project.
You just
need to keep a local variable that tracks the current drive, and add
code to the four functions that use this variable to determine which
I/O lines to toggle when the library tries to access an SD card.
To allow the SD card to power-up cleanly, allow a considerable delay
(up to a second) following power-up of the SD card before invoking
sd_register(). If you have some way of monitoring the quality of
the power to the SD card, even better.
Note that invoking sd_register() always causes the SD library to
initialize your SD card. The assumption is that you will only
call sd_register() after the card's power supply is stable.
Therefore, sd_register() invokes power() directly, then performs the
power-up initialization of the SD card. If your target does not
have a power control line for the SD card, sd_register() will still
perform the initialization. The initialization issues CMD0 to the
card to force it to an idle state, then issues a sequence of CMD55 -
ACMD41 commands, looking for a ready response. The sd_register()
routine will NOT work with MMC cards and will NOT work with V2 SD cards
(4GB or higher).
If you must invoke the power() function prior to calling sd_register(),
go ahead. sd_register() will still invoke power() directly and
will still initialize the SD card.
The sd_register() routine returns SDCARD_OK if the initialization
succeeded. Error values include SDCARD_NO_DETECT if the SD card
never reported an idle state in response to CMD0 and SDCARD_TIMEOUT if
the SD card never issued a ready response after a large number of CMD55
- ACMD41 polls.
When you invoke sd_register(), you must set up the SPI to use a clock
frequency between 100 kHz and 400 kHz. If sd_register() reports
that initialization succeeded, you are then free to boost the SPI clock
frequency, up to 25 MHz.
A
sample implementation
Here are snippets from the sddemo.c program showing how I implemented
the four SD card functions needed by the library. These should
give a clear example of how you can set up your own functions.
//
====================================================
/*
* The following
defines are target-dependent and could vary, based on your
* chosen MCU and
hardware design. The values shown here are for an
* ATmega328p with SS
(PB2) used as chip-select (active-low). I've also
* wired PD4 as a
power-control line for the SD card. If your design
* doesn't require
power-control, see further comments below for setting
* up your callback
functions.
*/
/*
* Define the bits
used by the SPI for target device.
*/
#define
MOSI_BIT 3
#define
MISO_BIT 4
#define
SCK_BIT 5
#define
SS_BIT 2
/*
* Define the port and
DDR used by the SPI for target device.
*/
#define
SPI_PORT PORTB
#define
SPI_DDR DDRB
/*
* Define the port,
DDR, and bit used as chip-select for the
* SD card on the
target device.
*/
#define
SD_CS_PORT PORTB
#define
SD_CS_DDR DDRB
#define
SD_CS_BIT 2
#define
SD_CS_MASK (1<<SD_CS_BIT)
/*
* (Optional)
Define the port, DDR, and bit used as a power-control
* line for the SD
card on the target device.
*
* If your hardware
does not provide a power-control line to the SD
* card, you can omit
these #defines.
*/
#define
SD_PWR_PORT PORTD
#define
SD_PWR_DDR DDRD
#define
SD_PWR_BIT 4
#define
SD_PWR_MASK (1<<SD_PWR_BIT)
//
====================================================
/*
*
my_sd_select select (enable) the SD card
*/
static void
my_sd_select(void)
{
SD_CS_PORT =
SD_CS_PORT & ~SD_CS_MASK;
}
/*
*
my_sd_deselect deselect (disable) the SD
card.
*/
static void
my_sd_deselect(void)
{
SD_CS_PORT =
SD_CS_PORT | SD_CS_MASK;
}
/*
*
my_sd_xchg exchange a byte of data with
the SD card via host's SPI bus
*/
static unsigned char
my_sd_xchg(unsigned char c)
{
SPDR = c;
while ((SPSR
& (1<<SPIF)) == 0) ;
return
SPDR;
}
/*
*
my_sd_power control power to the SD card
(optional routine)
*
* If your hardware
does not support power control of the SD card, omit
* this routine.
*/
static void
my_sd_power(unsigned char v)
{
if
(v)
// if turning
on SD card...
{
SD_PWR_PORT = SD_PWR_PORT & ~SD_PWR_MASK;
}
else
// no, turning
off SD card...
{
SD_PWR_PORT = SD_PWR_PORT | SD_PWR_MASK;
}
}
//
========================================================================
And here is code showing how to load up the function pointers and pass
them into the SD card library.
/*
* Fill the callback
structure with pointers to the target-dependent SD card support
* functions.
*
* If your hardware
does not support a power-control line, use 0 for the .power callback
* pointer.
*/
my_callbacks.select = &my_sd_select;
my_callbacks.deselect = &my_sd_deselect;
my_callbacks.xchg = &my_sd_xchg;
my_callbacks.power = &my_sd_power;
result = sd_register(&my_callbacks);
// call the library's register
function to connect to the routines
The files
Here is a zip file containing the sddemo
program and the source and object module for the SD card support
library. The sddemo program, with my UART library, takes just
less than 7,600 bytes of code space.
Home