; RAMlogger.src
;
; Receive GPS location messages, display to LCD and write to RAM.
; About 7 hours of data can be saved. A PC can connect via RS232
; and request this unit send all saved data from RAM.
;
; 512K RAM:
; 19+2 chars per second + 34+2 chars per minute -> 1296 total ch per min.
; Capacity: 404 minutes = 6.74 hours with logging every sec.
;
; This code contains a bunch of location processing code that isn't
; currently being used, such as code to convert longitude and latitude
; to 4 byte binary, save it, subtract it from earlier saved values, etc.
;
;-------------------------------------------------------

device SX28, OSCHS, TURBO, STACKX, OPTIONX
IRC_CAL IRC_FAST
freq 20_000_000
reset start_point

;-------------------------------------------------------
; variable assignments and name declarations

; Sx IO pin usage

LCD_OUT         equ     ra.0              ; these 4 are all used for RS232.
GPS_IN          equ     ra.1
PC_OUT          equ     ra.2
PC_IN           equ     ra.3

UNUSED_0        equ     rb.0
RED_LED         equ     rb.1
HCT139_ADDR_0   equ     rb.2
HCT139_ADDR_1   equ     rb.3
HCT139_EN_A     equ     rb.4
HCT139_EN_B     equ     rb.5
MODE_SW_0       equ     rb.6
MODE_SW_1       equ     rb.7

;-------------------------------------------------------
; 74HCT139 dual multiplexer use:
;   1. set two address bits (goes to both multiplexers) 
;   2. enable individual multiplexer -> addressed output bit goes low.
; Some multiplexer outputs latch data into 3 8-bit buffers to store the 
; 18-20 bit RAM address; other outputs go directly to RAM control bits.
;
; A 00 -> latch output of port C into RAM address low buffer
; A 10 -> latch output of port C into RAM address midrange buffer
; A 01 -> latch output of port C into RAM address high buffer
; A 11 -> multiplexer bit is applied directly to RAM output enable
; B 00 -> multiplexer bit is applied directly to RAM "write" pin
; B 01 -> unused
; B 10 -> unused
; B 11 -> unused

SET_OE macro                        ; Set the RAM output enable by selecting its 
    setb    HCT139_ADDR_0           ; address then CLRing the 139 multiplexer enable
    setb    HCT139_ADDR_1  
    clrb    HCT139_EN_A             ; dropping this pin applies the selected address
    nop                             ; How fast is RAM? 
    nop                             ; This does fail with 0 NOPs, but how
    nop                             ; few is necessary?
    nop
    nop
    nop
    nop
    nop
endm

CLR_OE macro                        ; CLR OE by disabling the 139 multiplexer enable
    setb    HCT139_EN_A
endm

WRITE_DATA macro
    clrb    HCT139_ADDR_0 
    clrb    HCT139_ADDR_1  
    clrb    HCT139_EN_B
    nop                             ; ? what is its write speed?
    nop
    nop
    nop
    nop
    setb    HCT139_EN_B
endm

LATCH_ADDR_LO macro                 ; HCT139 section 1 addr 0
    clrb    HCT139_ADDR_0           ; addr0 -> 0
    clrb    HCT139_ADDR_1           ; addr1 -> 0
    clrb    HCT139_EN_A             ; drop CS
    setb    HCT139_EN_A             ; raise CS
endm

LATCH_ADDR_MED macro                ; HCT139 section 1 addr 2
    clrb    HCT139_ADDR_0           ; addr0 -> 0
    setb    HCT139_ADDR_1           ; addr1 -> 1
    clrb    HCT139_EN_A             ; drop CS
    setb    HCT139_EN_A             ; raise CS
endm

LATCH_ADDR_HI macro                 ; HCT139 section 1 addr 1
    setb    HCT139_ADDR_0           ; addr0 -> 1
    clrb    HCT139_ADDR_1           ; addr1 -> 0
    clrb    HCT139_EN_A             ; drop CS
    setb    HCT139_EN_A             ; raise CS
endm

LATCH_AUX macro                     ; HCT139 section 1 addr 3? (untested)
    setb    HCT139_ADDR_0           ; addr0 -> 1
    setb    HCT139_ADDR_1           ; addr1 -> 1
    clrb    HCT139_EN_A             ; drop CS
    setb    HCT139_EN_A             ; raise CS
endm

RED_LED_ON macro
    clrb    RED_LED
endm

RED_LED_OFF macro
    setb    RED_LED
endm

; maybe there'll be a use for these someday...

;FIFO macro
;    dw    $047
;endm

;-------------------------------------------------
; BANK 0: global variables  ;    fieldptr and fsrByte will likely never leave the global area      
                            ;    because both are used in GPS char saving via FSR, which itself
org                    $8   ;    is used to hop around many different RAM banks.
bank_0          =       $   ;    If an FSR-save for LCD_write is ever designed then lcdByte may
fieldPtr        ds      1   ; 8  be able to be moved. (FSR-save needs 4 global bits of space.)
fsrByte         ds      1   ; 9  
flags1          ds      1   ; A  flags used all over - unlikely able to eliminate as global.  
lcdByte         ds      1   ; B
pointer1        ds      1   ; C
pointer2        ds      1   ; D
temp1           ds      1   ; E
flags2          ds      1   ; F
; full

;flags
                            ; these are bits in the "flags" byte in bank zero above.
LCD_FLAG        = 0         ; character waiting in LCD buffer when set
IN_MSG_HDR      = 1         ; set while receiving GPS header, like "$GPRMC"
IN_MESSAGE      = 2         ; set while receiving GPS message body
TO_PC_FLAG      = 3
FROM_PC_FLAG    = 4
NEGATIVE_FLAG   = 5
CHAR_SENT_FLAG  = 6
FROM_GPS_FLAG   = 7

; flags2

MODE_FLAG0      =     0
MODE_FLAG1      =     1

;-------------------------------------------------
; BANK 10: RAM bank

org                     $10
bank_ram        =       $
ramDatabyte     ds      1   ; 0
pushAddrLow     ds      1   ; 1
pushAddrMed     ds      1   ; 2
pushAddrHigh    ds      1   ; 3
popAddrLow      ds      1   ; 4
popAddrMed      ds      1   ; 5
popAddrHigh     ds      1   ; 6
ramByte         ds      1   ; 7
toPCbyte        ds      1   ; 8
fromPCbyte      ds      1   ; 9
; A-F unused

;-------------------------------------------------
; BANK 30: GPS message parsing

org                     $30     
bank_gps        =       $
screenToShow    ds      1   ;  0
msgFlags        ds      1   ;  1   LCD display loop
i               ds      1   ;  2 
msgLen          ds      1   ;  3
commaCount      ds      1   ;  4
statusStart     =       $   ;    GPS message status: valid, estimated, not valid.
statusStuff     ds      1   ;  5
lonBinStart     =       $   ; 
lonBin          ds      4   ;  6-9  Longitude, in 1/10000 degrees, as 32-bit integer
latBinStart     =       $   ; 
latBin          ds      4   ;  A-D  Latitude, in 1/10000 degrees, as 32-bit integer

; E, F unused

; GPS message type codes and "new message" flags, used in "msgFlags":

GPGGA     = 0  ;  $GPGGA,205343,3749.9327,N,12232.3580,W,1,08,1.1,12.5,M,-28.0,M,,*42
GPGGA_NEW = 1
GPRMC     = 2  ;  $GPRMC,225051,A,3755.5865,N,12203.0148,W,003.2,285.5,270305,015.4,E*68
GPRMC_NEW = 3
LAT_NEW   = 4
LON_NEW   = 5
;PGRMT     = 6  ;  $PGRMT,GPS 25-LVC VER 2.50 ,P,P,R,R,P,,19,R*15
;PGRMT_NEW = 7

;------------------------------------------------------
; BANK 50 

org                     $50
bank_isr  =   $

isr_skip        ds      1 ; 0
txCount_1       ds      1 ; 1  Transmit 1
txDivide_1      ds      1 ; 2
txLow_1         ds      1 ; 3
txFlag_1        ds      1 ; 4
rxCount_1       ds      1 ; 5  Receive 1
rxDivide_1      ds      1 ; 6
rxByte_1        ds      1 ; 7
txCount_2       ds      1 ; 8  Transmit 2
txDivide_2      ds      1 ; 9
txLow_2         ds      1 ; A
txFlag_2        ds      1 ; B
rxCount_2       ds      1 ; C  Receive 2
rxDivide_2      ds      1 ; D
rxByte_2        ds      1 ; E
; F unused

;------------------------------------------------------
; BANK 70: speed and direction storage

