; sx52logger.src
;
; Scenix SX52 
; one dynamic RAM SIMM attached.
;
; Receive characters from PC via 9600 baud serial port.
;
;
;-------------------------------------------------------

device SX52,OSCHS2
IRC_CAL IRC_FAST
freq 20_000_000
reset start_point

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

; Sx IO pin usage

PC_OUT          equ     ra.0              ; these 2 used for RS232.
PC_IN           equ     ra.1
LCD_OUT         equ     ra.2
WHEELSPIN       equ     ra.3
HEARTBEAT       equ     ra.4

; RC: dynamic RAM data bus
; RD: dynamic RAM address low byte
; RE: lo nybble: RAM address hi nybble
; RE: hi nybble: DRAM control

;-------------------------------------- Scenix version macros

SX48_52                              ; name target processor

_bank	macro	1
    noexpand
    bank  \1
    IF \1 & %10000000
    expand
      setb  fsr.7
    noexpand
    ELSE
    expand
      clrb  fsr.7
    noexpand
    ENDIF
  endm

_mode	macro	1          
      mov  w,#\1           
      mov  m,w          
    endm

;----------------------------------------------------------------
; Dynamic RAM macros

MEMORY_DELAY macro         ; 20 mHz clock -> 50 ns per command.
    nop                    ; 100 ns delays OK
    nop
endm

DROP_CAS_1 macro
    MEMORY_DELAY
    clrb    re.4
    MEMORY_DELAY
endm

DROP_RAS macro
    MEMORY_DELAY
    clrb    re.6
    MEMORY_DELAY
endm

RAISE_RAS macro
    setb    re.6
endm

RAISE_CAS_1 macro
    setb    re.4
endm

RAISE_CAS_AND_RAS macro
    setb    re.4
    setb    re.6
endm

DROP_WE macro
    MEMORY_DELAY
    clrb    re.7
    MEMORY_DELAY
endm

RAISE_WE macro
    setb    re.7
endm

REFRESH macro
    clrb    re.4           ; drop CAS
    MEMORY_DELAY
    clrb    re.6           ; drop RAS
    MEMORY_DELAY
    setb    re.6           ; raise RAS    
    MEMORY_DELAY
    setb    re.4           ; raise CAS
endm

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

FIFO macro
    dw    $047
endm

;-------------------------------------------------
; BANK 0: global variables  ;       
                            ;   
org                     $A  
flags           ds      1   ; A
state           ds      1   ; B
inbuffer        ds      1   ; C


FROM_PC_FLAG   = 0           ;  bits in the "flags" byte
MEMORY_IN_USE  = 1
FIFO_IN_USE    = 2
WHEELSPIN_FLAG = 3
NEWWHEELSPIN   = 4


org                     $10
bank_rs232       =      $
txCount         ds      1   ; 0
txDivide        ds      1   ; 1
txLow           ds      1   ; 2
txHigh          ds      1   ; 3
rxCount         ds      1   ; 4
rxDivide        ds      1   ; 5
rxbyte          ds      1   ; 6
rxHigh          ds      1   ; 7
command         ds      1   ; 8
rxStop          ds      1   ; 9


org                     $20
bank_LCD        =       $
lcdtxCount      ds      1   ; 0
lcdtxDivide     ds      1   ; 1
lcdtxLow        ds      1   ; 2
lcdtxHigh       ds      1   ; 3
string          ds      1   ; 4
fifoCount       ds      1   ; 5


org                     $30
bank_ram        =       $
ramDatabyte     ds      1   ; 0
popAddr1        ds      1   ; 1
popAddr2        ds      1   ; 2
popAddr3        ds      1   ; 3
pushAddr1       ds      1   ; 4 
pushAddr2       ds      1   ; 5
pushAddr3       ds      1   ; 6
addr1           ds      1   ; 7
addr2           ds      1   ; 8
addr3           ds      1   ; 9
data            ds      1   ; A

org                     $40
bank_assorted   =       $
t1              ds      1
t2              ds      1
t3              ds      1
t4              ds      1
t5              ds      1
hiNybble        ds      1
loNybble        ds      1

org                     $50
bank_math       =       $
Divisor         ds      3 ; 0-2
Temp            ds      4 ; 3-6
BitCount        ds      1 ; 7
Dividend        ds      6 ; 8-D


org                     $60
bank_counter    =       $
wh_curbin       ds      3
wh_snapbin      ds      3
wh_total        ds      5



