Interfacing with a Secure Digital (SD) card

From Mech
Revision as of 16:49, 11 February 2009 by Mat Kotowsky (talk | contribs) (→‎Code)
Jump to navigationJump to search

Original Assignment

Establish SPI communication between your PIC and a Secure Digital (SD) card for data logging. Demonstrate the ability to store data on the card and to read it back later.


Overview

Secure Digital Cards, or SD cards, are used to hold information in many common electronic devices from digital cameras to mobile phones and come in sizes as small as 16-32 MB and as large as 8 GB. In this lab, we will establish communication between a Microchip PIC 18F4520 and a 2GB SD card manufactured by Apacer.

SD cards can operate three different communication modes: One-bit SD mode, four-bit SD mode, and SPI mode. SPI is a more basic protocol and it is widely supported by many microcontrollers, including the PIC 18F4520. We'll be using SPI mode in this lab.


Circuit

An SD card has 9 pins. Only 7 of these pins are used to communicate with an SD card in SPI mode. SD cards require between 2 and 3.6 VDC. In this lab, we use a bench top power supply to provide 3.3 VDC to both the PIC and to the SD card. 50k pull-up resistors are essential, even for the pins that are not being used for SPI communications. Note that a pull-up resistor should not be used on the clock line.

Circuit Diagram


SD Card Holder


Code

The original, unmodified files were included in the CCS v4.081library. Modifications were made to change the pinouts, modify which header files were included for the pic, the RS232 use preprocessor command and modified the clock.


/////////////////////////////////////////////////////////////////////////
////                      ex_mmcsd.c                                 ////
////                                                                 ////
//// Similar to ex_extee.c, an example that demonstrates             ////
//// writing and reading to an MMC/SD card.                          ////
////                                                                 ////
/////////////////////////////////////////////////////////////////////////
////        (C) Copyright 2007 Custom Computer Services              ////
//// This source code may only be used by licensed users of the CCS  ////
//// C compiler.  This source code may only be distributed to other  ////
//// licensed users of the CCS C compiler.  No other use,            ////
//// reproduction or distribution is permitted without written       ////
//// permission.  Derivative programs created using this software    ////
//// in object code form are not restricted in any way.              ////
/////////////////////////////////////////////////////////////////////////

 //These settings are for the CCS PICEEC development kit which contains
 //an MMC/SD connector.
 #include <18f4520.h>
 #fuses NOWDT, HS, NOPROTECT
 #fuses HS,NOLVP,NOWDT,NOPROTECT
 #use delay(clock=40000000)
 #use rs232(baud=19200, UART1)       // hardware uart much better; uses  RC6/TX and RC7/RX
 // characters tranmitted faster than the pic eats them will cause UART to hang.

 #include <stdlib.h> // for atoi32

 //media library, a compatable media library is required for FAT.
 #use fast_io(c)
 #define MMCSD_PIN_SCL     PIN_B0 //o THIS WAS A HUGE PROBLEM, C3 DIDN'T WORK
 #define MMCSD_PIN_SDI     PIN_C4 //i
 #define MMCSD_PIN_SDO     PIN_C5 //o
 #define MMCSD_PIN_SELECT  PIN_C2 //o
 #include <mmcsd.c>

 #include <input.c>

 void main(void)
 {
  BYTE value, cmd;
  int32 address;
  printf("\r\n\nex_mmcsd.c\r\n\n");
  printf("\r\n\n");

  if (mmcsd_init())
  {
     printf("Could not init the MMC/SD!!!!");
     while(TRUE);
  }
  mmcsd_print_cid();

  do {
     do {
        printf("\r\nRead or Write: ");
        cmd=getc();
        cmd=toupper(cmd);
        putc(cmd);
     } while ( (cmd!='R') && (cmd!='W') );
     printf("\n\rLocation: ");
     address = gethex();
     address = (address<<8)+gethex();
     if(cmd=='R')
     {
        mmcsd_read_byte(address, &value);
        printf("\r\nValue: %X\r\n", value);
     }
     if(cmd=='W') {
        printf("\r\nNew value: ");
        value = gethex();
        printf("\n\r");
        mmcsd_write_byte(address, value);
        mmcsd_flush_buffer();
     }
  } while (TRUE);
}                                                   
                                                                     
                                                                   
                                             
