;notes:
;
; 1. after each 512 byte block is sent to the MMC the card must be given time
;    to actually save the bytes to flash.  this takes so long that the
;    microcontroller misses one cycle.  this must be taken into account in
;    any program that manipulates the data.  this happens the first time
;    on the 86th data sample and every 85 samples thereafter.
; 2. to accurately read timer 2 it must be turned off, read, then turned back
;    on.  it loses a total of 4 machine cycles every data sample so the
;    difference between successive reads of timer 2 is always short by 4.
;    4 must thus be added to the delta in any program that manipulates the
;    data.  to use the values from the timer exactly, 4 must be added to the
;    first value read, 8 to the second, 12 to the third etc.
; 3. if any known error occurs when reading or writing the MMC, the code resets
;    the system.

;constants not defined by assembler
        .EQU  SPCR,0D5H
        .EQU  SPSR,0AAH
        .EQU  SPDR,086H

;ram locations for some variables
        .EQU  ERRORNUM,120        ;error number
        .EQU  WC,121              ;write counter, 85 data points = 510 bytes
        .EQU  OUTL,122            ;low byte sent to MMC over MOSI
        .EQU  OUTH,123            ;high byte sent to MMC over MOSI

        .EQU  ADDR0,124           ;LSB of MMC memory address to write
        .EQU  ADDR1,125
        .EQU  ADDR2,126
        .EQU  ADDR3,127           ;MSB of MMC memory address to write

;reset interrupt service routine
        .ORG  0000h
        SJMP  start

;external interrupt 0 service routine (unused)
        .ORG  0003h
        RETI

;timer 0 interrupt service routine (unused)
        .ORG  000Bh
        RETI

;external interrupt 1 service routine
        .ORG  0013h
        ACALL serviceint1
        RETI

;timer 1 interrupt service routine (unused)
        .ORG  001Bh
        RETI

;serial interrupt service routine (unused)
        .ORG  0023h
        RETI

;timer 2 interrupt service routine (unused)
        .ORG  002Bh
        RETI

;-------------------------------------------------------------------------------
                                   ;main routine

        .ORG  0033h
start:

        SETB  P1.4                 ;SPI pins must be high before set SPI mode
        SETB  P1.5                 ; they are after reset but let's be sure
        SETB  P1.6                 ; as per the atmel manual
        SETB  P1.7

;----------------------------------Set up SPI communications
        MOV   SPCR,#01011100b      ;no SPI interrupt
                                   ;enable SPI
                                   ;send and receive most significant bit 1st
                                   ;set up the microcontroller as the master
                                   ;send high on SCK when idle
                                   ;latch incoming data on rising edge
                                   ;use fastest possible SCK speed (2 bits)

;----------------------------------Set up timers (timer 2 uses defaults)
        MOV   TMOD,#10011001b      ;timer 1 only counts when INT1* is high
                                   ;timer 1 used as a timer, not counter
                                   ;with TMOD.4, sets timer 1 as 16 bit
                                   ;with TMOD.5, sets timer 1 as 16 bit
                                   ;timer 0 only counts when INT1* is high
                                   ;timer 0 used as a timer, not counter
                                   ;with TMOD.0, sets timer 0 as 16 bit
                                   ;with TMOD.1, sets timer 0 as 16 bit

        SETB  TR2                  ;start timers running
        SETB  TR1
        SETB  TR0

;----------------------------------Set up interrupts
        CLR   ES                   ;Disable serial port interrupts
        CLR   ET1                  ;Disable timer 1 overflow interrupt
        CLR   EX1                  ;Disable INT1* (X) ext. interrupt for now
        CLR   ET0                  ;Disable timer 0 overflow interrupt
        CLR   EX0                  ;Disable INT0* (Y) external interrupt
        SETB  EA                   ;Enable interrupts

;----------------------------------Initialize MultiMediaCard
        MOV   ADDR3,#0
        MOV   ADDR2,#0
        MOV   ADDR1,#0
        MOV   ADDR0,#0
        ACALL initmmc

;----------------------------------If the erase button is down, erase the card
        JB    P1.0,noerase

        CLR   P3.7                 ;light the red LED to signal erase going
        ACALL erase                ;erase all blocks

        MOV   R7,20
