.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