SD card support for AVR
A detailed overview of how to expand your MCU with an SD card including basics for a deeper understanding of the functionality of SD cards.
Last updated
A detailed overview of how to expand your MCU with an SD card including basics for a deeper understanding of the functionality of SD cards.
Last updated
SD cards are suitable for storing large amounts of data and being able to retrieve them on the computer. This makes the use of SD cards just for microcontroller projects quite interesting, since a microcontroller usually provides only, comparatively small, EEPROM as a data store.
An SD card consists of an interface, a controller, a few registers and the actual storage element.
Pin
SD interface
SPI
1
DAT3
CS
2
CMD
DI
3
Vss
Vss
4
Vdd
Vdd
5
CLK
SCLK
6
Vss
Vss
7
DAT0
DO
8
DAT1
NC
9
DAT2
NC
As a communication interface, the SPI of the microcontroller should be used. I am using the USART-SPI of the XMega microcontroller in this example. A control command for the SD card is structured as shown:
In SPI mode, each command for the SD card consists of an 8-bit command with the last bit set to 0 and the penultimate bit 1. The remaining bits result from the index of the command. This is followed by a 4-byte command argument and a 1-byte CRC (optional in SPI mode).
After the command has been sent, the SD card takes some time to process and respond to this command. This time NCR is between 0 and 8 bytes for SD cards, and 1 to 8 bytes for MM cards. When the time has elapsed, the card sends the appropriate response to the command. During the NCR phase, a 0xFF must be permanently sent to the card, so that DO is high and the SD card is clocked via SCLK.
The following commands are defined for communication with the SD card:
Index
Argument
Response
Data
Description
CMD0
0
R1
No
Software reset of the SD card
CMD1
0
R1
No
Initialize card
ACMD41
0x40000000
R1
No
Initialize card (only SD)
CMD8
0x1AA
R3
No
Check Voltage (only SD V2)
CMD9
0
R1
Yes
Read CSD register
CMD10
0
R1
Yes
Read CID register
CMD12
0
R1b
No
Abort reading
CMD16
Block size
R1
No
Change block size
CMD17
Address
R1
Yes
Read single block
CMD18
Address
R1
Yes
Read several blocks
CMD23
Block count
R1
No
Set the number of blocks to send (MMC only)
ACMD23
Block count
R1
No
Number of blocks to be deleted at the next multi-block write (SDC only)
CMD24
Address
R1
Yes
Write block
CMD25
Address
R1
Yes
Write several blocks
CMD55
0
R1
No
Initiate ACMD command
CMD58
0
R3
No
Read OCR register
ACMD<n>: Combination of the command CMD55 and CMD<n>
The SD card responds to each command with a R1 or R3. An R1 response consists of 8 and an R3 response of 40 bits.
7
6
5
4
3
2
1
0
0
Parameter error
Address error
Erase sequence error
Command CRC error
Illegal command
Erase reset
Switch to idle
An R3 response always includes the current value of the status register (i.e., an R1 response) as well as the value of the OCR register.
Bits [39:32]
Bits [31:0]
R1
OCR
After a reset (e.g. after plugging the card into the card reader), an SD card must always be initialized first. During initialization, the host (in this example the microcontroller) also determines what type of memory card it is, whether it is an SD card up to 2 GB, or a card with a capacity of >2 GB or more Multimedia Card (MMC) acts. Depending on the card type, a different initialization procedure is used.
As mentioned above, the card is operated in SPI mode and the USART-SPI interface of the microcontroller is used. Of course, any other SPI interface can be used by adapting the code accordingly.
First, the interface will be initialized. According to the specification, SD cards must be operated in SPI mode 0 (CPOL = 0, CPHA = 0). The following interface connections have been used to connect the card:
Pin
Signal
PD1
SCLK
PD2
MISO
PD3
MOSI
PE0
CS
The connections for the signals SCK, MOSI and CS must be defined as output during initialization and set to high. In idle state, the data lines must also be high.
The interface configuration requires the following settings:
Switch to USART-SPI mode
Configure the clock phase and the clock polaritiy (CPHA = 0, CPOL = 0)
Set the data order (LSB first)
Enable the receiver and the transmitter
It is also recommended to generate at least 80 additional clock cycles on the SPI so that the SD card can complete its internal processes after unselecting the SD card. As an addition to that, I generate eight clock pulses on the SPI before I select the card.
SD cards should be operated at a clock rate of approximately 100 kHz to 400 kHz during initial initialization, as this clock rate is supported by both old and new cards. Newer cards can also be 1 MHz or more, which is why the clock frequency can be increased after card initialization.
Once the interface and all GPIOs have been configured, the card can be communicated and the initialization (see picture) can be processed.
After a power-on reset, the card is selected and then at least 74 clock pulses are generated (equivalent to 10x send 0xFF). This activates the card's native operating mode, which means that the card understands the above commands. Then, a CMD0 is sent to put the SD card in idle mode. As soon as the SD card has switched to idle mode, it responds with an R1 response (0x01 - see overview).
If the SD card does not respond to the command several times, an error will be issued and initialization aborted. After initialization, no checksum is required for the individual commands.
The routine for sending any command with arguments and checksum looks like this:
The function first checks whether the command to be sent is an ACMD command. If this is the case, the command 55 is sent first to initiate the command sequence. Then the corresponding command is sent.
In the next part of the function, the card is selected to then send the command transmitted with its command arguments and a checksum. Thereafter, 8 bytes of data are sent to bridge the time NCR and at the same time it is checked whether the card has already sent a reply. When a response has been received, the function is exited. It is also advisable to generate eight more clock cycles (ie to send an empty byte) after receiving the card's response. This gives the controller of the SD card some extra time to process the internal processes.
Upon a successful change to idle mode, the next steps are taken from the control flow graph to complete the initialization of the card:
In this initialization routine, the card type is also determined because an MMC uses a different instruction set than an SD card. In addition, a version 1 SD card (<2 GB) is initialized differently than a version 2 SD card. Finally, the card is deselected and the response byte returned. When the card has been successfully initialized, the speed of the SPI will be changed back to the original value, so that the SD card is ready to use and can be read and written.
Before data can be read from the SD card, a corresponding command (such as CMD9) must first be sent to the card. The SD card confirms this command with an R1 response. Then the card begins to transfer the requested data.
The transmitted data packet consists of a token, a data block up to 2048 bytes long and a 2-byte CRC.
Token
Function
0xFC
Acknowledge for CMD17/18/24
0xFD
Acknowledge for CMD25
0xFE
Stop token for CMD25
The sequence shown results in the following code:
This function allows you to read out the SD card. As an example you can read out the STATUS register of the SD card. For this, the command ACMD13 must be send to the card. The SD card responds to this command with a 64-byte data packet containing the status information of the SD card.
For the sake of clarity, I have stored the status information in a structure so that the received data can be directly identified.
This results in the following function call:
You can read out different parameter like the class of the SD card from the status information.
Speed class
Class description
0x00
Class 0
0x01
Class 1
0x02
Class 2
0x03
Class 3
Similarly, the CID or the CSD register of the SD card can be read out.
The card can be identified via the CID register. The register contains u. a. a manufacturer and an OEM ID, as well as the serial number of the SD card.
For the file system to write data to the SD card or to read data from the SD card, functions are required that read or write individual data blocks. The SD card offers the following commands:
Command
Description
CMD17
Read a single block
CMD18
Read n blocks
CMD24
Write a single block
CMD25
Write n blocks
A read or write access to a single data block looks like this.
For reading data access, the already presented ReadBlock function can be used. The write function looks like this:
Together with the SendCommand function, the ReadBlock and WriteBlock functions become the complete functionset for reading or writing a block of data.
With the SD_ReadDataBlock function, individual sectors, such as the first sector of the SD card, can be read out.
For a FAT file system, the first sector is always theboot sector for the operating system. The read data can be checked by looking at the last two digits of the array. For a valid FAT file system, the values 0x55 and 0xAA are stored there.
In a FAT file system, the first sector of the storage medium is always 512 bytes in size, no matter what storage media is used. This sector is read in by the BIOS and the code of the bootloader is executed, which then loads the operating system.
For large amounts of data, it makes sense to transfer all data in a single pass. SD cards have the CMD18 and CMD25 commands especially for the transmission of several data blocks, with which several blocks can be read or written.
Once initiated, the SD card sends or receives data until the host ends the transmission via a stop signal. This happens during a read operation via the command CMD12 and during a write operation through a stop token (see table) in an empty data packet.
The already created functions can be used to send or receive the individual data blocks so that the functions for sending or receiving several data blocks can be easily programmed.
The basic writing and reading functions are now fully programmed. We are thus able to exchange data between the SD card and the microcontroller and permanently store the data on the SD card.
The basis for the FAT implementation is the FatFs library from ElmChan.
This library provides a common interface for a FAT file system with support for various platforms. All the user has to develop is the interface to the storage medium (here the SD card). This tutorial use the FatFs version R0.13c. The source directory of the downloaded archive contain all necessary files to realize the FAT support for the microcontroller. Our task is to customize the file diskio.c according to our driver for SD cards.
Furthermore, a file with the name ffconf.h is used to configure the file system. The following configuration items have been changed:
Configuration
New value
Description
FF_USE_STRFUNC
1
Enable common string functions like f_puts
FF_CODE_PAGE
850
Set code page to 850 (Latin 1)
FF_USE_LFN
2
Support large file names
FF_FS_TINY
1
Shrink down the buffer size
FF_FS_NORTC
1
Don´t use RTC, because we don´t have one
The function prototypes of the functions that the user has to implement in the file diskio.c are all already created. Our task is therefore to write the following functions:
Function
Task
disk_initialize
Initialize the storage device
disk_status
Get the status of the storage device
disk_read
Read one or more sectors from the storage device
disk_write
Write one or more sectors from the storage device
disk_ioctl
Control the storage device
In addition, Elm Chan shows how several different storage media can be used with the module by giving each medium its own drive number:
In this tutorial, only one SD card will be used as a drive. The SD card should get the number 0. So change the definitions to the following:
The first function we want to implement is the function disk_initialize, which initializes the requested storage medium. This function has only the drive name as a transfer parameter, which must be evaluated by means of a switch statement:
If the drive has been initialized successfully, a corresponding status variable will also be set. This variable can then be queried with the disk_status function:
Next, the function disk_read is implemented. This function passes, in addition to the drive identifier, a pointer to a data buffer, the requested sector, and the number of sectors to be read. This information is evaluated and the corresponding functions of the SD card driver are called up:
The function disk_write works in the same way:
Finally, the function disk_ioctl must be implemented. This function has the task to provide a general interface for the SD card, which can be used to get informations such as the the sector size from the card. The requested command is passed as a parameter to the function and must then be evaluated and executed. The function of the individual command codes can be found in the documentation.
With the command GET_BLOCK_SIZE the Erase Block Size of the storage medium is returned. This information can be read out of the status register on SD cards with version 2 or later
The command GET_SECTOR_COUNT queries the number of sectors of the storage medium. This information can be found in the CSD register of the SD card. For SD cards version 2 or later, the number of sectors can be calculated via the CSD register.
With the help of the command GET_SECTOR_SIZE the size of a sector of the storage medium is queried. SD cards with a version 2 or later have a fixed sector size of 512 bytes. Therefore, this command returns only a constant value.
The last command named CTRL_SYNC serves to terminate open write operations of the storage medium. If the device has a cache, that cache must be flushed and written to memory. For the SD card, the card is deselected, causing the SD card to end all open operations:
Thus all necessary functions would be programmed and the SD card can be described. For this the following code snippet can be used:
Similar to Windows, the file name of FatFs consists of a drive name and the actual name. In this example, the file File.txt should be saved on the drive with the identifier 0. This corresponds to the SD card.
First of all, the SD card is integrated via the f_mount function. The storage medium is integrated directly via parameter 1, so that you can directly check whether the memory card is recognized.
Then create a new file with the path 0: File.txt, fill it with Hello, World and close it again. An existing file will be overwritten.
Finally, the created file should be opened, the complete content read out and closed again.
When the program is executed, the SD card is mounted. After that the file is created, filled with data and read into the ReadData array. Additionally you can check the contents of the SD card by pluging the card into a card reader and read the card with your PC.
Everything works? Wonderful! With this you have successfully equipped your AVR with an SD card and a FAT file system.
The final SD card driver, including an example implementation for a XMega384C3, can be found in my GitLab repository.