A recent move has put me out of the hobby loop for the past few months so it’s good to finally get back to tinkering. Even though my stuff was boxed up and my attention focused elsewhere, I still had my eye out for some fun things to hook up to a PIC. During my down time I got an e-mail from someone who wanted me to put together an interface so that he could use an SD card to store data. Back a number of posts ago we saw how we could use the PIC internal memory to store temperature readings but that only allowed for a minimal number of entries. I thought the SD card idea would be a fun one to work on so that is what this post is about. As a caveat, please be aware that this is a proof-of-concept design or, if you will, a basic building block for your data collection needs.
There have been a variety of SD cards types over the years but here we will focus on using the SDHC (High Capacity) cards that are common today. The specific cards I used for this project are all made by SanDisk and are 4GB, 8GB, and 16GB devices. Any of those should provide plenty of cheap mass storage for most PIC-based data collection tasks. What many people don’t know is that SD cards have a built in SPI interface that responds to a standard set of defined messages. While SD cards are generally formatted in FAT or exFAT, or NTFS, they can be directly written to without having to mess around with the Windows or Linux file system. The upside of that method is simpler code but the downside is that you can’t read the data as a file on your computer. Fortunately there are a variety of hex file readers out there including a free version of HxD.
SD Card Module
For this project I bought an inexpensive SD card module that provides a slot for a full size SD card. Please note that I did not try using a smaller SD card with an adapter. There is some question out on the internet as to whether or not the SPI interface commands will work the same with the micro or mini SD cards. The module I used is the most common one I saw advertised and includes a 3.3-volt regulator so you can use a 5-volt input. The schematic of the module is also shown here.
Because this is a proof-of-concept project I used my test board which has the 3-wire LCD interface I detailed in an earlier post. That allowed me to see the responses to the SPI command messages I sent to the SD card module. Because I am running the PIC and LCD on 5 volts it is necessary to add resistor voltage dividers on the signal lines from the PIC to the SD module. The output from the SD module (MISO) will be at a 3.3 volt logic level which is sufficient to drive the PIC input.
If you are not familiar with SPI it is a synchronous serial interface. That means that data bits are clocked in both directions at the same time. The MISO line is receive line for the PIC and the MOSI line is the transmit line for the PIC. Data are transmitted 8 bits at a time and each command from the PIC contains a command identifier byte, four data bytes (they may be zeros if no data is required) and a checksum byte. The checksum requires a non-trivial algorithm to properly calculate but, fortunately, the SPI spec says that the Slave device is to ignore the checksum after Cmd8 is received. The checksums for Reset and Cmd8 are already defined so no need to calculate them. The responses from the slave are usually a single byte but may be several bytes. There is a lot of information on the web about SPI commands and responses if you want to delve into it further. The CS (Chip Select) line allows the PIC to individually address several slave devices. Even though we have only a single device here the CS line is important because we need to send several dummy bytes to the slave while it is disabled. There will be more on that in the software section.
The software link is listed below. While it is targeted for the 16F688, it is easily ported to other versions of the PIC. Just make sure that you choose one that has the asynchronous serial port capability. You will also need to change the line that identifies the PIC version (LIST=) and the INCLUDE file but those are intuitive changes. The __CONFIG line may also need tweaking just because one or two of the labels used are spelled differently in some of the INCLUDE files.
Because I am using an existing test board with a 16F688 and an LCD interface, the software uses the bit-banging method for sending and receiving data on the SPI bus. There are newer versions of the PIC that have the SPI capability built in so you can treat it similar to the standard serial port built into most PICs and just send and receive in byte format. The routines in this software version would mostly remain intact with SPI_Xmt and SPI_Rcv being simplified if a built in SPI interface is used. The other advantage of using a newer version of PIC is that it would likely have a much larger RAM space available. That’s important for a practical application because the SD card requires data to be written and read as a 512-byte sector.
The basics of communication with the SD card start with the initialization sequence. It is recommended that the SPI Master (the PIC) output at least 74 clocks on the bus without enabling the SPI Slave. In practical terms that means sending 10 bytes of dummy data (80 clocks). After that the series of initialization commands will vary depending on the type of SD card involved. The software can actually be written to determine the card type but that is not within the scope of this project. Instead, we assume that the SD card is an SDHC which makes it a type 2 card.
The first command we send forces the SD card to do a software reset. That is also known as CMD0 in the literature. Because every SPI command has to have bit 6 of the byte set we always add 40 hex to our command number. A key thing to remember is that following every command the SD card will form a response. In order to get the response the PIC needs to send a dummy command of FF hex. We could do that in the SPI_Xmt routine but for streamlining purposes a separate SPI_Rcv routine was added. As you can see, the FF hex value is sent by simply setting the MOSI line high one time before clocking in the response bits. The response to the reset command should eventually be 01 hex which means that the SD card is in the Idle state. The Get_Response routine arbitrarily gets 8 bytes of response data from the SD card because some of the commands return several bytes. In addition, the first response byte received is usually FF hex. The Get_Response routine screens those values out and only returns the last non-FF value.
The next task is to tell the SD card to initialize and get ready for data communications. You might think it would logically do so following a reset command but it doesn’t. The actually command for this action is usually called ACmd41 which consists of two single commands – Cmd55 and Cmd41. But before those get sent we need to send a Cmd8. Cmd8 requests that the SD card send some status information including its voltage supply capability. Because we know our SDHC cards work on 3.3 volts this is useless information but the command is required anyway. The last non-FF byte we should receive for this command is AA hex.
If you look at the SPI command set you will see that there are a few commands that are compound commands like ACmd41. Cmd55 is what tells the SD card that there is a second part to the command sequence. The response we expect from Cmd55 is 01 hex (Idle). After that we can send Cmd41 with bit 6 of the first data byte set high to indicate that we are dealing with a High Capacity card. The SD card will take some time to complete the initialization process so it may take a lot of resending of ACmd41 before we get the response byte that says the SD card is ready (00 hex). I added a 1-second delay in order to decrease the number of reads required. Before I did that it took as many as 35 ACmd41 loops.
After the SD card indicates that it has initialized and is in the ready state we can read or write data. The software here only does a write in part because it doesn’t have the RAM area to store the 512 bytes the SD card would send. Another reason is that this program is intended to show how an SD card could be used for data collection. The write command is Cmd24 (58 hex). Normally, data would be collected in RAM and then output to the SD card in 512 byte blocks. which matches the sector size for the card. In our example the software just outputs alternating values of 33 hex and CC hex until the 512 byte limit is met.
Cmd24 requires the sending of four data bytes that specify the target sector in the SD card for the write. The address values in the sample code equate to a real address of 1000000 hex in the card. To get that value you multiply the sector address by the sector size (200 hex). If you do the math you see that it actually equates to 1100000 hex. The reason is that there is an offset that the SD card applies equal to 800 hex. I discovered that the offset may not always be the same for different SD cards so I made sure to use a tool other than Windows to format the cards. The tool I used is the free version of MiniTool Partition Wizard. When I used that to format the cards for FAT32, cluster size = 4096, I got all three cards (4GB, 8GB, and 16GB) to write to the same location in memory.
As I mentioned earlier, a good, free hex reader tool is HxD. It has an option to read from a device so you can look at the contents of the SD card when it is plugged into your computer. For some reason the sector address window in HxD only works in decimal but if you want to select a range of data you can use the “Edit/Select Block”. The pop-up window allows you to work in hex addresses. Once you have selected the data go to “File/Export/Editor View” to output the selection into a text file. The values will be separated by spaces so you can then easily import the file data into Excel and trim off the unwanted stuff like the addresses during the import process. In Excel 2007 that means going to the Data tab and selecting “Get External Data/From Text”. Excel will then walk you through the steps for trimming off the unwanted stuff.
The pictures shown here are the LCD display values output from the test code. After power up the LCD displays “SD Card Interface” for a couple of seconds and then displays the response values for the various commands. Line 1 shows the Response values for the Reset command (01), then the last non-FF byte in the response for Cmd8 (AA). That is followed by some number of Idle responses (01) for ACmd41 until finally a Ready response (00) is received. The software then outputs “R” for ready. The second LCD line shows responses for the data write command (Cmd24). It is important that the request for responses following Cmd55 immediately terminate when Ready (00) is received because the SD card is then looking for a second command. After Cmd24 is sent, followed by 512 bytes of data, followed by two dummy bytes for the data checksum, we expect to see an X5 response. That indicates that the data block was accepted by the SD card. Normally the response will be E5 because bit 4 should always be zero. That’s it for this post. Check out my other electronics projects.