org                     $70
bank_SpdDirDate =       $
dirStart        =       $
dirstuff        ds      4   ; ascii direction 
dirEnd          =       $-1
speedStart      =       $
speedstuff      ds      6   ; ascii speed
speedEnd        =       $-1
dateStart       =       $
dateStuff       ds      6
dateEnd         =       $-1
; full

;-------------------------------------------------
; BANK 90: time and latitude characters

org                     $90
bank_lat        =       $
bank_time       =       $
latStart        =       $   ; ascii lat: "03749.2345" 0DDMM.MMMM format 
asciiLatitude   ds      10
latEnd          =       $-1     
timeStart       =       $   ; ascii time: 6 chars HHMISS in 24:60:60 format (but no colons)
h1              ds      1   ; hours
h0              ds      1
m1              ds      1   ; minutes
m0              ds      1
s1              ds      1   ; seconds
s0              ds      1
timeEnd         =       $-1
; bank full

;-------------------------------------------------
; BANK B0: longitude and altitude chars

org                     $B0
bank_lon        =       $
lonStart        =       $   ; lon: 10 chars "12232.3580"  DDDMM.MMMM format
asciiLongitude  ds      10
lonEnd          =       $-1      
altStart        =       $   ; alt: from 0 to 6 chars "1022.6" meters
asciiAltitude   ds      6
altEnd          =       $-1
; full

;-------------------------------------------------
; BANK D0: 4 byte multiply storage

org                     $D0
bank_math       =       $
multStart       =       $
mult0           ds      1   ; 0  ( 4 input to 1 x 4 multiply)
mult1           ds      1   ; 1
mult2           ds      1   ; 2
mult3           ds      1   ; 3
accumStart      =       $     
accum0          ds      1   ; 4   (4 bytes output from multiply routine)
accum1          ds      1   ; 5
accum2          ds      1   ; 6  
accum3          ds      1   ; 7
eight           ds      1   ; 8   byte 1 input to 1 x 4 multiply routine. Maybe make global?
counter         ds      1   ; 9   "counter" used in the multiply loop
srcStart        =       $
src0            ds      1   ; A
src1            ds      1   ; B   These 5 hold ASCII digits destined for Dec-to-binary conversion
src2            ds      1   ; C
src3            ds      1   ; D
src4            ds      1   ; E
src5            ds      1   ; F
; full

;-------------------------------------------------
; BANK F0: assorted stuff: serial TX/RX parameters, etc.

org                     $F0
bank_assorted   =       $               
string          ds      1   ; 0
hexByte         ds      1   ; 1
t1              ds      1   ; 2
t2              ds      1   ; 3
t3              ds      1   ; 4
t4              ds      1   ; 5
displayToggle   ds      1   ; 6  probly only use a few bits of this var
LCDcharCount    ds      1   ; 7  probly only use a few bits of this var
hiNybble        ds      1   ; 8
loNybble        ds      1   ; 9
; C,D,E,F available


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

; Five things combine together to determine rep rate of each ISR task:
; 1. crystal speed: 20 mHz
; 2. RTCC divisor: 1 to 1 at the moment
; 3. ISR task-skipping, if used
; 4. individual task skipping within the ISR subsection, if used
; 5. ISR-exit adjustment to RTCC

org         $0                      ; On each interrupt call, do only one task.

    bank    bank_isr                ;
    inc     isr_skip
    jnb     isr_skip.2, :next      
    clrb    isr_skip.0              ; 
    clrb    isr_skip.1              ; 
    clrb    isr_skip.2              ; 
:next
    cje     isr_skip, #0, :xmit_1
    cje     isr_skip, #1, :rcv_1
    cje     isr_skip, #2, :xmit_2
    cje     isr_skip, #3, :rcv_2


; 2400 baud: (using RTCC divide-by-2 prescalar) every 4th ISR visit task; every 8th visit run task.
; 4800 baud: same except remove RTCC prescalar

:rcv_1                                ; Receive RS232 bytes.
    movb    C, /GPS_IN                ; read input pin into carry
    test    rxCount_1                 ; rxCount is non-zero during byte receive.
    jnz     :rxbit_1                  ; if zero, then test Carry...
    mov     W, #9                     ; Execute here when start bit.
    sc                                ; (skip on carry) Only set count if carry is clear
    mov     rxCount_1, W              ; If carry is clear then prepare to receive 9 bits.
    mov     rxDivide_1, #12           ; delay 1 1/2 delays
:rxbit_1 
    djnz    rxDivide_1,:endISR        ; skip "n" ISR occurrances. 
    mov     rxDivide_1, #8            ; Only read the bit every 4th pass.
    dec     rxCount_1                 ; rxCount goes like 9, 8, ... bits
    sz                                ; skip on rxCount = zero
    rr      rxByte_1                  ; roll carry bit into received byte
    jnz     :endISR                   ; don't set ready flag until rxCount = 0
    mov     W, rxByte_1               ; save byte to "W"
    call    parseCharacter            ; deal with character in "W"
    jmp     :endISR                   ; done.

; 9600 baud: (div 2 prescalar) every 4th ISR visit task; every 2nd visit run task.
; after rmv prescalar: every 4th pass

:xmit_1                               ; TRANSMIT bytes to LCD via RS232
    djnz    txDivide_1,:endISR        ; skip "n" ISR occurrances. 
;    mov     txDivide_1, #2            ; Only read the bit every 2nd pass.
    mov     txDivide_1, #4
    test    txCount_1                 ; If TRANSMIT is in the middle of sending a byte
    jnz     :txbit_1                  ; jump directly to continue sending.
    jnb     flags1.LCD_FLAG, :endISR  ; If nothing to send then exit.
    mov     W, lcdByte                ; Read next char to send.
    bank    bank_isr
    clrb    flags1.LCD_FLAG           ;  Clear buffer flag.
    mov     txFlag_1, W               ; Copy W to "transmit" buffer.
    mov     txLow_1, #$80             ; set hi bit; clr all other.
    not     txLow_1                   ; invert for proper RS232 level.
    mov     txCount_1,#12             ; was 10. making this 12 extends stop bits.
:txbit_1                              ; transmit if currently sending a byte and any bits remain
    test    txCount_1                 ; when done sending bits, 
    jz      :endISR                   ; quit.
    stc                               ; set carry flag to create stop bits
    rr      txFlag_1                  ; LSB of data -> carry
    rr      txLow_1                   ; carry -> bit 7 of lo; start -> bit 6.
    dec     txCount_1                 ; decrement count of bits needing sending.
    movb    LCD_OUT, /txLow_1.6       ; apply the bit to the output port
    jmp     :endISR

; PC send and receive

; (2400-9600 numbers all assume div-by-2 prescalar.)
; 2400 baud: every 4th ISR visit this task; every 8th visit execute this task.
; RCV 2400: 12, 8
; RCV 4800:  6, 4
; RCV 9600:  3, 2
; RCV 19200: same as 9600 since prescalar has been set to 1-1

:rcv_2                                ; Receive RS232 bytes.
    movb    C, /PC_IN                 ; read input pin into carry
    test    rxCount_2                 ; rxCount is non-zero during byte receive.
    jnz     :rxbit_2                  ; if zero, then test Carry...
    mov     W, #9                     ; Execute here when start bit.
    sc                                ; (skip on carry) Only set count if carry is clear
    mov     rxCount_2, W              ; If carry is clear then prepare to receive 9 bits.
    mov     rxDivide_2, #3            ; delay 1 1/2 delays
:rxbit_2
    djnz    rxDivide_2,:endISR        ; skip "n" ISR occurrances. 
    mov     rxDivide_2, #2            ; Only read the bit every 4th pass.
    dec     rxCount_2                 ; rxCount goes like 9, 8, ... bits
    sz                                ; skip on rxCount = zero
    rr      rxByte_2                  ; roll carry bit into received byte
    jnz     :endISR                   ; don't set ready flag until rxCount = 0
    mov     W, rxByte_2               ; save byte to "W"
    bank    bank_ram
    mov     fromPCbyte, W
    setb    flags1.FROM_PC_FLAG  
    jmp     :endISR                   ; done.

; 9600 baud: every 4th ISR visit this task; every 2nd visit execute this task.
; RCV 19200: same, since prescalar has been set to 1-1

