563 lines
15 KiB
NASM
563 lines
15 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
|
|
; 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
|
|
; -------------------------------------------------------
|
|
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 non-overlapping short and long pulse windows
|
|
; Boundary is the midpoint between both averages
|
|
; Short minimum and long maximum allow a 50% margin
|
|
; needs SHORT_AVG and LONG_AVG
|
|
; uses a,c
|
|
; -------------------------------------------------------
|
|
make_windows:
|
|
ld a,(SHORT_AVG)
|
|
srl a
|
|
ld (SHORT_MIN),a ; short minimum = 50% of short
|
|
|
|
ld a,(LONG_AVG)
|
|
ld c,a
|
|
inc a
|
|
srl a ; ceil(long/2)
|
|
add c
|
|
ld (LONG_MAX),a ; long maximum = 150% of long
|
|
|
|
ld a,(SHORT_AVG)
|
|
ld c,a
|
|
ld a,(LONG_AVG)
|
|
add c
|
|
inc a
|
|
srl a ; ceil((short+long)/2)
|
|
ld (LONG_MIN),a ; midpoint belongs to long
|
|
dec a
|
|
ld (SHORT_MAX),a ; short ends below midpoint
|
|
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:
|
|
in 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
|
|
; 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
|
|
; 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
|
|
call make_windows
|
|
; Find the marker: 8 consecutive short intervals
|
|
; Classify them using short*1.5
|
|
ld a,(SHORT_AVG)
|
|
ld c,a
|
|
srl a
|
|
add c
|
|
ld c,a ; c = 1.5*short
|
|
ld e,0 ; consecutive short counter
|
|
5$:
|
|
call measure_pulse
|
|
jr c,cal_err_marker_measure
|
|
ld a,b
|
|
cp c
|
|
jr c,6$ ; short interval found
|
|
ld e,0 ; long interval, restart marker
|
|
jr 5$
|
|
6$:
|
|
inc e
|
|
ld a,e
|
|
cp 8
|
|
jr c,5$
|
|
; marker found
|
|
; we are at the beginning of the first data pulse
|
|
; next byte 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, error value in a
|
|
; -------------------------------------------------------
|
|
read_sync:
|
|
call read_byte
|
|
jr c,sync_err_read_1
|
|
cp 05ah
|
|
jr nz,sync_err_value_1
|
|
call read_byte
|
|
jr c,sync_err_read_2
|
|
cp 0c3h
|
|
jr nz,sync_err_value_2
|
|
or a
|
|
ret
|
|
create_error_return sync_err_read_1, 001h
|
|
create_error_return sync_err_read_2, 002h
|
|
create_error_return sync_err_value_1, 003h
|
|
create_error_return sync_err_value_2, 004h
|
|
|
|
; -------------------------------------------------------
|
|
; 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
|
|
call BIOS_FLUSH
|
|
di
|
|
; 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 b,a
|
|
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 ; byte in a
|
|
jr nc,5$
|
|
; read error
|
|
exx
|
|
ld a,e
|
|
exx
|
|
ld b,a ; store count in block in specific error
|
|
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
|
|
ei
|
|
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
|
|
di
|
|
; 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
|
|
ei
|
|
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
|