; gpslog.src
; 
; Receive GPS data; log to MMC/SD card.
;
;-------------------------------------------------------

device SX28, OSCHS, TURBO, STACKX, OPTIONX

IRC_CAL IRC_FAST

freq 20_000_000
reset start_point

;-------------------------------------------------------
; variable assignments and name declarations
;
; error codes

SH_RSP_ERR   = 1  ; card init short response error
LG_RSP_ERR   = 2  ; card init long response error
LST_RSP_ERR  = 3  ; card init last response error
INIT_SD_ERR  = 4  ; card init 55 did not return success
INIT_MC_ERR  = 5  ; card init 1  did not return success
ERROR_TEST   = 6  ; 
REG_RD_ERR   = 7  ; cmd 9 did not return 0 or $FE
SECT_RD_ERR  = 8  ; cmd 17 read sector failed to return 0 or FE
SECT_WR_ERR  = 9  ; cmd 24 write sector failed 
MAX_SECT_ERR = 10 ; all "max card sector address bytes are zero

;-------------------------------------------------------

rtcc_0          equ      $01        ; this define lets the RTCC byte look like other clock bytes

; Sx IO pin usage

LCD_RS232_OUT   equ     ra.0
RED_LED_OUT     equ     ra.1
GPS_RS232_IN    equ     ra.2
GPS_1PPS_IN     equ     ra.3

; sensor pins. 
; Also, maybe lets configure a bit for output to oscilliscope for ISR timing tests?

EDGE_0_IN       equ     rb.0
EDGE_1_IN       equ     rb.1
EDGE_2_IN       equ     rb.2
EDGE_3_IN       equ     rb.3
SCOPE_OUT       equ     rb.4

; MMC/SD pins

CARD_DATA_OUT   equ     rc.0        ; FROM card
CARD_CLK        equ     rc.1        ; TO card 
CARD_CS         equ     rc.2        ; TO card
CARD_DATA_IN    equ     rc.3        ; TO card

;-------------------------------------------------------
; buncha macros.

FIFO macro
    dw    $047
endm

RED_LED_OFF macro
    clrb   RED_LED_OUT
endm

RED_LED_ON macro
    setb   RED_LED_OUT
endm

clock_card macro             ; substantial delay IS required...
    setb  CARD_CLK   
    call  @microdelay        ; 6 nops here not enuf...
    clrb  CARD_CLK
    call  @microdelay        ; 
endm

set_cs_hi macro
    setb  CARD_CS
endm

set_cs_lo macro
    clrb  CARD_CS
endm

set_data_hi macro
    setb  CARD_DATA_IN
endm

set_data_lo macro
    clrb  CARD_DATA_IN
endm

;-------------------------------------------------------
; global variables

org             $8      

isr_fsr_save    ds      1       ;  8   used by ISR to save callers bank
flags           ds      1       ;  9
temp0           ds      1       ;  A   
temp1           ds      1       ;  B 
temp2           ds      1       ;  C   
temp3           ds      1       ;  D 
temp4           ds      1       ;  E
temp5           ds      1       ;  F

rx_flag         equ      flags.5

; Time - RTCC, cascaded to six bytes.

org             $10             ;        The first group are incremented by the ISR.

cascade =       $               ;        Time byte zero is RTCC, aliased to rtcc_0
rtcc_1          ds      1       ; 0     
rtcc_2          ds      1       ; 1
rtcc_3          ds      1       ; 2
rtcc_4          ds      1       ; 3
rtcc_5          ds      1       ; 4
snapshot =      $               ;       This group for storing a snapshot of time.
a0              ds      1       ; 5
snaphead =      $               ;       start of bytes 1-7
a1              ds      1       ; 6
a2              ds      1       ; 7
a3              ds      1       ; 8
a4              ds      1       ; 9
a5              ds      1       ; A
delay_index     ds      1       ; B
delay_loop      ds      1       ; C


; cardio bank

org             $30

bank_card =     $
sendget_index   ds      1       ; 0  used by card byte-send command
cardin          ds      1       ; 1  byte coming in from card
cardout         ds      1       ; 2  byte to go out to card
cmdbyte         ds      1       ; 3  command being sent to card
addr1           ds      1       ; 4  sector address 1,2,3.
addr2           ds      1       ; 5
addr3           ds      1       ; 6
card_index      ds      1       ; 7
card_error      ds      1       ; 8
cmd_response    ds      1       ; 9
card_temp       ds      1       ; A
sector_bytes    ds      1       ; B
sectorcount     ds      1       ; C
sequence        ds      1       ; D
                               

org             $50             ;    Address management. Card address is 3 bytes.

bank_carddata =  $
max_addr1       ds      1       ; 0  read from CSR. This is 1 greater than number of sectors.
max_addr2       ds      1       ; 1
max_addr3       ds      1       ; 2
data_addr1      ds      1       ; 3  This is where data will be written. The data write
data_addr2      ds      1       ; 4  routine copies these to "addr1,2,3" when writing data.
data_addr3      ds      1       ; 5  
log_addr1       ds      1       ; 6  This is where the current address checkpoint is stored.
log_addr2       ds      1       ; 7  Usually will be one less than max_addr.
log_addr3       ds      1       ; 8


;-------------------------------------------------

FIFO_BITS = 3

org             $70

lcd_fifo_base = $
lcd_fifo_data   ds      8       ; 0-7
lcd_fifo_head   ds      1       ; 8
lcd_fifo_tail   ds      1       ; 9
lcd_fifo_count  ds      1       ; A
lcd_fifo_temp   ds      1       ; B
lcd_fifo_total  ds      1       ; C

org             $90