:xmit_2                               ; TRANSMIT bytes to PC via RS232
    djnz    txDivide_2,:endISR        ; skip "n" ISR occurrances. 
    mov     txDivide_2, #2            ; Only read the bit every 2nd pass.
    test    txCount_2                 ; If TRANSMIT is in the middle of sending a byte
    jnz     :txbit_2                  ; jump directly to continue sending.
    jnb     flags1.TO_PC_FLAG,:endISR ; If nothing to send then exit.
    bank    bank_ram
    mov     W, toPCbyte               ; Read next char to send.
    bank    bank_isr
    clrb    flags1.TO_PC_FLAG         ; Clear buffer flag.
    mov     txFlag_2, W               ; Copy W to "transmit" buffer.
    mov     txLow_2, #$80             ; set hi bit; clr all other.
    not     txLow_2                   ; invert for proper RS232 level.
    mov     txCount_2,#12             ; was 10. making this 12 extends stop bits.
:txbit_2                              ; transmit if currently sending a byte and any bits remain
    test    txCount_2                 ; when done sending bits, 
    jz      :endISR                   ; quit.
    stc                               ; set carry flag to create stop bits
    rr      txFlag_2                  ; LSB of data -> carry
    rr      txLow_2                   ; carry -> bit 7 of lo; start -> bit 6.
    dec     txCount_2                 ; decrement count of bits needing sending.
    movb    PC_OUT, /txLow_2.6        ; apply the bit to the output port
    jmp     :endISR

;----------------------
; (these comments may be out of date)
; Adjust RTCC for next interrupt.
;   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 adjust the RTCC by 255 - 130 = 125.
;   RTCC divide rate: bits 2 ^ ((2,1,0)+1) 010=2; 2+1=3; 2^3=8; so RTCC=clock/8
;   Options to set the RTCC prescalar as above and enable interrupts:
;     mov     !option, #%10010010 
;
; After the theoretical calculations, tweak timing by adjusting up and down
; until fast and slow limits are found that just start to corrupt received GPS
; message strings. Then center timing between those limits.
;   123-126: "hello world" messed up.
;   129-133 compiler warning, but works perfectly.
;   134: first slight character messing up, similar to 126, so use 130.
;   "-130" works but causes compiler warning; "7E" works with no warning.

:endISR

    mov W,  #$7E
    retiw   

; If the RTCC is 1/8 the CPU speed, then at about 130 RTCC counts,
; we have about 1040 CPU cycles that we can use before our ISRs would collide.
; At about 65 cycles per longest GPS character processing (called in "rx" section) 
; it looks like we got pleeeenty of time.

ENABLE_INTERRUPTS macro             ; 1001=RTCC->addr1, enabl int,cpu->rtcc,clk fall edge
;   mov     !option, #%10010000     ; 0010=prescale RTCC at 2^(000+1)=2^1=2
    mov     !option, #%10011000     ; 1000=prescaler assigned to watchjdog; RTCC=1-1 
endm


;-------------------------------------------------------
; some strings

hello       dw      'Hello World! HaWaYa?',0
version     dw      'RAMlogger Jun 01, 05',0
spacechar   dw      ' ',0
noSync      dw      ' - no sync - ',0
pushMsg     dw      'psh ', 0
popMsg      dw      'pop ', 0


;-------------------------------------------------------
; "parseCharacter" is called by the ISR above.
;
;        1      2         3 4          5 6 7  8   9    A B     C DE
;  $GPGGA,205343,3749.9327,N,12232.3580,W,1,08,1.1,12.5,M,-28.0,M,,*42
;  $PGRMV,0.0,0.0,0.0*5C
;  $PGRMT,GPS 25-LVC VER 2.50 ,P,P,R,R,P,,19,R*15
;
;        1      2 3         4 5          6 7     8     9      A     B
;  $GPRMC,225051,A,3755.5865,N,12203.0148,W,003.2,285.5,270305,015.4,E*68
;
; in Guenther's Simulator: 
;  hdr: about 36 cycles; 1.8 usec per char
;  body: (when saving byte) about 65 cycles; 3.25 usec per char
;  Looks OK to call from ISR.

parseCharacter

    bank    bank_gps                        ; 
    mov     fsrByte, W
    jb      flags1.IN_MESSAGE, @inMessage   ; If "in message" then go there...

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

notinMessage                                ; start here if not "in message".
    cjne    fsrByte, #'$', @justReturn      ; If byte is not $ then just return.
enterMessage
    clr     commaCount                      ; count fields within NMEA message
    clr     fieldPtr                        ; count chars within single field
    mov     msgLen, #1                      ; count NMEA message length
    clr     msgFlags                        ; msg type "GPGGA", "PGRMC"...
    setb    flags1.IN_MESSAGE               ; set state to "in message"
    setb    flags1.IN_MSG_HDR               ; when we enter a message we also enter the header
    clrb    msgFlags.GPGGA
    clrb    msgFlags.GPRMC
    retp

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

inMessage
    inc     msgLen                          ; '$' is count 1, so here, we incr to 2.
    jnb     flags1.IN_MSG_HDR, @inMsgBody   ; If not in header, jump to "in msg body"

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

inHeader                                    ; header is first 6 characters like "$GPGGA
    cje     msgLen, #2,  @msgChar2          ; test char 2: "G" ? "P"?
    cjbe    msgLen, #5,  @justReturn        ; char 3-5: skip. Go get the next character.
    cje     msgLen, #6,  @msgChar6          ; char 6: use this to determine msg type.
    cjae    msgLen, #120, @setNotInMsg      ; sanity check... no msg is this long.
    retp                               
msgChar2                                    ; Not using any "P...." messages, so
    cjne    fsrByte, #'G', @setNotInMsg     ; if not 'G' wait for the next message.
    retp                                    ; otherwise return for the rest of the header.
msgChar6
    clrb    flags1.IN_MSG_HDR               ; set "no longer in header"
    cje     fsrByte, #'A', @isGPGGA         ; message is GPGGA
    cje     fsrByte, #'C', @isGPRMC         ; message is GPRMC
    jmp     @setNotInMsg                    ; we don't want it...
isGPGGA
    setb    msgFlags.GPGGA
    retp
isGPRMC
    setb    msgFlags.GPRMC
    retp
setNotInMsg
    clrb    flags1.IN_MESSAGE                ; after clearing flag, fall into return...
justReturn
    retp

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

inMsgBody
    cje     fsrByte, #',', @comma           ; if comma then we are between fields.
    cje     fsrByte, #'*', @setNotInMsg     ; if asterisk then end of message.
    jmp     @inField                        ; any other - process as field character

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

comma
    inc     commaCount                      ; count of message fields
    cjae    commacount, #20, @setNotInMsg   ; its an error for commacount to grow too much
    clr     fieldPtr                        ; count of bytes within field
    retp

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

inField                                     ; switch based on message type
    jb      msgFlags.GPGGA, @fieldsGPGGA
    jb      msgFlags.GPRMC, @fieldsGPRMC
    jmp     @setNotInMsg                    

fieldsGPGGA                                 ; switch based on commaCount
    cje     commaCount, #1,   @timeField    ; 6 time chars
    cje     commaCount, #2,   @latField     ; 8 latitude chars 
    cje     commaCount, #4,   @lonField     ; 9 longitude chars
    cje     commaCount, #6,   @statusField  ; 1 status char
    cje     commaCount, #9,   @altField     ; 0 to 5 altitude chars
    cjae    commacount, #10,  @GPGGAready
    retp                                    ; all other fields go into the dumpster

; played with saving "direction", but not wanted at the moment. 
; Lets leave in place for example of how to add additional field.

fieldsGPRMC
    cje     commaCount, #7,   @speedField   ; 0 to 6 speed chars
;    cje     commaCount, #8,   @dirField     ; 0 to 3 direction chars
    cje     commaCount, #9,   @dateField    ; 6 date chars 
    cjae    commacount, #10,  @GPRMCready
    retp

;----- below here, as soon as we hit the FSR we've left bank_gps...

timeField                                   ; always 6 characters: $10 to $15
    mov     FSR, #timeStart
    jmp     @saveChar
latField                                    ; probably always 8 characters: $16 to $1D 
    mov     FSR, #latStart                  ; GPS sends only 2 digits of lat degrees
    inc     FSR                             ; so Incr FSR once to skip 3rd digit '0'.
    jmp     @saveChar                   
lonField                                    ; probably always 9 chars
    setb    msgFlags.LAT_NEW                ; User may now process the ASCII latitude.
    mov     FSR, #lonStart
    jmp     @saveChar
statusField                                 ; always 1 character in length
    setb    msgFlags.LON_NEW                ; User may now process the ASCII longitude
    mov     FSR, #statusStart
    jmp     @saveChar
altField                                    ; varies from 0 to 4 or 5:
    setb    msgFlags.GPGGA_NEW              ; User may now process the entire GPGGA message
    mov     FSR, #altStart
    jmp     @saveChar
