; sx52logger.src
; David Beals 2/25/2006
;
; operate a dynamic RAM
; Send characters to LCD (9600 baud)
; Receive and send characters over RS232 to run demo routines (9600 baud)
;
;-------------------------------------------------------

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              ; serial out to MAX232 to 9-pin 'D'
PC_IN       equ     ra.1              ; serial in  to MAX232 to 9-pin 'D'
LCD_OUT     equ     ra.2              ; serial out directly to Seetron LCD

; ra.5-7 unused
; rb: unused

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

;----------------------------------------------------------------
; Scenix model-based macros

SX48_52

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

mode52	macro	1
  expand
    mov     w, #\1           
    mov     m, w          
  noexpand
endm

;----------------------------------------------------------------
; Dynamic RAM macros
;                                        
; Note: CAS_2 is connected to the DRAM2 socket.
; While untested, second SIMM socket should work, but would probably
; require code changes when transitioning from one SIMM to the other.
; Would need to add CAS_2 macros (to re.5) similar to CAS_1 below.
                                        
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

;-------------------------------------------------
; variables   

org             $A  
flags       ds  1   ; A
inbuffer    ds  1   ; B
pointer1    ds  1   ; C
gtemp       ds  1   ; D


; flag bits used by "flags" variable

FROM_PC_FLAG  = 0                       
MEMORY_IN_USE = 1
FIFO_IN_USE   = 2

org             $10
bank_rs232  =   $
txCount     ds  1   ; 0
txDivide    ds  1   ; 
txLow       ds  1   ; 
txHigh      ds  1   ; 
rxCount     ds  1   ; 
rxDivide    ds  1   ; 
rxbyte      ds  1   ; 
rxHigh      ds  1   ; 
command     ds  1   ; 
rxStop      ds  1   ; 9


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


org             $30
bank_ram    =   $
ramDatabyte ds  1   ; 0
popAddr1    ds  1   ; 
popAddr2    ds  1   ; 
popAddr3    ds  1   ; 
pushAddr1   ds  1   ;  
pushAddr2   ds  1   ; 
pushAddr3   ds  1   ; 
addr1       ds  1   ; 
addr2       ds  1   ; 
addr3       ds  1   ; 
data        ds  1   ; 
readAddr1   ds  1   ; 
readAddr2   ds  1   ; 
readAddr3   ds  1   ; D

org             $40
bank_assort =   $
t1          ds  1   ; 0
t2          ds  1
t3          ds  1
t4          ds  1
t5          ds  1
hiNybble    ds  1
loNybble    ds  1   ; 6
letter      ds  1


;-------------------------------------------------------
; 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. Counting down within each ISR task
;
; 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

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 to LCD.
;
; Two ways to send: 
; 1. User prepares txHigh, txLow, txCount. ISR sends until txCount is 0.
; 2. User fills 8-character FIFO then sets FIFO_IN_USE flag. ISR sends fifoCount bytes.
; Nothing to transmit if txCount is zero and the FIFO is empty.

    bank52  bank_LCD
    test    lcdtxCount                  ; If bits to send, continue to 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             ; prepare bitcounts: 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 if no MAX232)   
:lcd_end

; Transmit RS232 to PC. Like above but no FIFO usage.

    bank52  bank_rs232
    test    txCount                     ; If bits to send, continue in send routine.
    jz      :t_end                      ; 
: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 from PC.
; 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) (if DRAM is not in use)
; 256K (2^18) has 512 (2^9) rows; 512 * 13 -> 6.6 milliseconds. should be ok.

    jb      flags.MEMORY_IN_USE,:m_end 
    REFRESH
:m_end
   

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

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

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

hello       dw      'Hello World! HaWaYa?',0
itIsNow     dw      'Feb 25, 2006 9:27 AM',0

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

start_point

    call    @init
    call    @splashScreen

; Spin until something needs attention:

:loop

    jb      flags.FROM_PC_FLAG,  @:processPC
    jmp     @:loop

;----------------------------------------
; Jump based on incoming character.
;
; A: echo 'Y' back to PC
; B: read all dynamic RAM data and return to PC
; C: return 3 byte dynamic RAM current write address to PC
; D: push a letter to RAM
; E: pop next character from RAM and send to PC
; F: send splashScreen to LCD

:processPC
    clrb    flags.FROM_PC_FLAG          ; Clear the "used" flag
    cje     inbuffer, #'A', @:commandA  ;
    cje     inbuffer, #'B', @:commandB  ; 
    cje     inbuffer, #'C', @:commandC  ; 
    cje     inbuffer, #'D', @:commandD  ; 
    cje     inbuffer, #'E', @:commandE  ; 
    cje     inbuffer, #'F', @:commandF  ;
    jmp     @:loop