/////////////////////////////////////////////////////////////////////////
////                           MMCSD.c                               ////
////                                                                 ////
////    This is a low-level driver for MMC and SD cards.             ////
////                                                                 ////
//// --User Functions--                                              ////
////                                                                 ////
//// mmcsd_init(): Initializes the media.                            ////
////                                                                 ////
//// mmcsd_read_byte(a, p)                                           ////
////  Reads a byte from the MMC/SD card at location a, saves to      ////
////  pointer p.  Returns 0 if OK, non-zero if error.                ////
////                                                                 ////
//// mmcsd_read_data(a, n, p)                                        ////
////  Reads n bytes of data from the MMC/SD card starting at address ////
////  a, saves result to pointer p.  Returns 0 if OK, non-zero if    ////
////  error.                                                         ////
////                                                                 ////
//// mmcsd_flush_buffer()                                            ////
////  The two user write functions (mmcsd_write_byte() and           ////
////  mmcsd_write_data()) maintain a buffer to speed up the writing  ////
////  process.  Whenever a read or write is performed, the write     ////
////  buffer is loaded with the specified page and only the          ////
////  contents of this buffer is changed.  If any future writes      ////
////  cross a page boundary then the buffer in RAM is written        ////
////  to the MMC/SD and then the next page is loaded into the        ////
////  buffer.  mmcsd_flush_buffer() forces the contents in RAM       ////
////  to the MMC/SD card.  Returns 0 if OK, non-zero if errror.      ////
////                                                                 ////
//// mmcsd_write_byte(a, d)                                          ////
////  Writes data byte d to the MMC/SD address a.  Intelligently     ////
////  manages a write buffer, therefore you may need to call         ////
////  mmcsd_flush_buffer() to flush the buffer.                      ////
////                                                                 ////
//// mmcsd_write_data(a, n, p)                                       ////
////  Writes n bytes of data from pointer p to the MMC/SD card       ////
////  starting at address a.  This function intelligently manages    ////
////  a write buffer, therefore if you may need to call              ////
////  mmcsd_flush_buffer() to flush any buffered characters.         ////
////  returns 0 if OK, non-zero if error.                            ////
////                                                                 ////
//// mmcsd_read_block(a, s, p)                                       ////
////  Reads an entire page from the SD/MMC.  Keep in mind that the   ////
////  start of the read has to be aligned to a block                 ////
////  (Address % 512 = 0).  Therefore s must be evenly divisible by  ////
////  512.  At the application level it is much more effecient to    ////
////  to use mmcsd_read_data() or mmcsd_read_byte().  Returns 0      ////
////  if successful, non-zero if error.                              ////
////                                                                 ////
//// mmcsd_write_block(a, s, p):                                     ////
////  Writes an entire page to the SD/MMC.  This will write an       ////
////  entire page to the SD/MMC, so the address and size must be     ////
////  evenly  divisble by 512.  At the application level it is much  ////
////  more effecient to use mmcsd_write_data() or mmcsd_write_byte().////
////  Returns 0 if successful, non-zero if error.                    ////
////                                                                 ////
//// mmcsd_print_cid(): Displays all data in the Card Identification ////
////                     Register. Note this only works on SD cards. ////
////                                                                 ////
//// mmcsd_print_csd(): Displays all data in the Card Specific Data  ////
////                     Register. Note this only works on SD cards. ////
////                                                                 ////
////                                                                 ////
//// --Non-User Functions--                                          ////
////                                                                 ////
//// mmcsd_go_idle_state(): Sends the GO_IDLE_STATE command to the   ////
////                        SD/MMC.                                  ////
//// mmcsd_send_op_cond(): Sends the SEND_OP_COND command to the     ////
////                        SD. Note this command only works on SD.  ////
//// mmcsd_send_if_cond(): Sends the SEND_IF_COND command to the     ////
////                        SD. Note this command only works on SD.  ////
//// mmcsd_sd_status(): Sends the SD_STATUS command to the SD. Note  ////
////                     This command only works on SD cards.        ////
//// mmcsd_send_status(): Sends the SEND_STATUS command to the       ////
////                       SD/MMC.                                   ////
//// mmcsd_set_blocklen(): Sends the SET_BLOCKLEN command along with ////
////                        the desired block length.                ////
//// mmcsd_app_cmd(): Sends the APP_CMD command to the SD. This only ////
////                   works on SD cards and is used just before any ////
////                   SD-only command (e.g. send_op_cond()).        ////
//// mmcsd_read_ocr(): Sends the READ_OCR command to the SD/MMC.     ////
//// mmcsd_crc_on_off(): Sends the CRC_ON_OFF command to the SD/MMC  ////
////                      along with a bit to turn the CRC on/off.   ////
//// mmcsd_send_cmd(): Sends a command and argument to the SD/MMC.   ////
//// mmcsd_get_r1(): Waits for an R1 response from the SD/MMC and    ////
////                  then saves the response to a buffer.           ////
//// mmcsd_get_r2(): Waits for an R2 response from the SD/MMC and    ////
////                  then saves the response to a buffer.           ////
//// mmcsd_get_r3(): Waits for an R3 response from the SD/MMC and    ////
////                  then saves the response to a buffer.           ////
//// mmcsd_get_r7(): Waits for an R7 response from the SD/MMC and    ////
////                  then saves the response to a buffer.           ////
//// mmcsd_wait_for_token(): Waits for a specified token from the    ////
////                          SD/MMC.                                ////
//// mmcsd_crc7(): Generates a CRC7 using a pointer to some data,    ////
////                and how many bytes long the data is.             ////
//// mmcsd_crc16(): Generates a CRC16 using a pointer to some data,  ////
////                and how many bytes long the data is.             ////
////                                                                 ////
/////////////////////////////////////////////////////////////////////////
////        (C) Copyright 2007 Custom Computer Services              ////
//// This source code may only be used by licensed users of the CCS  ////
//// C compiler.  This source code may only be distributed to other  ////
//// licensed users of the CCS C compiler.  No other use,            ////
//// reproduction or distribution is permitted without written       ////
//// permission.  Derivative programs created using this software    ////
//// in object code form are not restricted in any way.              ////
/////////////////////////////////////////////////////////////////////////

#ifndef MMCSD_C
#define MMCSD_C

/////////////////////
////             ////
//// User Config ////
////             ////
/////////////////////

#ifndef MMCSD_PIN_SCL
 #define MMCSD_PIN_SCL     PIN_B0 //o
 #define MMCSD_PIN_SDI     PIN_C4 //i
 #define MMCSD_PIN_SDO     PIN_C5 //o
 #define MMCSD_PIN_SELECT  PIN_C2 //o
#endif

#use spi(MASTER, DI=MMCSD_PIN_SDI, DO=MMCSD_PIN_SDO, CLK=MMCSD_PIN_SCL, BITS=8, MSB_FIRST, IDLE=1, stream=mmcsd_spi)

////////////////////////
////                ////
//// Useful Defines ////
////                ////
////////////////////////

enum MMCSD_err
   {MMCSD_GOODEC = 0,
   MMCSD_IDLE = 0x01,
   MMCSD_ERASE_RESET = 0x02,
   MMCSD_ILLEGAL_CMD = 0x04,
   MMCSD_CRC_ERR = 0x08,
   MMCSD_ERASE_SEQ_ERR = 0x10,
   MMCSD_ADDR_ERR = 0x20,
   MMCSD_PARAM_ERR = 0x40,
   RESP_TIMEOUT = 0x80};

#define GO_IDLE_STATE 0
#define SEND_OP_COND 1
#define SEND_IF_COND 8
#define SEND_CSD 9
#define SEND_CID 10
#define SD_STATUS 13
#define SEND_STATUS 13
#define SET_BLOCKLEN 16
#define READ_SINGLE_BLOCK 17
#define WRITE_BLOCK 24
#define SD_SEND_OP_COND 41
#define APP_CMD 55
#define READ_OCR 58
#define CRC_ON_OFF 59

#define IDLE_TOKEN 0x01
#define DATA_START_TOKEN 0xFE

#define MMCSD_MAX_BLOCK_SIZE 512

////////////////////////
///                  ///
/// Global Variables ///
///                  ///
////////////////////////

int g_mmcsd_buffer[MMCSD_MAX_BLOCK_SIZE];

int1 g_CRC_enabled;
int1 g_MMCSDBufferChanged;

int32 g_mmcsdBufferAddress;

enum _card_type{SD, MMC} g_card_type;