card_fifo_base = $
card_fifo_data  ds      8       ; 0-7
card_fifo_head  ds      1       ; 8
card_fifo_tail  ds      1       ; 9
card_fifo_count ds      1       ; A
card_fifo_temp  ds      1       ; B
card_fifo_total ds      1       ; C

;-------------------------------------------------

org             $B0

serial =        $
tx_high         ds      1
tx_low          ds      1
tx_count        ds      1
tx_divide       ds      1
rx_count        ds      1
rx_divide       ds      1
rx_byte         ds      1

;-------------------------------------------------

org             $D0

; for lease

;-------------------------------------------------
; last bank has assorted stuff

org             $F0

bank_assorted = $               
string          ds      1       ; 0
nybs            ds      1       ; 1
hexByte         ds      1       ; 2

;-------------------------------------------------------
; interrupt service routine. Called on RTCC rollover.
;-------------------------------------------------------

org         $0

    mov     isr_fsr_save, FSR       ; Save caller's bank for restore at ISR end

:receive 

    bank    serial
    movb    C, /GPS_RS232_IN        ; read input pin into carry
    test    rx_count                ; Zero on start bit - preset variables below.
    jnz     :rxbit                  ; if data bits, jmp into bit processing
    mov     W, #9                   ; Execute here when start bit.
    sc                              ; (skip on carry) Only set count if carry is clear
    mov     rx_count, W             ; expect 9 bits
    mov     rx_divide, #6           ; delay 1 1/2 delays
:rxbit 
    djnz    rx_divide,:rxdone       ; skip "n" ISR occurrances. 6 for start bit.
    mov     rx_divide, #4           ;
    dec     rx_count                ; 9, 8, ... bits
    sz                              ; skip on rx_count = zero
    rr      rx_byte
    jnz     :rxdone                 ; don't set ready flag until rx_count = 0

; OK, we have a byte. Insert it into FIFO storage if there is space; otherwise discard it.

    mov     W, rx_byte 
    bank    card_fifo_base                      ; Insert char into card FIFO.
    jb      card_fifo_count.FIFO_BITS, :rxdone  ; if FIFO full then exit.
    mov     card_fifo_temp, W                   ; temporarily save the character so we can borrow W
    mov     W, #card_fifo_base
    add     W, card_fifo_head
    mov     FSR, W                              ; point FSR to next FIFO slot
    mov     IND, card_fifo_temp                 ; and insert the character
    inc     card_fifo_head
    clrb    card_fifo_head.FIFO_BITS            ; circular buffer: if we exceed size goto 0
    inc     card_fifo_count

:rxdone

; If TRANSMIT is sending a byte, continue sending it.

    bank    serial
    test    tx_count                ; 
    jnz     :transmit               ; 

; If not sending, check the FIFO for a new byte to send.

    bank    lcd_fifo_base             
    test    lcd_fifo_count          ; skip TRANSMIT section if LCD FIFO empty 
    jz      :endTransmit

    mov     W,  #lcd_fifo_base      ; FIFO non empty. Read a character and start sending.
    add     W,  lcd_fifo_tail
    mov     FSR, W
    mov     lcd_fifo_temp, IND
    inc     lcd_fifo_tail
    clrb    lcd_fifo_tail.FIFO_BITS ; circular buffer: if we exceed size goto 0
    dec     lcd_fifo_count
    mov     W, lcd_fifo_temp        ; copy byte into W...
    bank    serial 
    mov     tx_high, W              ; and copy W to tx place.
    mov     tx_low, #$80            ; set hi bit; clr all other.
    not     tx_low
    mov     tx_count,#12            ; was 10. making this 11 will send two stop bits

; Transmit:

:transmit                           ; transmit if currently sending a byte and any bits remain

; maybe alls we'll need to do is remove tx_divide to convert this
; to a full-speed transmit?

    djnz    tx_divide, :endTransmit ; decr divide. if not zero, quit.
    mov     tx_divide, #4           ; preset tx_divide for next time.
    test    tx_count                ; when done sending bits, 
    jz      :endTransmit            ; quit.
    stc                             ; set carry flag to create stop bits
    rr      tx_high                 ; LSB of data -> carry
    rr      tx_low                  ; carry -> bit 7 of lo; start -> bit 6.
    dec     tx_count
    movb    LCD_RS232_OUT, /tx_low.6 ; apply the bit to the output port

:endTransmit

;----------------------
; Keep a clock running.
; rtcc_0 IS the system register RTCC itself.
; At 1/4 cpu speed, this ticks at 5 mHz (200 nanos per tick).
; We interrupt (currently) at RTCC~=128, so the clock runs at twice the below rates:

                                    ; rollover periods
    bank    cascade                 ; rtcc_0: 51 micros
    inc     rtcc_1                  ; rtcc_1: 13 millis
    snz                
    inc     rtcc_2                  ; rtcc_2: 3.3 seconds
    snz               
    inc     rtcc_3                  ; rtcc_3: 14 minutes
    snz
    inc     rtcc_4                  ; rtcc_4: 61 hours
    snz                
    inc     rtcc_5                  ; rtcc_5: what - like 2 years?

    mov     FSR, isr_fsr_save
 
    mov     w,#$7E
    retiw   

;**********************************************************
; some strings 

hello       dw      'Hello World! HaWaYa?',0
version     dw      'Cardlog 1.2 03.23.05',0
ErrTitle    dw      'Error: ',0
sawSD       dw      'Detected SD card', 0
sawMMC      dw      'Detected MMC card', 0
unknownCard dw      'CARD not SD or MMC ?', 0
sectors     dw      'Sectors: ',0
space       dw      ' ',0

;-------------------------------------------------------
; main program
;-------------------------------------------------------