:commandA                               
    mov     W, #'Y'
    call    @sendWtoPC
    jmp     @:loop

:commandB                               
    call    @sendAllToPC
    jmp     @:loop
  
:commandC 
    call    @sendPushAddr
    jmp     @:loop

:commandD
    call    @pushLetter
    jmp     @:loop

:commandE
    call    @popnext
    jmp     @:loop

:commandF
    call    @splashScreen
    jmp     @:loop

;-----------------------------------------------------------------------------------
; end of main
;-----------------------------------------------------------------------------------

; demo: push character to RAM
_pushLetter

    bank52  bank_assort
    mov     W, letter
    call    @pushRAM
    bank52  bank_assort
    inc     letter    
    cjbe    letter, #'z', :done
    mov     letter, #'A'
:done
    retp

;-----------------------------------------------------------------------------------
; pop character from RAM and send to PC
_popNext

    call    @popRAM
    call    @sendWtoPC

    retp

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

    mode52  $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

    mode52  $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
          
    bank52  bank_rs232                  ; RS232 init
    clr     txCount                     ; clear RS232 transmit flag
    clr     rxStop

    bank52  bank_ram
    clr     pushAddr1
    clr     pushAddr2
    clr     pushAddr3
    clr     popAddr1
    clr     popAddr2
    clr     popAddr3

    clr     flags

    bank52  bank_assort
    mov     letter, #'A'

    setb    rb.2                        ; The SEETRON LCD requires about a second
    call    @delay1s                    ; to initialize. Keep its line set for that time.

    ENABLE_INTERRUPTS

    retp
 
;------------------------------------------------------------------
    
_splashScreen

    call    @lcdLine1
    mov     W, #hello
    call    @sendString
    call    @lcdLine2
    mov     W, #itIsNow
    call    @sendString
    call    @delay1s
    call    @clearScreen
    call    @delay1s

    retp

;-------------------------------------------------------------------------------
; SendAll is not tested. Like, how would this react if zero bytes in DRAM?
;
_sendAllToPC

    bank52  bank_ram
    clr     popAddr1                    ; by clearing the pop address each time,
    clr     popAddr2                    ; we may repeat the DRAM read if we want.
    clr     popAddr3
    mov     readAddr1, pushAddr1
    mov     readAddr2, pushAddr2
    mov     readAddr3, pushAddr3

:readMore
    call    @popRAM
    call    @sendWtoPC

    bank52  bank_ram
    cjne    popAddr3, readAddr3, @:readMore
    cjne    popAddr2, readAddr2, @:readMore
    cjne    popAddr1, readAddr1, @:readMore

    retp

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

_sendString

    bank52  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    @sendWtoLCD
    inc     string
    jmp     @:loop   
:end
     retp

;------------------------------------------------------
; send character in W to PC via RS232.
; First wait for transmit buffer to be empty, then
; prepare transmit parameters, including the character
; and the number of stop bits.
; Setting txCount starts transmission.

_sendWtoPC

    bank52  bank_rs232
:wait1
    test    txCount                     ; If bits being sent, 
    jnz     @:wait1                     ; wait
    mov     txHigh,  W 
    mov     txLow,   #0
    mov     txCount, #10                ; 10 = 1 stop bit, 11 = 2, etc.
    retp

;------------------------------------------------------
; Send character in W to LCD via RS232.
; Wait for hardware FIFO to become empty;
; Wait for transmit buffer to be empty.
; Then prepare transmit parameters, including the character
; and the number of stop bits. 
; Setting lcdtxCount starts transmission.

_sendWtoLCD

    bank52  bank_LCD
:wait1
    jb      flags.FIFO_IN_USE, @:wait1
:wait2
    test    lcdtxCount                  ; If bits being sent, 
    jnz     @:wait2                     ; wait
    mov     lcdtxHigh,  W 
    mov     lcdtxLow,   #0
    mov     lcdtxCount, #10             ; 10 = 1 stop bit, 11 = 2, etc.
    retp

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

_sendHexToPC

    call    @byteToHex
    bank52  bank_assort
    mov     W, hiNybble
    call    @sendWtoPC
    bank52  bank_assort
    mov     W, loNybble
    call    @sendWtoPC
    retp

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