GPGGAready
    setb    msgFlags.GPGGA_NEW              ; After last wanted field set "ready" flag
    clrb    msgFlags.GPGGA
    jmp     @setNotInMsg                    ; exit this message... We got all we wanted.
speedField                                  ; varies from 0 to 6 chars in length
    mov     FSR, #speedStart
    jmp     @saveChar
dateField                                   ; varies from 0 to 6 chars in length
    mov     FSR, #dateStart
    jmp     @saveChar
;dirField                                    ; varies from 0 to 3 chars in length
;    setb    msgFlags.GPRMC_NEW              ; On last wanted field set "ready" flag
;    mov     FSR, #dirStart
;    jmp     @saveChar
GPRMCready
    setb    msgFlags.GPRMC_NEW              ; After last wanted field set "ready" flag
    clrb    msgFlags.GPRMC
    jmp     @setNotInMsg                    ; exit this message... We got all we wanted.

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

saveChar                                    ; Here we do the work of char saving.                   
    add     FSR, fieldPtr                   ; FSR contains start of save location.
    mov     IND, fsrByte                    ; Add the current bytecount of this field.
    inc     fieldPtr                        ; Save our data byte.
    cjae    fieldPtr, #12, @setNotInMsg     ; error for fieldPtr to grow too much
    retp

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

start_point

    call    @init
    ENABLE_INTERRUPTS
    call    @splashScreen           ; send message to LCD
    call    @delay1s
    
    bank    bank_gps                ; 
    clrb    flags1.IN_MESSAGE       ; start by waiting for "$" that starts GPS message
    clrb    flags1.FROM_PC_FLAG     ; do not respond to PC until new character arrives

;--------------------------------
; main loop
; 
; Tetsing for each msg completion isn't working exactly right, but works well enuf. 
; Only problem seems to be very first line of data.

:displayLoopTop
    bank    bank_gps
    clrb    msgFlags.GPGGA_NEW      ; Needs work - when should I clear this flag?
;    clrb    msgFlags.GPRMC_NEW     ; and this flag?

    RED_LED_OFF

:waitNewMessageLoop
    call    @checkExport            ; See if request for data export is waiting

; Spin until the GPGGA message is complete
    bank    bank_gps  
    jnb     msgflags.GPGGA_NEW, @:waitNewMessageLoop
;    jnb     msgFlags.GPRMC_NEW, :loop

    RED_LED_ON

; Every minute, on zero seconds, store a record with full date and time
    bank    bank_time
    cjne     s0, #'0', @:next
    cjne     s1, #'0', @:next
    bank    bank_gps
    cjne    statusStuff, #'1', @:next              ; Skip RAM if GPS is not synced
    call    @minutesDataToRAM
:next

    call    @readModeSwitch
    jb      flags2.MODE_FLAG0, @:everyTwoSeconds   ; 1: RAM every 2 seconds
    jb      flags2.MODE_FLAG1, @:everyFiveSeconds  ; 2: RAM every 5 seconds
    jmp     @:ram                                  ; 0: RAM every second

:everyFiveSeconds
    bank    bank_time
    cje     s0, #'0', @:ram
    cje     s0, #'5', @:ram
    jmp     @:disp

:everyTwoSeconds
    bank    bank_time
    jnb     s0.0, @:ram
    jmp     @:disp

:ram
    bank    bank_gps
    cjne    statusStuff, #'1', @:disp     ; Skip RAM if GPS is not synced
    call    @secondsDataToRAM             ; seconds, lat, lon, alt, speed, ...
:disp
    call    @LCDdisplay
    jmp     @:displayLoopTop

; End main display loop
;-------------------------------------------------------

_readModeSwitch
    movb    flags2.MODE_FLAG0, /MODE_SW_0
    movb    flags2.MODE_FLAG1, /MODE_SW_1
    retp

;----------------------------------------------------------------------------
; Toggle among several screens per call, always displaying Lat and Lon on top, 
; but vary the bottom line display.

_LCDdisplay

    call    @lcdLine1                   ; Always display latitude and longitude on line 1.
    call    @showlatlon
    call    @lcdLine2                   ; Vary second LCD line...

    bank    bank_assorted                 
    inc     displayToggle
    cjbe    displayToggle, #1, @:d0     ; 1   : d0
    cjbe    displayToggle, #3, @:d1     ; 2-3 : d1
    cjbe    displayToggle, #5, @:d2     ; 4-5 : d2
    cjbe    displayToggle, #7, @:d3     ; 6-7 : d3
    clr     displayToggle               ; 8   : set to 0 and fall into d0
:d0
    call    @showTimeAlt
    retp
:d1
    call    @showTimeAlt
    retp
:d2
    call    @showTimeAlt
    retp
:d3
    call    @showRAMAddresses
    retp

;---------------------------------------------------------
; Here we check whether somebody is requesting a data download.
; 'A': return all data
; 'B': reset "pop" RAM address
; 'C': echo 'Y' in response

_checkExport

    jnb     flags1.FROM_PC_FLAG, @:next     ; if nothing here then skip
    bank    bank_ram
    cje     fromPCbyte, #'A', @:rcvdA
    cje     fromPCbyte, #'B', @:rcvdB
    cje     fromPCbyte, #'C', @:rcvdC
    retp

:rcvdA
    clrb    flags1.FROM_PC_FLAG             ; send all data to PC
:sendAllLoop
    jb      flags1.TO_PC_FLAG, @:sendAllLoop
    call    @popRAM
    bank    bank_ram
    mov     toPCbyte, W
    setb    flags1.TO_PC_FLAG
    call    @incPopRAMAddr
    test    W
    jz      @:next
    jmp     @:sendAllLoop

:rcvdB                                      ; reset "pop" address to zero
    clrb    flags1.FROM_PC_FLAG 
    mov     W, #0
    mov     popAddrLow, W
    mov     popAddrMed, W    
    mov     popAddrHigh, W
    retp

:rcvdC                                      ; hello-response
    clrb    flags1.FROM_PC_FLAG 
    jb      flags1.TO_PC_FLAG, @:rcvdC
    bank    bank_ram
    mov     W, #'Y'                         ; On rcvd C respond Y
    mov     toPCbyte, W
    setb    flags1.TO_PC_FLAG
    retp

:next
    retp

;-------------------------------------------------------
; Once a minute save date, time, full Lat, full Lon to RAM

_minutesDataToRAM

    mov     pointer1, #dateStart            ; date
:loop1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #dateEnd, @:loop1

    mov     W, #','
    call    @pushRAM

    mov     pointer1, #timeStart            ; time 
:loop2
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #timeEnd, @:loop2

    mov     W, #','
    call    @pushRAM

    mov     pointer1, #latStart+1            ; Latitude
:loop3
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #latEnd, @:loop3
  
    mov     W, #','
    call    @pushRAM

    mov     pointer1, #lonStart             ; Longitude
:loop4
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #lonEnd, @:loop4

    mov     W, #13 
    call    @pushRAM                        ; CR
    mov     W, #10                    
    call    @pushRAM                        ; LF

    retp

;-------------------------------------------------------
; every second save seconds of time, fractional minutes of Lat, lon, alt, speed

_secondsDataToRAM

    mov     pointer1, #timeStart+4          ; Time (seconds only)
:loop1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #timeEnd, @:loop1

    mov     pointer1, #latStart+6             ; Latitude
:loop2
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #latEnd, @:loop2
  
    mov     pointer1, #lonStart+6             ; Longitude
:loop3
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram
    inc     pointer1
    cjbe    pointer1, #lonEnd, @:loop3

    call    @sendAltitude                    ; Altitude: add leading zeroes

    mov     pointer1, #speedStart           ; speed
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                        ; hundreds
    inc     pointer1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                        ; tens
    inc     pointer1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                        ; units
    inc     pointer1
    inc     pointer1                        ; skip decimal pt
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                        ; tenths

    mov     W, #13 
    call    @pushRAM                        ; CR
    mov     W, #10                    
    call    @pushRAM                        ; LF

    retp

;---------------------------------------------------------
; add leading zeroes to short altitudes and eliminate decimal pt.
; examples:
; If 1234.5 then send 12345
; if 234.5  then send 02345
; if 34.5   then send 00345
; if 4.5    then send 00045
; FIXME - this isn't right for negative altitudes; the "-" should lead all zeroes,
;         but instead is embedded within zeroes. Oh well, for my area, I only get 
;         negative altitudes when satellite geomatry is really bad anyway.