;-------------------------------------------------------
; interrupt service routine. Called on RTCC rollover.
;-------------------------------------------------------
;
; RTCC timing calculation/adjustments
;
; Four things combine together to determine rep rate of each ISR task:
; 1. crystal speed
; 2. ISR-exit adjustment to RTCC
; 3. RTCC divisor, if used
; 4. individual task skipping within the ISR subsection

; 9600 baud needs 1/9600 * 10^6 -> 104.167 usec between bits.
;   20 mHz crystal -> 20 cycles per usec 
;   20 * 104.167 -> 2083 Scenix clock cycles between bits.
;   Power of 2 that divides 2083 to a number between 0-255 -> 2083/16 = 130 
;   RTCC must count 130 -> 126 to 256 so reset RTCC upon exit to 126 (0x7E)
;   So we need divide-bys totalling 16.

int_period equ $7E

; experiment with several hardware RTCC divisors:

ENABLE_INTERRUPTS macro            ; 1001xxxx 1=RTCC->addr1 0=enabl int 0=cpu->rtcc 1=clk fall edge
    mov     !option, #%10010000    ; xxxx0000 = prescale -> RTCC; = divide clock by 2
endm

; hardware divide-by 2 means we need 16/2 = divide-by 8 in the task-skipping:

N_9600_FULL = 8
N_9600_1_5  = 12
N_STOPS     = 8

;----------------------------------
org         $0                        
ISR

; Transmit RS232.
; Two ways to send: 
; 1. User prepares txHigh, txLow, txCount.
; 2. User fills 8-character FIFO, ending with "10", then set FIFO_IN_USE flag.
;
; Nothing to transmit if txCount is zero and the FIFO is empty.


    _bank   bank_LCD
    test    lcdtxCount                ; If bits to send, continue in send routine.
    jnz     :lcdxmit                  ; Otherwise check if the FIFO contains stuff.
    jnb     flags.FIFO_IN_USE, :lcd_end ; if FIFO empty then we're done.
    FIFO                              ; pop next byte outa FIFO
    mov     lcdtxHigh,  W             ; and prepare it for sending.
    mov     lcdtxLow,   #0            ; preset the start bit.
    mov     lcdtxCount, #11           ; 10 = 1 stop bit, 11 = 2, etc.
    djnz    fifoCount, :lcdxmit       ; After "fifoCount" bytes, the FIFO is empty.
    clrb    flags.FIFO_IN_USE         ; so clear the flag.
:lcdxmit
    djnz    lcdtxDivide, :lcd_end     ; skip "n" ISR occurrances. 
    mov     lcdtxDivide, #N_9600_FULL ; Only xmit every nth pass.
    dec     lcdtxCount                ; decrement bitcount
    stc                               ; Create stop bit(s) to rotate onto tail of byte
    rr      lcdtxHigh                 ; LSB of data -> carry
    rr      lcdtxLow                  ; carry -> bit 7 of lo; start -> bit 6.
    movb    LCD_OUT, /txLow.6         ; apply bit to output port. (Invert cause no MAX232)   
:lcd_end

; send next bit to PC

    _bank   bank_rs232
    test    txCount                   ; If bits to send, continue in send routine.
    jz      :t_end                    ; Otherwise check if the FIFO contains stuff.
:xmit
    djnz    txDivide, :t_end          ; skip "n" ISR occurrances. 
    mov     txDivide, #N_9600_FULL    ; Only xmit every nth pass.
    dec     txCount                   ; decrement bitcount
    stc                               ; Create stop bit(s) to rotate onto tail of byte
    rr      txHigh                    ; LSB of data -> carry
    rr      txLow                     ; carry -> bit 7 of lo; start -> bit 6.
    movb    PC_OUT, txLow.6           ; apply bit to output port. 
:t_end

; Receive a byte. 
; The received byte is available to the user when FROM_PC_FLAG is set.

    test    rxStop                    ; First check if waiting for stop bits to pass
    jz      :receive                  ; go on to normal processing when rxStop is zero
    djnz    rxStop, :r_end            ; skip receive if we're still receiving stop bits
:receive
    test    rxCount                   ; If we're receiving byte 
    jnz     :rxbit                    ; then go to bit-receive part;
    movb    C, PC_IN                  ; otherwise see if port holds startbit.
    jc      :r_end                    ; No startbit? done!
    mov     rxDivide, #N_9600_1_5     ; We see startbit. Set 1.5 delays,
    mov     rxCount, #8               ; and prepare to get 8 bits,
    jmp     :r_end                    ; and then wait for first data bit.
