Working on my 6502 computer I have gathered a significant amount of wisdom around working with SD Cards and FAT32. I am documenting all this as much for myself as others.
An SD Card in SPI mode
When an SD Card is powered up it is in SD Mode.
Most documentation says to wait for 1 millisecond after power up before doing anything.
To get to SPI mode we set the CS line high and cycle the SPI clock 80 times. Remember that CS is active low and so we are unselecting the card and then clocking SPI 80 times. Depending on your SPI code you can achieve this by sending a 0 byte 10 times.
Sending a command
A command is a total of 6 bytes or 48 bits (0 to 47).
When sending via SPI we send bit 47 first and then run along to bit 0.
The first bit of a command is always 0 and the second bit is always 1, so we OR the command number with $40 to get the byte we send on the SPI bus.
Sending an application specific command
Standard commands are prefixed as CMD, but application specific commands are prefixed as ACMD. To use an application specific command you first send CMD55. CMD55 just lets the SD card know that the next command is an application specific command.
Receiving Response Code
Depending on the command sent you will either get an R1 or an R7 response code. Remember in the physical wiring we have a pull up resistor in the MISO line, this means that if the SD Card is not sending data then we can expect to receive an $FF byte on the SPI bus. Then the code to receive the R1 or R7 code needs to loop until it gets something other than $FF.
An R1 response code is one byte, and a R7 response code is 5 bytes.
Overview of commands
CMD0 - Resets card to idle state.
CMD8 - Set card interface.
CMD16 - Set block length of read and write, 512 bytes is default.
CMD17 - Read block.
CMD24 - Write block.
CMD55 - Next command is Application command.
CMD58 - Read OCR (operation conditions register).
ACMD41 - Set card capacity infomation.
Init flow
Pseudocode
Sleep 100 ; Sleep 100 Milliseconds
SetCS(High) ; Sets the CS line high
For 1 to 20 do ; Sending 20 bytes, gets 160 clocks with CS set high
Write_SPI($FF) ; Send a $FF byte to SPI bus
Next ; End of the for loop
SetCS(Low) ; Sets the CS line low
Write_SPI($FF) ; Always send an $FF byte after CS change
Write_SPI($40) ; CMD0 - Note we OR command number with $40
Write_SPI($00) ; CMD0 - no arguments
Write_SPI($00) ; CMD0 - no arguments
Write_SPI($00) ; CMD0 - no arguments
Write_SPI($00) ; CMD0 - no arguments
Write_SPI($95) ; Correct CRC for above command.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $01) then Error ; Return an error
Write_SPI($FF) ; Always send an $FF byte before a command
Write_SPI($48) ; CMD8 - Note we OR command number with $40
Write_SPI($00) ; CMD8 - no arguments.
Write_SPI($00) ; CMD8 - no arguments.
Write_SPI($01) ; CMD8 - Set voltage.
Write_SPI($AA) ; CMD8 - Recommended check pattern is $AA.
Write_SPI($87) ; Correct CRC for above command.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $01) then Error ; Return an error
Result = Read_SPI ; Read result byte #2
Result = Read_SPI ; Read result byte #3
Result = Read_SPI ; Read result byte #4
Result = Read_SPI ; Read result byte #5
If (Result != $AA) then Error ; Check that Result byte 5 matches check patten of $AA.
Write_SPI($FF) ; Always send an $FF byte before a command
Write_SPI($77) ; CMD55 - Note we OR command number with $40
Write_SPI($FF) ; CMD55 - no arguments.
Write_SPI($FF) ; CMD55 - no arguments.
Write_SPI($FF) ; CMD55 - no arguments.
Write_SPI($FF) ; CMD55 - no arguments.
Write_SPI($FF) ; CMD55 - no CRC needed.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $01) then Error ; Return an error
Write_SPI($FF) ; Always send an $FF byte before a command
Write_SPI($69) ; ACMD41 - Note we OR command number with $40
Write_SPI($40) ; ACMD41 - Request support for High Capacity Cards.
Write_SPI($00) ; ACMD41 - no arguments.
Write_SPI($00) ; ACMD41 - no arguments.
Write_SPI($00) ; ACMD41 - no arguments.
Write_SPI($FF) ; ACMD41 - no CRC needed.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $00) then try again ; keep sending CMD55/ACMD41 until R1 == 00
Write_SPI($FF) ; Always send an $FF byte before a command
Write_SPI($7A) ; CMD58 - Note we OR command number with $40
Write_SPI($00) ; CMD58 - no arguments.
Write_SPI($00) ; CMD58 - no arguments.
Write_SPI($00) ; CMD58 - no arguments.
Write_SPI($00) ; CMD58 - no arguments.
Write_SPI($FF) ; CMD58 - no CRC needed.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $00) then Error ; Return an error
Result = Read_SPI ; Read the result byte #2, bit 6 is SDHC flag.
Result = Read_SPI ; Read the result byte #3
Result = Read_SPI ; Read the result byte #4
Result = Read_SPI ; Read the result byte #5
Write_SPI($FF) ; Always send an $FF byte before a command
Write_SPI($50) ; CMD16 - Note we OR command number with $40
Write_SPI($00) ; CMD16 - set block length to $00000200.
Write_SPI($00) ; CMD16 - set block length to $00000200.
Write_SPI($02) ; CMD16 - set block length to $00000200.
Write_SPI($00) ; CMD16 - set block length to $00000200.
Write_SPI($FF) ; CMD16 - no CRC needed.
Result = Read_SPI ; Read the R1 result byte
While(Result == $FF) ; Wait till returned byte is not $FF
Result = Read_SPI ; Read the R1 result byte
If (Result != $00) then Error ; Return an error
Write_SPI($FF) ; Always send an $FF byte before a CS change
SetCS(High) ; Sets the CS line high
Write_SPI($FF) ; Always send an $FF byte after a CS change
What’s next?
In part 3 we will read a sector.