_sendAltitude

    mov     pointer1, #altStart+4      ; if '.' appears at digit 5...
    mov     FSR, pointer1
    mov     pointer1, #altStart
    cje     IND, #'.', @:five          ; like "1234.5"

    mov     W, #'0'
    call    @pushram

    mov     pointer1, #altStart+3       ; if '.' appears at digit 4...
    mov     FSR, pointer1
    mov     pointer1, #altStart
    cje     IND, #'.', @:four           ; like "234.5"

    mov     W, #'0'
    call    @pushram

    mov     pointer1, #altStart+2       ; if '.' appears at digit 3...
    mov     FSR, pointer1
    mov     pointer1, #altStart
    cje     IND, #'.', @:three         ; like "34.5"

    mov     W, #'0'
    call    @pushram

    mov     pointer1, #altStart+1       ; if '.' appears at digit 2...
    mov     FSR, pointer1
    mov     pointer1, #altStart
    mov     FSR, pointer1
    cje     IND, #'.', @:two            ; look for "4.5"

    mov     W, #'0'
    call    @pushram                   ; don't see any point, so
    mov     W, #'0'                    ; emit two more zeros and quit.
    call    @pushram
    retp
 
:five
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                          ; "1"
    inc     pointer1
:four
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                          ; "2"
    inc     pointer1
:three
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                          ; "3"
    inc     pointer1
:two
    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                          ; "4"
    inc     pointer1

    inc     pointer1                          ; skip point

    mov     FSR, pointer1
    mov     W, IND
    call    @pushram                          ; "2"

    retp

;---------------------------------------------------------
; display current PUSH and POP addresses onto LCD

_showRAMAddresses

    mov     W, #pushMsg
    call    @sendString
    call    @showPushAddress
    call    @sendSpace
    mov     W, #popMsg
    call    @sendString
    call    @showPopAddress
    call    @sendSpace
    retp

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

_showPushAddress

    bank    bank_ram
    jb      pushAddrHigh.0, @:emit_1
:emit_0
    mov     W, #'0'
    call    @byteToHex
    bank    bank_assorted
    mov     W, loNybble
    call    @writeLCDwait
    jmp     @:next
:emit_1
    mov     W, #'1'
    call    @byteToHex
    bank    bank_assorted
    mov     W, loNybble
    call    @writeLCDwait
:next
    bank    bank_ram
    mov     W, pushAddrMed
    call    @showHexByte
    bank    bank_ram
    mov     W, pushAddrLow
    call    @showHexByte
    retp

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

_showPopAddress

    bank    bank_ram
    jb      popAddrHigh.0, @:emit_1
:emit_0
    mov     W, #'0'
    call    @byteToHex
    bank    bank_assorted
    mov     W, loNybble
    call    @writeLCDwait
    jmp     @:next
:emit_1
    mov     W, #'1'
    call    @byteToHex
    bank    bank_assorted
    mov     W, loNybble
    call    @writeLCDwait
:next
    bank    bank_ram
    mov     W, popAddrMed
    call    @showHexByte
    bank    bank_ram
    mov     W, popAddrLow
    call    @showHexByte
    retp

;-------------------------------------------------------
; convert the current longitude and latitude to 4 byte integers 
; representing 1/10000 of a minute, and display.

_showbinary

    call    @clearScreen             ; prepare the LCD to show results

    mov     pointer1, #latStart      ; point (1 below) Latitude DDMM.MMMM
    call    @DDMMtobin               ; convert to binary
    call    @lcdLine1                ; On the LCD top line...
    mov     pointer1, #accumStart
    call    @showhex32               ; show 4 hex bytes

    mov     pointer1, #lonStart      ; point to Longitude DDDMM.MMMM
    call    @DDMMtobin               ; convert to binary
    call    @lcdLine2                ; On the LCD bottom line...
    mov     pointer1, #accumStart
    call    @showhex32               ; show 4 hex bytes
    retp

;-------------------------------------------------------
; Display the current latitude and Longitude to the current LCD line.

_showLatLon

    mov     FSR, #latStart           ; send 9 latitude chars
    inc     FSR                      ; skip dummy "0" at lat beginning
:loop1  
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #latEnd, @:loop1

    call    @sendSpace
              
    mov     FSR, #lonStart          ; send 10 longitude characters
:loop2
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #lonEnd, @:loop2

    retp

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

_showDirSpeed

    bank    bank_gps
    cjne    statusStuff, #'1', @sayNoSync

    mov     FSR, #dirStart               ; send ? dir chars
:loop1  
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #dirEnd, @:loop1

    call    @sendSpace
              
    mov     FSR, #speedStart             ; send ? speed characters
:loop2  
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #speedEnd, @:loop2

    retp

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

_showTimeAlt

    mov     FSR, #timeStart                 ; send 6 time characters
:loop1   
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #timeEnd, @:loop1

    call    @sendSpace
    mov     W, #'A'                         ; send A for altitude
    call    @writeLCDWait

    bank    bank_gps                        ; Alt only valid when synced,
    cjne    statusStuff, #'1', @sayNoSync   ; so if synced,
    mov     FSR, #altStart                  ; send 4 or so altitude characters
:loop2
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjb     FSR, #altEnd, @:loop2

    call    @sendSpace
    mov     W, #'V'                         ; send velocity
    call    @writeLCDWait
        
    mov     FSR, #speedStart                ; send ? speed characters
:loop3
    mov     W, IND
    call    @writeLCDWait
    inc     FSR
    cjbe    FSR, #speedEnd, @:loop3

    retp

sayNoSync
    mov     W, #noSync
    call    @sendString
    retp

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

_procLatLon

    call    @DDMMtobin               ; Convert ASCII latitude or longitude to binary 
    mov     pointer1, #accumStart    ; copy the binary lon to storage.
    mov     FSR, pointer1
    mov     W, IND
    call    @pushRAM                 ; byte3
    inc     pointer1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushRAM                 ; byte2
    inc     pointer1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushRAM                 ; byte1
    inc     pointer1
    mov     FSR, pointer1
    mov     W, IND
    call    @pushRAM                 ; byte0
    retp

;------------------------------------------------------------
; Next- variable length decimal to binary conversion.
; Altitude and speed are variable length, and all 3 contain a decimal point...
; Like to have a single possibly-variable-length conversion routine...
; Could simply copy speed, or alt, or dir to alternate place, convert, then push?
; Each should need exactly 2 binary characters of result pushed...

; alt: copy 6? ahhh, but I ... don't...have... six........

_processAltitude

    bank   bank_math              ; 
    mov    W, #'0'
    mov    src4, W                ; preset leading 3 digits to '0'
    mov    src3, W
    mov    src2, W

    mov    pointer2, srcStart+5   ; preset pointer2 to end of destination

    mov    pointer1, altStart+1   ; set pointer1 to first place a point may be seen
    mov    FSR, pointer1

:loop
    cje   IND, #'.', @:isDP       ; if pointer1 is at a decimal point then ...
:notDP
    inc    pointer1               ; keep seeking that elusive decimal point
    cje    pointer1, #altEnd, @:end ; failed our quest
    jmp    @:loop
:isDP                             ; is DP: move pointer1 one more time.
    inc    pointer1               

    mov    FSR, pointer1          ; p1 points to raw least sig digit
    mov    W, IND                 ; copy it to least src location spot
    mov    FSR, pointer2          ; p1 points to raw least sig digit
    mov    IND, W
    dec    pointer1               ; skip decimal point
:P  dec    pointer1
    dec    pointer2
    mov    FSR, pointer1          ; p1 points to raw least sig digit
    mov    W, IND                 ; copy it to least src location spot
    mov    FSR, pointer2          ; p1 points to raw least sig digit
    mov    IND, W
    cja    pointer1, #altStart, @:p ; if more to copy then keep on truckin

    call   @decToBin

    bank   bank_math        
    mov    W, mult0               ; Whaddya think - will 2 binary chars do it?
    call   @pushRAM               ; I think so...
    mov    W, mult1
    call   @pushRAM

:end
    retp

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

; Convert a quantity in degrees, minutes and decimal minutes from
; 10 ASCII characters in the form DDDMM.MMMM to a 4 byte binary value 
; representing ten-thousanths of a minute. (about 6 inches of distance 
; when the DDMM is latitude or longitude).
; Call with "pointer1" pointing to the start of the DDDMM.MMMM to convert;
; results will be in the 4 byte accumulator.
;
; Uses and modifies pointer1

