SX 52 based bike logger with PC RAM SIMM

From: http://forums.parallax.com/forums/default.aspx?f=7&p=1&m=111286#m111354

David B says:

I used a SIMM and socket from an old motherboard I had in the garage, fastening the socket to an extension I attached to the side of the SX52 experiment board.

This was mostly an educational experience, to learn about using dynamic RAMs, and to test whether DRAMs might be a worthwhile way to go in a logger, since I already have a few dozen SIMMs sitting around the garage.

It seemed to work just fine. The DRAM performed exactly as some document I'd found said they would, as soon as I found the magic number of delays to stick into various places. I haven't used it in any serious logger, but don't see any reason it wouldn't work just fine.

I'd been worried about satisfying the refresh requirements, but as I was using RTCC-controlled interrupts anyway for RS232 communication, I was able to just stick a refresh command into the ISR and it worked like a champ.

Here's some code. I was developing a bike speedometer/heartbeat sensor at the time, so you'll see lots of semi-developed code for those things. But the dynamic RAM and the RS232 ISR parts were working perfectly.

This demo uses a single 1 meg SIMM. It has routines to write to any individual address, read from any address, or to use it like a stack - write (push) to the next write address, or read (pop) from the next read address. And it does the DRAM refresh.

It includes RS232 send and get routines that let a PC communicate with the board. A few tests are built in - from a PC, you can send a character to the board to ask it to push a value to RAM, or send another character to pop from RAM and return the value to the PC. This way, you can push a few dozen characters, then pop them back to show the RAM is working. There also is RS232 code built in to run a Seetron LCD display. DRAM refresh is managed by the same ISR that runs the RS232 routines.

SIMM address lines are multiplexed into two parts, rows and column, of equal size. Since each address line is used twice, each additional line quadruples the capacity, rather than just doubling it. SIMMs come in 256K, 1, 4, and 16 meg sizes. 256K SIMMs have 9 pin row and column addressing, for 2^9 x 2^9 = 2^18 bytes, = 256K. So 256K needs 9 address pins, 1M use 10 pins, 4M use 11 pins and 16M use 12 pins. I used a 1 meg SIMM, but built the board to use all 12 address lines, so this design should be able to handle any of the sizes of DRAM.

The SIMM socket I used can hold two SIMMs. The column address strobe (CAS) is the chip select, so by using 16 meg SIMMs and two separate CAS lines, this same design should be able to control up to 32 megabytes of storage. But I haven't coded in the second CAS usage, or tried using 2 SIMMs. If 2 were used, the code would have to know when to switch over to the second CAS line, otherwise use of the higher addresses would just result in rolling over to point to address zero again. I measured that my SIMMs were 1 meg by testing for and detecting just this rollover.

When running from a 12V battery, at 20 mHz clock rate, the whole board draws 79 milliamps. Without the SIMM, it draws 74, so the SIMM itself seems to use about 5 mA at rest. I wasn't accessing the SIMM during this test, so I don't know if an active SIMM would be different.

I include a couple of documents that were helpful. But as I was learning how to do this, I also scanned the internet quite a bit and browsed many documents altogether to learn the general dynamic RAM process. But no single one of these other documents seemed comprehensive enough to include here.

My board includes a MAX232 to drive the RS232 to the PC. I used a 1K resistor array in the data bus to the RAM for protection in case I accidently set both the RAM and the SX data port to output at the same time. There are also resistors for protection of a few general purpose IO wires, one of which runs the Seetron LCD.

; 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


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