start_point 

    call    @init                 ; set IO port parms, set option reg, clr RAM.
    call    @splashscreen         ; send message to LCD
    call    @cardInit             ; detect and display MMC, or SD, or missing/unknown card
    call    @readCSR              ; read and display number of sectors from card
    call    @delay3sec            ; give the user time to read messages
    call    @clearscreen          ;

    call    @readLog              ; See where the last logging left off...

:loop
     call    @writeData
     call    @nextSector
     jmp     @:loop

;----------------------------------------------------------------

_nextSector

    bank    bank_carddata
    inc     data_addr1                   ; add 512 to address
    inc     data_addr1
    snz
    inc     data_addr2
    snz
    inc     data_addr3

    bank    bank_card
    inc     sequence


; todo:
; 1. check incr'd address against max/log address

; when the sequence crosses 0, write a log of the current data address.

    jnz    @:next
    call   @writeLog

:next
; DEBUG: lets see what we got: Show current address

    call    @lcdline1
    bank    bank_carddata
    mov     W, data_addr2
    call    @sendHexByte
    bank    bank_carddata
    mov     W, data_addr1
    call    @sendHexByte

    retp

;----------------------------------------------------------------
; We need two separate write-sector routines - one to write data and
; one to write logs. Use common "write_close" routine.

_writeLog

    bank    bank_carddata           ; copy log address to 
    mov     temp1, log_addr1        ; sector write-to address
    mov     temp2, log_addr2        ;
    mov     temp3, log_addr3        ;

    bank    bank_card               ; 
    mov     addr1, temp1            ; 
    mov     addr2, temp2            ;
    mov     addr3, temp3            ;

    bank    bank_card               ;
    mov     cmdbyte, #24            ; write data
    call    @command                ;

    bank    bank_card 
    mov     cardout, #$FE           ; send FE start-write token
    call    @sendgetByte

; Send 512 bytes to the card in one sequence byte, 3 address bytes, 1 251 loop and one 256 byte loop.
 
    bank    bank_card   
    mov     cardout, sequence
    call    @sendgetbyte

    bank    bank_carddata            ; 
    mov     temp1, data_addr1        ; 
    bank    bank_card   
    mov     cardout, temp1
    call    @sendgetbyte

    bank    bank_carddata            ; 
    mov     temp1, data_addr2        ; 
    bank    bank_card   
    mov     cardout, temp1
    call    @sendgetbyte

    bank    bank_carddata            ; 
    mov     temp1, data_addr3        ; 
    bank    bank_card   
    mov     cardout, temp1
    call    @sendgetbyte

    bank    bank_card
    mov     sector_bytes, #$FB
:firstHalf
    bank    bank_card   
    mov     cardout, #' '
    call    @sendgetbyte
    bank    bank_card
    djnz    sector_bytes, @:firstHalf

    bank    bank_card
    mov     sector_bytes, #0
:lastHalf
    bank    bank_card   
    mov     cardout, #' '
    call    @sendgetbyte
    bank    bank_card
    djnz    sector_bytes, @:lastHalf
    jmp     @close_write

;----------------------------------------------------------------
; write data. Set address to current data sector and write
; from the card_FIFO.

_writeData

    bank    bank_carddata           ; copy current data sector address
    mov     temp1, data_addr1       ; to sector write-to address
    mov     temp2, data_addr2       ;
    mov     temp3, data_addr3       ;

    bank    bank_card               ; 

    mov     addr1, temp1            ; 
    mov     addr2, temp2            ;
    mov     addr3, temp3            ;

    bank    bank_card
    mov     cmdbyte, #24            ; write data
    call    @command                ;

    bank    bank_card 
    mov     cardout, #$FE           ; send FE start token
    call    @sendgetByte

; Send 512 bytes to the card in one sequence byte, 1 255 loop and one 256 byte loop.
 
    bank    bank_card   
    mov     cardout, sequence
    call    @sendgetbyte

    bank    bank_card
    mov     sector_bytes, #$FF
:firstHalf
    call    @read_card_fifo_wait
    mov     temp0, W
    bank    bank_card   
    mov     cardout, temp0
    call    @sendgetbyte
    bank    bank_card
    djnz    sector_bytes, @:firstHalf

    bank    bank_card
    mov     sector_bytes, #0
:lastHalf
    call    @read_card_fifo_wait
    mov     temp0, W
    bank    bank_card   
    mov     cardout, temp0
    call    @sendgetbyte
    bank    bank_card
    djnz    sector_bytes, @:lastHalf
    jmp     @close_write

;-----------------------------------------------------
; generic write-sector close routine.
; Both write-data and write-logs jump here.

close_write                         ; (No ":" - keep this a global label)

; We're done sending 512 bytes to this page, so complete the write.

    call    @sendFFgetByte          ; CRC placeholder bytes
    call    @sendFFgetByte

; get response byte from cmd_response

    bank    bank_card 
    call    @sendFFgetByte
    mov     cmd_response, cardin

; after reading data response, we must loop while zeroes are received.

    mov     temp0, #0
:waitForWrite
    bank    bank_card    
    call    @sendFFgetbyte
    bank    bank_card      
    test    cardin
    jnz     @:writeFinished
    call    @delay1ms
    djnz    temp0, @:waitForWrite

    bank    bank_card              
    mov     card_error, #SH_RSP_ERR ; then set error flag 
    jmp     ErrorLabel

:writeFinished

; write done. What response? : xxx0bbb1 5 = good, 0xB, 0xD error

    and     cmd_response, #$1F
    cje     cmd_response,  #5, @:goodwrite
   
    bank    bank_card              
    mov     card_error, #SECT_WR_ERR ; then set error flag 
    jmp     ErrorLabel

:goodwrite
    call    @closeCommand       

    retp

;----------------------------------------------------------------
; Set log sector address and then read previous write addresses from it.
; We only read the log once during program initialization.