flash:     SETB  P3.7              ;flash the LED 20 times to give the user
           MOV   A,250             ;time to power off.  this is about 10 sec
           ACALL waitms
           CLR   P3.7
           MOV   A,250
           ACALL waitms
        DJNZ  R7,flash
noerase:                           ;jump here if we don't want to erase

;----------------------------------Find where existing data ends in memory
        MOV   ADDR3,#0             ;start reading at first byte
        MOV   ADDR2,#0
        MOV   ADDR1,#0
        MOV   ADDR0,#0


        MOV   R6,#245              ;check all 15680 blocks in an 8MB MMC
bloop1: MOV   R5,#64               ; until we find one that is all zero
           CPL   P2.0              ;blink the green LED while searching
bloop2:    MOV   R7,#0             ;start out assuming will find no data in blk
           ACALL readstart         ;open next block for reading
           MOV   R4,#4             ;these two loops run over the 512 bytes
bloop3:       MOV   R3,#128        ; in each block
bloop4:          MOV   A,#0FFH
                    ACALL spiout
                    CJNE  R0,#0FFH,hasdata
                    SJMP  hasnodata
hasdata:            MOV   R7,#1
hasnodata:
                 DJNZ R3,bloop4
              DJNZ  R4,bloop3
              ACALL readend
              CJNE  R7,#1,emptyblock
              ACALL incaddr        ;increment address
           DJNZ R5,bloop2
        DJNZ R6,bloop1

        MOV  ERRORNUM,#4           ;if we got here, memory is full, light the
        AJMP error                 ; just sit here

emptyblock:                        ;if we got here, the last block read was
                                   ; empty. fill it with zero to act as a
                                   ; separator between the old data and the
                                   ; new data we are about to save.

        ACALL writestart
        MOV   R7,#4
loopc1:    MOV R6,#128
loopc2:       MOV  A,#0
                 ACALL spiout
              DJNZ R6,loopc2
           DJNZ R7,loopc1
        ACALL writeend

;----------------------------------Set up writing to first empty block
        ACALL writestart           ;open the first MMC block for output
        MOV   WC,#85               ;initialize write counter,
                                   ; 6 bytes/loop*85=510 bytes,
                                   ; other 2 bytes will be filled with junk

;----------------------------------Turn on the INT1* interrupt
        JNB   P3.3,*               ;wait for X output hi so no immed. interrupt
        SETB  EX1                  ;enable INT1* (X) ext. interrupt

;----------------------------------Infinite loop, work done by INT1* interrupt
        CLR   P2.0                 ;light the green LED solid while running
do:
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        NOP
        SJMP  do

;-------------------------------------------------------------------------------
;-----------------------------SUBROUTINES---------------------------------------
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
;----------------------------------INT1* interrupt handler, you get the
;                                  interrupt that vectors here whenever the
;                                  X acceleration signal is low
serviceint1:
        CLR   TR2                  ;stop timer 2
        MOV   OUTL,TL2             ;read low byte of timer (1MC)
        MOV   OUTH,TH2             ;read high byte of timer and put in tmp (2MC)
        SETB  TR2                  ;start timer 2 again (1MC, lost 4 cnts total)
        ACALL spiwc                ;write the two bytes to the MMC

        MOV   OUTL,TL1             ;read and store timer 1 cnt (X acceleration)
        MOV   OUTH,TH1             ; don't have to wait for X low as do with Y
        ACALL spiwc                ; below since had to happen already for intr

        JB    P3.2,*               ;wait until Y acceleration bit goes low
        MOV   OUTL,TL0             ;read and store timer 0 cnt (Y acceleration)
        MOV   OUTH,TH0
        ACALL spiwc

        DJNZ  WC,waitint1hi        ;if block not full, jump over next 4 lines

           ACALL spiwc             ;fill last 2 bytes of block with whatever
                                   ; junk is already in OUTL and OUTH
           ACALL writeend          ;Allow MMC to store block to flash
           ACALL writestart        ;Open the next MMC block for output
           MOV   WC,#85            ;reinitialize write counter

waitint1hi:
        JNB   P3.3,*               ;wait here until X accel output goes high
                                   ; otherwise we will immediately interrupt
                                   ; again
        RET                        ;RETI handled in interrupt block