:rxbit
    djnz    rxDivide, :r_end          ; Skip "n" ISR occurrances. 
    mov     rxDivide, #N_9600_FULL    ; Preset 1.0 delays next call.
    movb    C, PC_IN                  ; Read input pin into carry.
    rr      rxByte                    ; Roll carry into received byte.
    djnz    rxCount, :r_end           ; If more bits to get, then skip stop bit part below.
    setb    flags.FROM_PC_FLAG        ; All data in. Tell user that a byte is ready,
    mov     rxStop, #N_STOPS          ; then prepare to skip n bits worth of passes
    mov     inbuffer, rxByte          ; 
:r_end

; refresh dynamic RAM every ISR call (13 microseconds) 
; 256K (2^18) has 512 (2^9) rows; 512 * 13 -> 6.6 milliseconds. should be ok.

    jb      flags.MEMORY_IN_USE,:m_end ; don't refresh if memory is in use
    REFRESH
:m_end

;--------------------------------------------------------------
; Increment the wheel sensor counters

    _bank    bank_counter

    inc     wh_curbin+0
    jnc     :next_1
    inc     wh_curbin+1
    jnc     :next_1
    inc     wh_curbin+2
:next_1

; Poll for sensor changes. 

    jb    WHEELSPIN, :ws_bit_hi           ; Poll for sensor state. if high go below.
    clrb  flags.WHEELSPIN_FLAG            ; If low then clear flag and vamoose.
    jmp   :ws_end                        

:ws_bit_hi                                ; If sensor flag bit already high then vamoose.
    jb    flags.WHEELSPIN_FLAG, :ws_end   ; 
    setb  flags.WHEELSPIN_FLAG            ; Otherwise: Take snapshot of current count, then...

    _bank    bank_counter
    mov     wh_snapbin+0, wh_curbin+0     ; Take a snapshot of the current count
    mov     wh_snapbin+1, wh_curbin+1
    mov     wh_snapbin+2, wh_curbin+2
    clr     wh_curbin+0                   ; then clear all current value counters. 
    clr     wh_curbin+1                   ; binary clear to 0
    clr     wh_curbin+2
    setb  flags.NEWWHEELSPIN              ; Notify user that new snapshot is ready

    inc     wh_total+0                    ; Increment overall running total wheel spins
    jnc     :next_2
    inc     wh_total+1
    jnc     :next_2
    inc     wh_total+2
    jnc     :next_2
    inc     wh_total+3
    jnc     :next_2
    inc     wh_total+4
:next_2

:ws_end


; Here we plug in heartbeat just like the wheel sensor...


;--------------------------------------------------------------
; and finally, exit the ISR.

    mov     W,  #int_period           ; preset RTCC for correct timing of
    retiw                             ; next interrupt for 9600 baud

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

hello       dw      'Hello World! HaWaYa?',13,10,0
;CR          dw      13,10,0
zeroed      dw      'RAM zeroed',13,10,0

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

start_point

    call    @init

    call    @lcdLine1
    mov     W, #hello
    call    @sendString

    call    @delay1s
    call    @writeBEAT
    call    @demoFIFO
    call    @delay1s

; Spin until a single input character is received. Then jump to a location determined by the state. 

:loop
    clrb    flags.FROM_PC_FLAG
:spin

    jb      flags.NEWWHEELSPIN, @:processWheel
    jb      flags.FROM_PC_FLAG, @:processPC
    jmp     @:spin


:processPC                              ; A character has arrived. Jump depending on the current state.
    cje     state, #$0, @:state0
    cje     state, #$1, @:state1
    cje     state, #$2, @:state2
:reset                                  ; May reserve reset for error conditions?
    mov     state, #0
    jmp     @:loop


:processWheel                           ; display ASCII wheel rotation counts
    call    @_FIFO_wheelcount           ; (will block until done)
    clrb    flags.NEWWHEELSPIN          ; Clear the "used" flag

; Here is where we'll calculate speed if we want:
; 38,461,000/counts = speed, hundredths of mph.

; Also, lets push raw counts to RAM here.

    mov     W, #'W'
    call    @pushRAM

    _bank    bank_counter
    mov     W, wh_snapbin+2
    call    @pushRAM
    _bank    bank_counter
    mov     W, wh_snapbin+1
    call    @pushRAM
    _bank    bank_counter
    mov     W, wh_snapbin+0
    call    @pushRAM

    jmp     @:loop