_readLog
  
    bank    bank_carddata                ; Test "max sector" extracted from card CSR register.
    test    max_addr1                    ; it is a fatal error for all 3 addresses
    jnz     @:p0                         ; to be zero, although 2 of the 3 may be.
    test    max_addr2
    jnz     @:p0
    test    max_addr3
    jnz     @:p0

    bank    bank_card                    ; If all max addresses are 0
    mov     card_error, #MAX_SECT_ERR    ; then set error flag 
    jmp     @ErrorLabel

:p0
    mov     temp1, max_addr1             ; We got a bunch of stuff to do with these
    mov     temp2, max_addr2             ; numbers. We gotta subtract 512 from the
    mov     temp3, max_addr3             ; address, then copy it to two separate places.

    mov     temp0, #0                    ; (borrow temp0 as a subtract underflow flag)
    sub     temp1, #2                    ; subtract 2 from the max address 
    jc      @:p1                         ; to get address of last valid sector.
    inc     temp0                        ; 
:p1 sub     temp2, temp0                 ; address 0 is always 0, so to subtract one
    jc      @:p2                         ; full sector (512 = 0x0200) we do (addr1 - 0x02)
    inc     temp0
:p2 sub     temp3, temp0
    jc      @:p3
    inc     temp0
:p3  
    mov     log_addr1, temp1             ; save our log address into bank carddata
    mov     log_addr2, temp2
    mov     log_addr3, temp3

    bank    bank_card                    ; and also temporarily write it to
    mov     addr1, temp1                 ; the current address location.
    mov     addr2, temp2
    mov     addr3, temp3

    call    @readFirst4                  ; read the log sector
    bank    bank_card                    ; and copy the results
    mov     sequence, temp0              ; to storage.
    bank    bank_carddata
    mov     data_addr1, temp1            ; 
    mov     data_addr2, temp2            ;
    mov     data_addr3, temp3            ;

; Until address log is working, start at zero

    bank    bank_card                    ; and copy the results
    mov     sequence, #1                 ; to storage.
    bank    bank_carddata
    mov     data_addr1, #0               ; 
    mov     data_addr2, #0               ;
    mov     data_addr3, #0               ;

; On the first use of a card, the final sector may contain anything.
; Likely the last sector will be either all zeroes (MMC) or all 0xFFs (SD).
; All zeroes could be used, but all 0xFFs would probably be an illegal address.
; Lets require that the address be between say sector 1 and max.
; If we don't read an address in that range, ashume that this is a fresh
; card and so we will want to initialize the addresses and sequence number.
; Do this before we try to scan for the last written sector.

    retp

;-----------------------------------------------------------------
; This routine used by both read data sector and read log sector, 
; as both only need the first few bytes read.
; Call with all 3 address bytes set.
; Returns first 4 bytes of data in globals temp0-3

_readFirst4

    bank    bank_card               
    mov     cmdbyte, #17            ; send command 17: read sector
    call    @command                ; send READ command
    bank    bank_card               ;  (BANK BANK_CARD NEEDED?)

    call    @sendFFgetbyte          ; byte 0
    mov     temp0, cardin
    call    @sendFFgetbyte          ; byte 1
    mov     temp1, cardin
    call    @sendFFgetbyte          ; byte 2
    mov     temp2, cardin
    call    @sendFFgetbyte          ; byte 3  
    mov     temp3, cardin

    mov     card_index, #$FC        ; read and discard 510 data and 2 CRC bytes
:loop3
    call    @sendFFgetbyte
    djnz    card_index, @:loop3
:loop4
    call    @sendFFgetbyte
    djnz    card_index, @:loop4

    call    @closecommand
    retp

;----------------------------------------------------
; Test/development: receive GPS chars and forward to the LCD buffer.

:displayGPSloop
    call    @lcdline1
    mov     temp1, #20
    mov     temp2, #0            ; temp2  = 1 means "dollar seen" for this line
:topline
    call    @read_card_fifo_wait  ; so if dollar not yet seen, then cmp char until is $
    mov     temp3, W
    cje     temp2, #1, :show1
    cjne    temp3, #'$', :topline
    mov     temp2, #1
:show1
    mov     W, temp3
    call    @write_lcd_fifo_wait  
    djnz    temp1, :topline
    call    @lcdline2
    mov     temp1, #20
    mov     temp2, #0
:botline
    call    @read_card_fifo_wait
    mov     temp3, W
    cje     temp2, #1, :show2
    cjne    temp3, #'$', :botline
    mov     temp2, #1
:show2
    mov     W, temp3
    call    @write_lcd_fifo_wait   
    djnz    temp1, :botline
    jmp    :displayGPSloop
 
;------------------------------------------------
; get card size from MMC/SD "CSR" register.
; This follows the algorithm on page 3-15 of the PDF document
; "MultiMediaCard Product Manual" rev 5.1, SanDisk corp.

_readCSR

    bank    bank_card 
    mov     cmdbyte, #9             ; send command 9
    mov     addr1, #0               ;
    mov     addr2, #0               ;
    call    @command                ; 

    bank    bank_card               ;   (BANK BAND_CARD needed?)
    
    mov     card_index, #5             
:loop1
    call    @sendFFgetbyte          ; read and throw 
    djnz    card_index, @:loop1     ; away bytes 0-5
 
    call    @sendFFgetbyte          ; temporarily save
    mov     temp1, cardin           ; bytes 6-10
    call    @sendFFgetbyte
    mov     temp2, cardin
    call    @sendFFgetbyte
    mov     temp3, cardin
    call    @sendFFgetbyte
    mov     temp4, cardin
    call    @sendFFgetbyte
    mov     temp5, cardin

    mov     card_index, #11
