Files
z80_monitor/tape.asm
T

550 lines
14 KiB
NASM

BLOCK_SIZE .equ 1+128+2 ; type + 64 bytes + crc-16
BLOCK_ERROR_CALIBRATION .equ 1
BLOCK_ERROR_SYNC .equ 2
BLOCK_ERROR_READ .equ 3
BLOCK_ERROR_CHSUM .equ 4
TAPE_WORKSPACE .equ END_OF_BIOS_WS
SHORT_AVG .equ TAPE_WORKSPACE + 000h ; byte
SHORT_MIN .equ TAPE_WORKSPACE + 001h ; byte
SHORT_MAX .equ TAPE_WORKSPACE + 002h ; byte
LONG_AVG .equ TAPE_WORKSPACE + 003h ; byte
LONG_MIN .equ TAPE_WORKSPACE + 004h ; byte
LONG_MAX .equ TAPE_WORKSPACE + 005h ; byte
BLOCK_BUFFER .equ TAPE_WORKSPACE + 006h ; BLOCK_SIZE
; -------------------------------------------------------
; Error return callback macro
; creates a simple macro which puts a return code in a
; and sets the carry flag
; -------------------------------------------------------
create_error_return MACRO &name, &code
&name:
ld a,&code
scf
ret
ENDM
; -------------------------------------------------------
; Measure edge-to-edge pulse
; needs d (comparator state)
; returns b (loop count)
; carry is timeout
; uses a
; loop is 36T extra 22T (0.6 loops)
; -------------------------------------------------------
measure_pulse:
ld b,0 ; 4T
1$:
; use b as timeout counter (246 loops)
inc b ; 4T
jr z,9$ ; 12/7T
in a,(TAPE_PORT) ; 11T
cp d ; 4T
jp z,1$ ; 10T
; loop has 36T
; edge found
ld d,a ; 4T
or a ; 4T, clear carry
ret ; 10T
9$:
scf
ret
; -------------------------------------------------------
; Ignore one pulse and then measure the next
; needs d (comparator state)
; returns b (loop count)
; carry is timeout
; uses a
; loop is 36T extra 26T (0.7 loops)
; -------------------------------------------------------
sample_pulse:
ld b,0 ; 4T
1$:
; use b as timeout counter (246 loops)
inc b ; 4T
jr z,9$ ; 12/7T
in a,(TAPE_PORT) ; 11T
cp d ; 4T
jp z,1$ ; 10T
; loop has 36T
; edge found
ld d,a ; 4T
; start timing from now
ld b,0 ; 4T
2$:
; use b as timeout counter (246 loops)
inc b ; 4T
jr z,9$ ; 12/7T
in a,(TAPE_PORT) ; 11T
cp d ; 4T
jp z,2$ ; 10T
; loop has 36T
; edge found
ld d,a ; 4T
or a ; 4T, clear carry
ret
9$:
scf
ret
; -------------------------------------------------------
; Average 8 pulse measurements
; needs d (comparator state)
; returns a (average)
; cary is timeout
; uses b,e,h
; -------------------------------------------------------
average8:
ld e,0
ld h,8
1$:
; synchronize to next pulse and ignore measurement
call sample_pulse
ret c ; return when timeout
call sample_pulse
ret c ; return when timeout
ld a,e ; add loop count to e
add b
ld e,a
dec h
jr nz,1$ ; repeat this 8 times
ld a,e
srl a
srl a
srl a ; divide by 8
or a ; cear carry
ret
; -------------------------------------------------------
; Make a +/-25% window around a
; needs a
; returns b (minimum) c (maximum)
; uses e,h
; NOTE: USES 8 BIT COUNTER
; @1MHZ t_long<1.14ms
; @2MHZ t_long<571us
; @4MHZ t_long<285us
; TODO: Check feasibility of using 16bit accumulator
; -------------------------------------------------------
make_window:
ld e,a
srl a
srl a
ld h,a ; h = a/4
ld a,e
sub h
ld b,a ; b = a-25%
ld a,e
add h
ld c,a ; c = a+25%
ret
; -------------------------------------------------------
; Calibration
; d needs to be kept from now on
; uses a,b,c,e,h,l
; carry is error, a contains error code
; -------------------------------------------------------
calibrate:
ld a,(TAPE_PORT) ; get current state
ld d,a
call sample_pulse ; make sure we start in silence
jp nc,cal_err ; should timeout
; wait for the first edge
ld l,0
1$:
call sample_pulse
jr nc,2$
; use l as timout counter
; inc l
; jr nx,1$
jr 1$
; timeout
jp cal_err ; TODO ignored timeout for now
2$:
; edge found
; get short leader timing
call average8
jr c,cal_err_short_leader_average
ld (SHORT_AVG),a
call make_window
ld a,b
ld (SHORT_MIN),a
ld a,c
ld (SHORT_MAX),a
; find the long leader
; need 8 consecutive pulses > short*1.5
ld a,(SHORT_AVG)
ld c,a
srl a
add c ; a = 1.5*short
ld c,a
; TODO no overall timeout here
3$:
ld e,4 ; repeat 4 times, each first pulse
; is ignored
4$:
call sample_pulse
jr c,cal_err_wait_for_long
call sample_pulse
jr c,cal_err_wait_for_long
ld a,b
; try again if its a short pulse
cp c
jr c,3$
; we found a long pulse
dec e
jr nz,4$ ; repeat this 4 times
; long leader found
call average8
jr c,cal_err_long_leader_average
ld (LONG_AVG),a
call make_window
ld a,b
ld (LONG_MIN),a
ld a,c
ld (LONG_MAX),a
; A quick sanity check: long>1.5*short
ld a,(SHORT_AVG)
ld c,a
srl a
add c
ld c,a ; c = 1.5*short
ld a,(LONG_AVG)
cp c
jr c,cal_err_sanity_check
; Find the marker. marker is 2*long
; threshold is 1.5*long
ld a,(LONG_AVG)
ld c,a
srl a
add c
ld c,a ; c = 1.5*long
; Find two consecutive marker-width intervals
5$:
ld e,0
6$:
call measure_pulse
jr c,cal_err_marker_measure
ld a,b
cp c
jr c,5$ ; not a marker
inc e ; marker interval found
ld a,e
cp 2 ; we need two of them
jr c,6$ ; not 2 yet, measure next
; both marker halves found
; we are at the beginning of the first data pulse
; next 8 bytes is the training byte 0x55
call read_byte
jr c,cal_err_training_read
cp a,055h
jr nz,cal_err_training_incorrect
; calibration complete
or a ; clear carry
ret
create_error_return cal_err, 000h
create_error_return cal_err_short_leader_average, 001h
create_error_return cal_err_wait_for_long, 002h
create_error_return cal_err_long_leader_average, 003h
create_error_return cal_err_sanity_check, 004h
create_error_return cal_err_marker_measure, 005h
create_error_return cal_err_training_read, 006h
create_error_return cal_err_training_incorrect, 007h
; -------------------------------------------------------
; Read a single bit
; needs d (comparator state)
; valid: z=1 c=bit
; error: z=0
; uses a,b,c
; -------------------------------------------------------
read_bit:
; entry may be late into first pulse
; wait for first edge to measure
ld b,0
1$:
inc b
jr z,bit_bad
in a,(TAPE_PORT)
cp d
jp z,1$
ld d,a
; ignored first edge
; measure second pulse
ld b,0
2$:
inc b
jr z,bit_bad
in a,(TAPE_PORT)
cp d
jp z,2$
ld d,a
; if in short window (second half is short pulse)
; 1
ld a,(SHORT_MIN)
ld c,a
ld a,b
cp c
jr c,bit_bad ; pulse<SHORT_MIN
ld a,(SHORT_MAX)
ld c,a
ld a,b
cp c
jr c,bit_one
jr z,bit_one ; pulse<=SHORT_MAX
; test long window (second half is long pulse)
ld a,(LONG_MIN)
ld c,a
ld a,b
cp c
jr c,bit_bad ; pulse<LONG_MAX
ld a,(LONG_MAX)
ld c,a
ld a,b
cp c
jr c,bit_zero
jr z,bit_zero ; pulse<=LONG_MAX
bit_bad:
ld a,1
or a
ret
bit_zero:
xor a
ret
bit_one:
xor a
scf
ret
; -------------------------------------------------------
; Read byte, MSB first
; returns a (byte)
; carry means error
; needs d (comparator state)
; uses e,h,b,c
; -------------------------------------------------------
read_byte:
ld e,0
ld h,8
1$:
call read_bit
jr nz,9$
; shift in the carry bit
rl e
; repeat 8 times
dec h
jr nz,1$
; byte done,
ld a,e
or a
ret
9$:
scf
ret
; -------------------------------------------------------
; Read and check sync bytes
; carry is error
; -------------------------------------------------------
read_sync:
call read_byte
ret c
cp 05ah
jr nz,9$
call read_byte
ret c
cp 0c3h
jr nz,9$
or a
ret
9$:
scf
ret
; -------------------------------------------------------
; Read a block into BLOCK_BUFFER
; needs d (comparator state)
; uses b,c,d,e,h,l
; returns error code in a, specific error code in b
; returns start of block in hl
; carry is fail
; -------------------------------------------------------
read_block:
; Block reading uses de' and hl' so preserve them
exx
push de
push hl
exx
; calibrate reader
call calibrate
jr nc,1$ ; if no error jump
ld b,a
ld a,BLOCK_ERROR_CALIBRATION
scf
jr 101$
1$:
; calibrated, read sync bytes
call read_sync
jr nc,2$ ; if no error jump
ld a,BLOCK_ERROR_SYNC
scf
jr 101$
2$:
; reader initialized, start reading bytes
exx
ld e,0
ld hl,BLOCK_BUFFER
exx
4$:
call read_byte
jr nc,5$
; read error
exx
ld a,BLOCK_ERROR_READ
scf
jr 101$
5$:
exx
ld (hl),a
inc hl
inc e
ld a,e
cp BLOCK_SIZE
exx
jr nz,4$
; Block is in BLOCK_BUFFER
; calculate crc16
ld hl, BLOCK_BUFFER
ld a, BLOCK_SIZE-2 ; dont use received crc value
call crc16
; todo compare received crc with hl
ld bc, (BLOCK_BUFFER+BLOCK_SIZE-2)
or a ; clear carry
sbc hl,bc ; do hl-bc-c
jr z,6$ ; if the same jump
; crc error
ld a,BLOCK_ERROR_CHSUM
scf
jr 101$
6$:
; Block received correctly
ld hl,BLOCK_BUFFER
or a ; clear carry
101$:
exx
pop hl
pop de
exx
ret
; -------------------------------------------------------
; Read a block but do not store
; needs d (comparator state)
; uses b,c,d,e,h,l
; returns error code in a
; carry is fail
; -------------------------------------------------------
read_block_dry:
; Block reading uses de' and hl' so preserve them
exx
push de
push hl
exx
; calibrate reader
call calibrate
jr nc,1$ ; if no error jump
ld a,BLOCK_ERROR_CALIBRATION
scf
jr 101$
1$:
; calibrated, read sync bytes
call read_sync
jr nc,2$ ; if no error jump
ld a,BLOCK_ERROR_SYNC
scf
jr 101$
2$:
; reader initialized, start reading bytes
exx
ld e,0
exx
4$:
call read_byte
jr nc,5$
; read error
exx
ld a,BLOCK_ERROR_READ
scf
jr 101$
5$:
exx
inc hl
inc e
ld a,e
cp BLOCK_SIZE
exx
jr nz,4$
; Block fully received
or a ; clear carry
101$:
exx
pop hl
pop de
exx
ret
; -------------------------------------------------------
; CRC16
; CRC-16/CCITT-FALSE (polynomial 1021h, initial value 0ffffh)
; needs hl (pointer to memory buffer)
; needs a (length of buffer)
; returns hl (crc16)
; uses a,b,c,d,e
; -------------------------------------------------------
crc16:
ld b,a
ld de,0ffffh
or a
jr z,4$
1$:
ld a,(hl)
inc hl
xor d
ld d,a
ld c,8
2$:
sla e
rl d
jr nc,3$
ld a,e
xor 021h
ld e,a
ld a,d
xor 010h
ld d,a
3$:
dec c
jr nz,2$
djnz 1$
4$:
ex de,hl
ret
; -------------------------------------------------------
; Wait for silence
; uses a,b,d
; -------------------------------------------------------
wait_silence:
call sample_pulse
jr nc,wait_silence ; no timeout -> no silence
ret
; -------------------------------------------------------
; Strings
; -------------------------------------------------------
msg_start: .db "PLAY TAPE", 0ah,0dh,00h