/////////////////////////////
////                     ////
//// Function Prototypes ////
////                     ////
/////////////////////////////

MMCSD_err mmcsd_init();
MMCSD_err mmcsd_read_data(int32 address, int16 size, int* ptr);
MMCSD_err mmcsd_read_block(int32 address, int16 size, int* ptr);
MMCSD_err mmcsd_write_data(int32 address, int16 size, int* ptr);
MMCSD_err mmcsd_write_block(int32 address, int16 size, int* ptr);
MMCSD_err mmcsd_go_idle_state(void);
MMCSD_err mmcsd_send_op_cond(void);
MMCSD_err mmcsd_send_if_cond(int r7[]);
MMCSD_err mmcsd_print_csd();
MMCSD_err mmcsd_print_cid();
MMCSD_err mmcsd_sd_status(int r2[]);
MMCSD_err mmcsd_send_status(int r2[]);
MMCSD_err mmcsd_set_blocklen(int32 blocklen);
MMCSD_err mmcsd_read_single_block(int32 address);
MMCSD_err mmcsd_write_single_block(int32 address);
MMCSD_err mmcsd_sd_send_op_cond(void);
MMCSD_err mmcsd_app_cmd(void);
MMCSD_err mmcsd_read_ocr(int* r1);
MMCSD_err mmcsd_crc_on_off(int1 crc_enabled);
MMCSD_err mmcsd_send_cmd(int cmd, int32 arg);
MMCSD_err mmcsd_get_r1(void);
MMCSD_err mmcsd_get_r2(int r2[]);
MMCSD_err mmcsd_get_r3(int r3[]);
MMCSD_err mmcsd_get_r7(int r7[]);
MMCSD_err mmcsd_wait_for_token(int token);
unsigned int8 mmcsd_crc7(char *data, unsigned int8 length);
unsigned int16 mmcsd_crc16(char *data, unsigned int8 length);
void mmcsd_select();
void mmcsd_deselect();

/// Fast Functions ! ///

MMCSD_err mmcsd_load_buffer(void);
MMCSD_err mmcsd_flush_buffer(void);
MMCSD_err mmcsd_move_buffer(int32 new_addr);
MMCSD_err mmcsd_read_byte(int32 addr, char* data);
MMCSD_err mmcsd_write_byte(int32 addr, char data);

//////////////////////////////////
////                          ////
//// Function Implementations ////
////                          ////
//////////////////////////////////

MMCSD_err mmcsd_init()
{
   int
      i,
      r1;

   g_CRC_enabled = TRUE;
   g_mmcsdBufferAddress = 0;

   output_drive(MMCSD_PIN_SCL);
   output_drive(MMCSD_PIN_SDO);
   output_drive(MMCSD_PIN_SELECT);
   output_float(MMCSD_PIN_SDI);

   mmcsd_deselect();
   delay_ms(15);
   
   /* begin initialization */
   i = 0;
   do
   {
      delay_ms(1);
      mmcsd_select();
      r1=mmcsd_go_idle_state();
      mmcsd_deselect();
      i++;
      if(i == 0xFF)
      {
         mmcsd_deselect();
         return r1;
      }
   } while(!bit_test(r1, 0));

   i = 0;
   do
   {
      delay_ms(1);
      mmcsd_select();
      r1=mmcsd_send_op_cond();
      mmcsd_deselect();
      i++;
      if(i == 0xFF)
      {
         mmcsd_deselect();
         return r1;
      }
   } while(r1 & MMCSD_IDLE);

   /* figure out if we have an SD or MMC */
   mmcsd_select();
   r1=mmcsd_app_cmd();
   r1=mmcsd_sd_send_op_cond();
   mmcsd_deselect();

   /* an mmc will return an 0x04 here */
   if(r1 == 0x04)
      g_card_type = MMC;
   else
      g_card_type = SD;

   /* set block length to 512 bytes */
   mmcsd_select();
   r1 = mmcsd_set_blocklen(MMCSD_MAX_BLOCK_SIZE);
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }
   mmcsd_deselect();

   /* turn CRCs off to speed up reading/writing */
   mmcsd_select();
   r1 = mmcsd_crc_on_off(0);
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }
   mmcsd_deselect();

   r1 = mmcsd_load_buffer();

   return r1;
}