:loop2
    call    @sendFFgetbyte          ; read and throw away  
    djnz    card_index, @:loop2     ; bytes 11-17 + 2 CRC bytes

    call    @closeCommand;

    rl      temp3                   ; determine C_SIZE
    rl      temp2                   ; Rotate 3 bytes left twice, from 8 to 7 to 6,
    rl      temp1                   ; letting the carry flag pass the carry bit. Then add 1.
    rl      temp3                   ; temp1 holds hi nybble and temp2 holds lo byte.
    rl      temp2                   ; [  reg 6   ][C][  reg 7  ][C][  reg 8   ]
    rl      temp1                   ;    temp1          temp2         temp3
    inc     temp2
    snz
    inc     temp1
    and     temp1, #$F              ; Done. 12 bits.

    rl      temp5                   ; determine C_SIZE_MULT
    rl      temp4                   ; Like above, except only two bytes, and one shift,
    inc     temp4                   ; and add 2 to result. Then temp4 holds multiplier.
    inc     temp4                   ;  [  reg 9   ][C][  reg 10  ]
    and     temp4, #$F              ;     temp4          temp5

:loop3                              ; Do the multiplication:
    clc                             ; shift temp2 and temp1 left by the multiplier.
    rl      temp2                   ;
    rl      temp1                   ;
    rl      temp0
    djnz    temp4, @:loop3          ;
 
    and     temp0, #$3              ; Mask off bottom 2 bits

    bank    bank_carddata
    mov     max_addr3, temp0
    mov     max_addr2, temp1        ; OK, we have the total number of sectors in the card.
    mov     max_addr1, temp2        ; This is actually 1 greater than the greatest card address.

; lets see what we got: Show number of sectors

    call    @lcdline2

    mov     W, #sectors
    call    @sendString
    bank    bank_carddata
    mov     W, max_addr3
    call    @sendHexByte
    bank    bank_carddata
    mov     W, max_addr2
    call    @sendHexByte
    bank    bank_carddata
    mov     W, max_addr1
    call    @sendHexByte
  
    retp

;-----------------------------------------------
; FATAL ERROR routine
; On fatal error flash red LED, send LCD error code

ErrorLabel

:loop1
    call    @clearscreen
    call    @lcdline1
    mov     W, #Errtitle
    call    @sendString
    bank    bank_card
    mov     W, card_error
    call    @sendHexByte
    bank    bank_card
    mov     card_index, card_error

; flash board LED "card_error" number of times
; Maybe change to a half-second delay between error count flashes?

:loop2
    RED_LED_ON
    call    @delay3sec
    RED_LED_OFF
    call    @delay3sec
    bank    bank_card
    djnz    card_index, @:loop2

    call    @delay3sec
    call    @delay3sec

    jmp     @:loop1

;------------------------------------------------------------------
; MMC/SD card initialization

_cardInit

    bank    bank_card    
    mov     card_index, #10
:loop1                              
    call    @sendFFgetByte          ; 1: 80 clocks = 10 bytes
    djnz    card_index, @:loop1

    mov     cmdbyte, #0             ; 2: send command 0.
    mov     addr1, #0               ;    general card reset.
    mov     addr2, #0               ;    ignore its response.
    call    @command                            
    call    @closeCommand

    bank    bank_card               ; 
    mov     cmdbyte, #55            ; 3: send command 55.
    call    @command                ;    this discovers the card type: MMC vs. SD
    bank    bank_card               ; 
    cje     cmd_response, #5, @:initMMC   ; if ret = 5, illegal cmd, card is probably an MMC.

; SD specific initialization part

:initSD                             ; 4A: send command 41.

    bank    bank_card               ; 
    mov     card_index, #0          ;    finishes SD card initialization.
:loop2                              ;    SD requires that we repeatedly issue
    bank    bank_card 
    mov     cmdbyte, #41            ;    command55 followed by command41
    call    @command                ;    until command 41 returns 0

    bank    bank_card               ; 
    test    cmd_response            ;    Because we already issued one cmd55,
    jz      @:sd_init_ok            ;    start this loop with the send cmd41, then issue 55.
                                    ;    But we're really issuing 55 followed by 41.
    call    @delay13ms              

    bank    bank_card 
    mov     cmdbyte, #55            ; 
    call    @command                ; 
    bank    bank_card               ; 
    djnz    card_index, @:loop2     ; 

    call    @clearscreen            ;    if above loop runs to completion
    call    @lcdline1               ;    then we have an error.
    mov     W, #unknownCard
    call    @sendString
    retp

:sd_init_ok
    call    @clearscreen
    call    @lcdline1
    mov     W, #sawSD
    call    @sendString
    retp

;--------------------------------------
; MMC specific initialization part

:initMMC                            ; 4B: send command 1

    bank    bank_card 
    mov     card_index, #0          ;    finishes MMC card initialization.
:loop3                              ;    repeatedly issue command 1
    mov     cmdbyte, #1             ;  
    call    @command                ;    until command 1 returns 0
    bank    bank_card               ; 
    test    cmd_response            ;  
    jz      @:mmc_init_ok           ;  
    call    @delay13ms              ; 
    bank    bank_card 
    djnz    card_index, @:loop3     ; 

    call    @clearscreen            ;    if above loop runs to completion
    call    @lcdline1               ;    then we have an error.
    mov     W, #unknownCard
    call    @sendString
    retp

:mmc_init_ok

    call    @clearscreen
    call    @lcdline1
    mov     W, #sawMMC
    call    @sendString
    retp


;--------------------------------------------------
; CARD command function.
; Send the command in "cmdbyte".
; Callers must set address 1,2,3 if the command requires them.
; Return result in "cmdres"

