558 lines
11 KiB
Text
558 lines
11 KiB
Text
|
|
; 6502 mode
|
|
.p02
|
|
|
|
; load useful register/memory locations
|
|
.include "pet.inc"
|
|
|
|
; for some reason, the PIA registers are missing in pet.inc...
|
|
PIA1 := $E810
|
|
PIA1_PA := PIA1+$0 ; PORT A or DDR A: Data Direction Register A
|
|
PIA1_CRA := PIA1+$1 ; CRA: Control Register A
|
|
PIA1_PB := PIA1+$2 ; PORT B or DDR B: Data Direction Register B
|
|
PIA1_CRB := PIA1+$3 ; CRB: Control Register B
|
|
PIA2 := $E820
|
|
PIA2_PA := PIA2+$0
|
|
PIA2_CRA := PIA2+$1
|
|
PIA2_PB := PIA2+$2
|
|
PIA2_CRB := PIA2+$3
|
|
|
|
; 1MHz phase2 clock
|
|
PHI2_CLOCK = 1000000
|
|
; don't set the baud rate too high, or other operations will be starved
|
|
BAUD_RATE = 600
|
|
|
|
; parameters and return values can reside in an unused area of the zero page
|
|
.zeropage
|
|
; available bytes to read or write
|
|
.export rs_available
|
|
rs_available: .byte 0
|
|
; single byte transfer from input / to output fifo
|
|
.export rs_data
|
|
rs_data: .byte 0
|
|
; status of operation
|
|
rs_status_ok = 0
|
|
rs_status_read_buffer_empty = 1
|
|
rs_status_write_buffer_full = 2
|
|
rs_status_device_not_initialized = 3
|
|
rs_status_device_already_initialized = 4
|
|
.export rs_status
|
|
rs_status: .byte 0
|
|
; TODO add a state variable that can be monitored by the BASIC WAIT command
|
|
|
|
; this is for the load address, so we can generate PRG files.
|
|
; works as long as the code segment comes right after these two bytes,
|
|
; i.e. as long as the LOADADDR segment resides at $load_address - 2
|
|
.segment "LOADADDR"
|
|
.export LOADADDR
|
|
LOADADDR:
|
|
.word *+2
|
|
|
|
; entry points
|
|
.code
|
|
; these are convenience entry points right at the beginning of the page,
|
|
; to reduce dependency on code size.
|
|
; relocatable code would be perfect, but that's a lot more work.
|
|
.export rs_install
|
|
rs_install:
|
|
jmp install
|
|
.export rs_uninstall
|
|
rs_uninstall:
|
|
jmp uninstall
|
|
.export rs_read
|
|
rs_read:
|
|
jmp read
|
|
.export rs_write
|
|
rs_write:
|
|
jmp write
|
|
|
|
; driver state data
|
|
.bss
|
|
; 1=driver initialized
|
|
initialized: .byte 0
|
|
; saved IRQ vector
|
|
oldvector: .word 0
|
|
; state of the CA1 input: bit2=0 low level, bit2=1 high level
|
|
ca1state: .byte 0
|
|
; output buffer
|
|
; this is 16bit, because we need to process start and stop bits as well
|
|
outbuf: .word 0
|
|
; current shifted bit (if 0, no data is being transferred)
|
|
outshift: .byte 0
|
|
; output queue (16 bytes)
|
|
; this is a push from the front - pop from the tail fifo
|
|
outqlen: .byte 0
|
|
outq: .res 16, 0
|
|
; input buffer
|
|
inbuf: .word 0
|
|
; current shifted bit (if 0, no data is being transferred)
|
|
inshift: .byte 0
|
|
; input queue (16 bytes)
|
|
; this is a push from the tail - pop from the front fifo
|
|
inqlen: .byte 0
|
|
inq: .res 16, 0
|
|
|
|
; main code follows
|
|
.code
|
|
|
|
; driver installation, must be called once to set up IRQs, etc.
|
|
install:
|
|
; FIXME this doesn't work if we're loading from ROM
|
|
lda #1
|
|
sta initialized
|
|
; check if we're already initialized
|
|
lda initialized
|
|
beq @hwinit
|
|
; signal error
|
|
lda #rs_status_device_already_initialized
|
|
sta rs_status
|
|
; and return
|
|
rts
|
|
|
|
@hwinit:
|
|
; disable interrupts, so we're not disturbed
|
|
sei
|
|
|
|
; save the previous handler
|
|
lda IRQVec
|
|
sta oldvector
|
|
lda IRQVec+1
|
|
sta oldvector+1
|
|
; assign our custom interrupt handler to the IRQ vector
|
|
lda #<irqhandler
|
|
sta IRQVec
|
|
lda #>irqhandler
|
|
sta IRQVec+1
|
|
|
|
; VIA timer 2 cannot be enabled and disabled on the fly, so we'll
|
|
; configure it here and arm it later
|
|
; one-shot mode
|
|
lda VIA_CR
|
|
and #%11011111
|
|
sta VIA_CR
|
|
; enable interrupt
|
|
; interrupt enable is done by setting bit7 and the desired interrupt
|
|
; bit in IER to 1 (no need to read-modify-write)
|
|
lda #%10100000
|
|
sta VIA_IER
|
|
|
|
; set up the PIA1 and VIA to process I/O
|
|
; TXD: VIA PB3, output
|
|
lda VIA_DDRB
|
|
ora #%00001000
|
|
sta VIA_DDRB
|
|
; RXD: PIA1 CA1, input, interrupt
|
|
lda PIA1_CRA
|
|
; we start with the negative transition (RS232 start bit: H->L)
|
|
and #%11111101
|
|
; IRQ enable
|
|
ora #%00000001
|
|
sta PIA1_CRA
|
|
; CTS: PIA1 CB2, output (optional)
|
|
; RTS: PIA1 PA4, input (optional)
|
|
|
|
; clear some of the state variables
|
|
; the buffers are controlled by other variables anyway and don't need clearing
|
|
lda #0
|
|
sta ca1state
|
|
sta outshift
|
|
sta outqlen
|
|
sta inshift
|
|
sta inqlen
|
|
|
|
; we're ready
|
|
lda #1
|
|
sta initialized
|
|
; and ok
|
|
lda #rs_status_ok
|
|
sta rs_status
|
|
; fire away
|
|
cli
|
|
; return
|
|
rts
|
|
|
|
; driver uninstallation, must be called once to set up IRQs, etc.
|
|
.export uninstall
|
|
uninstall:
|
|
; check if we're already initialized
|
|
lda initialized
|
|
bne @hwuninit
|
|
; signal error
|
|
lda #rs_status_device_not_initialized
|
|
sta rs_status
|
|
; and return
|
|
rts
|
|
|
|
@hwuninit:
|
|
; disable interrupts, so we're not disturbed
|
|
sei
|
|
|
|
; restore the previous handler
|
|
lda oldvector
|
|
ldx oldvector+1
|
|
sta IRQVec
|
|
stx IRQVec+1
|
|
|
|
; disable all interrupts
|
|
; RXD: PIA1 CA1
|
|
lda PIA1_CRA
|
|
; IRQ disable
|
|
and #%11111110
|
|
sta PIA1_CRA
|
|
; and the VIA timer 2
|
|
; clearing is done by setting bit 7 to 0 and the desired interrupt to 1
|
|
lda #%00100000
|
|
sta VIA_IER
|
|
|
|
; we leave the I/O pins and the state alone
|
|
|
|
; but we'll go back to uninitalized state
|
|
lda #0
|
|
sta initialized
|
|
|
|
; and ok
|
|
lda #rs_status_ok
|
|
sta rs_status
|
|
; the rest of the system will probably want interrupts enabled
|
|
cli
|
|
; return
|
|
rts
|
|
|
|
|
|
; write one byte to FIFO
|
|
.export write
|
|
write:
|
|
; check if we're already initialized
|
|
lda initialized
|
|
bne @dowrite
|
|
|
|
; signal error
|
|
lda #rs_status_device_not_initialized
|
|
sta rs_status
|
|
; and return
|
|
rts
|
|
|
|
@dowrite:
|
|
; disable interrupts while we write into the buffer
|
|
sei
|
|
|
|
; test if there's space in the buffer first
|
|
lda #16
|
|
cmp outqlen
|
|
beq @wrnodata
|
|
|
|
; move data up first
|
|
ldy outqlen
|
|
; check if we need to move data at all
|
|
bne @wrbufend
|
|
|
|
; move all existing elements up, starting from the top
|
|
@wrbufloop:
|
|
lda outq-1,y
|
|
sta outq,y
|
|
dey
|
|
bne @wrbufloop
|
|
@wrbufend:
|
|
|
|
; write data to head
|
|
lda rs_data
|
|
sta outq
|
|
; update queue length
|
|
ldy outqlen
|
|
iny
|
|
sty outqlen
|
|
; also store to return value
|
|
sty rs_available
|
|
|
|
; all right
|
|
lda #rs_status_ok
|
|
sta rs_status
|
|
|
|
; an unconditional branch would be useful...
|
|
clc
|
|
bcc @wrdone
|
|
|
|
@wrnodata:
|
|
; return error
|
|
lda #rs_status_write_buffer_full
|
|
sta rs_status
|
|
|
|
@wrdone:
|
|
; and re-enable
|
|
cli
|
|
|
|
; return
|
|
rts
|
|
|
|
; read one byte from FIFO
|
|
.export read
|
|
read:
|
|
; check if we're already initialized
|
|
lda initialized
|
|
bne @doread
|
|
|
|
; signal error
|
|
lda #rs_status_device_not_initialized
|
|
sta rs_status
|
|
; and return
|
|
rts
|
|
|
|
@doread:
|
|
; disable interrupts while we read the buffer
|
|
sei
|
|
|
|
; test if we have any data first
|
|
lda inqlen
|
|
beq @rdnodata
|
|
|
|
; return data from head
|
|
lda inq
|
|
sta rs_data
|
|
|
|
; update the queue length
|
|
ldy inqlen
|
|
dey
|
|
sty inqlen
|
|
; also store to return value
|
|
sty rs_available
|
|
; check if we need to move any data
|
|
bne @rdbufend
|
|
|
|
; move all other data elements down
|
|
ldx #0
|
|
@rdbufloop:
|
|
lda inq+1,x
|
|
sta inq,x
|
|
inx
|
|
dey
|
|
bne @rdbufloop
|
|
@rdbufend:
|
|
|
|
; all right
|
|
lda #rs_status_ok
|
|
sta rs_status
|
|
|
|
; an unconditional branch would be useful...
|
|
clc
|
|
bcc @rddone
|
|
|
|
@rdnodata:
|
|
; return error
|
|
lda #rs_status_read_buffer_empty
|
|
sta rs_status
|
|
|
|
@rddone:
|
|
; and re-enable
|
|
cli
|
|
|
|
; return
|
|
rts
|
|
|
|
; start the timer
|
|
starttimer:
|
|
; arm VIA timer 2 by latching the counter
|
|
period = PHI2_CLOCK/BAUD_RATE
|
|
lda #<period
|
|
sta VIA_T1CL
|
|
lda #>period
|
|
sta VIA_T1CH
|
|
; return
|
|
rts
|
|
|
|
; stop the timer
|
|
@stoptimer:
|
|
; VIA timer 1 disabled
|
|
lda VIA_CR
|
|
and #%10111111
|
|
sta VIA_CR
|
|
; return
|
|
rts
|
|
|
|
; IRQ handler
|
|
.interruptor irqhandler
|
|
irqhandler:
|
|
; disable interrupts
|
|
sei
|
|
; clear decimal flag to avoid unexpected behavior
|
|
cld
|
|
; save registers
|
|
pha
|
|
txa
|
|
pha
|
|
tya
|
|
pha
|
|
|
|
; test for the interrupts we're expecting
|
|
|
|
@handleflip:
|
|
; read PIA1 CRA, interrupt flag is automatically cleared
|
|
lda PIA1_CRA
|
|
; save CRA for later, so we don't need to fetch it again
|
|
tax
|
|
; bit 7 is the CA1 interrupt flag - fired?
|
|
and #%10000000
|
|
; nope, skip flip handling
|
|
beq @handletimer
|
|
|
|
; handle CA1 interrupt
|
|
; fetch saved CRA
|
|
txa
|
|
; save current value for later use:
|
|
; bit 2 is contains the active value of CA1, which is what we want to know
|
|
sta ca1state
|
|
; invert transition (H->L <-> L->H)
|
|
eor #%00000010
|
|
; mask out interrupt flag - we don't want another IRQ right away
|
|
and #%01111111
|
|
sta PIA1_CRA
|
|
|
|
; check if we're already in the middle of a reception
|
|
lda inshift
|
|
; yes, don't do anything
|
|
bne @handletimer
|
|
; nope, initiate reception
|
|
lda #10
|
|
sta inshift
|
|
|
|
@handletimer:
|
|
; VIA timer 2 fired?
|
|
lda VIA_IFR
|
|
and #%00100000
|
|
; zero, no interrupt
|
|
beq @irqreturn
|
|
|
|
; timer 2 fired: rearm (we're in one-shot mode!), and clear the interrupt flag
|
|
; FIXME this should be done earlier, for more accurate timing
|
|
; FIXME we could skip writing the low byte here, writing the high byte is enough
|
|
jsr starttimer
|
|
|
|
@loadout:
|
|
; do we have more bits to send?
|
|
lda outshift
|
|
; yes, skip loading next byte
|
|
bne @shiftout
|
|
|
|
; load next byte from FIFO
|
|
|
|
; do we have more bytes?
|
|
lda outqlen
|
|
; nope, skip ahead to input processing
|
|
beq @shiftin
|
|
|
|
; load length
|
|
ldx outqlen
|
|
; decrement, also gives the index
|
|
dex
|
|
; load next byte from fifo
|
|
lda outq,x
|
|
; store new queue length
|
|
stx outqlen
|
|
|
|
; prepare shift register
|
|
; layout: 000000E7 6543210S
|
|
; bit0 = start bit = 0
|
|
; bit1..7 = data0..6
|
|
clc
|
|
rol
|
|
sta outbuf
|
|
; bit8 = data7
|
|
; bit9 = stop bit = 1
|
|
lda #%00000001
|
|
rol
|
|
sta outbuf+1
|
|
; we're shifting 10 bits out
|
|
lda #10
|
|
sta outshift
|
|
|
|
@shiftout:
|
|
; make sure overflow flag is clear, so we can do unconditional branches
|
|
clv
|
|
; load shift register bit 0
|
|
lda outbuf
|
|
and #%00000001
|
|
; mark or blank?
|
|
beq @shiftoutblank
|
|
; set output high
|
|
lda #%00001000
|
|
ora VIA_PB
|
|
bvc @shiftoutstore
|
|
@shiftoutblank:
|
|
; set output low
|
|
lda #%11110111
|
|
and VIA_PB
|
|
@shiftoutstore:
|
|
sta VIA_PB
|
|
|
|
; shift the next bit in
|
|
; 0 -> high byte -> carry
|
|
lsr outbuf+1
|
|
; carry -> low byte
|
|
ror outbuf
|
|
|
|
; decrement counter
|
|
dec outshift
|
|
|
|
@shiftin:
|
|
; FIXME we should check the start and stop bits, to synchronize RS232 transmissions
|
|
|
|
; check first if a reception is in progress
|
|
lda inshift
|
|
; nope, skip input processing
|
|
beq @irqreturn
|
|
|
|
; for unconditional branch
|
|
clv
|
|
; pick up the current CA1 state
|
|
lda ca1state
|
|
and #%00000100
|
|
; mark or blank?
|
|
beq @shiftinblank
|
|
; shift in high bit
|
|
sec
|
|
bvc @shiftinstore
|
|
@shiftinblank:
|
|
; shift in low bit
|
|
clc
|
|
@shiftinstore:
|
|
; rotate carry bit into buffer
|
|
rol inbuf
|
|
rol inbuf+1
|
|
|
|
; decrement counter
|
|
dec inshift
|
|
|
|
@storein:
|
|
; did we complete a transmission?
|
|
bne @irqreturn
|
|
|
|
; check if we have space available in the buffer
|
|
lda inqlen
|
|
cmp #16
|
|
; nope, drop this byte
|
|
bcc @irqreturn
|
|
|
|
; yes, store it
|
|
tax
|
|
lda inbuf+1
|
|
lsr
|
|
lda inbuf
|
|
ror
|
|
sta inq, x
|
|
|
|
; and update the buffer length
|
|
dex
|
|
stx inqlen
|
|
|
|
@irqreturn:
|
|
; restore registers
|
|
pla
|
|
tay
|
|
pla
|
|
tax
|
|
pla
|
|
; we don't need to re-enable interrupts here,
|
|
; this will happen automatically when flags are restored later
|
|
;cli
|
|
; jump to previous handler
|
|
; we don't need to return or restore flags, this will be done by the chained interrupt handler
|
|
jmp (oldvector)
|