Generic SPI driver for SD cards





The file SDspi.fth contains a generic driver for SD and MMC cards in SPI mode. The low level SPI interface should provide the following words:


MMCon     ( -- )       Drives CS low.
MMCoff    ( -- )       Drives CS high.
spiFlush  ( -- )       Flush SPI receive queue (if any).
spi!      ( c -- )     Write SPI, discard received byte.
spi@      ( -- c )     Write $FF byte, return received byte.
spiwr     ( a -- a' )  Reads next byte and writes it to SPI
spird     ( a -- a' )  Reads SPI and writes to next byte.
initSDspi ( -- )       Initialise SPI for SD/MMC use.
spiSlow   ( -- )       Set SPI clock to low speed
spiFast   ( -- )       Set SPI clock to high speed

SPIRD and SPIWR are expected to be fast. Their semantics were chosen to minimize threading overhead for target Forths that use threaded code.

Please note the following restrictions.

For further details of SD and MMC cards see

  http://www.sdcard.org/about/memory_card/pls/
  http://wolverine.caltech.edu/referenc/SDMMCv52.pdf

The standard SD/MMC card pinout is as follows.


        SD mode                                 SPI mode
Pin     Name    Type    Description             Name    Type    Description
1       CD/DAT3 I/O/PP  Card detect / data 3    CS      I       Chip select (low)
2       CMD     PP      Command/Response line   DI      I       Data input
3       Vss1    S       Supply voltage (gnd)    VSS     S       Supply voltage
4       Vdd     S       Power supply            VDD     S       Power supply
5       CLK     I       Clock                   SCLK    I       Clock
6       Vss2    S       Supply voltage          VSS2    S       Supply voltage
7       DAT0    I/O/PP  data line 0             O       O/PP    Data output
8       DAT1    I/O/PP  data line 1             RSV
9       DAT2    I/O/PP  data line 2             RSV

where the pins are numbered on the connector as below:


  9 1 2 3 4 5 6 7 8
    * * * * * * * *
  *



Configuration

1 equ MciSem?   \ -- x
Set this non-zero to use a an exclusive access semaphore in the sector read/write routines SecRead and SecWrite. MciSem? can be defined before this file and will override this copy. This semaphore will be needed if the disc can be accessed by other sources such as USB.




SD/MMC functions

The SD error/throw codes are defined in terms of the err_FATread and err_FATwrite codes from XC6harn.fth.

err_FATread equ sd_read_fail            \ SD card failure codes
err_FATread 1+ equ sd_cmd_fail          \ command hung
err_FATwrite equ sd_write_fail          \ write error
err_FATwrite 1+ equ sd_reset_fail       \ failure to reset

1000 equ sd_timeout     \ -- ms
Timeout in milliseconds. Slower targets may want a smaller SD_TIMEOUT value, especially those whose SPI interface takes more than a millisecond per transfer. This usually only comes into play when using a bit-banged interface.

: SD_idle       \ cnt --
Send idle (0xFF) a specified number of times.

: SD_sendArg    \ arg32 --
Send arg32 in big-endian format.

: SD_prep       \ --
Prepare to send an SD command.

: SD_NB         \ -- res8|-1
Wait until the card is not busy or times out, returning -1 for no response or the 8-bit response. In SPI mode, all commands generate at least one byte of response.

: SD_send       \ arg32 cmd6 -- result8 ; -1=timeout, other = result
Sends a command and tests for time-out, returning -1 for no response or the 8-bit response. In SPI mode, all commands generate at least one byte of response. The line is left with CS active.

: +spi@         \ x -- x<<8+b
Shift x 8 bits left, read the next SPI byte in a response and merge it.

: SD_ReadResp32 \ -- x32
Read four bytes in big-endian format.

: SD_sync       \ --
Get the card into a known state.

0 value SD2card?        \ -- flag
Returns true when the card follows SD v2 specification.

0 value HCcard?
Returns true when the card is a High Capacity (SDHC) card.

: tryCMD0       \ -- ior
Try 10 times at 100 ms intervals to reset the card, returning 0 for success.

: CMD8          \ -- b
Send command 8 for range 2.7-3.6 volts, and return 0 for a good response, a non-zero response byte for a bad command, or -1 for bad response data.

: CheckInit     \ sd2? -- ior
Initialise the card. The parameter sd2 is -1 for an SD2 card or 0 for a SD1 card. Ior is returned 0 for success. N.B. This operation may take several seconds.

: SD_reset      \ --
Reset the card, and perform the initialisation sequence. On failure, an sd_reset_fail throw occurs.

: SD_cmd        \ arg32 cmd --
A more robust version of SD_send.

: SD_readstatus \ -- status
Read the SD card status register.

: SD_wait       \ xt -- timeout?
Wait for condition specified by xt, which has the stack effect ( -- flag) where flag is returned non-zero to finish the operation before timeout.

; equ SD_writedone?     \ -- xt
Gives the xt of a :NONAME word used with SD_wait while writing a data block.

; equ SD_readready?     \ -- xt
Gives the xt of a :NONAME word used with SD_wait before reading a data block.

: SD_waitread   \ --
Wait until ready to read.

: SD_readcsd    \ addr --
Read the CSD - 16 bytes.




Sector read and write

The basic sector read and write routines use the simple API above. If your SPI unit supports DMA operation, you can use it for the 512 byte block transfers by providing the words

  sdWriteBlk  \ asrc --
  sdReadBlk   \ adest --

These routines write and read the 512 bytes of data to and from the SPI bus.

: SD_addr       \ blockno -- sdaddr
Convert a sector number to an SD card address.

: SD_writeblock \ asrc blockno --
Write a 512-byte block from address asrc to block blockno.

: SD_writeblock \ asrc blockno --
Write a 512-byte block from address asrc to block blockno.

: SD_readblock  \ adest blockno --
Read a 512-byte block to address adest from block blockno.

: SD_readblock  \ adest blockno --
Read a 512-byte block to address adest from block blockno.




CSD handling

The card CSD is read during initialisation into CSDbuff. You can assume that the card supports a minimum block size of 512 bytes. The size information is contained in the following byte offsets in CSDbuff.


05  ----rrrr  Minimum read block size = 2^r bytes
06  ------ss
07  ssssssss  s = 12-bit size
08  ss------
09  ------mm  Size multiplier = 2^(m+2)
10  m-------

The number of 512-byte blocks in the SD card is s * 2^(m+2+r-9). Formatting involves writing a new boot record and clearing the FAT table. Searching for bad sectors is recommended but a brute force search will take a long time. Other data in the CSD includes write-protect status and access times. Card formatting is not yet supported as it is assumed that cards will be formatted on a PC.

: GetBit        \ caddr u -- 0/1
Get bit number u from a bit array. Bit 0 is the top bit of the first byte. Bit 15 is the bottom bit of the second byte.

: GetBits       \ caddr start len -- x
Get len bits starting at bit start from the bitmap starting at caddr.

0 value #Sectors
Number of sectors on the card.

#16 buffer: CSDbuff     \ -- addr
Buffer to read CSD.

: CheckCSD      \ --
Extract the CSD information, particularly the card capacity in sectors.




FAT file system API

The file system needs these words to access the mass storage:

SecRead

( addr sector dev -- ) Reads a sector from the specified device. THROWs on error.

SecWrite

( addr sector dev -- ) Writes a sector to the specified device. THROWs on error.

MassInit

( -- ) Initializes mass storage access.

MassTerm

( -- ) Terminates mass storage access.

To simplify the code, the raw read/write interface treats all read/write errors as fatal, and THROWs on error. Retries should be accommodated within the sector read/write code.

Semaphore SecSem        \ -- addr
Exclusive access semaphore for sector read/write routines.

: SecRead       \ addr sector dev --
Reads a sector from the specified device. THROWs on error.

: SecWrite      \ addr sector dev --
Writes a sector to the specified device. THROWs on error.

: MassInit      \ --
Initialize the mass storage manager. This is done when a new card is detected.

: MassTerm      \ --
Shut down the mass storage manager. This is done when a card is removed.




Test code

: .sector       \ u --
Read and dump a sector.

0 [if]
To compile the test code, change the conditional compilation in the file from 0 [if] to 1 [if].

: ParseCSD              \ *csd --
Parse the CSD structure and display the contents.

: .CSD          \ --
Read and display the CSD contents.