;-------------------------------------------------------------------------------
;----------------------------------Initialize MultiMediaCard
initmmc:
        SETB  P1.4                 ;Take CS* high
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout

        CLR   P1.4                 ;take CS* low to select card

        MOV   A,0FFH
        ACALL spiout
        MOV   A,0FFH
        ACALL spiout

        MOV   A,#0                 ;send CMD0 to enable SPI mode on MMC
        ACALL cmd
        MOV   A,#0FFH              ;enable the MMC to finish
        ACALL spiout

notready:
        MOV   A,#1
        ACALL cmd

        CJNE  R0,#1,ready          ;return if the MMC is ready
        MOV   A,#0FFH              ;enable the MMC to finish
        ACALL spiout
        SJMP  notready

ready:
        MOV   A,#0FFH              ;enable the MMC to finish if we got here
        ACALL spiout
        RET

;-------------------------------------------------------------------------------
;----------------------------------Send a byte from ACC to MOSI, result=R0
spiout:
        MOV   SPDR,A
        ACALL waitspi
        MOV   R0,SPDR

        RET

;-------------------------------------------------------------------------------
;----------------------------------Writes two bytes of data to the MMC
spiwc:
        MOV   SPDR,OUTL            ;Send the low byte out on MOSI
        ACALL waitspi              ;Wait for the byte to shift out
        MOV   SPDR,OUTH            ;Send the high byte out on MOSI
        ACALL waitspi              ;Wait for the byte to shift out

        RET

;-------------------------------------------------------------------------------
;----------------------------------Send a CMD to the MMC
cmd:
        ORL   A,#01000000b         ;CMD's always have bit 7 set
        ACALL spiout               ;send command byte
        MOV   A,ADDR3              ;send 4 argument bytes
        ACALL spiout
        MOV   A,ADDR2
        ACALL spiout
        MOV   A,ADDR1
        ACALL spiout
        MOV   A,ADDR0
        ACALL spiout
        MOV   A,#95H               ;checksum, 95H for CMD0, ignored for rest
        ACALL spiout

        MOV   R1,#8                ;read up to 8 times for valid response
checkr:
        MOV   A,#0FFH
        ACALL spiout
        CJNE  R0,#255,response
        DJNZ  R1,checkr

        MOV   ERRORNUM,#1          ;didn't get a valid response, stop
        AJMP  error

response:                          ;can't check to see if good: cmd0/1 not
        RET

;-------------------------------------------------------------------------------
;----------------------------------Wait for the SPI data to shift out
waitspi:
        MOV   A,SPSR               ;read the status register
        ANL   A,#10000000b         ;upper bit=1 when data is gone
        CJNE  A,#10000000b,waitspi ;if only bit 7 set, the data is gone
        RET

;-------------------------------------------------------------------------------
;----------------------------------Erases the entire MMC by filling with 0xffh
erase:
        MOV   ADDR3,#0            ;start at address zero
        MOV   ADDR2,#0
        MOV   ADDR1,#0
        MOV   ADDR0,#0

        MOV   OUTL,#255           ;will use "spiwc" to write 2 bytes at a time
        MOV   OUTH,#255

        MOV   R7,#64
eloop1: MOV   R6,#245
           CPL   P2.0             ;flash green LED
eloop2:    ACALL writestart
              MOV   R5,#128
eloop3:       ACALL spiwc
                 ACALL spiwc
              DJNZ  R5,eloop3
              ACALL writeend
            DJNZ R6,eloop2
        DJNZ R7,eloop1

        SETB  P2.0                 ;turn the green LED off when done

        RET

;-------------------------------------------------------------------------------
;----------------------------------Starts writing a block of data
writestart:
        MOV   A,#24                ;command 24 = "write block"
        ACALL cmd
        MOV   A,#0FFH              ;let MMC finish
        ACALL spiout
        MOV   A,#0FEH              ;send data start token
        ACALL spiout
        ACALL incaddr              ;increment the address for next time

        RET

;-------------------------------------------------------------------------------
;----------------------------------Finishes writing a block of data
writeend:
        MOV   A,#0FFH              ;Send dummy data CRC to card
        ACALL spiout

waitr:                             ;Loop waiting for response from card
        MOV   A,#0FFH
        ACALL spiout
        CJNE  R0,#0FFH,gotr
        SJMP waitr

