Yahoo Groups archive

Lpc2000

Index last updated: 2026-04-28 23:31 UTC

Thread

reading and writing MMC disk

reading and writing MMC disk

2004-08-01 by Frank Sergeant

I often see requests for information about accessing an MMC
(MultiMediaCard) disk with the LPC2106.  

So, going off half cocked with the flush of initial success, I thought
I'd post the details of how I am doing it from Forth.  I include the
actual code below as it is fairly short.

I am accessing an MMC disk with my Riscy Pygness Forth, using an Olimex
board to which I wired an MMC socket.  Pictures (click on the thumbnails
to see larger versions) are at

      http://pygmy.utoh.org/riscy/

(I'll be releasing Riscy Pygness as a completely free Forth (in the
MIT/BSD-license sense) for the Olimex and tiniARM boards.  A *very*
preliminary version is already present on the above site.)

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

   Definitions (code entry points) begin with a colon and end
   (i.e. exit) with a semicolon.  This is in the ColorForth style in
   that a word can have multiple entry and exit points.

   Comments begin with a left parenthesis followed by a space, and 
   extend until the following right parenthesis.

   All characters are allowed in a definition's name.  So
   (GET-MMC-RESPONSE is not part of a comment but is a secondary entry
   point as part of the definition for GET-MMC-RESPONSE.  How can you
   tell the left parenthesis did not start a comment?  Because the left
   parenthesis was not followed immediately by a space.

   Stack comments are used at the beginning of a definition and at
   various points within the definition, to show what is on the data
   stack, etc.  For example,  the comment ( c - c)  for SPI!@
   (pronounced "ess pee eye store fetch" indicates that SPI!@ expects a
   byte on the stack (which it will transmit via the SPI interface) and
   will return a byte on the stack (the incoming byte from the SPI
   interface). 

   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 initialize the SPI interface on the LPC2106
with the word SPI-CONFIGURE.  Then, 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 go 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.

Further plans include at least a minimal FAT16 layer.


-- 
Frank

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

( 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)  ;

Move to quarantaine

This moves the raw source file on disk only. The archive index is not changed automatically, so you still need to run a manual refresh afterward.