_DDMMtobin

    call    @clearMath               ; example: 3749.9327

    call    @multPreset
    mov     mult3, #$03
    mov     mult2, #$93              ; hundreds of degrees to minutes: x 6000
    mov     mult1, #$87              ; minutes to 1/10000 of minute:   x 10,000
    mov     mult0, #$00              ; 600 000 000 = $03 93 87 00
    call    @multiply32

    call    @multPreset
    mov     mult2, #$5B              ; tens of degrees to minutes:   x 600
    mov     mult1, #$8D              ; minutes to 1/10000 of minute: x 10,000
    mov     mult0, #$80              ; 60 000 000 = $5B 8D 80
    call    @multiply32

    call    @multPreset
    mov     mult2, #$09              ; degrees to minutes:           x 60
    mov     mult1, #$27              ; minutes to 1/10000 of minute: x 10,000
    mov     mult0, #$C0              ; 600 000 = $09 27 C0
    call    @multiply32

    call    @multPreset
    mov     mult2, #$01
    mov     mult1, #$86              ; tens of minutes to 1/10000 of minute: x 100,000
    mov     mult0, #$A0              ; 100,000 = $01 86 A0
    call    @multiply32

    call    @multPreset
    mov     mult1, #$27              ; minutes to 1/10000 of minute: x 10,000 
    mov     mult0, #$10              ; 10,000 = $27 10
    call    @multiply32
 
    inc     pointer1                 ; skip the decimal point

    call    @multPreset
    mov     mult1, #$03              ; tenths of minute to 1/10000 of minute: x 1000 
    mov     mult0, #$E8              ; 1000 = $03 E8
    call    @multiply32

    call    @multPreset
    mov     mult0, #$64              ; 100 = $64
    call    @multiply32

    call    @multPreset
    mov     mult0, #$0A              ; 10 = $0A
    call    @multiply32

    call    @multPreset              ; am I multiplying by one???
    mov     mult0, #$01              ; 1
    call    @multiply32

    retp

_multPreset

    mov     FSR, pointer1           ; operate on the digit that "pointer"
    mov     W, IND                  ; currently is pointing to
    bank    bank_math               ;
    mov     eight, W
    sub     eight, #'0'             ; adjust by ASCII '0'

    mov     W, #0
    mov     mult3, W                 ; these usually must be zero. When
    mov     mult2, W                 ; not, let the above prg overwrite 0.
    mov     mult1, W                 ; 

    inc     pointer1                 ;
    retp

;----------------------------------------------------------------------------
; start with pointer1 pointing to start of 5 ASCII digits to multiply
; end with mult3-mult0 containing 4 binary digits

_DECtoBin

    bank    bank_math
    mov     pointer1, src0

    call    @multPreset
    mov     mult1, #$27              ; thousands of units to 1/10 of unit: *10000 
    mov     mult0, #$10              ; 10000 = $2710
    call    @multiply32

    call    @multPreset
    mov     mult1, #$03              ; hundreds of units to 1/10 of units: x 1000
    mov     mult0, #$E8              ; 1000 = $03 E8
    call    @multiply32

    call    @multPreset              ; tens of units to 1/10 unit: *100
    mov     mult0, #$64              ; 100 = $64
    call    @multiply32

    call    @multPreset              ; ones of units to 1/10 unit: *10
    mov     mult0, #$0A              ; 10 = $0A
    call    @multiply32

    call    @multPreset              ; useless multiply by one to accomplish
    mov     mult0, #$01              ; necessary add to accumulator
    call    @multiply32

    retp                             ; result is in mult3-mult0


;**************************************************************************
; math subroutines
;
;-------------------------------------------------------
; Multiply the 8 bit number in "eight" times the 32 bits in mult0-mult3,
; storing the result in the 4 accumulator bytes accum0-accum3.
; Uses "counter".
; This does not preclear the accumulator, so it can be used for repeated
; multiplications where the results all need to be accumulated together,
; so should call clearmult before its first use.

_multiply32
    bank    bank_math
    mov     counter, #8
multLoop:
    rr      eight           ; move least bit of "eight" into carry register
    jnc     @rotate         ; if it's 0, don't do any adding
    clc
    add     accum0, mult0   ; add byte 0...
    addb    accum1, C       ; then first add the carry bit into byte1,
    add     accum1, mult1   ; then add the byte, making the byte 2 carry...
    addb    accum2, C
    add     accum2, mult2
    addb    accum3, C
    add     accum3, mult3
rotate: 
    clc                     ; When we rotate, the carry bit would rotate
    rl      mult0           ; into the low bit of mult0, which we don't
    rl      mult1           ; want, so clear it.
    rl      mult2
    rl      mult3
    djnz    counter, @multLoop

    retp

;-------------------------------------------------------
; divide the number pointed to by pointer1 by 2 to the power of (eight)
; lets divide the accum if that's all we'll ever use...

_divideBy32               ; shift right (eight) times.

    bank    bank_gps
    add     pointer1, #3  
    
:divideBy                              
    mov     FSR, pointer1       
    clr     C                      
    rr      FSR           ; start with byte 3
    dec     IND
    rr      FSR           ; byte 2                   
    dec     IND
    rr      FSR           ; byte 1                   
    dec     IND
    rr      FSR           ; byte 0                   

    djnz    eight, @:divideBy         ; How can I use "eight" if I've set FSR?

    retp

;-------------------------------------------------------
; Subtract 4 bytes:  Mult - Accum -> Mult.
;
; If the result is below zero then fix: invert and add 1, a la 2's complement,
; and set a "negative" flag.

_subtract32

    bank    bank_math
    clrb    flags1.NEGATIVE_FLAG

    sub     mult0, accum0

    jc      @:p1
    inc     accum1
:p1 sub     mult1, accum1
 
    jc      @:p2
    inc     accum2
:p2 sub     mult2, accum2
 
    jc      @:p3
    inc     accum3
:p3 sub     mult3, accum3

    jc      @:done                  ; if result >= 0 then done.;

    setb    flags1.NEGATIVE_FLAG     ; otherwise set negative flag
    not     mult0                   ; and twiddle the results to be
    not     mult1                   ; as if we subtracted mult from accum.
    not     mult2                   ; NOT the 4 bytes, then
    not     mult3                   ; add 1, like 2's complement.
    clr     C 
    add     mult0, #1
    addb    mult1, C  
    addb    mult2, C
    addb    mult3, C

:done
    retp

;-------------------------------------------------------
; Is it right to always erase the entire math partition? Maybe
; better to explicitly zero banks of 4 bytes and the "eight" byte?

_clearMath

    mov     FSR, #bank_math
:loop
    mov     IND, #0
    inc     FSR                     ; bit 4 is set in local data banks, so as soon as it
    jb      FSR.4, @:loop           ; shows clear, then we've incr'd back to global.
 
    retp

;-------------------------------------------------------
; copy values of 4 bytes starting at pointer1 to 4 bytes at pointer2
; modifies pointer1, pointer2, temp

_Copy32

    call    @CopyByte
    call    @CopyByte
    call    @CopyByte
    call    @CopyByte
    retp

_CopyByte                             ; macro or sub? faster speed or compacter code?
    mov     FSR, pointer1             ; copy from "pointer1" to "pointer2"
    mov     temp1, IND                ; 
    mov     FSR, pointer2             ; 
    mov     IND, temp1
    inc     pointer1                  ;
    inc     pointer2
    retp  

;-------------------------------------------------------
; show contents of four bytes in hex pointed to by "pointer1".

_showHex32
 
    mov     FSR, pointer1+3
    mov     W, IND
    call    @showHexByte
    mov     FSR, pointer1+2
    mov     W, IND
    call    @showHexByte
    mov     FSR, pointer1+1
    mov     W, IND
    call    @showHexByte
    mov     FSR, pointer1+0
    mov     W, IND
    call    @showHexByte
    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                  ; initialization