_command

    bank    bank_card                ; 
 
    set_cs_lo                       ; First: set chip select low.

    mov     W, cmdbyte              ; Send command byte
    or      W, #$40                 ; (with bit 6 set)
    mov     cardout, W              ;
    call    @sendgetByte            ;
    mov     cardout, addr3          ; caller must have set address if
    call    @sendgetByte            ; this command needs it.
    mov     cardout, addr2          ; caller must set addresses 3,2,1.
    call    @sendgetByte            ; 
    mov     cardout, addr1          ; 
    call    @sendgetByte            ;
    mov     cardout, #0             ; address 0 always 0
    call    @sendgetByte            ;
    mov     cardout, #$95           ; CRC required for very first cmd0 only
    call    @sendgetByte            ; but placeholder needed for every command.

    bank    bank_card               ; all commands require training FF
    call    @sendFFgetByte

; Cmds 9 (read CSD register), 17 (read sector) and 38 (erase?) loop until $FE received.

    bank    bank_card                        ; 
    cje     cmdbyte,  #9, @:loop_until_FE
    cje     cmdbyte, #17, @:loop_until_FE
    cje     cmdbyte, #38, @:loop_until_7F

; remaining commands: (0,1,5,41,...) get result byte, then get one more byte.

    bank    bank_card                 ; 
    call    @sendFFgetByte
    bank    bank_card                  ; 
    mov     cmd_response, cardin
    call    @sendFFgetByte
    retp

;----------------------------------
; Some errors will be reported by card here, such as a read of an illegal address.
; If we loop 256 times without seeing expected "FE" then somethng's wrong.

:loop_until_FE

    bank    bank_card 
    mov     card_index, #0
:loop3                              ; Loop while response is FF
    bank    bank_card 
    call    @sendFFgetByte
    cje     cardin, #$FE, @:label6  ; Jump out when FE detected
    call    @delay13ms              ; otherwise delay a bit
    bank    bank_card 
    djnz    card_index, @:loop3     ; and retry card response
    bank    bank_card               ; If loop runs to completion (BANK NEEDED?)
    mov     card_error, #SH_RSP_ERR ; then set error flag 
    jmp     ErrorLabel
:label6
    mov     temp1, cardin
    call    @sendFFgetByte          ; clean out card with one last set of 8 clocks
    mov     cardin, temp1
    retp

;----------------------------------
; If we loop 256 times without seeing expected "7F" then somethng's wrong.

:loop_until_7F

    bank    bank_card 
    mov     card_index, #0
:loop4                               ; Loop while response is FF
    bank    bank_card 
    call    @sendFFgetByte
    cje     cardin, #$7F, @:label7   ; Jump out when 7F detected
    call    @delay13ms               ; otherwise delay a bit
    bank    bank_card 
    djnz    card_index, @:loop4      ; and retry card response
    bank    bank_card                ; If loop runs to completion 
    mov     card_error, #SH_RSP_ERR  ; then set error flag 
    jmp     ErrorLabel
:label7
    mov     temp1, cardin
    call    @sendFFgetByte           ; clean out card with one last set of 8 clocks
    mov     cardin, temp1
    retp

;------------------------------------------------------------------
; Send a byte to MMC/SD card and receive a byte of data from the card.
; Send data in variable "cardout".
; Receive incoming byte of data; return it to caller in "cardin".
; Contents of "cardout" are destroyed.
; Requires that "bank bank_card" be set by caller.
; 
; 1. incoming bits arrive MSB first. 
; 2. First bit is read before applying first clock.
; 3. send bits MSB first 

_sendGetByte

    mov     sendget_index, #8
:loop
    rl      cardin                  ; shift previously received bits left over one
    movb    cardin.0, CARD_DATA_OUT ; read and store first bit.
    movb    CARD_DATA_IN, cardout.7
    rl      cardout
    clock_card
    djnz    sendget_index, @:loop

    retp

;----------------------------------------------------------
; So many operations require sending FF that a separate routine is helpful.

_sendFFGetByte

    mov     sendget_index, #8
    mov     cardout, #$FF
:loop
    rl      cardin                  ; shift previously received bits left over one
    movb    cardin.0, CARD_DATA_OUT ; read and store first bit.
    movb    CARD_DATA_IN, cardout.7
    rl      cardout
    clock_card
    djnz    sendget_index, @:loop

    retp

;------------------------------------------------------------------
; finish most card commands.

_closeCommand

    bank    bank_card

    call    @sendFFgetByte
    call    @sendFFgetByte
    set_cs_hi
    call    @sendFFgetByte
    call    @sendFFgetByte
 
    retp

;----------------------------------------------------------
; general utility commands

_splashscreen

    call    @clearscreen

    call    @lcdline1
    mov     W, #hello
    call    @sendString

    call    @lcdline2
    mov     W, #version
    call    @sendString

    call    @delay3sec
    
    retp

;**************************************************************************
; LCD functions
;
; write zero-terminated character string to LCD pointed to by address in W

_sendString 

    bank    bank_assorted
    mov     string, W
:loop                
    mov     W,string
    mov     M,#0
    iread                           ; reads value from 11-bit code address in M, W
    test    W
    jnz     @:send
    retp
:send
    call    @write_lcd_fifo_wait   
    bank    bank_assorted
    inc     string
    jmp     @:loop   

;---------------------------------------------------------
; write value, 0-255, in W, to LCD as two hex bytes.

_sendHexByte

    bank    bank_assorted
    mov     hexByte, W              ; save the value because W
    mov     nybs, hexByte           ; is destroyed in hexNybble
    swap    nybs
    call    @hexNybble
    bank    bank_assorted
    mov     nybs, hexByte
    call    @hexNybble      
    retp

_hexNybble      
    and     nybs, #$0F              ; cvrt 4 bits to hex and send to LCD
    cjb     nybs, #10, @:label      ; if > 9, skip jmp and do add.                 
    add     nybs, #$7