;--------------------------------------------------------------------------
; state 0 is the main command state. Commands begin with a letter A-Za-z.

:state0

    cje     inbuffer, #'A', @:commandA   ; A: return "Hello..." to PC
    cje     inbuffer, #'B', @:commandB   ; B: write zeroes to RAM. return "Zeroed" msg after few seconds.
    cje     inbuffer, #'C', @:commandC   ; C: RAM test: poke 3 bytes.

;   add more cje command letters here; or else fall into reset.

    jmp     @:reset

; Some commands execute immediately. These return to "reset".
; some set state to receive additional bytes from the PC. These return to "loop".

:commandA                 
    mov     W, #hello
    call    @sendString
    jmp     @:reset
:commandB                 
    call    @clearRAM
    mov     W, #zeroed
    call    @sendString
    jmp     @:reset
:commandC                 
    mov     W, #$22
    call    @pushRAM
    mov     W, #$33
    call    @pushRAM
    mov     W, #$44
    call    @pushRAM
    jmp     @:reset
  
; read in sensor byte and return to PC
    jmp     @:reset


; add more command letter codes here

;------------------------------------------------------------
; states 1-3 belong to F: "Push a step command". 
; "F" is followed by any number of groups of 3 numbers. 
; Send <CR> to exit Command F loading.
; While most commands are 100% printeable ASCII, CommandF may take any values
; for its three arguments for load speed. For testing, printeable ASCII arguments
; will operate CommandF but CommandF is not restricted to those.
; The user may want to check available RAM space before pushing too many commands...
                     
; In test: just push 3 bytes then end.
;              
:state1                              ; The first byte describes step and dir for 3 motors by bits.
    mov     W, inbuffer              ; the step command character.
    call    @pushRAM                 ; Otherwise push to RAM and
    mov     state, #2                ; expect the delay character to follow.
    jmp     @:loop
:state2                             ; The second is the hi delay byte
    mov     W, inbuffer             
    call    @pushRAM
    mov     state, #3  
    jmp     @:loop
:state3                             ; The third is the lo delay byte.
    mov     W, inbuffer
    call    @pushRAM
    jmp     @:reset        
;
; add more state switching codes here


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

;---------------------------------------------------------------
; Jump table
; Is org $600 appropriate for the SX52?
; If too many switch states above, may move down.
; But if too many functions below, may move up.
 
org $600

init            jmp @_init                  
delay1ms        jmp @_delay1ms          
delay10ms       jmp @_delay10ms
delay100ms      jmp @_delay100ms
delay1s         jmp @_delay1s
delay10s        jmp @_delay10s
sendString      jmp @_sendString
sendW           jmp @_sendW
byteToHex       jmp @_byteToHex
showHexByte     jmp @_showHexByte
pushRAM         jmp @_pushRAM
popRAM          jmp @_popRAM
clearRAM        jmp @_clearRAM
showPushAddr    jmp @_showPushAddr
showPopAddr     jmp @_showPopAddr
readRAM         jmp @_readRAM
writeRAM        jmp @_writeRAM
demoFIFO        jmp @_demoFIFO
writeBEAT       jmp @_writeBEAT
FIFO_wheelcount jmp @_FIFO_wheelcount
lcdline1        jmp @_lcdline1  
lcdline2        jmp @_lcdline2

;hexToBin        jmp @_hexToBin

;----------------------------------------------------------
; Scenix initialization
;
; Rather than blindly write zeroes to all RAM, lets explicitely clear those
; bytes where program logic expects them to be clear.
 
_init
                                      ; configure ports (1 = in, 0 = out)
    _mode   $1F                       ; in/out direction mode (0 out 1 in)
    mov     !ra, #%11111010           ; a: 
    mov     !rb, #%11111111           ; b: 
    mov     !rc, #$FF                 ; c: in for now. Program will modify this.
    mov     !rd, #0                   ; d out
    mov     !re, #0                   ; e out

    _mode   $1E                       ; pull-up resistor mode (0 enable 1 disable)
    mov     !ra, #%00000101           ; ins pulled-up
    mov     !rb, #0                   ; ins pulled-up
    mov     !rc, #0                   ; pull up all Cs
    mov     !rd, #$FF                 ; D out: DRAM address 0-7
    mov     !re, #$FF                 ; E out: DRAM address 8-11; controls
          
    _bank   bank_rs232                ; RS232 init
    clrb    flags.FROM_PC_FLAG        ; clear RS232 receive flag
    mov     txCount, #0               ; clear RS232 transmit flag
    mov     rxStop, #0

    _bank   bank_ram
    mov     W, #0
    mov     pushAddr1, W
    mov     pushAddr2, W    
    mov     pushAddr3, W
    mov     popAddr1, W
    mov     popAddr2, W    
    mov     popAddr3, W

