Added base driver code without bit transfer

This commit is contained in:
Gregor Riepl 2022-08-13 20:45:08 +02:00
parent 3f3403eaa3
commit 4a6e099f5f
2 changed files with 440 additions and 0 deletions

12
Makefile Normal file
View file

@ -0,0 +1,12 @@
MACHINE := pet
.PHONY: all
all: rs232.bin
rs232.bin: driver.o
cl65 -t ${MACHINE} -o $@ $^
%.o: %.a65
ca65 -t ${MACHINE} -o $@ $<

428
driver.a65 Normal file
View file

@ -0,0 +1,428 @@
; 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 = 300
; we expect to be loaded somewhere in the middle of user ram
; TODO verify if this is safe with the Editor/Basic 2.0 ROM
.org $7000
; 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
; parameters and return values
.data
; 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
; driver state data
; 1=driver initialized
initialized: .byte 0
; saved IRQ vector
oldvector: .word 0
; current CRA value on CA1 change (bit contains the state of CA1)
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:
; 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
cli
; 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
; the timer is only started when a transmission is actually initiated
; but we'll enable VIA timer 1 interrupt
lda VIA_IER
ora #%01000000
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
sei
; 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
cli
; 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 1
lda VIA_IER
and #%10111111
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
sei
; 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
cli
; 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
sei
; 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
cli
; 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
sei
; return
rts
; start the timer
@starttimer:
; set up the VIA timer 1 to fire periodically
period = PHI2_CLOCK/BAUD_RATE
lda #>period
sta VIA_T1CL
lda #<period
sta VIA_T1CH
; VIA timer 1 free-run mode
lda VIA_CR
ora #%01000000
sta VIA_CR
; 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
cli
; 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 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
@handletimer:
; VIA timer 1 fired?
lda VIA_IFR
and #%01000000
; zero, no interrupt
beq @irqreturn
; handle timer interrupt
; reset timer 1 interrupt
; FIXME should we do this? perhaps someone else is also expecting a VIA interrupt
; if not, we *must* reset it
lda VIA_T1CL
@processfifo:
; TODO process input and output
@irqreturn:
; restore registers
pla
tay
pla
tax
pla
; enable interrupts
; FIXME should we do this here? what's common practice? is this compatible with other interrupt handlers
sei
; jump to previous handler
; we don't need to return or restore flags, this will be done by the chained interrupt handler
jmp (oldvector)