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