sendString          jmp @_sendString            ; LCD display
clearScreen         jmp @_clearScreen
lcdLine1            jmp @_lcdLine1
lcdLine2            jmp @_lcdLine2
showHexByte         jmp @_showHexByte
splashScreen        jmp @_splashScreen
padLine             jmp @_padLine
writeLCDWait        jmp @_writeLCDWait
delay1ms            jmp @_delay1ms          
delay10ms           jmp @_delay10ms
delay100ms          jmp @_delay100ms
delay1s             jmp @_delay1s
pushRAM             jmp @_pushRAM             
popRAM              jmp @_popRAM
incPopRAMAddr       jmp @_incPopRAMAddr
secondsDataToRAM    jmp @_secondsDataToRAM
minutesDataToRAM    jmp @_minutesDataToRAM
showPushAddress     jmp @_showPushAddress
showPopAddress      jmp @_showPopAddress
clearMath           jmp @_clearMath             
multPreset          jmp @_multPreset
sendSpace           jmp @_sendSpace
showbinary          jmp @_showbinary
showhex32           jmp @_showhex32
multiply32          jmp @_multiply32
Copy32              jmp @_Copy32
copyByte            jmp @_copyByte
subtract32          jmp @_subtract32
DDMMtobin           jmp @_DDMMtobin
loadLatLon          jmp @_loadLatLon
showLatLon          jmp @_showLatLon
showTimeAlt         jmp @_showTimeAlt
showDirSpeed        jmp @_showDirSpeed
clrGPSvars          jmp @_clrGPSvars
readModeSwitch      jmp @_readModeSwitch
checkExport         jmp @_checkExport
showRAMAddresses    jmp @_showRAMAddresses
sendAltitude        jmp @_sendAltitude
LCDdisplay          jmp @_LCDdisplay
byteToHex           jmp @_byteToHex
zeroRAM             jmp @_zeroRAM
DECtoBin            jmp @_DECtoBin
procLatLon          jmp @_procLatLon

;Copy1byte           jmp @_Copy1byte
;multtest           jmp @_multtest              ; temporary test routines
;lattobintest       jmp @_lattobintest
;latsubtest         jmp @_latsubtest


;----------------------------------------------------------
; RAM access routines
;
; write byte from W to RAM

_pushRAM

    bank    bank_ram
                                     ; 128K RAM uses up to bit 0 of high address
                                     ; 512K RAM uses up to bit 3
;    jnb     pushAddrHigh.1, @:go    ; 128: exit if last incr has set bit 1 of high address
    jnb     pushAddrHigh.4, @:go        ; 512: exit if bit 4 set
    mov     W, #0                   ; 
    retp
:go mov     ramDatabyte, W
    CLR_OE                          ; Lets disable RAM output before setting C to out
    mode    $0F                     ; in/out direction mode (0 out 1 in)
    mov     !rc, #0                 ; c: out
    mov     W, pushAddrLow
    mov     rc, W
    LATCH_ADDR_LO

    mov     W, pushAddrMed
    mov     rc, W
    LATCH_ADDR_MED

    mov     W, pushAddrHigh
    mov     rc, W
;    setb    rc.1                    ; in 128K RAM, hi addr 1 = CS
;                                    ; in 512K RAM is next addr bit
    LATCH_ADDR_HI

    inc     pushAddrLow
    snz                
    inc     pushAddrMed
    snz               
    inc     pushAddrHigh
 
    mov     W, ramDatabyte
    mov     rc, W                   ; Data now is at RAM data port
    WRITE_DATA
 
    mov     W, #1                   ; return 1 in case we ever care...
    retp

;---------------------------------------------------------
; read byte into W from RAM (but do not incr address)

_popRAM

    bank    bank_ram
    CLR_OE                          ; Lets disable RAM output before setting C to out
    mode    $0F                     ; in/out direction mode (0 out 1 in)
    mov     !rc, #0                 ; c: out

    mov     W, popAddrLow
    mov     rc, W
    LATCH_ADDR_LO

    mov     W, popAddrMed
    mov     rc, W
    LATCH_ADDR_MED

    mov     W, popAddrHigh
    mov     rc, W
;    setb    rc.1                    ; in 128K RAM, hi addr 1 = CS
    LATCH_ADDR_HI

    mode    $0F                     ; in/out direction mode (0 out 1 in)
    mov     !rc, #$FF               ; c: in
    SET_OE
 
    mov     W, rc
    mov     ramDatabyte, W          ; Drop this? Leave data in W?
    CLR_OE
    retp

;----------------------------------------------------
; incr address. Stop if equal to push address.

_incPopRAMAddr

    bank    bank_ram
    cjne    popAddrLow,  pushAddrLow, :inc   ; only continue if popaddr < pushaddr
    cjne    popAddrMed,  pushAddrMed, :inc   ; return 0 if not.
    cjne    popAddrHigh, pushAddrHigh, :inc
    mov    W, #0
    retp
:inc
    inc     popAddrLow
    snz                
    inc     popAddrMed
    snz               
    inc     popAddrHigh
    mov    W, #1
    retp

;-------------------------------------------------------
; Scenix initialization
 
_init
                                    ; configure ports (1 = in, 0 = out)
    mode    $0F                     ; in/out direction mode (0 out 1 in)
    mov     !ra, #%1010             ; a: 3 in, 2 out, 1 in, 0 out
    mov     !rb, #%11000001         ; b: 7,6 in, 5-1 out, 0 in
    mov     !rc, #%11111111         ; c: in for now. Program will modify this.

    mode    $0E                     ; pull-up resistor mode (0 enable 1 disable)
    mov     !ra, #%0111             ; 3 up, 2 not, 1 up, 0 not
    mov     !rb, #%00111100         ; 
    mov     !rc, #%00000000         ; 
                                    ; preset ports
    clrb    HCT139_ADDR_0           ; HCT139 addr A
    clrb    HCT139_ADDR_1           ; HCT139 addr B
    setb    HCT139_EN_A             ; HCT139 enable A
    setb    HCT139_EN_B             ; HCT139 enable B

    call    @zeroRAM

    call    @clrGPSvars

    RED_LED_ON
    clrb    LCD_OUT                 ; Set LCD data bit low and hold it there for a second.
    call    @delay1s                ; delay for LCD to initialize
    RED_LED_OFF
                                  
    retp

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

_zeroRAM

    mov     FSR, #8                 ; zero global RAM area
:clearMem
    CLR     IND
    INC     FSR
    JNB     FSR.5, @:clearMem
:zeroRam                           ; zero banked memory
    SETB    FSR.4
    CLR     IND 
    IJNZ    FSR, @:zeroRam
    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    @writeLCDWait   
    bank    bank_assorted
    inc     string
    jmp     @:loop   

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

_showHexByte

    call    @byteToHex
    bank    bank_assorted
    mov     W, hiNybble
    call    @writeLCDWait
    bank    bank_assorted
    mov     W, loNybble
    call    @writeLCDWait
    retp

;--------------------------------------------------------------------
; convert binary byte in W to two ASCII Hex digits '0'-'F' 

_byteToHex

    bank  bank_assorted
    mov loNybble, W
    mov hiNybble, W
    swap hiNybble
    mov  W, #$F
    and loNybble, W
    and hiNybble, W
    mov W, #'0'
    add loNybble, W
    add hiNybble, W
    cjb loNybble, #':', :s1
    add loNybble, #7
:s1 cjb hiNybble, #':', :s2
    add hiNybble, #7
:s2 retp

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

_splashScreen

    call    @lcdLine1
    mov     W, #hello
    call    @sendString

    call    @lcdLine2
    mov     W, #version
    call    @sendString

    call    @delay1s
    call    @clearScreen

    retp

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

_lcdLine1        

    mov     W, #$FE                 ; set cursor at line 1
    call    @writeLCDWait
    mov     W, #$80                 ; #$FE, #$80 are Seetron LCD control codes
    call    @writeLCDWait
    retp

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

_lcdLine2

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

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

_clearScreen    
    
    mov     W, #$FE                 ; Clear LCD screen
    call    @writeLCDWait 
    mov     W, #$01
    call    @writeLCDWait
    call    @delay10ms              ; cls requires at least 10 MS delay
    retp

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

_sendSpace

    mov     W, #' '
    call    @writeLCDWait
    retp

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

_padLine

:p1
    bank    bank_assorted
    cjae    LCDcharCount, #20, @:end
    mov     W, #' '
    call    @writeLCDWait
    jmp     @:p1
:end
    retp

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

_writeLCDWait

:spin
    jb      flags1.LCD_FLAG, @:spin
    mov     lcdByte, W
    setb    flags1.LCD_FLAG
    retp

;**************************************************************************
; delay functions
; pretty accurate w/o interrupts; maybe not so accurate but still useable with.

_delay1s
    bank    bank_assorted
    mov     t4, #100
:l1 call    @delay10ms
    djnz    t4, @:l1
    retp

_delay100ms
    bank    bank_assorted
    mov     t4, #100
:l1 call    @delay1ms
    djnz    t4, @:l1
    retp

_delay10ms                 ; delay 192936 cycles =! 10 ms @ 20 mHz
    bank    bank_assorted
    mov     t3, #10
:l1 call    @delay1ms
    djnz    t3, @:l1
    retp

_delay1ms                  ; delay 19302 cycles with "15" loop =~ 1 ms @ 20 mHz
    bank    bank_assorted
    mov     t2, #15
:l2 mov     t1, #0
:l1 djnz    t1, @:l1
    djnz    t2, @:l2
    retp

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