; global flags. All start low, but explicitly clearing each flag makes this clear.

    clrb    flags.MEMORY_IN_USE       ; 
    clrb    flags.FIFO_IN_USE         ;
    clr     state

; This is for initializing the SEEtron LCD

    setb    rb.2
    call    @delay1s
   
    ENABLE_INTERRUPTS

    call    @lcdLine1

    retp

;-----------------------------------------------------------------
; Untested.

;_hexToBin
;    _bank    bank_command
;    cja     hexhi, #'@', :sub1
;    sub     hexhi, #'0'
;    jmp     :next1
;:sub1
;    sub     hexhi, #'A'
;:next1
;    cja     hexlo, #'@', :sub2
;    sub     hexlo, #'0'
;    jmp     :next2
;:sub2
;    sub     hexlo, #'A'
;:next2
;    mov     hexbin, hexlo
;    and     hexbin, #$F
;    swap    hexbin
;    add     hexbin, hexlo
;    mov     W, hexbin
;    retp


;------------------------------------------------------------
; Position LCD cursor to where wheel rotation counts are to be shown,
; then push the numbers into the FIFO for sending to LCD display.

_FIFO_wheelcount

:wait
    jb     flags.FIFO_IN_USE, @:wait

    _bank    bank_counter

    mov     W, #$FE             ; next byte is LCD command
    FIFO
    mov     W, #$C5             ; position cursor at start of second line
    FIFO
;    mov     W, wh_snapdec+4     ; MSB
    FIFO
;    mov     W, wh_snapdec+3
    FIFO
;    mov     W, wh_snapdec+2
    FIFO
;    mov     W, wh_snapdec+1
    FIFO
;    mov     W, wh_snapdec+0     ; LSB
    FIFO
    FIFO
    _bank   bank_LCD
    mov     fifoCount, #7
    setb    flags.FIFO_IN_USE   
    retp

;------------------------------------------------------------
; demo using FIFO to stage up to 8 bytes for RS232 transmission.

_demoFIFO

:wait
    jb     flags.FIFO_IN_USE, @:wait

    mov     W, #$FE         ; next byte is LCD command
    FIFO
    mov     W, #$C5         ; position cursor at start of second line
    FIFO
    mov     W, #'4'
    FIFO
    mov     W, #'5'
    FIFO
    mov     W, #'2'
    FIFO
    mov     W, #'7'
    FIFO
    mov     W, #'9'
    FIFO
    _bank   bank_LCD
    mov     fifoCount, #7
    setb    flags.FIFO_IN_USE   
    retp
;------------------------------------------------------------
; demo using FIFO to stage up to 8 bytes for RS232 transmission.

_writeBEAT

:wait
    jb     flags.FIFO_IN_USE, @:wait

    mov     W, #$FE         ; next byte is LCD command
    FIFO
    mov     W, #$C0         ; position cursor at start of second line
    FIFO
    mov     W, #'B'
    FIFO
    mov     W, #'E'
    FIFO
    mov     W, #'A'
    FIFO
    mov     W, #'T'
    FIFO
    mov     W, #' '
    FIFO
    _bank   bank_LCD
    mov     fifoCount, #7
    setb    flags.FIFO_IN_USE   
    retp

;----------------------------------------------------------
; Wanna convert this to use the FIFO?
; Not for hex: need 8 for 3 addr and 1 data byte, expressed in HEX, but could
; work if binary is returned.

_showPushAddr

    _bank    bank_ram
    mov    W, pushAddr3
    call    @showHexByte
    _bank    bank_ram
    mov    W, pushAddr2
    call    @showHexByte
    _bank    bank_ram
    mov    W, pushAddr1
    call    @showHexByte
    mov     W, #' '
    call    @sendW

    retp

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

_showPopAddr

    _bank   bank_ram
    mov     W, popAddr3
    call    @showHexByte
    _bank   bank_ram
    mov     W, popAddr2
    call    @showHexByte
    _bank   bank_ram
    mov     W, popAddr1
    call    @showHexByte
    mov     W, #' '
    call    @sendW

    retp

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

_sendString

    _bank   bank_LCD
    mov     string, W