_sendHexToLCD

    call    @byteToHex
    bank52  bank_assort
    mov     W, hiNybble
    call    @sendWtoLCD
    bank52  bank_assort
    mov     W, loNybble
    call    @sendWtoLCD
    retp

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

_byteToHex

    bank52  bank_assort
    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    @sendWtoLCD
    mov     W, #$80
    call    @sendWtoLCD
    retp

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

_clearScreen
    mov     W, #$FE
    call    @sendWtoLCD
    mov     W, #$01                     ; clear screen
    call    @sendWtoLCD
    retp

;-----------------------------------------------------------
; Jump table.
; You can JMP to any address, as long as the page is properly set.
; But can only CALL to the "lower" half of the eight 512 ($200) byte pages:
;  SI: 000-0FF, 200-2FF, 400-4FF, 600-6FF, 800-8FF, A00-AFF, C00-CFF, E00-EFF 
;  NO: 100-1FF, 300-3FF, 500-5FF, 700-7FF, 900-9FF, B00-BFF, D00-DFF, F00-FFF 
;
; This is located at 0x400, the start of an eligible code bank. 
; Above here is code that doesn't change much, which may possibly
; extend into address 0x400 if enough changes are made.
 
org $400

init            jmp @_init
delay1ms        jmp @_delay1ms          
delay10ms       jmp @_delay10ms
delay100ms      jmp @_delay100ms
delay1s         jmp @_delay1s
delay10s        jmp @_delay10s
sendString      jmp @_sendString
sendWtoLCD      jmp @_sendWtoLCD
byteToHex       jmp @_byteToHex
sendHexToLCD    jmp @_sendHexToLCD
sendHexToPC     jmp @_sendHexToPC
pushRAM         jmp @_pushRAM
popRAM          jmp @_popRAM
clearRAM        jmp @_clearRAM
sendPushAddr    jmp @_sendPushAddr
sendPopAddr     jmp @_sendPopAddr
readRAM         jmp @_readRAM
writeRAM        jmp @_writeRAM
lcdline1        jmp @_lcdline1  
lcdline2        jmp @_lcdline2
clearScreen     jmp @_clearScreen
clearSXRAM      jmp @_clearSXRAM
sendWtoPC       jmp @_sendWtoPC
sendAllToPC     jmp @_sendAllToPC
pushLetter      jmp @_pushLetter
popNext         jmp @_popNext
splashScreen    jmp @_splashScreen


;--------------------------------------------------------------------------
; clear Scenix RAM
_clearSXRAM

    mov     w,#$0a                      ;reset all ram starting at $0A
    mov     fsr,w
:zero_ram  
    clr     ind                         ;clear using indirect addressing
    incsz   fsr                         ;repeat until done
    jmp     :zero_ram

    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

;------------------------------------------------------------
; Only tested with single 1M DRAM.
; Really no need for this, as long as PUSHed and POPPed addresses are used.

_clearRAM

    bank52  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

    bank52  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
    mode52   $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
    mode52   $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  ; this is more for if we approach memory full
:end
    clrb    flags.MEMORY_IN_USE
    retp

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

_popRAM

    bank52  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

    bank52  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
    mode52   $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
    mode52   $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

    bank52  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

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

_sendPushAddr

    bank52  bank_ram
    mov     W, pushAddr3
    call    @sendHexToPC
    bank52  bank_ram
    mov     W, pushAddr2
    call    @sendHexToPC
    bank52  bank_ram
    mov     W, pushAddr1
    call    @sendHexToPC
    mov     W, #' '
    call    @sendWtoPC

    retp

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

_sendPopAddr

    bank52  bank_ram
    mov     W, popAddr3
    call    @sendHexToPC
    bank52  bank_ram
    mov     W, popAddr2
    call    @sendHexToPC
    bank52  bank_ram
    mov     W, popAddr1
    call    @sendHexToPC
    mov     W, #' '
    call    @sendWtoPC

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

_delay10s
    bank52  bank_assort
    mov     t5, #10
:d  call    @delay1s
    djnz    t5, @:d
    retp

_delay1s
    bank52  bank_assort
    mov     t4, #100
:d  call    @delay10ms
    djnz    t4, @:d
    retp

_delay100ms
    bank52  bank_assort
    mov     t4, #10
:d  call    @delay10ms
    djnz    t4, @:d
    retp

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

_delay1ms                               ; 16, F9: 20014 cycles =~ 1 ms @ 20 mHz
    bank52  bank_assort
    mov     t2, #16                      
:s  mov     t1, #$F9
:d  djnz    t1, @:d
    djnz    t2, @:s
    retp

    end

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