MMCSD_err mmcsd_read_data(int32 address, int16 size, int* ptr)
{
   MMCSD_err r1;
   int16 i;  // counter for loops

   for(i = 0; i < size; i++)
   {
      r1 = mmcsd_read_byte(address++, ptr++);
      if(r1 != MMCSD_GOODEC)
         return r1;
   }
   
   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_read_block(int32 address, int16 size, int* ptr)
{  
   MMCSD_err ec;
   int16 i; // counter for loops

   // send command
   mmcsd_select();
   ec = mmcsd_read_single_block(address);
   if(ec != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return ec;
   }
   
   // wait for the data start token
   ec = mmcsd_wait_for_token(DATA_START_TOKEN);
   if(ec != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return ec;
   }
   
   // read in the data
   for(i = 0; i < size; i += 1)
      ptr[i] = spi_xfer(mmcsd_spi, 0xFF);

   if(g_CRC_enabled)
   {
      /* check the crc */
      if(make16(spi_xfer(mmcsd_spi, 0xFF), spi_xfer(mmcsd_spi, 0xFF)) != mmcsd_crc16(g_mmcsd_buffer, MMCSD_MAX_BLOCK_SIZE))
      {
         mmcsd_deselect();
         return MMCSD_CRC_ERR;
      }
   }
   else
   {
      /* have the card transmit the CRC, but ignore it */
      spi_xfer(mmcsd_spi, 0xFF);
      spi_xfer(mmcsd_spi, 0xFF);
   }
   mmcsd_deselect();

   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_write_data(int32 address, int16 size, int* ptr)
{
   MMCSD_err ec;
   int16 i;  // counter for loops
  
   for(i = 0; i < size; i++)
   {
      ec = mmcsd_write_byte(address++, *ptr++);
      if(ec != MMCSD_GOODEC)
         return ec;
   }
   
   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_write_block(int32 address, int16 size, int* ptr)
{ 
   MMCSD_err ec;
   int16 i;

   // send command
   mmcsd_select();
   ec = mmcsd_write_single_block(address);
   if(ec != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return ec;
   }
   
   // send a data start token
   spi_xfer(mmcsd_spi, DATA_START_TOKEN);
   
   // send all the data
   for(i = 0; i < size; i += 1)
      spi_xfer(mmcsd_spi, ptr[i]);

   // if the CRC is enabled we have to calculate it, otherwise just send an 0xFFFF
   if(g_CRC_enabled)
      spi_xfer(mmcsd_spi, mmcsd_crc16(ptr, size));
   else
   {
      spi_xfer(mmcsd_spi, 0xFF);
      spi_xfer(mmcsd_spi, 0xFF);
   }
   
   // get the error code back from the card; "data accepted" is 0bXXX00101
   ec = mmcsd_get_r1();
   if(ec & 0x0A)
   {
      mmcsd_deselect();
      return ec;
   }
   
   // wait for the line to go back high, this indicates that the write is complete
   while(spi_xfer(mmcsd_spi, 0xFF) == 0);
   mmcsd_deselect();

   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_go_idle_state(void)
{
   mmcsd_send_cmd(GO_IDLE_STATE, 0);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_send_op_cond(void)
{
   mmcsd_send_cmd(SEND_OP_COND, 0);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_send_if_cond(int r7[])
{
   mmcsd_send_cmd(SEND_IF_COND, 0x45A);

   return mmcsd_get_r7(r7);
}

MMCSD_err mmcsd_print_csd()
{  
   int
      buf[16],
      i,
      r1;

   // MMCs don't support this command
   if(g_card_type == MMC)
      return MMCSD_PARAM_ERR;

   mmcsd_select();   
   mmcsd_send_cmd(SEND_CSD, 0);
   r1 = mmcsd_get_r1();
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }
   
   r1 = mmcsd_wait_for_token(DATA_START_TOKEN);
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }

   for(i = 0; i < 16; i++)
      buf[i] = spi_xfer(mmcsd_spi, 0xFF);
   mmcsd_deselect();

   printf("\r\nCSD_STRUCTURE: %X", (buf[0] & 0x0C) >> 2);
   printf("\r\nTAAC: %X", buf[1]);
   printf("\r\nNSAC: %X", buf[2]);
   printf("\r\nTRAN_SPEED: %X", buf[3]);
   printf("\r\nCCC: %lX", (make16(buf[4], buf[5]) & 0xFFF0) >> 4);
   printf("\r\nREAD_BL_LEN: %X", buf[5] & 0x0F);   
   printf("\r\nREAD_BL_PARTIAL: %X", (buf[6] & 0x80) >> 7);
   printf("\r\nWRITE_BLK_MISALIGN: %X", (buf[6] & 0x40) >> 6);
   printf("\r\nREAD_BLK_MISALIGN: %X", (buf[6] & 0x20) >> 5);
   printf("\r\nDSR_IMP: %X", (buf[6] & 0x10) >> 4);
   printf("\r\nC_SIZE: %lX", (((buf[6] & 0x03) << 10) | (buf[7] << 2) | ((buf[8] & 0xC0) >> 6)));
   printf("\r\nVDD_R_CURR_MIN: %X", (buf[8] & 0x38) >> 3);
   printf("\r\nVDD_R_CURR_MAX: %X", buf[8] & 0x07);
   printf("\r\nVDD_W_CURR_MIN: %X", (buf[9] & 0xE0) >> 5);
   printf("\r\nVDD_W_CURR_MAX: %X", (buf[9] & 0x1C) >> 2);
   printf("\r\nC_SIZE_MULT: %X", ((buf[9] & 0x03) << 1) | ((buf[10] & 0x80) >> 7));
   printf("\r\nERASE_BLK_EN: %X", (buf[10] & 0x40) >> 6);
   printf("\r\nSECTOR_SIZE: %X", ((buf[10] & 0x3F) << 1) | ((buf[11] & 0x80) >> 7));
   printf("\r\nWP_GRP_SIZE: %X", buf[11] & 0x7F);
   printf("\r\nWP_GRP_ENABLE: %X", (buf[12] & 0x80) >> 7);
   printf("\r\nR2W_FACTOR: %X", (buf[12] & 0x1C) >> 2);
   printf("\r\nWRITE_BL_LEN: %X", ((buf[12] & 0x03) << 2) | ((buf[13] & 0xC0) >> 6));
   printf("\r\nWRITE_BL_PARTIAL: %X", (buf[13] & 0x20) >> 5);
   printf("\r\nFILE_FORMAT_GRP: %X", (buf[14] & 0x80) >> 7);
   printf("\r\nCOPY: %X", (buf[14] & 0x40) >> 6);
   printf("\r\nPERM_WRITE_PROTECT: %X", (buf[14] & 0x20) >> 5);
   printf("\r\nTMP_WRITE_PROTECT: %X", (buf[14] & 0x10) >> 4);
   printf("\r\nFILE_FORMAT: %X", (buf[14] & 0x0C) >> 2);
   printf("\r\nCRC: %X", buf[15]);

   return r1;
}

MMCSD_err mmcsd_print_cid()
{
   int
      buf[16],
      i,
      r1;

   // MMCs don't support this command
   if(g_card_type == MMC)
      return MMCSD_PARAM_ERR;
   
   mmcsd_select();
   mmcsd_send_cmd(SEND_CID, 0);
   r1 = mmcsd_get_r1();
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }
   r1 = mmcsd_wait_for_token(DATA_START_TOKEN);
   if(r1 != MMCSD_GOODEC)
   {
      mmcsd_deselect();
      return r1;
   }
   
   for(i = 0; i < 16; i++)
      buf[i] = spi_xfer(mmcsd_spi, 0xFF);
   mmcsd_deselect();
   
   printf("\r\nManufacturer ID: %X", buf[0]);
   printf("\r\nOEM/Application ID: %c%c", buf[1], buf[2]);
   printf("\r\nOEM/Application ID: %c%c%c%c%c", buf[3], buf[4], buf[5], buf[6], buf[7]);
   printf("\r\nProduct Revision: %X", buf[8]);
   printf("\r\nSerial Number: %X%X%X%X", buf[9], buf[10], buf[11], buf[12]);
   printf("\r\nManufacturer Date Code: %X%X", buf[13] & 0x0F, buf[14]);
   printf("\r\nCRC-7 Checksum: %X", buf[15]);

   return r1;
}

MMCSD_err mmcsd_sd_status(int r2[])
{
   int i;

   mmcsd_select();
   mmcsd_send_cmd(APP_CMD, 0);
   r2[0]=mmcsd_get_r1();
   mmcsd_deselect();

   mmcsd_select();
   mmcsd_send_cmd(SD_STATUS, 0);

   for(i = 0; i < 64; i++)
      spi_xfer(mmcsd_spi, 0xFF);      

   mmcsd_deselect();

   return mmcsd_get_r2(r2);
}

MMCSD_err mmcsd_send_status(int r2[])
{
   mmcsd_send_cmd(SEND_STATUS, 0);   
   
   return mmcsd_get_r2(r2);
}

MMCSD_err mmcsd_set_blocklen(int32 blocklen)
{
   mmcsd_send_cmd(SET_BLOCKLEN, blocklen);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_read_single_block(int32 address)
{
   mmcsd_send_cmd(READ_SINGLE_BLOCK, address);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_write_single_block(int32 address)
{
   mmcsd_send_cmd(WRITE_BLOCK, address);
  
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_sd_send_op_cond(void)
{
   mmcsd_send_cmd(SD_SEND_OP_COND, 0);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_app_cmd(void)
{
   mmcsd_send_cmd(APP_CMD, 0);
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_read_ocr(int r3[])
{
   mmcsd_send_cmd(READ_OCR, 0);
   
   return mmcsd_get_r3(r3);
}

MMCSD_err mmcsd_crc_on_off(int1 crc_enabled)
{
   mmcsd_send_cmd(CRC_ON_OFF, crc_enabled);
   
   g_CRC_enabled = crc_enabled;
   
   return mmcsd_get_r1();
}

MMCSD_err mmcsd_send_cmd(int cmd, int32 arg)
{   
   int packet[6]; // the entire command, argument, and crc in one variable
  
   // construct the packet
   // every command on an SD card is or'ed with 0x40
   packet[0] = cmd | 0x40;
   packet[1] = make8(arg, 3);
   packet[2] = make8(arg, 2);
   packet[3] = make8(arg, 1);
   packet[4] = make8(arg, 0);

   // calculate the crc if needed
   if(g_CRC_enabled)
      packet[5] = mmcsd_crc7(packet, 5);
   else
      packet[5] = 0xFF;

   // transfer the command and argument, with an extra 0xFF hacked in there
   spi_xfer(mmcsd_spi, packet[0]);
   spi_xfer(mmcsd_spi, packet[1]);
   spi_xfer(mmcsd_spi, packet[2]);
   spi_xfer(mmcsd_spi, packet[3]);
   spi_xfer(mmcsd_spi, packet[4]);
   spi_xfer(mmcsd_spi, packet[5]);

   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_get_r1(void)
{
   int
      response = 0,  // place to hold the response coming back from the SPI line
      timeout = 0xFF; // maximum amount loops to wait for idle before getting impatient and leaving the function with an error code
    
   // loop until timeout == 0
   while(timeout)
   {
      // read what's on the SPI line
      //  the SD/MMC requires that you leave the line high when you're waiting for data from it
      response = spi_xfer(mmcsd_spi, 0xFF);
      
      // check to see if we got a response
      if(response != 0xFF)
      {   
         // fill in the response that we got and leave the function
         return response;
      }

      // wait for a little bit longer
      timeout--;
   }
   
   // for some reason, we didn't get a response back from the card
   //  return the proper error codes
   return RESP_TIMEOUT;
}

MMCSD_err mmcsd_get_r2(int r2[])
{
   r2[1] = mmcsd_get_r1();
   
   r2[0] = spi_xfer(mmcsd_spi, 0xFF);
   
   return 0;
}

MMCSD_err mmcsd_get_r3(int r3[])
{
   return mmcsd_get_r7(r3);
}

MMCSD_err mmcsd_get_r7(int r7[])
{
   int i;   // counter for loop
   
   // the top byte of r7 is r1
   r7[4]=mmcsd_get_r1();
   
   // fill in the other 4 bytes
   for(i = 0; i < 4; i++)
      r7[3 - i] = spi_xfer(mmcsd_spi, 0xFF);

   return r7[4];
}

MMCSD_err mmcsd_wait_for_token(int token)
{
   MMCSD_err r1;
   
   // get a token
   r1 = mmcsd_get_r1();
   
   // check to see if the token we recieved was the one that we were looking for
   if(r1 == token)
      return MMCSD_GOODEC;
   
   // if that wasn't right, return the error
   return r1;   
}

unsigned int8 mmcsd_crc7(char *data, unsigned int8 length)
{
   unsigned int8 i, ibit, c, crc;
    
   crc = 0x00;                                                                // Set initial value

   for (i = 0; i < length; i++, data++)
   {
      c = *data;

      for (ibit = 0; ibit < 8; ibit++)
      {
         crc = crc << 1;
         if ((c ^ crc) & 0x80) crc = crc ^ 0x09;                              // ^ is XOR
         c = c << 1;
      }

       crc = crc & 0x7F;
   }

   shift_left(&crc, 1, 1);                                                    // MMC card stores the result in the top 7 bits so shift them left 1
                                                                              // Should shift in a 1 not a 0 as one of the cards I have won't work otherwise
   return crc;
}

unsigned int16 mmcsd_crc16(char *data, unsigned int8 length)
{
   unsigned int8 i, ibit, c;

   unsigned int16 crc;

   crc = 0x0000;                                                                // Set initial value

   for (i = 0; i < length; i++, data++)
   {
      c = *data;

      for (ibit = 0; ibit < 8; ibit++)
      {
         crc = crc << 1;
         if ((c ^ crc) & 0x8000) crc = crc ^ 0x1021;                              // ^ is XOR
         c = c << 1;
      }

       crc = crc & 0x7FFF;
   }

   shift_left(&crc, 2, 1);                                                    // MMC card stores the result in the top 7 bits so shift them left 1
                                                                              // Should shift in a 1 not a 0 as one of the cards I have won't work otherwise
   return crc;
}

void mmcsd_select()
{
   output_low(MMCSD_PIN_SELECT);
}

void mmcsd_deselect()
{
   spi_xfer(mmcsd_spi, 0xFF);
   output_high(MMCSD_PIN_SELECT);
}

MMCSD_err mmcsd_load_buffer(void)
{
   g_MMCSDBufferChanged = FALSE;
   return(mmcsd_read_block(g_mmcsdBufferAddress, MMCSD_MAX_BLOCK_SIZE, g_mmcsd_buffer));
}

MMCSD_err mmcsd_flush_buffer(void)
{
   if (g_MMCSDBufferChanged)
   {
      g_MMCSDBufferChanged = FALSE;
      return(mmcsd_write_block(g_mmcsdBufferAddress, MMCSD_MAX_BLOCK_SIZE, g_mmcsd_buffer));
   }
   return(0);  //ok
}

MMCSD_err mmcsd_move_buffer(int32 new_addr)
{
   MMCSD_err ec = MMCSD_GOODEC;
   int32
      //cur_block,
      new_block;
   
   // make sure we're still on the same block
   //cur_block = g_mmcsdBufferAddress - (g_mmcsdBufferAddress % MMCSD_MAX_BLOCK_SIZE);
   new_block = new_addr - (new_addr % MMCSD_MAX_BLOCK_SIZE);
   
   //if(cur_block != new_block)
   if(g_mmcsdBufferAddress != new_block)
   {
      // dump the old buffer
      if (g_MMCSDBufferChanged)
      {
         ec = mmcsd_flush_buffer();
         if(ec != MMCSD_GOODEC)
            return ec;
         g_MMCSDBufferChanged = FALSE;
      }
         
      // figure out the best place for a block
      g_mmcsdBufferAddress = new_block;

      // load up a new buffer
      ec = mmcsd_load_buffer();
   }
   
   return ec;
}

MMCSD_err mmcsd_read_byte(int32 addr, char* data)
{
   MMCSD_err ec;
   
   ec = mmcsd_move_buffer(addr);
   if(ec != MMCSD_GOODEC)
   {
     return ec;
   }
 
   *data = g_mmcsd_buffer[addr % MMCSD_MAX_BLOCK_SIZE];

   return MMCSD_GOODEC;
}

MMCSD_err mmcsd_write_byte(int32 addr, char data)
{  
   MMCSD_err ec;
   ec = mmcsd_move_buffer(addr);
   if(ec != MMCSD_GOODEC)
     return ec;
   
   g_mmcsd_buffer[addr % MMCSD_MAX_BLOCK_SIZE] = data;
   
   g_MMCSDBufferChanged = TRUE;

   return MMCSD_GOODEC;
}

#endif