:loop                
    mov     W,string
    mov     M, #0
    iread                           ; reads value from 11-bit code address in M, W
    test    W
    jz      @:end
    call    @sendW
    inc     string
    jmp     @:loop   
:end
     retp

;------------------------------------------------------
; send character in W via RS232.
; ALL user-level send routines go through this function.
; First wait for transmit buffer to be empty, then
; prepare transmit parameters, including the character
; and the number of stop bits.
;
; Also need to wait for hardware FIFO to become empty.

_sendW
    _bank   bank_LCD

:wait1
    test    lcdtxCount               ; If bits being sent, 
    jnz     @:wait1                ; wait

:wait2
    jb     flags.FIFO_IN_USE, @:wait2

    mov     lcdtxHigh,  W 
    mov     lcdtxLow,   #0
    mov     lcdtxCount, #10          ; 10 = 1 stop bit, 11 = 2, etc.
    retp

;----------------------------------------------------------
; Dynamic RAM configuration
;
; pushAddr1 is used for the low row addresses bit 0-7
; pushAddr2 is used for the low column addresses bit 0-7
; pushAddr3 bits are alternated between rows and columns for the high bits.
; 256K DRAM: bit 0 -> row bit 8; bit 1 -> column bit 8
; 1M   DRAM: bit 0-> row 8, 1-> col 8, 2->row 9, 3->col 9
; 4M   DRAM: same thing. Bits 0-5 used.
; 16M  DRAM: same thing. All bits used.
; 
; For 256K DRAMs, when push or pop Addr3 bit 2 becomes set then overflow has occurred.
; 1M: bit 4 is set on overflow
; 4M bit 6 set on overflow
; 16M: increment of Addr3 results in zero.
;
; Sure would be nice to automatically set this...
; Like, write number to addr 0.
; If addressing 256K+1 gets the number in 0 then this is a 256K device!
; If addressing 1M+1 gets the number in 0 then this is a 1M device!
; etc.

DRAMSIZE = 2

;------------------------------------------------------------
; Not guaranteed to work well with any size DRAM.
; 

_clearRAM

    _bank   bank_ram
    clr     pushAddr1
    clr     pushAddr2
    clr     pushAddr3

:loop 
    mov     W, #0
    call    @pushRAM
    jnb     pushAddr3.DRAMSIZE, :loop

    clr     pushAddr1
    clr     pushAddr2
    clr     pushAddr3
    clr     popAddr1
    clr     popAddr2
    clr     popAddr3

    retp

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

_pushRAM

    _bank   bank_ram

    mov     ramDatabyte, W           ; Save byte we want to write into RAM
    setb    flags.MEMORY_IN_USE      ; Disable refresh till we're done
    mov     rd,   pushAddr1
    movb    re.0, pushAddr3.0
    movb    re.1, pushAddr3.2        ; Set the 9-13 row bits
    movb    re.2, pushAddr3.4
    movb    re.3, pushAddr3.6
    DROP_RAS                         ; latch the row
    DROP_WE                          ; declare early write in progress
    _mode   $1F                      ; in/out direction mode (0 out 1 in)
    mov     !rc,  #0                 ; Set direction of port C out
    mov     rc,   ramDatabyte
    mov     rd,   pushAddr2
    movb    re.0, pushAddr3.1
    movb    re.1, pushAddr3.3        ; Set the 9-13 column bits
    movb    re.2, pushAddr3.5
    movb    re.3, pushAddr3.7
    DROP_CAS_1                       ; Latch the column
    RAISE_WE                         ; This latches the data into RAM
    _mode   $1F                      ; in/out direction mode (0 out 1 in)
    mov     !rc,  #$FF               ; Set direction of post C in
    RAISE_CAS_AND_RAS                ; Done.

    inc     pushAddr1
    jnz     @:end
    inc     pushAddr2
    jnz     @:end
    inc     pushAddr3
;    jnb     pushAddr3.DRAMSIZE, @:end
:end
    clrb    flags.MEMORY_IN_USE
    retp

;---------------------------------------------------------
; read byte into W from RAM 

_popRAM

    _bank   bank_ram
    setb    flags.MEMORY_IN_USE
    mov     rd,   popAddr1
    movb    re.0, popAddr3.0
    movb    re.1, popAddr3.2
    movb    re.2, popAddr3.4
    movb    re.3, popAddr3.6
    DROP_RAS
    mov     rd,   popAddr2
    movb    re.0, popAddr3.1
    movb    re.1, popAddr3.3
    movb    re.2, popAddr3.5
    movb    re.3, popAddr3.7
    DROP_CAS_1
    mov     ramDataByte, rc
    RAISE_CAS_AND_RAS
    inc     popAddr1
    jnz     @:end
    inc     popAddr2
    jnz     @:end
    inc     popAddr3
