Difference between revisions of "Interfacing with a Secure Digital (SD) card"

From Mech
Jump to navigationJump to search
 
(43 intermediate revisions by 3 users not shown)
Line 1: Line 1:
=== 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.

<br clear=all>
<br clear=all>


=== Overview ===
== Overview ==


[http://en.wikipedia.org/wiki/Secure_Digital_card 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 [http://www.apacer.com Apacer].
[http://en.wikipedia.org/wiki/Secure_Digital_card 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 4 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 [http://www.apacer.com Apacer].


SD cards can operate three different communication modes: One-bit SD mode, four-bit SD mode, and [http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus 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.
SD cards can operate three different communication modes: One-bit SD mode, four-bit SD mode, and [http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus 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.
Line 13: Line 9:
<br clear=all>
<br clear=all>


=== Circuit ===
== Hardware ==


=== Card Interface ===
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.
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.


[[Image:SD_Circuit.JPG|right|thumb|400px|Circuit Diagram]]
[[Image:SD_Circuit.JPG|center|thumb|400px|Wiring diagram for an SD card [http://www.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf (Foust, 2004)]]]


<br clear=all>
<br clear=all>


=== Code ===
=== SD Card Holder ===



The original, unmodified files were included in the [http://en.wikipedia.org/wiki/CCS_C CCS v4.081]library.

SD card holders are typically surface-mount components that are designed to be placed on printed circuit boards by machines. As such, they can be very difficult to handle and connect. At first, we were given an [http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=HR846CT-ND HR8464CT-ND] SD card holder. Attempts to solder solid-core to the pins of this device caused the pins to fall out. Instead, we used an [http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=HR845CT-ND HR845CT-ND] which had pins that were more resistant to axial force and thus easier to solder.

For this lab, we cut a strip of [http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=S2121-25-ND square-pinned header] to the proper length and simply soldered the pins of the holder to it. See the photograph below for more detail.

=== Completed Circuit ===

[[Image:SD_hardware.JPG|center|thumb|400px|Assembled circuit]]

Note that the PIC is being powered from a bench top power supply and that it is hooked up to a PC via the [[PIC RS232| RS-232]] cable provided in lab.


<br clear=all>

== Software ==

=== Driver ===

Note that this lab was completed by making slight modifications to an example application included in the [http://en.wikipedia.org/wiki/CCS_C CCS v4.081] library. The [[mmcsd.c|low level driver code]], included in the Drivers directory of the CCS installation, must be copied verbatim into the same directory as the application code.

=== Application ===

The original, unmodified application code was included in the newer version of CCS mentioned above. Note that as of Winter Quarter 2009, these drivers are not available in the Mechatronics lab but are available on the installation media provided in our lab kits. Modifications were made to the code to change the assignment of the SPI pins, tailor the preprocessor directives to use our PIC, properly configure RS232 and set the main clock frequency. Additional modification was made to make the application display the contents of the SD card identification register on startup. All modifications contain "MODIFIED" in their comments.


<pre>
<pre>
Line 41: Line 61:
//// permission. Derivative programs created using this software ////
//// permission. Derivative programs created using this software ////
//// in object code form are not restricted in any way. ////
//// in object code form are not restricted in any way. ////
//// ////
//// Modifications for ME333 Winter 2009 Lab 5 by ////
//// Colleen Fryer, Sean Wood, and Mat Kotowsky ////
//// ////
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


//These settings are for the CCS PICEEC development kit which contains
//These settings are for the CCS PICEEC development kit which contains
//an MMC/SD connector.
//an MMC/SD connector.
#include <18f4520.h>
#include <18f4520.h> // MODIFIED
#fuses NOWDT, HS, NOPROTECT
#fuses HS,NOLVP,NOWDT,NOPROTECT // MODIFIED
#use delay(clock=40000000) // MODIFIED
#fuses HS,NOLVP,NOWDT,NOPROTECT
#use rs232(baud=19200, UART1) // MODIFIED hardware UART uses RC6/TX and RC7/RX
#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
#include <stdlib.h> // for atoi32
Line 56: Line 78:
//media library, a compatable media library is required for FAT.
//media library, a compatable media library is required for FAT.
#use fast_io(c)
#use fast_io(c)
#define MMCSD_PIN_SCL PIN_B0 //o THIS WAS A HUGE PROBLEM, C3 DIDN'T WORK
#define MMCSD_PIN_SCL PIN_B0 //o MODIFIED THIS WAS A HUGE PROBLEM, C3 DIDN'T WORK
#define MMCSD_PIN_SDI PIN_C4 //i
#define MMCSD_PIN_SDI PIN_C4 //i
#define MMCSD_PIN_SDO PIN_C5 //o
#define MMCSD_PIN_SDO PIN_C5 //o
Line 76: Line 98:
while(TRUE);
while(TRUE);
}
}
mmcsd_print_cid();
mmcsd_print_cid(); // MODIFIED


do {
do {
Line 102: Line 124:
} while (TRUE);
} while (TRUE);
}
}
/<pre>


<pre>
</PRE>
/////////////////////////////////////////////////////////////////////////
//// 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. ////
/////////////////////////////////////////////////////////////////////////


== Operation ==
#ifndef MMCSD_C
#define MMCSD_C


Interacting with the SD card is done via a PC connected by RS-232 to the PIC. Any terminal emulator can be used to connect to the correct COM port at 19200 baud. Once connected, powering up or resetting the system will cause the PIC to communicate with a card. If there is an error and the PIC can't connect, the program will report an error message and quit. If it is successful in initializing communication with the card, it will immediately print the contents of the SD card's identification register, then prompt the user to read or write to a given memory address.
/////////////////////
//// ////
//// User Config ////
//// ////
/////////////////////


[[Image:Operation.jpg|center|thumb|400px|Operation of the card interface program]]
#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


The user can select whether to read or write by pressing R or W at the prompt. The user then selects a 16-bit memory address by entering 4 hexadecimal integers. After 4 integers are typed, the program will display the one byte of data at the given memory location or will prompt the user to enter data, depending on if read or write has been selected. If write has been selected, the user can enter two hexadecimal integers. After they are entered, the PIC will write out the value to the SD card at the specified memory location. The user is then returned to the read/write prompt.
#use spi(MASTER, DI=MMCSD_PIN_SDI, DO=MMCSD_PIN_SDO, CLK=MMCSD_PIN_SCL, BITS=8, MSB_FIRST, IDLE=1, stream=mmcsd_spi)


== Future Work ==
////////////////////////
//// ////
//// Useful Defines ////
//// ////
////////////////////////


Though this lab demonstrated the ability to read and write to an SD card, there are a few improvements that can be made.
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};


=== File System ===
#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


The [http://en.wikipedia.org/wiki/CCS_C CCS v4.081] library does provide a driver to create and manipulate a FAT-16 filesystem on an SD card. The driver assumes the use of a [http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en026445 PIC18F67J60] microcontroller which has 128 KB of program memory and 3808 bytes of RAM. Unfortunately, the PIC that we're using in this lab, the [http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en010297 PIC18F4520] only has 32 KB of program memory and 1536 bytes of RAM. This smaller amount of RAM proves to be insufficient to run the filesystem driver designed for the more powerful PIC. It is possible that the driver can be modified such that it would run on the 18F4520 should the required RAM overhead be decreased.
#define IDLE_TOKEN 0x01
#define DATA_START_TOKEN 0xFE


=== Capacity Availability ===
#define MMCSD_MAX_BLOCK_SIZE 512


For ultimate simplicity, this application allows the user to provide a 2-byte memory address and enter one byte into that address. That amounts to 64 KB of total memory -- considerably less than the 2 GB of memory the SD card is purported to have. This is because the example program only reads and writes '''bytes''' of memory from specific '''16-bit''' addresses. In reality, the SD card stores data not in single bytes but in 512-byte blocks, and the driver assumes a '''32-bit''' address space, not a '''16-bit''' address space. In order to realize the full capacity of the SD card, one must read and write entire 512-byte buffers and use most of the 32-bit address space. This allows for total theoretical capacity of 2 TB, though no SD cards of this capacity currently exist (as of early 2009).
////////////////////////
/// ///
/// Global Variables ///
/// ///
////////////////////////


== References and Further Reading ==
int g_mmcsd_buffer[MMCSD_MAX_BLOCK_SIZE];


[http://www.cs.ucr.edu/~amitra/sdcard/Additional/sdcard_appnote_foust.pdf F. Foust (2004) -- Application Note: Secure Digital Card Interface for the MSP430]
int1 g_CRC_enabled;
int1 g_MMCSDBufferChanged;


[http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en026445 PIC18F67J60 product web page]
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;
}


[http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en010297 PIC18F4520 product web page]
#endif
</pre>

Latest revision as of 02:54, 16 February 2009


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 4 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.


Hardware

Card Interface

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.

Wiring diagram for an SD card (Foust, 2004)


SD Card Holder

SD card holders are typically surface-mount components that are designed to be placed on printed circuit boards by machines. As such, they can be very difficult to handle and connect. At first, we were given an HR8464CT-ND SD card holder. Attempts to solder solid-core to the pins of this device caused the pins to fall out. Instead, we used an HR845CT-ND which had pins that were more resistant to axial force and thus easier to solder.

For this lab, we cut a strip of square-pinned header to the proper length and simply soldered the pins of the holder to it. See the photograph below for more detail.

Completed Circuit

Assembled circuit

Note that the PIC is being powered from a bench top power supply and that it is hooked up to a PC via the RS-232 cable provided in lab.



Software

Driver

Note that this lab was completed by making slight modifications to an example application included in the CCS v4.081 library. The low level driver code, included in the Drivers directory of the CCS installation, must be copied verbatim into the same directory as the application code.

Application

The original, unmodified application code was included in the newer version of CCS mentioned above. Note that as of Winter Quarter 2009, these drivers are not available in the Mechatronics lab but are available on the installation media provided in our lab kits. Modifications were made to the code to change the assignment of the SPI pins, tailor the preprocessor directives to use our PIC, properly configure RS232 and set the main clock frequency. Additional modification was made to make the application display the contents of the SD card identification register on startup. All modifications contain "MODIFIED" in their comments.


/////////////////////////////////////////////////////////////////////////
////                      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.              ////
////                                                                 ////
//// Modifications for ME333 Winter 2009 Lab 5 by                    ////
////      Colleen Fryer, Sean Wood, and Mat Kotowsky                 ////
////                                                                 ////
/////////////////////////////////////////////////////////////////////////

 //These settings are for the CCS PICEEC development kit which contains
 //an MMC/SD connector.
 #include <18f4520.h>                // MODIFIED
 #fuses HS,NOLVP,NOWDT,NOPROTECT     // MODIFIED
 #use delay(clock=40000000)          // MODIFIED
 #use rs232(baud=19200, UART1)       // MODIFIED hardware UART uses  RC6/TX and RC7/RX

 #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 MODIFIED  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();     // MODIFIED

  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);
}

Operation

Interacting with the SD card is done via a PC connected by RS-232 to the PIC. Any terminal emulator can be used to connect to the correct COM port at 19200 baud. Once connected, powering up or resetting the system will cause the PIC to communicate with a card. If there is an error and the PIC can't connect, the program will report an error message and quit. If it is successful in initializing communication with the card, it will immediately print the contents of the SD card's identification register, then prompt the user to read or write to a given memory address.

Operation of the card interface program

The user can select whether to read or write by pressing R or W at the prompt. The user then selects a 16-bit memory address by entering 4 hexadecimal integers. After 4 integers are typed, the program will display the one byte of data at the given memory location or will prompt the user to enter data, depending on if read or write has been selected. If write has been selected, the user can enter two hexadecimal integers. After they are entered, the PIC will write out the value to the SD card at the specified memory location. The user is then returned to the read/write prompt.

Future Work

Though this lab demonstrated the ability to read and write to an SD card, there are a few improvements that can be made.

File System

The CCS v4.081 library does provide a driver to create and manipulate a FAT-16 filesystem on an SD card. The driver assumes the use of a PIC18F67J60 microcontroller which has 128 KB of program memory and 3808 bytes of RAM. Unfortunately, the PIC that we're using in this lab, the PIC18F4520 only has 32 KB of program memory and 1536 bytes of RAM. This smaller amount of RAM proves to be insufficient to run the filesystem driver designed for the more powerful PIC. It is possible that the driver can be modified such that it would run on the 18F4520 should the required RAM overhead be decreased.

Capacity Availability

For ultimate simplicity, this application allows the user to provide a 2-byte memory address and enter one byte into that address. That amounts to 64 KB of total memory -- considerably less than the 2 GB of memory the SD card is purported to have. This is because the example program only reads and writes bytes of memory from specific 16-bit addresses. In reality, the SD card stores data not in single bytes but in 512-byte blocks, and the driver assumes a 32-bit address space, not a 16-bit address space. In order to realize the full capacity of the SD card, one must read and write entire 512-byte buffers and use most of the 32-bit address space. This allows for total theoretical capacity of 2 TB, though no SD cards of this capacity currently exist (as of early 2009).

References and Further Reading

F. Foust (2004) -- Application Note: Secure Digital Card Interface for the MSP430

PIC18F67J60 product web page

PIC18F4520 product web page