diff --git a/libsrc/c128/Makefile b/libsrc/c128/Makefile index e20a5d342..22840346a 100644 --- a/libsrc/c128/Makefile +++ b/libsrc/c128/Makefile @@ -22,6 +22,7 @@ OBJS = _scrsize.o \ kbhit.o \ mouse.o \ readjoy.o \ + rs232.o \ write.o all: $(OBJS) diff --git a/libsrc/c128/rs232.s b/libsrc/c128/rs232.s new file mode 100644 index 000000000..4c3f38163 --- /dev/null +++ b/libsrc/c128/rs232.s @@ -0,0 +1,673 @@ +; +; SwiftLink/Turbo-232 v0.90 device driver, by Craig Bruce, 14-Apr-1998. +; +; This software is Public Domain. It is in Buddy assembler format. +; +; This device driver uses the SwiftLink RS-232 Serial Cartridge, available from +; Creative Micro Designs, Inc, and also supports the extensions of the Turbo232 +; Serial Cartridge. Both devices are based on the 6551 ACIA chip. It also +; supports the "hacked" SwiftLink with a 1.8432 MHz crystal. +; +; The code assumes that the kernal + I/O are in context. On the C128, call +; it from Bank 15. On the C64, don't flip out the Kernal unless a suitable +; NMI catcher is put into the RAM under then Kernal. For the SuperCPU, the +; interrupt handling assumes that the 65816 is in 6502-emulation mode. +; +;-------------------------------------------------------------------------- +; +; Adapted for the use with the cc65 runtime library by +; Ullrich von Bassewitz (uz@musoftware.de) 02-May-1999. +; +; All external functions are C callable, the return value is an error code. +; + + + .importzp ptr1, ptr2, tmp1, tmp2 + .import popa, popax + .export _rs232_init, _rs232_params, _rs232_done, _rs232_get + .export _rs232_put, _rs232_pause, _rs232_unpause, _rs232_status + + +NmiExit = $ff33 ;exit address for nmi + +ACIA = $DE00 + + + +;---------------------------------------------------------------------------- +; +; Global variables +; + +.bss +DropCnt: .res 4 ; Number of bytes lost from rx buffer full +Initialized: .res 1 ; Flag indicating driver is initialized +Stopped: .res 1 ; Flow-stopped flag +RtsOff: .res 1 ; +Errors: .res 1 ; Number of bytes received in error, low byte +Turbo232: .res 1 ; Flag indicating turbo-232 +HackedFlag: .res 1 ; Flag indicating hacked-crystal swiftlink +CpuSpeed: .res 1 ; In MHz +RecvHead: .res 1 ; Head of receive buffer +RecvTail: .res 1 ; Tail of receive buffer +RecvFreeCnt: .res 1 ; Number of bytes in receive buffer +SendHead: .res 1 ; Head of send buffer +SendTail: .res 1 ; Tail of send buffer +SendFreeCnt: .res 1 ; Number of bytes free in send buffer +BaudCode: .res 1 ; Current baud in effect + +; Send and receive buffers: 256 bytes each +RecvBuf: .res 256 +SendBuf: .res 256 + +.data +NmiContinue: .byte $4c ; JMP instruction for NMI save -- continue +NmiSave: .res 2 ; normal NMI handler + +; Switftlink register offsets +RegData = 0 ; Data register +RegStatus = 1 ; Status register +RegCommand = 2 ; Command register +RegControl = 3 ; Control register +RegClock = 7 ; Turbo232 external baud-rate generator + +; Error codes. Beware: The codes must match the codes in the C header file +ErrNotInitialized = $01 +ErrBaudTooFast = $02 +ErrBaudNotAvail = $03 +ErrNoData = $04 +ErrOverflow = $05 + + +.code + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_init (char hacked); +; /* Initialize the serial port, install the interrupt handler. The parameter +; * must be true (non zero) for a hacked swiftlink and false (zero) otherwise. +; */ +; + +_rs232_init: + bit Initialized ;** shut down if started + bpl @L1 + pha + jsr _rs232_done + pla + +;** set hacked-crystal + +@L1: sta HackedFlag + +;** check for turbo-232 + + lda #$00 + sta ACIA+RegControl + tax + lda ACIA+RegClock + beq @L3 + dex +@L3: stx Turbo232 + +;** get C128 cpu speed + + lda $d030 + and #$01 + clc + adc #1 + sta CpuSpeed + +;** check for super-cpu at 20 MHz + + bit $d0bc + bmi @L4 + bit $d0b8 + bvs @L4 + lda #20 + sta CpuSpeed + +;** initialize buffers & control + +@L4: lda #0 + sta RecvHead + sta SendHead + sta RecvTail + sta SendTail + sta Errors + sta Stopped + lda #255 + sta RecvFreeCnt + sta SendFreeCnt + +;** set up nmi's + + lda $318 + ldy $319 + sta NmiSave+0 + sty NmiSave+1 + lda #NmiHandler + sta $318 + sty $319 + +;** set default to 2400-8N1, enable interrupts + + lda ACIA+RegData + lda ACIA+RegStatus + lda #$18 + bit HackedFlag + bpl @L5 + lda #$1a +@L5: sta ACIA+RegControl + + lda #$01 + sta RtsOff + ora #$08 + sta ACIA+RegCommand + lda #$06 + sta BaudCode + +;** return + lda #$ff + sta Initialized + lda #$00 + tax + rts + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_params (unsigned char params, unsigned char parity); +; /* Set the port parameters. Use a combination of the #defined values above. */ +; +; Set communication parameters. +; +; baud rates stops word | parity +; --------------------- ----- ----- | --------- +; $00=50 $08=9600 $00=1 $00=8 | $00=none +; $01=110 $09=19200 $80=2 $20=7 | $20=odd +; $02=134.5 $0a=38400 $40=6 | $60=even +; $03=300 $0b=57600 $60=5 | $A0=mark +; $04=600 $0c=115200 | $E0=space +; $05=1200 $0d=230400 +; $06=2400 $0e=future +; $07=4800 $0f=future +; + +_rs232_params: + jsr CheckInitialized ;** check initialized + bcc @L1 + rts + +; Save new parity + +@L1: and #%11100000 + ora #%00000001 + sta tmp2 + +; Check cpu speed against baud rate + + jsr popa + sta tmp1 + and #$0f + cmp #$0c + bcc @L3 + ldx CpuSpeed + cpx #1+1 + bcc @L2 + cmp #$0c + beq @L3 + cpx #4 + bcs @L3 +@L2: lda #ErrBaudTooFast + bne @L9 + +; Set baud/parameters + +@L3: lda tmp1 + and #$0f + tax + lda NormBauds,x + bit HackedFlag + bpl @L4 + lda HackBauds,x +@L4: cmp #$ff + bne @L5 + lda #ErrBaudNotAvail + bne @L9 + +@L5: tax + and #$30 + beq @L6 + bit Turbo232 + bmi @L6 + lda #ErrBaudNotAvail + bne @L9 + +@L6: lda tmp1 + and #$0f + sta BaudCode + lda tmp1 + and #%11100000 + ora #%00010000 + sta tmp1 + txa + and #$0f + ora tmp1 + sta ACIA+RegControl + txa + and #%00110000 + beq @L7 + lsr + lsr + lsr + lsr + eor #%00000011 + sta ACIA+RegClock + +; Set new parity + +@L7: lda tmp2 + sta RtsOff + ora #%00001000 + sta ACIA+RegCommand + lda #0 +@L9: ldx #0 + rts + +.rodata + +NormBauds: + .byte $ff,$ff,$ff,$05,$06,$07,$08,$0a,$0c,$0e,$0f,$10,$20,$30,$ff,$ff +HackBauds: + .byte $01,$03,$04,$06,$07,$08,$0a,$0c,$0e,$0f,$ff,$ff,$00,$ff,$ff,$ff + ;in: 0 1 2 3 4 5 6 7 8 9 a b c d e f + ;baud50 110 134 3 6 12 24 48 96 19 38 57 115 230 exp exp + ;out masks: $0F=Baud, val$FF=err + ; $30=t232ExtBaud: $00=none, $10=57.6, $20=115.2, $30=230.4 +.code + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_done (void); +; /* Close the port, deinstall the interrupt hander. You MUST call this function +; * before terminating the program, otherwise the machine may crash later. If +; * in doubt, install an exit handler using atexit(). The function will do +; * nothing, if it was already called. +; */ +; + + +_rs232_done: + bit Initialized ;** check initialized + bpl @L9 + +; Stop interrupts, drop DTR + + lda RtsOff + and #%11100010 + ora #%00000010 + sta ACIA+RegCommand + +; Restore NMI vector + + lda NmiSave+0 + ldy NmiSave+1 + sta $318 + sty $319 + +; Flag uninitialized + +@L9: lda #$00 + sta Initialized + tax + rts + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_get (char* B); +; /* Get a character from the serial port. If no characters are available, the +; * function will return RS_ERR_NO_DATA, so this is not a fatal error. +; */ +; + +_rs232_get: + jsr CheckInitialized ; Check if initialized + bcc @L1 + rts + +; Check for bytes to send + +@L1: sta ptr1 + stx ptr1+1 ; Store pointer to received char + ldx SendFreeCnt + cpx #$ff + beq @L2 + lda #$00 + jsr TryToSend + +; Check for buffer empty + +@L2: lda RecvFreeCnt + cmp #$ff + bne @L3 + lda #ErrNoData + ldx #0 + rts + +; Check for flow stopped & enough free: release flow control + +@L3: ldx Stopped + beq @L4 + cmp #63 + bcc @L4 + lda #$00 + sta Stopped + lda RtsOff + ora #%00001000 + sta ACIA+RegCommand + +; Get byte from buffer + +@L4: ldx RecvHead + lda RecvBuf,x + inc RecvHead + inc RecvFreeCnt + ldx #$00 + sta (ptr1,x) + txa ; Return code = 0 + rts + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_put (char B); +; /* Send a character via the serial port. There is a transmit buffer, but +; * transmitting is not done via interrupt. The function returns +; * RS_ERR_OVERFLOW if there is no space left in the transmit buffer. +; */ +; + +_rs232_put: + jsr CheckInitialized ; Check initialized + bcc @L1 + rts + +; Try to send + +@L1: ldx SendFreeCnt + cpx #$ff + beq @L2 + pha + lda #$00 + jsr TryToSend + pla + +; Put byte into send buffer & send + +@L2: ldx SendFreeCnt + bne @L3 + lda #ErrOverflow + ldx #$00 + rts + +@L3: ldx SendTail + sta SendBuf,x + inc SendTail + dec SendFreeCnt + lda #$ff + jsr TryToSend + lda #$00 + tax + rts + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_pause (void); +; /* Assert flow control and disable interrupts. */ +; + +_rs232_pause: +; Check initialized + jsr CheckInitialized + bcc @L1 + rts + +; Assert flow control + +@L1: lda RtsOff + sta Stopped + sta ACIA+RegCommand + +; Delay for flow stop to be received + + ldx BaudCode + lda PauseTimes,x + jsr DelayMs + +; Stop rx interrupts + + lda RtsOff + ora #$02 + sta ACIA+RegCommand + lda #0 + tax + rts + + +.rodata +; Delay times: 32 byte-receive times in milliseconds, or 100 max. +; Formula = 320,000 / baud +PauseTimes: + .byte 100,100,100,100,100,100,100,067,034,017,009,006,003,002,001,001 + ;in: 0 1 2 3 4 5 6 7 8 9 a b c d e f + ;baud50 110 134 3 6 12 24 48 96 19 38 57 115 230 exp exp +.code + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_unpause (void); +; /* Re-enable interrupts and release flow control */ +; + +_rs232_unpause: +; Check initialized + jsr CheckInitialized + bcc @L1 + rts + +; Re-enable rx interrupts & release flow control + +@L1: lda #$00 + sta Stopped + lda RtsOff + ora #%00001000 + sta ACIA+RegCommand + +; Poll for stalled char & exit + + jsr PollReceive + lda #0 + tax + rts + +;---------------------------------------------------------------------------- +; +; unsigned char __fastcall__ rs232_status (unsigned char* status, +; unsigned char* errors); +; /* Return the serial port status. */ +; + +_rs232_status: + sta ptr2 + stx ptr2+1 + jsr popax + sta ptr1 + stx ptr1+1 + jsr CheckInitialized + bcs @L9 + +; Get status + + lda ACIA+RegStatus + ldy #0 + sta (ptr1),y + jsr PollReceive ; bug-recovery hack + lda Errors + sta (ptr2),y + tya + tax +@L9: rts + +;---------------------------------------------------------------------------- +; +; NMI handler +; C128 NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=33, ROMexit=30 +; C64 NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=34, ROMexit=29 +; +; timing: normal=76+43+9=128 cycles, assertFlow=76+52+9=137 cycles +; +; C128 @ 115.2k: 177 cycles avail (fast) +; C64 @ 57.6k: 177 cycles avail, worstAvail=177-43? = 134 +; SCPU @ 230.4k: 868 cycles avail: for a joke! +; + +NmiHandler: + lda ACIA+RegStatus ;(4) ;status ;check for byte received + and #$08 ;(2) + beq NmiNorm ;(2*) + lda ACIA+RegStatus ;(4) opt ;status ;check for receive errors + and #$07 ;(2) opt + beq @L1 ;(3*)opt + inc Errors ;(5^)opt +@L1: lda ACIA+RegData ;(4) ;data ;get byte and put into receive buffer + ldy RecvTail ;(4) + ldx RecvFreeCnt ;(4) + beq @L3 ;(2*) + sta RecvBuf,y ;(5) + inc RecvTail ;(6) + dec RecvFreeCnt ;(6) + cpx #33 ;(2) ;check for buffer space low + bcc @L2 ;(2*) + jmp NmiExit ;(3) + +; Assert flow control + +@L2: lda RtsOff ;(3) ;assert flow control if buffer space too low + sta ACIA+RegCommand ;(4) ;command + sta Stopped ;(3) + jmp NmiExit ;(3) + +; Drop this char + +@L3: inc DropCnt+0 ;not time-critical + bne @L4 + inc DropCnt+1 + bne @L4 + inc DropCnt+2 + bne @L4 + inc DropCnt+3 +@L4: jmp NmiExit + +@L9: jmp NmiContinue + +;---------------------------------------------------------------------------- +; +; CheckInitialized - internal check if initialized +; Set carry and an error code if not initialized, clear carry and do not +; change any registers if initialized. +; + +CheckInitialized: + bit Initialized + bmi @L1 + lda #ErrNotInitialized + ldx #0 + sec + rts + +@L1: clc + rts + +;---------------------------------------------------------------------------- +; Try to send a byte. Internal routine. A = TryHard + +TryToSend: + sta tmp1 ; Remember tryHard flag +@L0: lda SendFreeCnt + cmp #$ff + beq @L3 ; Bail out + +; Check for flow stopped + +@L1: lda Stopped + bne @L3 ; Bail out + +;** check that swiftlink is ready to send + +@L2: lda ACIA+RegStatus + and #$10 + bne @L4 + bit tmp1 ;keep trying if must try hard + bmi @L0 +@L3: rts + +;** send byte and try again + +@L4: ldx SendHead + lda SendBuf,x + sta ACIA+RegData + inc SendHead + inc SendFreeCnt + jmp @L0 + +;---------------------------------------------------------------------------- +; +; PollReceive - poll for rx char +; This function is useful in odd cases where the 6551 has a character in +; it but it fails to raise an NMI. It might be edge-triggering conditions? +; Actually, I'm not entirely sure that this condition can still arrise, but +; calling this function does no harm. +; + +PollReceive: + lda #$08 + and ACIA+RegStatus + beq @L9 + and ACIA+RegStatus + beq @L9 + lda ACIA+RegData + ldx RecvFreeCnt + beq @L9 + ldx RecvTail + sta RecvBuf,x + inc RecvTail + dec RecvFreeCnt +@L9: rts + +;---------------------------------------------------------------------------- +; +; DelayMs : delay for given number of milliseconds +; This implementation isn't very rigerous; it merely delays for the +; approximate number of clock cycles for the processor speed. +; Algorithm: +; repeat for number of milliseconds: +; repeat for number of MHz of cpu speed: +; delay for 1017 clock cycles +; + +DelayMs: ;( .A=milliseconds ) +@L1: ldy CpuSpeed +@L2: ldx #203 ;(2) +@L3: dex ;(2) + bne @L3 ;(3) // 1017 cycles + dey + bne @L2 + sec + sbc #1 + bne @L1 + rts + +.end + + +