:label  
    add     nybs, #$30
    mov     W, nybs
    call    @write_lcd_fifo_wait
    retp

;--------------------------------------------------

_clearScreen    
    
    mov     W, #$FE                 ; Clear LCD screen
    call    @write_lcd_fifo_wait 
    mov     W, #$01
    call    @write_lcd_fifo_wait
    call    @delay13ms               ; cls requires at least 1 MS delay
    retp

;--------------------------------------------------

_lcdLine1        

    mov     W, #$FE                 ; set cursor at line 1
    call    @write_lcd_fifo_wait
    mov     W, #$80
    call    @write_lcd_fifo_wait
    retp

;--------------------------------------------------

_lcdLine2

    mov     W, #$FE                 ; set cursor at line 2
    call    @write_lcd_fifo_wait
    mov     W, #$C0
    call    @write_lcd_fifo_wait
    retp

;**************************************************************************
; delay functions
;
; Not particularly accurate, but close enough.

_delay3sec                                 ; well, sorta like 3 seconds.

    bank    cascade                        ; 
    mov     delay_index, rtcc_2            ; get current value
:loop1
    cje     delay_index, rtcc_2, @:loop1   ; Loop while same value
:loop2
    cjne    delay_index, rtcc_2, @:loop2   ; Then loop until it changes.

    retp

;--------------------------------------------------
_delay13ms

    bank    cascade                        ; 
    mov     delay_index, rtcc_1            ; get current value
:loop1
    cje     delay_index, rtcc_1, @:loop1   ; Loop while same value
:loop2
    cjne    delay_index, rtcc_1, @:loop2   ; Then loop until it changes.

    retp


;--------------------------------------------------
_delay1ms

    bank    cascade       
    mov     delay_loop, #20
:loop3                 ; 
    mov     delay_index, rtcc_0            ; get current value
:loop1
    cje     delay_index, rtcc_0, @:loop1   ; Loop while same value
:loop2
    cjne    delay_index, rtcc_0, @:loop2   ; Then loop until it changes.
    djnz    delay_loop, @:loop3

    retp

;-----------------------------------------------------------
; delay about one microsecond. 
; Created for MMC/SD access.
; With 20 mHz crystal, with the overhead of banking and redirected procedure
; calls, all we need are a few nops.

_microdelay

    nop
    nop
    nop
    nop
    nop
    nop

    retp

;------------------------------------------------------------------
; FIFO algorithm adapted from Gunther's Scenix book Pg. 384
; Two FIFOs:
; 1. characters waiting to be sent to the LCD via RS232
; 2. characters waiting to be sent to the MMC/SD card
;
; Both FIFOs have "FIFO_BITS" as a buffersize; like 3 bits = 8 bytes.
; So when fifo_count increases such that FIFO_BITS is set then it is full.
;
; first version throws away chars if FIFO is full 
; These may be called by ISR, but to save cycles, may copy code body into ISR instead.

_write_lcd_fifo

    bank    lcd_fifo_base
    jb      lcd_fifo_count.FIFO_BITS, @:end  ; if FIFO full then exit
    mov     lcd_fifo_temp, W                 ; temporarily save the character so we can borrow W
    mov     W, #lcd_fifo_base
    add     W, lcd_fifo_head
    mov     FSR, W                           ; point FSR to next FIFO slot
    mov     IND, lcd_fifo_temp               ; and insert the character
    inc     lcd_fifo_head
    clrb    lcd_fifo_head.FIFO_BITS          ; circular buffer: if we exceed size goto 0
    inc     lcd_fifo_count
:end
    retp

; second version spins while FIFO is full.
; (used by user-level display routines, where spinning is OK)

_write_lcd_fifo_wait

:top
    bank    lcd_fifo_base
    jb      lcd_fifo_count.FIFO_BITS, @:top  ; if FIFO full then wait
    mov     lcd_fifo_temp, W                 ; temporarily save the character so we can borrow W
    mov     W, #lcd_fifo_base
    add     W, lcd_fifo_head
    mov     FSR, W                           ; point FSR to next FIFO slot
    mov     IND, lcd_fifo_temp               ; and insert the character
    inc     lcd_fifo_head
    clrb    lcd_fifo_head.FIFO_BITS          ; circular buffer: if we exceed size goto 0
    inc     lcd_fifo_count
    retp

; quietly return if FIFO empty - meaning it is up to the caller
; to perform the empty test before calling this routine, otherwise
; caller will receive garbage in the W register.

_read_lcd_fifo

    bank    lcd_fifo_base             
    test    lcd_fifo_count                   ; return if FIFO empty 
    snz                                      ; (skip return statement if count not zero)
    retp                                     ; and silently return
    mov     W,  #lcd_fifo_base
    add     W,  lcd_fifo_tail
    mov     FSR, W
    mov     lcd_fifo_temp, IND
    inc     lcd_fifo_tail
    clrb    lcd_fifo_tail.FIFO_BITS          ; circular buffer: if we exceed size goto 0
    dec     lcd_fifo_count
    mov     W, lcd_fifo_temp
    retp
  
;-------------------------------------------------
; Anything to be written to the MMC/SD card gets stuck into this here FIFO.
; May be RS232, mnay be sensor nybbles, may be timestamps...
; This routine silently exits if FIFO is full.

_write_card_fifo

    bank    card_fifo_base
    jb      card_fifo_count.FIFO_BITS, @:end ; if FIFO full then exit.
    mov     card_fifo_temp, W                ; temporarily save the character so we can borrow W
    mov     W, #card_fifo_base
    add     W, card_fifo_head
    mov     FSR, W                           ; point FSR to next FIFO slot
    mov     IND, card_fifo_temp              ; and insert the character
    inc     card_fifo_head
    clrb    card_fifo_head.FIFO_BITS         ; circular buffer: if we exceed size goto 0
    inc     card_fifo_count
