SD Cards have mentioned in a number of topics recently. My personal interest is to interface an SD card to my CHOCHI board
http://forum.6502.org/viewtopic.php?f=10&t=2644. Currently it has a serial loader, but it would be nice to be able to boot from an SD card if it's attached.
The last time I tried to interface an SD card I did in in hardware, using a shift register on an FPGA and a Picoblaze core to read data. I don't remember the details other than it was very touchy, worked with a small selection of cards I have on hand, and was an incredible pain in the butt (prone to not working for no reason, then working again). I somehow got it working well enough for the project, but had always meant to go back at figure it out. So here goes.
This time I decided to bitbang SPI (since I'm running at 45MHz I can do it pretty fast). SD Cards are pretty lax about operating speed - at least some of my cards initialize at 2Hz!. Also, clock jitter seems to not matter much as far as I can tell. With software I can monitor the signals and make adjustments easier. And I can share the results - this can be done with any 6502 that has 3 output lines and 1 input lines available.
There is a lot of conflicting information about connecting SD cards, and the flavor of SPI to be used. Some indicate mode 0, some mode 3. I finally got consistent results using mode 3 - clock is normally high, transitions happen on the falling edge, and are sampled on the rising edge.
First thing: send 74 clocks to the card:
Code:
;--------------------------------------------------------------
; Cold-start procedure - 74+ clocks
sd_cold:
jsr sd_clk_up ;normal=1
jsr sd_data_up ;normal=1
jsr sd_sel_dn ;assert SEL
sd_init:
jsr tick
ldx #74 ;output 74 clocks
sd_init_loop: jsr sd_clk_dn
jsr tick
jsr sd_clk_up
jsr tick
dex
bne sd_init_loop
jsr tick
jsr sd_sel_up
; For some reason, it really helps to send 8 more
sd_init1:
lda #$FF
jsr sd_op
rts
Now, to send/receive a byte, a shift register following SPI mode 3 rules (I think...)
Code:
;--------------------------------------------------------------
; read/write a single byte.
sd_op:
sta TEMP ;preserve byte
ldx #8
sd_op1:
jsr sd_clk_dn
;---------------------------------
; output a bit and shift TEMP
lda TEMP ;load rcv/transmit byte
sta SD_MOSI ;store high bit to SPI
clc
rol a ;<< Free up low bit
sta TEMP ;and save
;---------------------------------
jsr tick
;---------UP----------------------
jsr sd_clk_up
;---------------------------------
; input low bit
lda SD_MISO ;read low bit from SPI
ora TEMP ;or in current value
sta TEMP ;and save back
;---------------------------------
jsr tick
;---------------------------------
dex
bne sd_op1
jsr tick
jsr sd_data_up ;data up is normal
lda TEMP ;return value
rts
In order to use the code above, some obvious routines matching your hardware must be built. I use bit 7 of the output port and bit 0 of the input port (I have an FPGA); adjust the code to match your hardware.
After sending a command, the response has the high bit 0, but first, one or more $FF may be sent. So I send $FF and check return values like this:
Code:
;--------------------------------------------------------------
; wait for an R0 response. R0 has bit 7 clear...
sd_R0: lda #$FF ;send $FF
jsr sd_op
rol a ;high bit set?
bcs sd_R0 ;continue waiting
ror a
rts
This should be enough to initialize the card like this:
Code:
init: jsr sd_cold ;intialize the SD card
jsr sd_sel_dn ;select
lda #$40 ;CMD0 - reset and idle
jsr sd_op
lda #$00
jsr sd_op
lda #$00
jsr sd_op
lda #$00
jsr sd_op
lda #$00
jsr sd_op
lda #$95
jsr sd_op
ldy #8
init1: lda #$ff
jsr sd_op
jsr hex_out
dey
bne init1
jsr cr
jsr cin
xxx: jmp init
The code above resets the card and sends command 0 to put the card into idle state. It then outputs the next 8 bytes returned.
Here is where trouble starts. Out of 4 cards I have on hand, only one returns the expected
Code:
FF 01 FF FF FF FF FF FF
.
Another card returns
Code:
3F 01 FF FF FF FF FF FF
.
3F has a low high bit, so it would be confused with a proper R0 response byte... However, running init again returns
Code:
BF 01 FF FF FF FF FF FF
.
This is disturbing, but more acceptable (BF is meaningless and would be filtered).
This reminds me of the trouble I had the last time... I could just skip any bytes with high bit set, and re-initialize if the response is not 01. However it could be indicative of some kind of a problem with the shift registers getting out of sync. It might bite me later...
I will proceed as if it makes sense to re-initialize until a reasonable response is obtained.