;    jnb     popAddr3.DRAMSIZE, @:end    ; do any special top-of-memory tests here
:end
    clrb    flags.MEMORY_IN_USE
    mov     W, ramDataByte 
    retp

;----------------------------------------------------------
; Write byte in W to address in addr3:addr2:addr1

_writeRAM

    _bank   bank_ram

    mov     ramDatabyte, W           ; Save byte we want to write into RAM
    setb    flags.MEMORY_IN_USE      ; Disable refresh till we're done
    mov     rd,   addr1
    movb    re.0, addr3.0
    movb    re.1, addr3.2            ; Set the 9-13 row bits
    movb    re.2, addr3.4
    movb    re.3, addr3.6
    DROP_RAS                         ; latch the row
    DROP_WE                          ; declare early write in progress
    _mode   $1F                      ; in/out direction mode (0 out 1 in)
    mov     !rc,  #0                 ; Set direction of port C out
    mov     rc,   ramDatabyte
    mov     rd,   addr2
    movb    re.0, addr3.1
    movb    re.1, addr3.3            ; Set the 9-13 column bits
    movb    re.2, addr3.5
    movb    re.3, addr3.7
    DROP_CAS_1                       ; Latch the column
    RAISE_WE                         ; This latches the data into RAM
    _mode   $1F                      ; in/out direction mode (0 out 1 in)
    mov     !rc,  #$FF               ; Set direction of post C in
    RAISE_CAS_AND_RAS                ; Done.
    clrb    flags.MEMORY_IN_USE
    mov     W, #1                    ; return 1 in case we ever care...
    retp


;---------------------------------------------------------
; Read byte to W from address in addr3:addr2:addr1

_readRAM

    _bank   bank_ram
    setb    flags.MEMORY_IN_USE
    mov     rd,   addr1
    movb    re.0, addr3.0
    movb    re.1, addr3.2
    movb    re.2, addr3.4
    movb    re.3, addr3.6
    DROP_RAS
    mov     rd,   addr2
    movb    re.0, addr3.1
    movb    re.1, addr3.3
    movb    re.2, addr3.5
    movb    re.3, addr3.7
    DROP_CAS_1
    mov     ramDataByte, rc
    RAISE_CAS_AND_RAS
    clrb    flags.MEMORY_IN_USE
    mov     W, ramDataByte 
    retp

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

_showHexByte

    call    @byteToHex
    _bank   bank_assorted
    mov     W, hiNybble
    call    @sendW
    _bank   bank_assorted
    mov     W, loNybble
    call    @sendW
    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'                   ; Adding the char having the value "0" translates
    add     loNybble, W               ; a numeric 0 to the ASCII "0".
    add     hiNybble, W
    cjb     loNybble, #':', @:s1      ; The colon ":" is one beyond "9" in ASCII,
    add     loNybble, #7              ; so if a ":" is seen then we need to skip
:s1 cjb     hiNybble, #':', @:s2      ; 7 characters to get us to ASCII "A".
    add     hiNybble, #7
:s2 retp

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

_lcdline1        
        mov     W, #$FE
        call    @sendW
        mov     W, #$80
        call    @sendW
        retp

_lcdline2
        mov     W, #$FE
        call    @sendW
        mov     W, #$C0
        call    @sendW
        retp

;--------------------------------------------------------------   
; delay functions
; pretty accurate w/o interrupts; less accurate but still useable with.

_delay10s
    _bank   bank_assorted
    mov     t5, #10
:d  call    @delay1s
    djnz    t5, @:d
    retp

_delay1s
    _bank   bank_assorted
    mov     t4, #100
:d  call    @delay10ms
    djnz    t4, @:d
    retp

_delay100ms
    _bank   bank_assorted
    mov     t4, #100
:d  call    @delay1ms
    djnz    t4, @:d
    retp

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

_delay1ms                  ; delay 19302 cycles with "15" loop =~ 1 ms @ 20 mHz
    _bank   bank_assorted
    mov     t2, #4         ; 3 -> 0.74 seconds
:s  mov     t1, #0
:d  djnz    t1, @:d
    djnz    t2, @:s
    retp

;---the end----------------------------------------------------
; Really need is divide 32 by 24...

