Created
March 30, 2023 15:34
-
-
Save gazsp/060a012f3d95ca5561ec6e8370f1be3e to your computer and use it in GitHub Desktop.
Spectrum +3 UART driver over printer port (57600, 8N1, +CTS, -RTS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; ============================================== =============================================== | |
; RS232 through +3 printer port, 57600, 8N1, +CTS, -RTS | |
; 57600bps (17,3611μs) 61.57813T on ZX128k, 61T will take 17.19811μs, error -0.9% (58146bps) | |
; 60.76389T on ZX48k, 61T will take 17.42857μs, error + 0.4% (57377bps) | |
; Based on some *amazing* work here: https://cygnus.speccy.cz/popis_zx-spectrum_dg192k_rs232.php | |
; ============================================== =============================================== | |
; ---------------------------------- | |
; 0xFFD: Printer port data latch (W) | |
; ---------------------------------- | |
; Bit 7 TX | |
; Bit 0 CTS | |
; ---------------------------------- | |
; 0xFFD: Printer port busy (R) | |
; ---------------------------------- | |
; Bit 0 RX | |
; ---------------------------------- | |
uartBegin: | |
ld bc, 0xFFD ; 10T printer port to BC | |
ld a, 0x80 ; 7T TX = 1, CTS = 0 | |
out (c), a ; 12T | |
ei ; Assume interrupts are disabled, just in case | |
ld a, 50 ; Flush anything left in TX buffer | |
1: halt | |
dec a | |
jr nz, 1B | |
di | |
ld a, 0x81 ; 7T TX = 1, CTS = 1 | |
out (c), a ; 12T | |
ld a, (ix + 0) ; 19T Wait at least one bit | |
ld a, (ix + 0) ; 19T | |
ld a, (ix + 0) ; 19T | |
ret | |
uartWriteByte: | |
di | |
ld bc, 0xFFD ; 10T Printer port to BC | |
ld h, 8 ; 7T H serves as an 8-bit counter | |
ld l, a ; 4T copy data from A to L | |
nop ; 4T | |
nop ; 4T | |
nop ; 4T | |
nop ; 4T | |
; start bit | |
ld a, 1 ; TX = 1 (Start bit), CTS = 0 | |
out (c), a ; 12T write to port | |
nop ; 4T | |
nop ; 4T | |
nop ; 4T | |
nop ; 4T | |
; data bits | |
txLoop: | |
ld a, 0 ; 7T delay | |
ld a, 0 ; 7T delay | |
rrc l ; 8T next bit at bit position 7 | |
ld a, 1 ; 7T prepare a mask | |
or l ; 4T add a mask | |
; from the beginning start bit 12 + 8 + 8 + 7 + 7 + 8 + 7 + 4 = 61T | |
; from last bit 12 + 4 + 12 + 7 + 7 + 8 + 7 + 4 = 61T | |
out (c), a ; 12T write to port | |
dec h ; 4T bit counter | |
jr nz, txLoop ; 12 / 7T repeat | |
; stop bit | |
ld a, (ix + 0) ; 19T delay | |
inc hl ; 6T delay | |
dec hl ; 6T delay | |
ld a, 0x81 ; 7T, keep bit 0 (CTS) and 7 (TX - stop bit) high | |
; since the last out in the cycle 12 + 4 + 7 + 19 + 6 + 6 + 7 = 61T | |
out (c), a ; 12T write to port | |
ei | |
ret ; 10T extends the stop bit duration | |
uartReadBlocking: | |
di | |
ld bc, 0xFFD ; 10T printer port to BC | |
; check if the transfer did not start - then it would be wrong to synchronize with the start bit | |
rxRetry: | |
in a, (c) ; 12T reads the port, sets the sign flag according to MSB | |
and 1 ; 7T | |
jp z, rxError ; 10T error, RxD should be in log. 1 (idle state / stop bit) | |
; detection delay 12 + 7 + 10 = 29T | |
ld a, 0x80 ; 7T TX = 1, CTS = 0 | |
out (c), a ; 12T | |
; waiting for start bit | |
rxWFSB: | |
1: in a, (c) ; 12T read port | |
rrca ; 4T bit 0 to carry flag | |
jp c, 1B ; 10T Repeat to start bit | |
; waiting loop 12 + 4 + 10 = 26T | |
ld a, 0x81 ; 7T TX = 1, CTS = 1 | |
out (c), a ; 12T | |
; start bit started, waiting for bit 0 | |
; the wait length is chosen so that bit 0 is somewhere in its middle | |
; at least 15T has passed from the edge of the start bit, at most 41T, we take an average of 28T | |
; you still have to wait 1.5 x 61T - 28T = 63.5T (of which 11T for another IN instruction) | |
exx ; 4T secondary set | |
ld bc, 0xFFD ; 10T printer port to BC '(and delay at the same time) | |
inc hl ; 6T delay | |
dec hl ; 6T delay | |
ld h, 7 ; 7T in the cycle will be read 7 bits and 8th at the end of the cycle | |
; 6 + 6 + 4 + 10 + 6 + 6 + 7 + 7 = 52T | |
; now bit 0 is somewhere in the middle, we can read 8 times with a distance of 61T | |
rxLoop: | |
in a, (c) ; 12T load port | |
rrca ; 4T bit 0 to carry flag | |
rr l ; 8T build byte in L | |
ld a, 0 ; 7T delay | |
ld a, 0 ; 7T delay | |
ld a, r ; 9T delay | |
dec h ; 4T counter | |
jp nz, rxLoop ; 10T reads more bits | |
; 12 + 4 + 8 + 7 + 7 + 9 + 4 + 10 = 61T if repeated (bits 0 to 6) | |
; followed by reading the MSB, bit 7 | |
in a, (c) ; 12T read port (MSB is somewhere in the middle of the duration, to the stop bit approx. 31 ± 13T ± 2T accuracy deviation 3%) | |
rrca ; 4T bit 0 to carry flag | |
ld a, l ; 4T almost assembled byte to A | |
rra ; 4T rotate the MSB to bit 7 in A | |
; 12 + 4 + 4 + 4 = 24T | |
exx ; 4T primary set | |
; Delay for stop bit | |
ex af, af' ; 4T | |
ld a, (ix + 0) ; 19T delay | |
ex af, af' ; 4T | |
ei | |
ret ; 10T Return with byte in A | |
; saving the apartment takes 4 + 7 + 4 + 4 + 10 = 29T | |
; total 24 + 29 = 53T | |
; including line status detection, 75T with verification before start bit | |
; otherwise, you can directly wait for the next start bit | |
rxError: | |
ld a, 2 ; 7T DEBUG | |
out (254), a ; 11T DEBUG | |
jp rxRetry |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment