Files
z80_monitor/apps/casette/tape_loader.asm
T
2026-10-05 10:07:47 +02:00

585 lines
8.9 KiB
NASM

.include "bios.inc"
.area TAPELOAD (ABS)
.org TAPE_LOADER_ROM_ADDRESS
TAPE_PORT = 0x18
MAX_CYCLE_COUNT = 20
MAX_BLOCK_SIZE = 0x200
STACK_TOP = 0xBFFF
WORK_BASE = 0xB000
OLD_SP = WORK_BASE + 0x00 ; word
OLD_IFF = WORK_BASE + 0x02 ; byte
SHORT_LEN = WORK_BASE + 0x03 ; byte
LONG_LEN = WORK_BASE + 0x04 ; byte
TRESHOLD = WORK_BASE + 0x05 ; byte
SHORT_COUNT = WORK_BASE + 0x06 ; byte
LONG_COUNT = WORK_BASE + 0x07 ; byte
BLOCK_LENGTH = WORK_BASE + 0x08 ; word
BLOCK_NUMBER = WORK_BASE + 0x0a ; byte
NR_OF_BLOCKS = WORK_BASE + 0x0b ; word
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
; Initial message.
ld hl,#msg_start
call print_with_irqs
; Read the first block
call read_block
jr z,1$
ld hl,#msg_error_checksum
ei
call print_string
call print_crlf
di
jp exit
1$:
; nr of blocks and filename stored in buffer
ld hl,(BLOCK_BUFFER)
ld (NR_OF_BLOCKS),hl
; Print info of blocks
ei
ld hl,#msg_filename
call print_string
ld hl,#BLOCK_BUFFER
inc hl
inc hl
call print_string
call print_crlf
ld hl,#msg_nr_of_blocks
call print_string
ld hl,(NR_OF_BLOCKS)
call print_hex16
call print_crlf
call BIOS_FLUSH
di
; #################################################3
; Read a consecutive list of blocks
; #################################################3
read_consecutive_blocks:
ld bc,(NR_OF_BLOCKS)
push bc
1$:
call read_block
jr z,2$
ei
ld a,#'x'
call BIOS_PUTC
call BIOS_FLUSH
di
jr 3$
2$:
ei
ld a,#'*'
call BIOS_PUTC
call BIOS_FLUSH
di
3$:
pop bc
dec bc
push bc
; Check for zero
ld a,b
or a,c
jr nz,1$
pop bc
; All blocks are read
ei
ld a,#'V'
call BIOS_PUTC
call print_crlf
call BIOS_FLUSH
di
; #################################################3
; Exit
; #################################################3
exit:
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
; Wait for next edge
; D holds current comparator state
; Returns B = loop count
; #################################################3
wait_edge:
ld b,#0
1$:
inc b
in a,(TAPE_PORT)
cp a,d
jr z,1$
ld d,a
ret
; #################################################3
; Wait for a full cycle
; D holds the current comparator state
; Returns B = loop count
; #################################################3
wait_cycle:
ld b,#0
1$:
inc b
in a,(TAPE_PORT)
cp a,d
jp z,1$
ld d,a
2$:
inc b
in a,(TAPE_PORT)
cp a,d
jp z,2$
ld d,a
ret
; #################################################3
; Eat A cycles
; #################################################3
eat_cycles:
or a,a
ret z
ld c,a
1$:
call wait_cycle
dec c
jr nz,1$
ret
; #################################################3
; Average 16 cycle intervals
; Returns A = average loop count
; preserves bc
; #################################################3
average16:
push bc
ld c,#16
ld e,#0
1$:
call wait_cycle
; Add loop count to e and take moving average
ld a,e
add a,b
srl a
ld e,a
dec c
jr nz,1$
ld a,e
pop bc
ret
; #################################################3
; Calibrate
; Gets short and long pulse lenghts and bit pulse counts
; from training sequence
; carry = succeeded
; #################################################3
calibrate:
; establish comparator state
in a,(TAPE_PORT)
ld d,a
; Wait for the first edge
call wait_edge
; Wait for the next edge to be back in phase with the
; audio stream
call wait_edge
; Measure shoft pulses
call average16
ld (SHORT_LEN), a
; Threshold for finding long pulse -> short*1.5
; stored in c
ld c,a
srl a
add a,c
ld c,a
; Wait for the first long pulse
1$:
call average16
cp a,c
jr c,1$
; Measure long pulses
call average16
ld (LONG_LEN), a
; Calculate period treshold
ld b,a
ld a,(SHORT_LEN)
; Average between the two lengths
add a,b
srl a
ld (TRESHOLD),a
; Wait for first short for training
ld c,a
3$:
call wait_cycle
ld a,b
cp a,c
jr nc,3$
; Count short cycles
ld e,#1
4$:
call wait_cycle
ld a,b
cp a,c
jr nc,5$
inc e
ld a,#MAX_CYCLE_COUNT
cp a,e
jp c,error_cycle_count
jr 4$
5$:
ld a,e
ld (SHORT_COUNT),a
; Count long cycles
ld e,#1
6$:
call wait_cycle
ld a,b
cp a,c
jr c,7$
inc e
ld a,#MAX_CYCLE_COUNT
cp a,e
jp c,error_cycle_count
jr 6$
7$:
ld a,e
ld (LONG_COUNT),a
; First cycle of short was already consymed
ld a,(SHORT_COUNT)
dec a
call eat_cycles
; Consume rest of training bits
; 6 L/S pairs then last L
ld h,#6
8$:
ld a,(LONG_COUNT)
call eat_cycles
ld a,(SHORT_COUNT)
call eat_cycles
dec h
jr nz,8$
ld a,(LONG_COUNT)
call eat_cycles
; Test calibration with sync byte
call check_sync_byte
ret
error_cycle_count:
ei
ld hl,#msg_error_cycle_count
call print_string
call print_crlf
di
jp exit
; #################################################3
; Read one bit
; carry = value
; #################################################3
read_bit:
call wait_cycle
ld a,(TRESHOLD)
ld c,a
ld a,b
cp a,c
jr c,2$
; zero bit
1$:
ld a,(LONG_COUNT)
dec a
call eat_cycles
or a,a ; clear carry
ret
; one bit
2$:
ld a,(SHORT_COUNT)
dec a
call eat_cycles
scf ; set carry
ret
; #################################################3
; Read byte
; A = byte
; #################################################3
read_byte:
ld e,#0
ld h,#8
1$:
call read_bit
rl e
dec h
jr nz,1$
ld a,e
ret
; #################################################3
; Check sync byte
; carry = succeeded
; #################################################3
check_sync_byte:
call read_byte
ld e,a
ld a,#0xf0
cp a,e
jr z,1$
or a,a
ret
1$:
scf
ret
error_wrong_sync_byte:
ei
ld hl,#msg_error_sync_not_found
call print_string
call print_crlf
di
jp exit
; #################################################3
; Read a block
; checksum in a
; #################################################3
read_block:
call calibrate
jp nc,error_wrong_sync_byte
; Get block number
call read_byte
ld (BLOCK_NUMBER),a
; Get block length
call read_byte
ld b,a
push bc
call read_byte
pop bc
ld c,a
ld (BLOCK_LENGTH),bc
; Check block length against max length
;ld hl,#MAX_BLOCK_SIZE
;and a,a
;sbc hl, bc
;jp c,error_block_too_big
exx
ld bc,(BLOCK_LENGTH)
ld hl,#BLOCK_BUFFER
1$:
exx
call read_byte
exx
ld (hl),a
inc hl
dec bc
; Check for zero
ld a,b
or a,c
jr nz,1$
; Read checksum
exx
call read_byte
; Calculate checksum of block
ld hl,#BLOCK_BUFFER
ld bc,(BLOCK_LENGTH)
push af
xor a,a
ld e,a
2$:
ld a,e
add a,(hl)
ld e,a
inc hl
dec bc
; Check for zero
ld a,b
or a,c
jr nz,2$
pop af
; e is calculated checksum
; a is received checksum
ld h,a
ld l,e
cp a,e
ret
error_block_too_big:
ei
ld hl,#msg_error_block_too_big
call print_string
call print_crlf
di
jp exit
; #################################################3
; IO Helper functions
; #################################################3
print_with_irqs:
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_short_len:
.ascii "Short len: "
.db 0
msg_long_len:
.ascii "Long len: "
.db 0
msg_treshold:
.ascii "Treshold: "
.db 0
msg_short_count:
.ascii "Short count: "
.db 0
msg_long_count:
.ascii "Long count: "
.db 0
msg_error_cycle_count:
.ascii "Cycle count out of bounds"
.db 0
msg_error_sync_not_found:
.ascii "Sync not found"
.db 0
msg_error_checksum:
.ascii "Checksum error"
.db 0
msg_filename:
.ascii "Filename: "
.db 0
msg_nr_of_blocks:
.ascii "Blocks: "
.db 0
msg_error_block_too_big:
.ascii "Block too big"
.db 0