DIVIDE_48by24

    _bank    bank_math
        ; Test for zero division
	mov	W, Divisor
	or	W, Divisor+1
	or	W, Divisor+2
	snz
	retp			; divisor = zero, not possible to calculate -
				; return with set zero flag (Z)

        ; clear remainder
	clr	Temp
	clr	Temp+1
	clr	Temp+2
	clr	Temp+3

	mov	W, #48		; initialize bit counter
	mov	BitCount, W

DIVIDE_LOOP_48by24
	; shift in next result bit and 
	; shift out next bit of dividend
	; (first time in loop carry is garbage, but
	; it's okay)
	rl	Dividend+5
	rl	Dividend+4
	rl	Dividend+3
	rl	Dividend+2
	rl	Dividend+1
	rl	Dividend
        ; shift in highest bit from dividend through carry in temp
	rl	Temp+3
	rl	Temp+2
	rl	Temp+1
	rl	Temp

	mov	W, Divisor+2	; load w with LSB of divisor
				; to use it in subtraction or
				; addition afterwards

	; If reminder is positive or zero,
	; subtract divisor from remainder,
	; else add divisor to remainder
	snb	Temp.7
	jmp	Div48by24_add

        ; subtract 24 bit divisor from 32 bit temp,
        ; divisor LSB is in w
        sub	Temp+3, W	; subtract LSB

	mov	W, Divisor+1	; get middle byte
	sc			;  if overflow ( from prev. subtraction )
	movsz	W, ++Divisor+1	; incresase source
	sub	Temp+2, W	; and subtract from dest.

	mov	W, Divisor	; get top byte
	sc			;  if overflow ( from prev. subtraction )
	movsz	W, ++Divisor	; increase source
	sub	Temp+1, W	; and subtract from dest.

	mov	w, #1		; if borrow,
	sc                      ; subtract one 
	sub	Temp, w         ; from Temp MSB

	jmp	DIVIDE_NEXT_48by24 ; jump to loop end

Div48by24_add
        ; add 24 bit divisor to 32 bit temp
        ; divisor LSB is in w

	add	Temp+3, W	; add LSB

	mov	W, Divisor+1	; middle byte
	snc 			; check carry for overflow from previous addition
	movsz	W, ++Divisor+1	; if carry set we add 1 to the source
	add	Temp+2, W	; and add it if not zero

	mov	W, Divisor	; MSB byte
	snc			; check carry for overflow from previous addition
	movsz	W, ++Divisor
	add	Temp+1, W	; add

	mov	w, #1		; if borrow,
	snc                     ; add one
	add	Temp, w         ; to Temp MSB

DIVIDE_NEXT_48by24
	; carry contains next result bit
	decsz	BitCount	; decrement loop counter (carry not influenced)
	jmp	DIVIDE_LOOP_48by24 ; another run

        ; finally shift in the last carry
	rl	Dividend+5
	rl	Dividend+4
	rl	Dividend+3
	rl	Dividend+2
	rl	Dividend+1
	rl	Dividend

	clz                     ; done, clear zero flag (NZ) and
	retp			

;**********************************************************************

; Heartbeat: 120 bpm = 2 bps. 
; So. 8 bytes per second of heartbeat data.
; 90 minutes = 5400 seconds = 10800 heartbeats per ride.
;
; Wheel rotation: 15 mph = 79200 fph = 1320 fpm = 22 fps = 264 ips
; 27 inch diam wheel -> 84.8 inches circumference. 264/85 = 3 rps.
; So. 12 bytes per second of wheel data.
; 90 minutes = 5400 seconds = 16200 wheel rotations per ride
;
; So. 20 bytes per second. 1200 bpm. 72000 bph. 108,000 bytes typical Iron Hoss ride!
;  --------------------
;  BEATS  12234  123.1
;  SPINS  34456  15.22
;  --------------------
;
; 13 microseconds -> 76923 ISRs per second 
; 25641 ISRs per wheel rev at 15 mph.
; 38461 ISRs per wheel rev at 10 mph
; 76923 ISRs per wheel rev at  5 mph
;
; Then 38,461,000/counts = speed, hundredths of mph. End of story.
; (02 4A DE 48)/counts = speed. 4 digits.
;
; So at 0.1 mph we'll get how many ISR counts? 
; well, 38,461 at 10, 384,610 at 1, 3,846,100 at 0.1 (3A AF D4)
; so 3 digits will do.
;
; Arright - 4 digits (fixed) divided by 3 digits (that vary)
;