MMC

I often see requests for information about accessing an MMC (MultiMediaCard) or SD (SecureDigital) disk with the lpc2106.

Here are the details of how I am doing it from Forth. I include the actual code as it stood in late July or early August, 2004. For the current code as it improves, see the file mmc.forth included in the full Riscy Pygness download.

I am accessing an MMC disk with my Riscy Pygness Forth, using an Olimex lpc2106 board to which I wired an MMC socket. Here are some pictures of the board and socket (click on the thumbnails to see larger versions).

Here is how I connected the MMC socket to the ARM:w_beg(table)

MMC pin# MMC function CPU pin# CPU function
1 /CS P0.13 41 GPIO
2 Data In P0.6 24 MOSI
3 Ground ground - -
4 Vdd 3.3V - -
5 Clock P0.4 22 SCK
6 Ground ground - -
7 Data Out P0.5 23 MISO

This uses the LPC2106's built-in SPI interface, so, in addition to the above pins, CPU pin 28 (/SSEL) must be pulled high (and, on the Olimex board, disconnected from the LED, I believe).

The pin nearest the notch is pin 1, e.g.

     _________________________
    /  1  2  3  4  5  6  7   |
    |                        |
    |                        |

Rather than using an MMC socket, I would suggest using an SD (SecureDigital) socket instead. It will accept either SD or MMC and an SD will work in MMC SPI mode as if it were an MMC.

The pinouts for an SD socket are almost the same as for an MMC except the SD has two extra pins:

     _________________________
    /  1  2  3  4  5  6  7 8 |
    |9                       |
    |                        |

To use SD in MMC SPI mode, just leave pins 8 and 9 unconnected.


If you don't read Forth (yet), the following hints might make the MMC code below understandable enough for you to translate to assembler or C.

- Hexadecimal numbers begin with a leading dollar sign (e.g. $80).

Mini-glossary:

! ( u a -)
"store" stores the 32-bit number u at the address a
@ ( a - u)
"fetch" fetches the 32-bit number u from the address a
C@ ( a - c)
"C fetch" fetches the byte c from address a
C! ( c a -)
"C store" stores the byte c into address a
ROR24 ( u - u')
rotates the 32-bit number u right by 24 bits, thus moving the most significant byte into the least significant byte, i.e. in assembler (where TOS stands for "top of stack" and is an alias for R0): mov TOS, TOS, ror #24

To access the card, first I initialize the SPI interface on the lpc2106 with the word SPI-CONFIGURE. Then, I initialize the disk itself with the word MMC-INIT. If all went well, MMC-INIT returns zero. After that, blocks of data can be read or written with READ-MMC-BLOCK or WRITE-MMC-BLOCK. The data for the writes come from a buffer address that you specify. The data from the reads goes into a 512-byte buffer named MMC-BUFFER. Generally, the MMC-accessing words return a zero if all went well and a non-zero error code otherwise.

I'll be glad to attempt to answer questions about it or to receive comments or corrections.


( constants )
 : PINSEL0 $E002C000  ;
 : IOPIN   $E0028000  ;
 : IOSET   $E0028004  ;
 : IODIR   $E0028008  ;
 : IOCLR   $E002800C  ;
 : SPCR    $E0020000  ;
 : SPSR    $E0020004  ;
 : SPDR    $E0020008  ;
 : SPCCR   $E002000C  ;
 : SPINT   $E002001C  ;

Note, in the most recent version of Riscy Pygness, the above constants would not necessarily be defined, as they could be referenced as literals such as &PINSEL0 where the lookup would happen on the host.

( SPI interface to MMC disk )
 : BITS-ON  ( mask -)  IOSET !  ;
 : BITS-OFF ( mask -)  IOCLR !  ;
 : PIN13 ( - mask)     $00002000  ;
 : DISABLE-MMC ( -)   PIN13 BITS-ON   ;
 : ENABLE-MMC  ( -)   PIN13 BITS-OFF  ;
 : SPI!@ ( c - c)  ( send a byte then collect the returned byte)
   SPDR !  BEGIN SPSR @ $80 AND UNTIL SPDR @  ;
 : SPI!  ( c -)  ( send a byte but throw away returned byte)
   SPI!@ DROP  ;
 : SPI@ ( - c) ( send a dummy byte and collect returned byte)
   $FF SPI!@  ;
 : DUMMY-SPI-BYTES ( # -)  FOR $FF SPI! NEXT  ;

 : GET-MMC-RESPONSE ( - f)
   ( Try up to 256 times to get a response.  A zero response          )
   (  means no error.  A valid response is a byte whose MSBit is zero.)
   (  Return -1 in case of a time-out. )
   256 ( remaining tries)   ( fall through to next word)
 : (GET-MMC-RESPONSE ( remaining# - f)    (  PAUSE  )
   SPI@ DUP $80 AND NOT IF NIP ( cardResponse)  ; THEN  DROP
   ( remaining#)  1- DUP 0= IF DROP -1 ; THEN
   (GET-MMC-RESPONSE  ;

 : GET-DATA-TOKEN ( - error)
   ( Try up to 256 times to get a non $FF response.  $FE is the value )
   (  we hope for.  Return -1 in case of a time-out. )
   256 ( remaining tries)  ( fall through to next word)
 : (GET-DATA-TOKEN ( remaining# - f)    (  PAUSE  )
   SPI@ DUP $FF - IF NIP ( cardResponse)  ; THEN  DROP
   ( remaining#)  1- DUP 0= IF DROP -1 ; THEN
   (GET-DATA-TOKEN  ;

 : FLUSH-MMC ( -)  ( send it 8 clocks to finish whatever it needs to do)
   DISABLE-MMC  $FF SPI!  ;

 : SEND-MMC-COMMAND ( data cmd - error)
   FLUSH-MMC  ( Perhaps should be done at end of transaction rather )
              ( than at the start of the new transaction? )
   ENABLE-MMC
   $40 OR SPI!
   ( data) 4 FOR ROR24 DUP SPI! NEXT DROP
   $95 ( fakeCRC) SPI!
   GET-MMC-RESPONSE ( 0 means no error)  ( error)  ;

  : CMD0  ( - f)  0  0 SEND-MMC-COMMAND  ;          ( go into idle state)
  : CMD1  ( - f)  0  1 SEND-MMC-COMMAND  ;          ( is card initialized yet?)
  : CMD9  ( - f)  0  9 SEND-MMC-COMMAND  ;          ( read CSD)
  : CMD10 ( - f)  0 10 SEND-MMC-COMMAND  ;          ( read CID)
  : CMD13 ( - f)  0 13 SEND-MMC-COMMAND  ;          ( get status)
  : CMD16 ( blkLength - f)  16 SEND-MMC-COMMAND  ;  ( set block length)
  : CMD17 ( a - f)  17 SEND-MMC-COMMAND  ;          ( start block read)
  : CMD24 ( a - f)  24 SEND-MMC-COMMAND  ;          ( start block write)
  : CMD58 ( - f)  0 58 SEND-MMC-COMMAND  ;          ( read OCR)

 : SPI-CONFIGURE ( -)
   ( P.13  27:26 = 00 for gpio for MMC CS )
   ( P.07  15:14 = 01 for SPI SSEL )
   ( P.06  13:12 = 01 for SPI MOSI  )
   ( P.05  11:10 = 01 for SPI MISO  )
   ( P.04   9:8  = 01 for SPI SCK   )
   ( 0000 0000 0000 0000 0101 0101 0000 0000 = $00005500 )
   PINSEL0 @   $5500 OR  ( leave serial Tx and Rx undisturbed) PINSEL0 !
   IODIR @  PIN13 OR  IODIR !  ( make pin 13 an output)
   DISABLE-MMC
   8 SPCCR !  ( for debugging, use 254 to slow it down)
              ( for fastest speed, use 8, which is the smallest allowed)
   ( cclk is about 15MHz so pclk is about 3.75MHz)
   ( We must set SPCCR to 8 or greater.  At 8, spi clock would be about .47 MHz.)
   ( Divisor must be even.)
   ( divisor      spi clock   )
   (       8      .47 MHz     )
   (     128      29297 Hz    )
   (     254      14763 Hz    )
   $20 SPCR  !  ( make it be master with CPOL=0, CPHA=0)   ;

 : MMC-INIT ( - errorFlag)
   DISABLE-MMC  20 DUMMY-SPI-BYTES
   CMD0 ( result)
   ( Result should be 1 if card successfully went into idle mode.)
   DUP 1- IF ( utoh, not a 1, so return result as an error)  ;  THEN
   DROP
   ( Ok so far, so send CMD1 up to 256 times until card exits from idle mode.)
   256 BEGIN
        CMD1 0= IF DROP 0 ; THEN
        1- DUP 0=
   UNTIL  DROP -1 ( timed out)  ;

 CREATE MMC-BUFFER  LISP (eallot 512)

 : SET-BLOCK-LEN  ( # - error)  CMD16  ;

 : READ-MMC-BYTES ( # - error)
   ( Call this only after receiving a good data token.  Read # bytes into )
   (  MMC-BUFFER then read and throw away the 2-byte checksum)
   ( #) MMC-BUFFER SWAP ( a #)
   FOR ( a) SPI@ OVER C! 1+ NEXT DROP
   SPI@ DROP  SPI@ DROP  (    )   0 ( ok)   ;

 : BLK@ ( blk# - error)  512   512  ( fall through)
 : READ-MMC-BLOCK ( cardAddress # - error)
   SWAP ( # cardAddress) CMD17 DUP IF NIP ( error) ; THEN  DROP
   ( #) GET-DATA-TOKEN DUP $FE - IF ( # token)  NIP ( token) ; THEN DROP
   ( #)  READ-MMC-BYTES ( error)  ;

 : WRITE-MMC-BYTES ( fromAddr # -)
   ( Send a data token.  Then send the block of data.  Then send )
   (   a dummy 2-byte checksum.  Caller is responsible for getting)
   (   the data response.)
   ( fromAddr #)    $FE SPI!  ( i.e. send start of block data token)
   ( fromAddr #)  FOR ( a) DUP C@  SPI!  1+ NEXT DROP  (   )
   $FF SPI!   $FF SPI!  ( i.e. send dummy 2-byte checksum)  ;

 : BLK! ( a blk# - error)  512   512  ( fromAddr cardAddr #)
   ( fall through)
 : WRITE-MMC-BLOCK ( fromAddr cardAddress # - error)
   SWAP ( fromAddr # cardAddress) CMD24 ( fromAddr # error)
    DUP IF NIP NIP ( error) ; THEN  DROP
   ( fromAddr #) WRITE-MMC-BYTES (   )
   GET-MMC-RESPONSE ( error)  ;