:end
    retp

; Since reading the card FIFO is a user-level operation, lets just
; spin while the FIFO is empty. After all, what else are we gonna do?
; So in operation, this here spinloop may be where most free user time is spent.

_read_card_fifo_wait 

:top
    bank    card_fifo_base                   ; 
    test    card_fifo_count
    jz      @:top                            ; if FIFO empty then wait  
    mov     W, #card_fifo_base
    add     W, card_fifo_tail
    mov     FSR, W
    mov     card_fifo_temp, IND
    inc     card_fifo_tail
    clrb    card_fifo_tail.FIFO_BITS         ; circular buffer: if we exceed size goto 0
    dec     card_fifo_count
    mov     W, card_fifo_temp
    retp

;-------------------------------------------------------
; Scenix initialization
 
_init

   RED_LED_ON                       ; signal user that init is in progress

;------------------------------------------
; configure ports
 
    mode    $0F                     ; in/out direction mode (0 out 1 in)
    mov     !ra, #%1100             ; a0, a1 outie; a2, a3 innie
    mov     !rb, #%11101111         ; b: all in except bit 4 out
    mov     !rc, #%11110001         ; c: 0 in, 1,2,3 out, 4,5,6,7 in.

    mode    $0E                     ; pull-up resistor mode (0 enable 1 disable)
    mov     !ra, #%0011             ; port A: enable pull-up on the innies
    mov     !rb, #%00010000         ; port B: enable all pull-ups except bit 4
    mov     !rc, #%00001110         ; port C: pullup the innies

;------------------------------------------
; configure option register

    mov     !option, #%11110001     ; disable RTCC interrupts

;------------------------------------------
; clear memory

    mov     FSR, #8                 ; zero small global RAM area
:clearmem
    CLR     IND
    INC     FSR
    JNB     FSR.5, @:clearmem

:zero_ram                           ; zero ALL banked memory
    SETB    FSR.4
    CLR     IND 
    IJNZ    FSR, @:zero_ram

;------------------------------------------
; preset LCD and card pins

    clrb    LCD_RS232_OUT           ; Set LCD data bit low and hold it there for a second.
    clrb    CARD_CS                 ; set card chip-select low
    clrb    CARD_CLK                ; 
    clrb    CARD_DATA_IN            ; 

;------------------------------------------
; reconfigure option register

    mov     !option, #%10010001     ; enable RTCC interrupts

;------------------------------------------
; If the LCD is being used, we need to delay a little for it
; to initialize, otherwise it goes into debug mode.
; Interrupts must be enabled for the delay function to work.

    call    @delay3sec              ; delay for LCD to initialize
    RED_LED_OFF                     ; signal that init is finished.

    retp

;-------------------------------------------------------
; Jump table
; locate near end of code area, but in lower half of page.
;
; Can't call a function located in the second half of any 
; code page, so use this jump table to reference functions 
; on any part of any page. This table must exist entirely 
; in a first page half. Check filename.lst: the second column 
; of numbers to the left of these table entries must be in:
;       YES                      NO
;   0000 - 00FF             0100 - 01FF
;   0200 - 02FF             0300 - 03FF
;   0400 - 04FF             0500 - 05FF
;   0600 - 06FF             0700 - 07FF
;------------------------------------------------------
org $600

init                jmp @_init

; LCD
sendString          jmp @_sendString
clearScreen         jmp @_clearScreen
lcdLine1            jmp @_lcdLine1
lcdLine2            jmp @_lcdLine2
sendHexByte         jmp @_sendHexByte
hexNybble           jmp @_hexNybble
splashscreen        jmp @_splashscreen

; delay
microdelay          jmp @_microdelay
delay3sec           jmp @_delay3sec
delay13ms           jmp @_delay13ms
delay1ms            jmp @_delay1ms

; MMC/SD card
cardInit            jmp @_cardInit
command             jmp @_command
closeCommand        jmp @_closeCommand
sendGetByte         jmp @_sendGetByte  
sendFFGetByte       jmp @_sendFFGetByte
readCSR             jmp @_readCSR
readLog             jmp @_readLog
readFirst4          jmp @_readFirst4
writeData           jmp @_writeData
nextSector          jmp @_nextSector
writeLog            jmp @_writeLog

; queues
write_lcd_fifo_wait jmp @_write_lcd_fifo_wait
write_lcd_fifo      jmp @_write_lcd_fifo
read_lcd_fifo       jmp @_read_lcd_fifo
write_card_fifo     jmp @_write_card_fifo
read_card_fifo_wait jmp @_read_card_fifo_wait
 
;-------------------------------------------------------
;
;
; Notes
; takes 23 millis to write a sector to the MMC/SD card.
; At 9600 baud incoming, we could get about 23 bytes in that time; larger than our FIFO,
; so we would lose characters. And we are seeing misisng characters.
; Either enlarge FIFO or slow the GPS sending.
; What about 1200 baud GPS incoming and 9600 baud LCD outgoing?
; currently :
;    9600 baud -> 104 microseconds per bit
;    Get about 2087 cycles per 104 microseconds @ 20 mHz crystal
;    To interrupt 4 times per bit, want 522 cycles per interrupt cycle
;    Divide clock by 4 in RTCC prescalar -> ~130 RTCC cycles between interrupts
;    So we need to adjust the RTCC by 255 - 130 = 125
; new:
;    prescale by 16
;    write 9600 baud on every ISR pass.
;    read incoming every 8 passes -> 1200 baud in.
;
;---the end----------------------------------------------------
;
