585 lines
8.9 KiB
NASM
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
|
|
|