569 lines
9.7 KiB
NASM
569 lines
9.7 KiB
NASM
.include "bios.inc"
|
|
|
|
.area TAPELOAD (ABS)
|
|
.org TAPE_LOADER_ROM_ADDRESS
|
|
|
|
.macro print_var arg1
|
|
ld a, (arg1)
|
|
ei
|
|
call print_hex8
|
|
call print_crlf
|
|
di
|
|
.endm
|
|
|
|
.macro error_ret arg1,arg2
|
|
arg1:
|
|
ld a,#arg2
|
|
scf
|
|
ret
|
|
.endm
|
|
|
|
TAPE_PORT = 0x18
|
|
MAX_BLOCK_SIZE = 0x100
|
|
|
|
STACK_TOP = 0xBFFF
|
|
WORK_BASE = 0xB000
|
|
|
|
OLD_SP = WORK_BASE + 0x00 ; word
|
|
OLD_IFF = WORK_BASE + 0x02 ; byte
|
|
SHORT_AVG = WORK_BASE + 0x03 ; byte
|
|
SHORT_MAX = WORK_BASE + 0x04 ; byte
|
|
SHORT_MIN = WORK_BASE + 0x05 ; byte
|
|
LONG_AVG = WORK_BASE + 0x06 ; byte
|
|
LONG_MAX = WORK_BASE + 0x07 ; byte
|
|
LONG_MIN = WORK_BASE + 0x08 ; byte
|
|
|
|
BLOCK_BUFFER = WORK_BASE + 0x100 ; MAX_BLOCK_SIZE
|
|
|
|
; #################################################3
|
|
; Entry
|
|
; #################################################3
|
|
tape_loader:
|
|
ld (OLD_SP),sp
|
|
; Save interrupt state.
|
|
ld a,i
|
|
jp po,iff_was_off
|
|
ld a,#1
|
|
jr save_iff
|
|
iff_was_off:
|
|
xor a
|
|
save_iff:
|
|
ld (OLD_IFF),a
|
|
di
|
|
ld sp,#STACK_TOP
|
|
|
|
main:
|
|
; Initial message.
|
|
ld hl,#msg_start
|
|
call print_string_ei
|
|
|
|
; Calibrate reader
|
|
call calibrate
|
|
jr nc,1$
|
|
ei
|
|
call print_hex8
|
|
call print_crlf
|
|
ld hl,#msg_cal_fail
|
|
call print_string
|
|
di
|
|
jp exit
|
|
1$:
|
|
; try to read the sync bytes
|
|
call read_sync
|
|
jr nc,2$
|
|
ld hl,#msg_sync_error
|
|
call print_string_ei
|
|
jp exit
|
|
2$:
|
|
|
|
; Test, read Hello World!\0 (13 bytes)
|
|
exx
|
|
ld e,#0
|
|
ld hl,#BLOCK_BUFFER
|
|
exx
|
|
4$:
|
|
call read_byte
|
|
jr nc,5$
|
|
ld hl,#msg_read_error
|
|
call print_string_ei
|
|
exx
|
|
ei
|
|
call print_hex16
|
|
call print_crlf
|
|
di
|
|
exx
|
|
jp exit
|
|
5$:
|
|
exx
|
|
ld (hl),a
|
|
inc hl
|
|
inc e
|
|
ld a,e
|
|
cp a,#13
|
|
exx
|
|
jr nz,4$
|
|
|
|
; #################################################3
|
|
; Exit
|
|
; #################################################3
|
|
exit:
|
|
ei
|
|
call BIOS_FLUSH
|
|
di
|
|
ld sp,(OLD_SP)
|
|
; Return code 0
|
|
ld a,#0
|
|
ld c,a
|
|
; Restore caller's interrupt state.
|
|
ld a,(OLD_IFF)
|
|
or a
|
|
ld a,c
|
|
jr z,return_di
|
|
ei
|
|
ret
|
|
return_di:
|
|
ret
|
|
|
|
; #################################################3
|
|
; 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)
|
|
; #################################################3
|
|
measure_pulse:
|
|
ld b,#0 ; 4T
|
|
1$:
|
|
; Use b as timeout counter (256 loops)
|
|
inc b ; 4T
|
|
jr z,9$ ; 12/7T
|
|
in a,(TAPE_PORT) ; 11T
|
|
cp a,d ; 4T
|
|
jp z,1$ ; 10T
|
|
; loop has 36T
|
|
; Edge found
|
|
ld d,a ; 4T
|
|
or a,a ; 4T
|
|
ret ; 10T
|
|
9$:
|
|
scf
|
|
ret
|
|
|
|
; #################################################3
|
|
; Ignore one pulse then measure next pulse
|
|
; needs d (comparator state)
|
|
; returns b (loop count)
|
|
; carry is timeout
|
|
; uses a
|
|
; loop is 36T, extra 26T (0.7 loops)
|
|
; #################################################3
|
|
sample_pulse:
|
|
; wait for first edge
|
|
ld b,#0 ; 4T
|
|
1$:
|
|
; Use b as timeout counter (256 loops)
|
|
inc b ; 4T
|
|
jr z,9$ ; 12/7T
|
|
in a,(TAPE_PORT) ; 11T
|
|
cp a,d ; 4T
|
|
jp z,1$ ; 10T
|
|
; loop has 36T
|
|
; Edge found
|
|
ld d,a ; 4T
|
|
; start timing pulse length
|
|
ld b,#0 ; 4T
|
|
2$:
|
|
; Use b as timeout counter (256 loops)
|
|
inc b ; 4T
|
|
jr z,9$ ; 12/7T
|
|
in a,(TAPE_PORT) ; 11T
|
|
cp a,d ; 4T
|
|
jp z,2$ ; 10T
|
|
; loop has 36T
|
|
; Edge found
|
|
ld d,a ; 4T
|
|
or a,a ; 4T
|
|
ret ; 10T
|
|
9$:
|
|
scf
|
|
ret
|
|
|
|
; #################################################3
|
|
; Average 8 pulse measurements
|
|
; needs d (comparator state)
|
|
; returns a (average)
|
|
; carry is timeout
|
|
; uses b,e,h
|
|
; #################################################3
|
|
average8:
|
|
ld e,#0
|
|
ld h,#8
|
|
1$:
|
|
; synchronize to next pulse
|
|
; ignore first measurement
|
|
; contains part of the calculations
|
|
call sample_pulse
|
|
call sample_pulse
|
|
; when timeout just return with carry
|
|
ret c
|
|
; Add loop count to e
|
|
ld a,e
|
|
add a,b
|
|
ld e,a
|
|
; repeat 8 times
|
|
dec h
|
|
jr nz,1$
|
|
; divide by 8
|
|
ld a,e
|
|
srl a
|
|
srl a
|
|
srl a
|
|
; clear carry
|
|
or a,a
|
|
ret
|
|
|
|
; #################################################3
|
|
; Make +/-25% window around a
|
|
; needs a (average)
|
|
; 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
|
|
; #################################################3
|
|
make_window:
|
|
ld e,a
|
|
; get 25%
|
|
srl a
|
|
srl a
|
|
ld h,a
|
|
; a-25%
|
|
ld a,e
|
|
sub h
|
|
ld b,a
|
|
; a+25%
|
|
ld a,e
|
|
add a,h
|
|
ld c,a
|
|
ret
|
|
|
|
; #################################################3
|
|
; Calibration
|
|
; #################################################3
|
|
calibrate:
|
|
; Get current comparator state
|
|
in a,(TAPE_PORT)
|
|
ld d,a
|
|
|
|
; make sure we start in silence
|
|
call sample_pulse
|
|
; should timeout
|
|
jp nc,cal_fail_no_silence
|
|
|
|
; Wait for first edge
|
|
ld l,#0
|
|
1$:
|
|
call sample_pulse
|
|
jr nc,2$
|
|
; Use b as timeout counter (256 loops)
|
|
; inc l
|
|
; jr nz,1$
|
|
jr 1$
|
|
; timeout
|
|
;jp cal_fail_no_short_leader ; TODO IGNORED TIMEOUT FOR NOW HERE
|
|
2$:
|
|
; edge found
|
|
|
|
; get short leader timing
|
|
call average8
|
|
jr c,cal_fail_short
|
|
ld (SHORT_AVG),a
|
|
|
|
call make_window
|
|
ld a,b
|
|
ld (SHORT_MIN),a
|
|
ld a,c
|
|
ld (SHORT_MAX),a
|
|
|
|
; find long leader
|
|
; need 4 consecutive pulses > short*1.5
|
|
ld a,(SHORT_AVG)
|
|
ld c,a
|
|
srl a
|
|
add a,c
|
|
ld c,a
|
|
; c = short*1.5
|
|
|
|
; TODO no overal timeout here. a continuous
|
|
; short pulse leader will keep on waiting
|
|
; forever
|
|
3$:
|
|
ld e,#4
|
|
4$:
|
|
; ignore first to get accurate measurement
|
|
call sample_pulse
|
|
call sample_pulse
|
|
jr c,cal_fail_find_long
|
|
ld a,b
|
|
; if it is no long pulse try again
|
|
cp a,c
|
|
jr c,3$
|
|
; long pulse found
|
|
dec e
|
|
jr nz,4$
|
|
; 4 (or well... 8) long pulses found
|
|
|
|
; get long leader timning
|
|
call average8
|
|
jr c,cal_fail_long
|
|
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 a,c
|
|
ld c,a
|
|
; c = 1.5*short
|
|
ld a,(LONG_AVG)
|
|
cp a,c
|
|
jr c,cal_fail_sanity
|
|
|
|
; Find the marker
|
|
; marker is 2*long high 2*long low
|
|
; threshold is 1.5*long
|
|
ld a,(LONG_AVG)
|
|
ld c,a
|
|
srl a
|
|
add a,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_fail
|
|
ld a,b
|
|
cp a,c
|
|
jr c,5$
|
|
inc e
|
|
ld a,e
|
|
cp a,#2
|
|
jr c,6$
|
|
; Both marker halves consumed. We are now at the
|
|
; beginning of the first data pulse.
|
|
|
|
; next pulse is first pulse of training byte
|
|
; must decode as 0x55
|
|
call read_byte
|
|
jr c,cal_fail_read_error
|
|
cp a,#0x55
|
|
jr nz,cal_fail_training_byte
|
|
|
|
ld a,#0
|
|
or a,a
|
|
ret
|
|
|
|
error_ret cal_fail 0
|
|
error_ret cal_fail_short 1
|
|
error_ret cal_fail_find_long 2
|
|
error_ret cal_fail_long 3
|
|
error_ret cal_fail_sanity 4
|
|
error_ret cal_fail_training_byte 5
|
|
error_ret cal_fail_no_silence 6
|
|
error_ret cal_fail_no_short_leader 7
|
|
error_ret cal_fail_read_error 8
|
|
|
|
; #################################################3
|
|
; Read single bit
|
|
; needs d (comparator state)
|
|
; valid: z=1 c=bit
|
|
; error: z=0
|
|
; uses a,b,c
|
|
; #################################################3
|
|
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 a,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 a,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 a,c
|
|
jr c,bit_bad ; pulse<SHORT_MIN
|
|
ld a,(SHORT_MAX)
|
|
ld c,a
|
|
ld a,b
|
|
cp a,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 a,c
|
|
jr c,bit_bad ; pulse<LONG_MAX
|
|
ld a,(LONG_MAX)
|
|
ld c,a
|
|
ld a,b
|
|
cp a,c
|
|
jr c,bit_zero
|
|
jr z,bit_zero ; pulse<=LONG_MAX
|
|
bit_bad:
|
|
ld a,#1
|
|
or a,a
|
|
ret
|
|
bit_zero:
|
|
xor a,a
|
|
ret
|
|
bit_one:
|
|
xor a,a
|
|
scf
|
|
ret
|
|
|
|
; #################################################3
|
|
; Read byte, MSB first
|
|
; returns a (byte)
|
|
; carry means error
|
|
; needs d (comparator state)
|
|
; uses e,h,b,c
|
|
; #################################################3
|
|
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
|
|
|
|
; #################################################3
|
|
; Read and check sync bytes
|
|
; carry is error
|
|
; #################################################3
|
|
read_sync:
|
|
call read_byte
|
|
ret c
|
|
cp a,#0x5a
|
|
jr nz,9$
|
|
call read_byte
|
|
ret c
|
|
cp a,#0xc3
|
|
jr nz,9$
|
|
or a,a
|
|
ret
|
|
9$:
|
|
scf
|
|
ret
|
|
|
|
; #################################################3
|
|
; IO Helper functions
|
|
; #################################################3
|
|
print_string_ei:
|
|
ei
|
|
call print_string
|
|
call BIOS_FLUSH
|
|
di
|
|
ret
|
|
|
|
print_string:
|
|
ld a,(hl)
|
|
or a
|
|
ret z
|
|
call BIOS_PUTC
|
|
inc hl
|
|
jr print_string
|
|
|
|
print_crlf:
|
|
ld a,#13
|
|
call BIOS_PUTC
|
|
ld a,#10
|
|
jp BIOS_PUTC
|
|
|
|
print_hex16:
|
|
push hl
|
|
ld a,h
|
|
call print_hex8
|
|
pop hl
|
|
ld a,l
|
|
call print_hex8
|
|
ret
|
|
|
|
print_hex8:
|
|
push af
|
|
rrca
|
|
rrca
|
|
rrca
|
|
rrca
|
|
and #0x0F
|
|
call print_hex_nibble
|
|
pop af
|
|
and #0x0F
|
|
|
|
; fall through
|
|
print_hex_nibble:
|
|
cp #10
|
|
jr c,print_hex_digit
|
|
add a,#('A' - 10)
|
|
jp BIOS_PUTC
|
|
|
|
print_hex_digit:
|
|
add a,#'0'
|
|
jp BIOS_PUTC
|
|
|
|
; #################################################3
|
|
; Strings
|
|
; #################################################3
|
|
msg_start:
|
|
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
|
.db 13,10,0
|
|
msg_cal_fail:
|
|
.ascii "Calibration failed"
|
|
.db 13,10,0
|
|
msg_sync_error:
|
|
.ascii "Sync bytes not found"
|
|
.db 13,10,0
|
|
msg_read_error:
|
|
.ascii "read error"
|
|
.db 13,10,0 |