Files
z80_monitor/apps/casette/tape_loader.asm
T
Joppe Blondel 0ca388c1d0 Pulse width encoding
Working hello world transfer in sim and irl
2026-10-05 18:14:23 +02:00

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