gotr:                              ;Got a response
        MOV   A,R0
        ANL   A,#00011111b         ;Mask off top three bits since undefined
        CJNE  A,#00000101b,badr    ;reset if the response was bad
        SJMP  goodr                ;skip the error stuff if response was good

badr:   MOV   ERRORNUM,#2          ;had an error, stop
        AJMP  error

goodr:
busy:                              ;wait for the card to finish programming
        MOV   A,#0FFH
        ACALL spiout
        CJNE  R0,#00H,programmed
        SJMP busy

programmed:
        MOV   A,#0FFH
        ACALL spiout
;        MOV   A,#13
;        ACALL cmd

;        MOV   A,#0FFH              ;check 2nd status byte (1st done by "cmd")
;        ACALL spiout
;        MOV   A,#0FFH              ;let the card finish
;        ACALL spiout
;        CJNE  R0,#00000000b,badstatus
;        SJMP  goodwrite

badstatus:                         ;had an error, restart the system
;         AJMP  start

goodwrite:
        RET

;-------------------------------------------------------------------------------
;----------------------------------Opens a block for reading
readstart:
        MOV   A,#17                ;send cmd 17, "read block"
        ACALL cmd

        MOV  R2,#255
wfe1:   MOV  R1,#255
wfe2:      MOV   A,#0FFH           ;wait for start of data byte (FEH)
           ACALL spiout
           CJNE  R0,#0FFH,gotfe
        DJNZ R1,wfe2
        DJNZ R2,wfe1

        MOV   ERRORNUM,#3          ;never received FEh, stop
        AJMP  error

gotfe:                             ;got either a data token or data error token
        CJNE  R0,#0FEH,badfe
        SJMP  goodfe

badfe:  MOV   ERRORNUM,#5
        AJMP  error

goodfe:                            ;got a valid data token
        RET

;-------------------------------------------------------------------------------
;----------------------------------Read a byte of data from the MMC
spirc:
        MOV   SPDR,#0FFH           ;shift out 0FFH to cause data to shift in
        ACALL waitspi              ;wait for the byte to finish shifting in
        MOV   R0,SPDR              ;store the byte read in R0
        RET

;-------------------------------------------------------------------------------
;----------------------------------Finish reading a block of data
readend:
        MOV   A,#0FFH              ;"read" dummy 16bit data CRC
        ACALL spiout
        MOV   A,#0FFH
        ACALL spiout
        MOV   A,#0FFH              ;make sure card is done
        ACALL spiout
        RET

;-------------------------------------------------------------------------------
;----------------------------------Increment the MMC memory address
incaddr:
        MOV   ADDR0,#0            ;ensure LSB of address always zero
        MOV   A,ADDR1
        CLR   C
        ADDC  A,#2                 ;increment write address by 2 means jump
        MOV   ADDR1,A
        JNC   donea
        MOV   A,ADDR2
        CLR   C
        ADDC  A,#1                 ;increment 3rd address byte if needed
        MOV   ADDR2,A
        JNC   donea
        CLR   C
        INC   ADDR3               ;increment 4th address byte if needed
donea:
        RET

;-------------------------------------------------------------------------------
;----------------------------------Millisecond delay routine, waits ACC ms
waitms: DEC   A                    ;Note, if A=1, may fail
        MOV   R1,A
loopm1: MOV   R2,#4                ;1000 loops just over 1ms @ 24MHz
loopm2: MOV   R3,#250
        DJNZ  R3,*
        DJNZ  R2,loopm2
        DJNZ  R1,loopm1           
        RET

;-------------------------------------------------------------------------------
;----------------------------------Error handler, num blinks each time=error #
error:
        SETB P2.0
        SETB P3.7
        MOV  R7,ERRORNUM              ;blink red led number of times=error #
count:  CLR  P3.7                     ;light the red LED
           MOV  A,#166                ;wait 0.166 secs
           ACALL waitms
           SETB P3.7                  ;turn off the red LED
           MOV  A,#167                ;wait 0.167 secs
           ACALL waitms
        DJNZ R7,count
        MOV  A,#250                   ;wait a second with red LED off
        ACALL waitms
        MOV  A,#250
        ACALL waitms
        MOV  A,#250
        ACALL waitms
        MOV  A,#250
        ACALL waitms
        SJMP error                    ;repeat the error endlessly

;------------------------------------------------------------------------
 	.END
