Interfacing an SD Card to a PIC16F690 in SPI mode to retrieve files stored in FAT16 Format
While working on a project requiring an SD card for memory storage, I had a difficult time finding out how to initialize the card into SDI mode. There is a lot of information covering various parts of the sequence, but it seemed like everything I read seemed to leave out one key part of the initialization sequence. This is my attempt to fill in the gaps.
I interfaced the SD Card to a Microchip PIC16F690. The 16F690 pin functions are as follows:
RC7 = SDO - transmit data to memory chip
RB6 = SCK - clock for transmitting data to memory chip
RB7 = Chip select for memory chip
RB4 = data in from memory chip
To see the full code, click here.
This application uses the PIC's SPI peripheral to communicate to the SD Card. The key to the initialization sequence is that the SPI clock must operate at a very slow speed. I've read some places that it must be less than 400khz, but to be on the safe side I've slowed it down to less than 100khz. This was done by reducing the oscillator speed by setting OSCCON=0b01000111 (1 mhz) and setting SSPCON=00000010 (FOSC/64). Now that we've got the SPI clock set at an acceptably low level we can start the SDI Initialization sequence.
Step 1 - "Wake up" the SD Card Before we can start clocking in instructions, we must clock in at least 74 "warm up" pulses to the card. As a practical matter, most people clock in 0xFF through the SDI module 10 times which results in 80 clock pulses. The chip select pin should not be asserted during this process (should be in the high state)
Step 2 - Assert the Chip Select pin Bring RB7 low to assert the chip select pin.
Step 3 - Clock in CMD0 into the SD Card Clock in the following bytes: 0x40 0x00 0x00 0x00 0x00 0x95. Clock in two dummy bytes to get the response from the SDI Card, which should be 0x00.
Step 4 - Clock in CMD8 into the SD Card Clock in the following bytes: 0x48 0x00 0x00 0x00 0b10101010 0x87. Clock in two dummy bytes to get the response from the SDI Card, which will be 0b10101010 if you are using a new card. The card I use is an older card, which doesn't recognize this command and returns 0x01, which is an error message but does not impact the initialization sequence.
Step 5 - Clock in ACMD41 into the SD Card This step fits into the "more conplicated than it should be" category, in my opinion. The key to this step is to first understand that it is not a single command, but two commands back to back. The first command is CMD55, which is clocked in by writing the following six bytes to SSPBUF: 0b01110111 0x00 0x00 0x00 0x00 0xFF. After this is clocked in, deassert chip select, then reassert it. Write one dummy byte (0xFF) to SSPBUF, then clock in ACMD41 by clocking in the following bytes: 0b01101001 0x00 0x00 0x00 0x00 0xFF. Then write two dummy bytes (0xFF 0xFF) to get the response from the SD card. The response will be 0x01 until the card is ready, so you have to continue doing Step 5 until you get a response of 0x00. The best way to do this is with a while loop as follows:
while(SSPBUF!=0)
{
RB7=1; //deassert CS
RB7=0; //assert CS
WriteSPI(0xFF);
WriteSPI(0b01110111);WriteSPI(0b00000000);WriteSPI(0b00000000);WriteSPI(0x00);WriteSPI(0x00);WriteSPI(0xFF); //CMD55
RB7=1; //deassert CS
RB7=0; //assert CS
WriteSPI(0xFF);
WriteSPI(0b01101001);WriteSPI(0b00000000);WriteSPI(0b00000000);WriteSPI(0x00);WriteSPI(0x00);WriteSPI(0xFF); //ACMD41
WriteSPI(0xFF);
WriteSPI(0xFF);
}
That's it for the initialization sequence - once you get a response of 0x00 you are in SDI mode. I had problems getting this to work until I read somewhere that you have to deassert and reassert the chip select pin between the CMD55 and the AMCD41 commands in step 5. This may be in the SD card specification somewhere, but I never saw it.
Now that the SD card is initialized, we can now use the SPI interface to find the files that have been loaded onto the card. The following assumes that the files were loaded in a FAT16 file structure. There are many sources on the web that show the diffrent elements of the FAT 16 structure, so I'm only going to focus on pulling the specific pieces of information needed to navigate through the card's file allocation tables.
Step 1 - Find the address of the first FAT16 partition entry The following SPI commands will retrieve a 4 byte block that gives the starting address in little endian format:
RB7=1; //deassert CS
RB7=0; //assert CS
WriteSPI(0xFF);
WriteSPI(0b01010000);WriteSPI(0);WriteSPI(0);WriteSPI(0);WriteSPI(4);WriteSPI(0xFF); //CMD16 4 byte block - want a return of "0"
WriteSPI(0xFF);
WriteSPI(0xFF);
WriteSPI(0xFF);
WriteSPI(0b01010001);WriteSPI(0x00);WriteSPI(0x00);WriteSPI(0x01);WriteSPI(0xC6);WriteSPI(0xFF); //CMD17 (Read Single Block) - address of 0x1C6 is the partition address datablock in the first partition entry (byte 454 in decimal)
while(SSPBUF!=0xFE) //Keep clocking until we get valid info (Follows 0xFE)
{
WriteSPI(0xFF);
}
WriteSPI(0xFF);
Address=SSPBUF; //Loads LSB of address
WriteSPI(0xFF);
Address=Address+SSPBUF*256; //loads second byte into address
WriteSPI(0xFF);
AddressH=Address+SSPBUF*65536; //loads third byte into address
WriteSPI(0xFF);
WriteSPI(0xFF);
WriteSPI(0xFF);//dummy blocks at end to get through check sums
Step 2 - Find the address of the root directory The following SPI commands will retrieve a 13 byte block that gives the address of the root directory in little endian format:
RB7=1; //deassert CS
Delay(5);
RB7=0; //assert CS
Address=Address*2; //Since address is in sectors, multiply by 2 then shift a byte over
Address=Address<<8;
AddressL=Address;
AddressM=Address>>8;
AddressH=Address>>16;
WriteSPI(0b01010000);WriteSPI(0);WriteSPI(0);WriteSPI(0);WriteSPI(13);WriteSPI(0xFF); //CMD16 13 byte block - want a return of "0"
WriteSPI(0xFF);
WriteSPI(0xFF);
WriteSPI(0xFF);
WriteSPI(0b01010001);WriteSPI(0);WriteSPI(AddressH);WriteSPI(AddressM);WriteSPI(11);WriteSPI(0xFF); //CMD17 (Read single Block) - want a response of XFE signaling that data is coming
while(SSPBUF!=0xFE) //Keep clocking until we get valid info (Follows 0xFE)
{
WriteSPI(0xFF);
}
WriteSPI(0xFF);
BytesPerSector=SSPBUF; //LSB of Bytes per Sector
WriteSPI(0xFF);
BytesPerSector=BytesPerSector+SSPBUF*256; //MSB of Bytes per sector
WriteSPI(0xFF); //sectors per cluster, unused
WriteSPI(0xFF);
ReservedSectors=SSPBUF; //LSB of reserved sectors
WriteSPI(0xFF); //MSB of reserved sectors (ignore)
WriteSPI(0xFF);
FATCount=SSPBUF; //number of FATs
WriteSPI(0xFF); //number of root entries, unused
WriteSPI(0xFF); //number of root entries, unused
WriteSPI(0xFF); //small sectors, unused
WriteSPI(0xFF); //small sectors, unused
WriteSPI(0xFF); //Media Type, unused
WriteSPI(0xFF);
SectorsPerFat=SSPBUF; //LSB of sectors per FAT
WriteSPI(0xFF);
SectorsPerFat=SectorsPerFat+SSPBUF*256; //MSB of sectors per FAT
WriteSPI(0xFF); //Two dummy bytes to clear the CRC
WriteSPI(0xFF);
Address=Address+BytesPerSector*ReservedSectors; //Adds the number of reserved sectors to the starting address
FatSpace=FATCount;
FatSpace=FatSpace*BytesPerSector;
FatSpace=FatSpace*SectorsPerFat;
Address=Address+FatSpace; //add FAT data space to the address to get to the root directory. Address now equals the starting point of the root directory.
The data that follows will contain the file names and the addresses that they can be found at on the SD Card.
Make sure you check out my projects:
USB Serial Communication using the Microchip MCP2200 - Interfacing microcontrollers with a computer using this cheap chip.
Interfacing a PCM1725 to a PIC16F690 - Adding audio to a project by interfacing a PCM1725 DAC to a PIC16F690.
Using the Avago ADNS-2610 Sensor - Programming an optical sensor from an optical mouse.
USB Communications using the PIC18F4550 - Programming a microcontroller to communicate with a computer via the USB port
Interfacing to Microcontrollers to SD Cards - Accessing files stored on SD Cards in FAT16 format via the SPI module in a microcontroller
Building Electronic Drums - How to construct electronic drums using piezo sensors
Circuit Archive - If you are looking for other sample circuits for various uses and components, this is the place for you.
Component List - If you are looking for suppliers for different project components, I've compiled some good ones here.
Any questions or comments - contact me at pwclark1977@hotmail.com