_clrGPSvars

    mov     FSR, #latStart           ; Lat
:loop1
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #latEnd, :loop1

    mov     FSR, #lonStart           ; Lon
:loop2
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #lonEnd, :loop2

    mov     FSR, #altStart           ; Altitude
:loop3
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #altEnd, :loop3

    mov     FSR, #timeStart         ; Time
:loop4
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #timeEnd, :loop4

    mov     FSR, #dirStart          ; Direction
:loop5
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #dirEnd, :loop5

    mov     FSR, #speedStart        ; Speed
:loop6
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #speedEnd, :loop6

    mov     FSR, #dateStart         ; Date
:loop7
    mov     IND, #' '
    inc     FSR
    cjbe    FSR, #dateEnd, :loop7
              
    bank    bank_ram              ; zero RAM addresses
    mov     W, #0
    mov     pushAddrLow, W
    mov     pushAddrMed, W    
    mov     pushAddrHigh, W
    mov     popAddrLow, W
    mov     popAddrMed, W    
    mov     popAddrHigh, W
    retp


_loadLatLon

;    mov     FSR, #latStart          ; load sample latitude 37 41.3557
;    mov     IND, #'0'
;    inc     FSR
;    mov     IND, #'3'  ;3
;    inc     FSR
;    mov     IND, #'7'  ;7
;    inc     FSR
;    mov     IND, #'4'  ; 4
;    inc     FSR
;    mov     IND, #'1' ; 1
;    inc     FSR
;    mov     IND, #'.'
;    inc     FSR
;    mov     IND, #'3'  ; 3
;    inc     FSR
;    mov     IND, #'5'  ; 5
;    inc     FSR
;    mov     IND, #'5'  ; 5
;    inc     FSR
;    mov     IND, #'7'  ; 7

;    mov     FSR, #lonStart          ; load 10 longitude chars
;    mov     IND, #'1'
;    inc     FSR
;    mov     IND, #'2'
;    inc     FSR
;    mov     IND, #'1'
;    inc     FSR
;    mov     IND, #'5'
;    inc     FSR
;    mov     IND, #'3'
;    inc     FSR
;    mov     IND, #'.' 
;    inc     FSR
;    mov     IND, #'3'
;    inc     FSR
;    mov     IND, #'3'
;    inc     FSR
;    mov     IND, #'6'
;    inc     FSR
;    mov     IND, #'5'
    retp

; Checkpoint: this place must be in a below the RAM limit 7FF
;             (not a problem - compiler will flag errors here)

;-------------------------------------------------------
;  test 4x4 subtracter by poking in values, timesing, displaying.
;subtest;

;:loop
;        call    @delay100ms
;        call    @delay100ms
;        call    @delay100ms
;        call    @delay100ms;

;        bank    bank_math   

;        mov     mult0, #4
;        mov     mult1, #0
;        mov     mult2, #0
;        mov     mult3, #0

;        mov     accum0, #2
;        mov     accum1, #6
;        mov     accum2, #0
;        mov     accum3, #0
 
;        call    @clearScreen
;        call    @lcdLine1 

;        bank    bank_gps
;        mov     pointer1, #multstart
;        call    @showhex32

;        call    @sendspace

;        bank    bank_gps 
;        mov     pointer1, #accumstart
;        call    @showhex32

;        call    @subtract32    ; mult - accum -> mult

;        call    @lcdLine2 
;        bank    bank_gps 
;        mov     pointer1, #multstart
;        call    @showhex32

;        call    @sendspace
;        mov     W, negFlag
;        call    @showHexByte

;        jmp     :loop

;        retp
 
;-------------------------------------------------------
;  test 1x4 multiplier by poking in values, timesing, displaying.

;_multtest
;        bank    bank_math     
;        mov     math0, #$01 
;:loope
;        call    @delay100ms
;        call    @delay100ms
;        call    @delay100ms
;        bank    bank_math   
;        mov     mult0, #2
;        mov     mult1, #0
;        mov     mult2, #1
;        mov     mult3, #0
;        mov     accum0, #0
;        mov     accum1, #0
;        mov     accum2, #0
;        mov     accum3, #0
;        inc     math0  
;        mov     eight, math0
;        call    @clearScreen
;        call    @lcdLine1 
;        mov     pointer1, #multstart
;        call    @showhex32
;        call    @sendspace
;        bank    bank_math 
;        mov     gps_databyte, math0
;        call    @showHexByte
;        call    @multiply32
;        call    @lcdLine2 
;        mov     pointer1, #accumstart
;        call    @showhex32
 ;       jmp     :loope
        
;        retp

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

; test latitude subtraction

;_latsubtest
;        call    @loadLatLon              ; write test lat + lon into "current"
;        call    @clearScreen
;        call    @lcdLine1
;        call    @showLatLon              ; outputs exactly 20 characters
;        mov     pointer1, #latStart
;        call    @DDMMtobin               ; convert to binary in accumulator
;        mov     pointer1, #accumstart
;        mov     pointer2, #multstart
;        call    @copy32
;        bank    bank_math 
;        clc
;        add     mult0, #1               ; test: tweak one of the numbers: 1 00
;        stc
;        sub     mult0, #1                ; test other dir: 1 01
;        call    @subtract32
;        call    @lcdLine2   
;        mov     pointer1, #multstart    ; show what we've got:
 ;       call    @showhex32
;        call    @sendspace
;        bank    bank_math 
;        mov     gps_databyte, negFlag
;        call    @showHexByte
;        retp
;-----------------------------------------------------------------------------------
;
; testing latlon to bin
;
;                                   ; clear memory
;    mov     FSR, #8                 ; zero small global RAM area
;:clearMem
;    CLR     IND
;    INC     FSR
;    JNB     FSR.5, @:clearMem
;
;:zeroRam                           ; zero ALL banked memory
;    SETB    FSR.4
;    CLR     IND 
;    IJNZ    FSR, @:zeroRam
;    nop

;    call    @loadLatLon      ; lon 121 53.3365 bin 04 5B ED 35 lat 37 41.3557 bin 01 59 0E 35
;    nop

;    mov     pointer1, #lonStart
;    call    @DDMMtobin               ; Lon 121 53.3365 bin 04 5B ED 35
;    nop
;    mov     pointer1, #accumStart    ; 
;    mov     pointer2, #lonBinstart;
;    call    @Copy32                  ; 
;    nop
;    mov     pointer1, #latStart      ; 
;    call    @DDMMtobin               ; Lat 37 41.3557 bin 01 59 0E 35
;    nop
;    mov     pointer1, #accumStart    ; Copy from accumulator 
;    mov     pointer2, #latBinstart   ; to Latitude binary store
;    call    @Copy32                  ; 
;    nop

; end of test section
;-------------------------------------------------------
; Notes
;
; Scenix SX28 processor
; Seetron 2 row by 20 character LCD 
;-------------------------------------------------------
;
; Port usage
;
; Port A   bit 0  pin  6  out  RS232 serial (TTL level) to LCD
;          bit 1  pin  7  out  Red LED
;          bit 2  pin  8  in   GPS data
;          bit 3  pin  9  in   GPS 1 PPS
;        
; Port B   bit 0  pin 10    
;          bit 1  pin 11    
;          bit 2  pin 12  
;          bit 3  pin 13    
;          bit 4  pin 14  
;          bit 5  pin 15  
;          bit 6  pin 16  
;          bit 7  pin 17  
;
; Port C   bit 0  pin 18 
;          bit 1  pin 19 
;          bit 2  pin 20
;          bit 3  pin 21 
;          bit 4  pin 22 
;          bit 5  pin 23 
;          bit 6  pin 24  
;          bit 7  pin 25
;          
;-------------------------------------------------------
;
; data RAM
;
;   00-07                        8 CPU registers
;   08-0F                        8 bytes of global RAM
;   10-1F, 30-3F, 50-5F...F0-FF  8 banks of 16 bytes user RAM.
;
; code ROM
;
;   4 banks of 512 ($200) bytes.
;   org $000 = bank 0 top half ( subroutine calleable)
;   org $100 = bank 0 lower half (NOT sub calleable)
;   org $200 = bank 1 top half ( subroutine calleable)
;   org $300 = bank 1 lower half (NOT sub calleable)
;   org $400 = bank 2 top half ( subroutine calleable)
;   org $500 = bank 2 lower half (NOT sub calleable)
;   org $600 = bank 3 top half ( subroutine calleable)
;   org $700 = bank 3 lower half (NOT sub calleable)
;
;
;---the end----------------------------------------------------
;
