.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