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|
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.
(GET-MMC-RESPONSEis 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.
! ( u a -)
@ ( a - u)
C@ ( a - c)
C! ( c a -)
ROR24 ( u - u')
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
MMC-INIT. If all went well,
MMC-INIT returns zero. After
that, blocks of data can be read or written with
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
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
&PINSEL0where 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) ;