Added base driver code without bit transfer
This commit is contained in:
parent
3f3403eaa3
commit
4a6e099f5f
2 changed files with 440 additions and 0 deletions
12
Makefile
Normal file
12
Makefile
Normal 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
428
driver.a65
Normal 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)
|
Loading…
Reference in a new issue