1614 lines
26 KiB
NASM
1614 lines
26 KiB
NASM
.include "bios.inc"
|
|
|
|
.area TAPELOAD (ABS)
|
|
.org TAPE_LOADER_ROM_ADDRESS
|
|
|
|
|
|
; =====================================================================
|
|
; Cassette Manchester decoder diagnostic
|
|
;
|
|
; CPU: Z80 @ 1 MHz
|
|
; Comparator port: 0x18
|
|
;
|
|
; This stage:
|
|
;
|
|
; 1. Calibrates HIGH carrier from leader
|
|
; 2. Detects LOW carrier at start of 0x55 preamble
|
|
; 3. Learns FULL-bit HIGH/LOW run lengths from 0x55
|
|
; 4. Derives HALF/FULL run thresholds
|
|
; 5. Manchester-decodes bits
|
|
; 6. Searches bit-by-bit for sync word D3 91
|
|
; 7. Captures 30 following bytes
|
|
; 8. Prints the decoded bytes
|
|
;
|
|
; Nothing is written to 0x8000 yet.
|
|
;
|
|
; Interrupts remain disabled during tape reception.
|
|
; =====================================================================
|
|
|
|
|
|
COMP_PORT = 0x18
|
|
|
|
STACK_TOP = 0xBFFF
|
|
|
|
WORK_BASE = 0xB000
|
|
|
|
OLD_SP = WORK_BASE + 0x00 ; word
|
|
OLD_IFF = WORK_BASE + 0x02 ; byte
|
|
|
|
LEADER_REF = WORK_BASE + 0x03 ; byte
|
|
LEADER_AVG = WORK_BASE + 0x04 ; byte
|
|
LEADER_MAX = WORK_BASE + 0x05 ; byte
|
|
|
|
TONE_THRESHOLD = WORK_BASE + 0x06 ; byte
|
|
LOW_TRIGGER = WORK_BASE + 0x07 ; byte
|
|
|
|
CURRENT_TONE = WORK_BASE + 0x08 ; byte
|
|
OLD_TONE = WORK_BASE + 0x09 ; byte
|
|
TEMP_TONE = WORK_BASE + 0x0A ; byte
|
|
|
|
RUN_COUNT = WORK_BASE + 0x0B ; byte
|
|
|
|
HIGH_RUN_SUM = WORK_BASE + 0x0C ; word
|
|
LOW_RUN_SUM = WORK_BASE + 0x0E ; word
|
|
|
|
HIGH_RUN_AVG = WORK_BASE + 0x10 ; byte
|
|
LOW_RUN_AVG = WORK_BASE + 0x11 ; byte
|
|
|
|
HIGH_RUN_THRESH = WORK_BASE + 0x12 ; byte
|
|
LOW_RUN_THRESH = WORK_BASE + 0x13 ; byte
|
|
|
|
TRAIN_REMAIN = WORK_BASE + 0x14 ; byte
|
|
|
|
RETURN_CODE = WORK_BASE + 0x15 ; byte
|
|
|
|
|
|
; Buffer contains:
|
|
;
|
|
; D3 91 + 30 following decoded bytes
|
|
|
|
CAPTURE_BUF = 0xB100
|
|
CAPTURE_COUNT = 32
|
|
|
|
|
|
; =====================================================================
|
|
; Entry
|
|
; =====================================================================
|
|
|
|
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
|
|
|
|
|
|
; =====================================================================
|
|
; STEP 1
|
|
;
|
|
; Find stable leader.
|
|
;
|
|
; First period is the reference.
|
|
; Require another 31 periods within +/-2 counts.
|
|
; =====================================================================
|
|
|
|
find_leader:
|
|
|
|
call measure_halfperiod
|
|
|
|
|
|
ld (LEADER_REF),a
|
|
ld (LEADER_MAX),a
|
|
|
|
|
|
; HL = running sum
|
|
|
|
ld l,a
|
|
ld h,#0
|
|
|
|
|
|
; One sample already obtained.
|
|
|
|
ld b,#31
|
|
|
|
|
|
leader_stability_loop:
|
|
|
|
call measure_halfperiod
|
|
|
|
|
|
; E = measurement
|
|
|
|
ld e,a
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; abs(measurement - reference)
|
|
; ---------------------------------------------------------------------
|
|
|
|
ld a,(LEADER_REF)
|
|
|
|
cp e
|
|
jr nc,leader_ref_ge_sample
|
|
|
|
|
|
; measurement > reference
|
|
|
|
ld a,e
|
|
ld c,a
|
|
|
|
ld a,(LEADER_REF)
|
|
ld d,a
|
|
|
|
ld a,c
|
|
sub d
|
|
|
|
jr leader_diff_ready
|
|
|
|
|
|
leader_ref_ge_sample:
|
|
|
|
sub e
|
|
|
|
|
|
leader_diff_ready:
|
|
|
|
; Difference 0, 1 or 2 accepted.
|
|
|
|
cp #3
|
|
jp nc,find_leader
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Update maximum observed leader period.
|
|
; ---------------------------------------------------------------------
|
|
|
|
ld a,(LEADER_MAX)
|
|
|
|
cp e
|
|
jr nc,leader_max_ok
|
|
|
|
|
|
ld a,e
|
|
ld (LEADER_MAX),a
|
|
|
|
|
|
leader_max_ok:
|
|
|
|
; Add E to HL.
|
|
|
|
ld d,#0
|
|
|
|
add hl,de
|
|
|
|
|
|
djnz leader_stability_loop
|
|
|
|
|
|
; =====================================================================
|
|
; Rounded leader average.
|
|
;
|
|
; 32 samples.
|
|
;
|
|
; Add 16 before dividing by 32:
|
|
;
|
|
; average = (sum + 16) / 32
|
|
; =====================================================================
|
|
|
|
ld de,#16
|
|
add hl,de
|
|
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
|
|
ld a,l
|
|
|
|
or a
|
|
jp z,find_leader
|
|
|
|
|
|
ld (LEADER_AVG),a
|
|
|
|
|
|
; =====================================================================
|
|
; Initial carrier discrimination.
|
|
;
|
|
; From our real measurements:
|
|
;
|
|
; HIGH carrier = 03 / 04
|
|
; LOW carrier = 05 / 06
|
|
;
|
|
; Therefore:
|
|
;
|
|
; threshold = rounded leader average + 1
|
|
; low trigger = threshold + 1
|
|
;
|
|
; Expected:
|
|
;
|
|
; LEADER AVG = 03
|
|
; CARRIER THRESHOLD = 04
|
|
; LOW TRIGGER = 05
|
|
;
|
|
; Classification:
|
|
;
|
|
; <= threshold : HIGH
|
|
; > threshold : LOW
|
|
; =====================================================================
|
|
|
|
ld a,(LEADER_AVG)
|
|
|
|
inc a
|
|
|
|
jr nz,tone_threshold_ok
|
|
|
|
|
|
ld a,#0xFE
|
|
|
|
|
|
tone_threshold_ok:
|
|
|
|
ld (TONE_THRESHOLD),a
|
|
|
|
|
|
inc a
|
|
|
|
jr nz,low_trigger_ok
|
|
|
|
|
|
ld a,#0xFF
|
|
|
|
|
|
low_trigger_ok:
|
|
|
|
ld (LOW_TRIGGER),a
|
|
|
|
|
|
; =====================================================================
|
|
; STEP 2
|
|
;
|
|
; Wait for THREE consecutive definitely-LOW measurements.
|
|
;
|
|
; This prevents a single slightly-long leader measurement from
|
|
; triggering the preamble detector.
|
|
; =====================================================================
|
|
|
|
wait_for_low_restart:
|
|
|
|
ld b,#3
|
|
|
|
|
|
wait_for_low:
|
|
|
|
call measure_halfperiod
|
|
|
|
|
|
ld e,a
|
|
|
|
|
|
ld a,(LOW_TRIGGER)
|
|
|
|
cp e
|
|
|
|
|
|
; E >= LOW_TRIGGER ?
|
|
|
|
jr z,low_candidate
|
|
jr c,low_candidate
|
|
|
|
|
|
jp wait_for_low_restart
|
|
|
|
|
|
low_candidate:
|
|
|
|
djnz wait_for_low
|
|
|
|
|
|
; =====================================================================
|
|
; We are now somewhere inside the first LOW run of the preamble.
|
|
;
|
|
; Discard the rest of that partial run.
|
|
;
|
|
; scan_run returns after detecting the transition to HIGH.
|
|
; =====================================================================
|
|
|
|
xor a ; LOW
|
|
|
|
call scan_run
|
|
|
|
|
|
; Must have changed to HIGH.
|
|
|
|
or a
|
|
jp z,training_error
|
|
|
|
|
|
ld (CURRENT_TONE),a
|
|
|
|
|
|
; =====================================================================
|
|
; STEP 3
|
|
;
|
|
; Train using 16 complete carrier runs from the 0x55 preamble.
|
|
;
|
|
; 0x55 produces alternating FULL runs:
|
|
;
|
|
; HIGH FULL
|
|
; LOW FULL
|
|
; HIGH FULL
|
|
; LOW FULL
|
|
; ...
|
|
;
|
|
; 16 runs gives:
|
|
;
|
|
; 8 HIGH
|
|
; 8 LOW
|
|
; =====================================================================
|
|
|
|
ld hl,#0x0000
|
|
|
|
ld (HIGH_RUN_SUM),hl
|
|
ld (LOW_RUN_SUM),hl
|
|
|
|
|
|
ld a,#16
|
|
ld (TRAIN_REMAIN),a
|
|
|
|
|
|
train_run_loop:
|
|
|
|
ld a,(CURRENT_TONE)
|
|
ld (OLD_TONE),a
|
|
|
|
|
|
call scan_run
|
|
|
|
|
|
; A = new tone.
|
|
|
|
ld (TEMP_TONE),a
|
|
|
|
|
|
; Reject empty run.
|
|
|
|
ld a,(RUN_COUNT)
|
|
|
|
or a
|
|
jp z,training_error
|
|
|
|
|
|
; DE = run count.
|
|
|
|
ld e,a
|
|
ld d,#0
|
|
|
|
|
|
ld a,(OLD_TONE)
|
|
|
|
or a
|
|
jr z,train_low_run
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; HIGH full run.
|
|
; ---------------------------------------------------------------------
|
|
|
|
ld hl,(HIGH_RUN_SUM)
|
|
|
|
add hl,de
|
|
|
|
ld (HIGH_RUN_SUM),hl
|
|
|
|
jr train_run_added
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; LOW full run.
|
|
; ---------------------------------------------------------------------
|
|
|
|
train_low_run:
|
|
|
|
ld hl,(LOW_RUN_SUM)
|
|
|
|
add hl,de
|
|
|
|
ld (LOW_RUN_SUM),hl
|
|
|
|
|
|
train_run_added:
|
|
|
|
ld a,(TEMP_TONE)
|
|
ld (CURRENT_TONE),a
|
|
|
|
|
|
ld a,(TRAIN_REMAIN)
|
|
|
|
dec a
|
|
|
|
ld (TRAIN_REMAIN),a
|
|
|
|
jp nz,train_run_loop
|
|
|
|
|
|
; =====================================================================
|
|
; Average 8 HIGH and 8 LOW runs.
|
|
; =====================================================================
|
|
|
|
ld hl,(HIGH_RUN_SUM)
|
|
|
|
call divide_hl_by_8
|
|
|
|
|
|
ld a,l
|
|
|
|
or a
|
|
jp z,training_error
|
|
|
|
|
|
ld (HIGH_RUN_AVG),a
|
|
|
|
|
|
ld hl,(LOW_RUN_SUM)
|
|
|
|
call divide_hl_by_8
|
|
|
|
|
|
ld a,l
|
|
|
|
or a
|
|
jp z,training_error
|
|
|
|
|
|
ld (LOW_RUN_AVG),a
|
|
|
|
|
|
; =====================================================================
|
|
; Derive HALF/FULL discriminator.
|
|
;
|
|
; threshold = ceil(3/4 * full_run)
|
|
;
|
|
; Expected from our current measurements:
|
|
;
|
|
; HIGH FULL ~= 3
|
|
; HIGH threshold = 3
|
|
;
|
|
; LOW FULL ~= 2
|
|
; LOW threshold = 2
|
|
;
|
|
; Thus:
|
|
;
|
|
; HIGH 1/2 -> HALF, 3+ -> FULL
|
|
; LOW 1 -> HALF, 2+ -> FULL
|
|
; =====================================================================
|
|
|
|
ld a,(HIGH_RUN_AVG)
|
|
|
|
call make_run_threshold
|
|
|
|
ld (HIGH_RUN_THRESH),a
|
|
|
|
|
|
ld a,(LOW_RUN_AVG)
|
|
|
|
call make_run_threshold
|
|
|
|
ld (LOW_RUN_THRESH),a
|
|
|
|
|
|
; =====================================================================
|
|
; We are currently at a Manchester midpoint in the remaining 0x55
|
|
; preamble.
|
|
;
|
|
; CURRENT_TONE is the carrier immediately following that midpoint.
|
|
;
|
|
; Search the decoded BIT STREAM for:
|
|
;
|
|
; D3 91
|
|
;
|
|
; Searching bit-by-bit means we do not need to know byte alignment.
|
|
;
|
|
; HL = 16-bit shift register
|
|
; DE = timeout counter
|
|
; =====================================================================
|
|
|
|
ld hl,#0x0000
|
|
|
|
ld de,#8192
|
|
|
|
|
|
sync_search_loop:
|
|
|
|
call get_bit
|
|
|
|
|
|
; C = decoded bit
|
|
|
|
ld c,a
|
|
|
|
|
|
; Shift previous bits left.
|
|
|
|
add hl,hl
|
|
|
|
|
|
; Append new bit.
|
|
|
|
ld a,l
|
|
|
|
or c
|
|
|
|
ld l,a
|
|
|
|
|
|
; D3 91 ?
|
|
|
|
ld a,h
|
|
|
|
cp #0xD3
|
|
jr nz,sync_not_found
|
|
|
|
|
|
ld a,l
|
|
|
|
cp #0x91
|
|
jp z,sync_found
|
|
|
|
|
|
sync_not_found:
|
|
|
|
dec de
|
|
|
|
|
|
; DEC DE doesn't affect flags.
|
|
|
|
ld a,d
|
|
or e
|
|
|
|
jp nz,sync_search_loop
|
|
|
|
|
|
jp sync_error
|
|
|
|
|
|
; =====================================================================
|
|
; Sync found.
|
|
;
|
|
; We are exactly byte-aligned immediately after the 0x91.
|
|
;
|
|
; Store D3 91 and then decode another 30 bytes.
|
|
; =====================================================================
|
|
|
|
sync_found:
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
|
|
ld a,#0xD3
|
|
ld (hl),a
|
|
inc hl
|
|
|
|
|
|
ld a,#0x91
|
|
ld (hl),a
|
|
inc hl
|
|
|
|
|
|
ld b,#30
|
|
|
|
|
|
capture_decoded_bytes:
|
|
|
|
call get_byte
|
|
|
|
ld (hl),a
|
|
|
|
inc hl
|
|
|
|
|
|
djnz capture_decoded_bytes
|
|
|
|
|
|
; =====================================================================
|
|
; Reception complete.
|
|
;
|
|
; We can now enable interrupts and print.
|
|
; =====================================================================
|
|
|
|
ei
|
|
|
|
|
|
ld hl,#msg_sync_ok
|
|
call print_string
|
|
|
|
|
|
call print_training_values
|
|
|
|
|
|
ld hl,#msg_bytes
|
|
call print_string
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
ld b,#CAPTURE_COUNT
|
|
ld c,#0
|
|
|
|
|
|
print_byte_loop:
|
|
|
|
ld a,(hl)
|
|
|
|
call print_hex8
|
|
|
|
|
|
ld a,#' '
|
|
call BIOS_PUTC
|
|
|
|
|
|
inc hl
|
|
inc c
|
|
|
|
|
|
; 16 bytes per line.
|
|
|
|
ld a,c
|
|
|
|
and #0x0F
|
|
|
|
jr nz,print_byte_no_crlf
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
print_byte_no_crlf:
|
|
|
|
djnz print_byte_loop
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
xor a
|
|
ld (RETURN_CODE),a
|
|
|
|
jp diagnostic_exit
|
|
|
|
|
|
; =====================================================================
|
|
; Error paths
|
|
; =====================================================================
|
|
|
|
training_error:
|
|
|
|
ld a,#1
|
|
ld (RETURN_CODE),a
|
|
|
|
|
|
ei
|
|
|
|
|
|
ld hl,#msg_training_error
|
|
call print_string
|
|
|
|
call print_training_values
|
|
|
|
jp diagnostic_exit
|
|
|
|
|
|
sync_error:
|
|
|
|
ld a,#2
|
|
ld (RETURN_CODE),a
|
|
|
|
|
|
ei
|
|
|
|
|
|
ld hl,#msg_sync_error
|
|
call print_string
|
|
|
|
call print_training_values
|
|
|
|
jp diagnostic_exit
|
|
|
|
|
|
timing_error:
|
|
|
|
ld a,#3
|
|
ld (RETURN_CODE),a
|
|
|
|
|
|
ei
|
|
|
|
|
|
ld hl,#msg_timing_error
|
|
call print_string
|
|
|
|
call print_training_values
|
|
|
|
|
|
; =====================================================================
|
|
; Exit
|
|
; =====================================================================
|
|
|
|
diagnostic_exit:
|
|
|
|
di
|
|
|
|
|
|
ld sp,(OLD_SP)
|
|
|
|
|
|
ld a,(RETURN_CODE)
|
|
ld c,a
|
|
|
|
|
|
; Restore caller's interrupt state.
|
|
|
|
ld a,(OLD_IFF)
|
|
|
|
or a
|
|
|
|
|
|
ld a,c
|
|
|
|
jr z,diagnostic_return_di
|
|
|
|
|
|
ei
|
|
ret
|
|
|
|
|
|
diagnostic_return_di:
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; measure_halfperiod
|
|
;
|
|
; Wait for one comparator edge, then measure the COMPLETE interval
|
|
; between that edge and the following edge.
|
|
;
|
|
; Returns:
|
|
;
|
|
; A = count 1..255
|
|
;
|
|
; Saturates at FF.
|
|
;
|
|
; NOTE:
|
|
;
|
|
; Each invocation first synchronizes to an edge and then measures the
|
|
; next interval, so sequential calls effectively skip one raw
|
|
; half-period between measured intervals.
|
|
;
|
|
; That's okay because training and decoding use the same mechanism.
|
|
; =====================================================================
|
|
|
|
measure_halfperiod:
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
ld d,a
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Synchronize to first edge.
|
|
; ---------------------------------------------------------------------
|
|
|
|
mhp_wait_first_edge:
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
cp d
|
|
|
|
jr z,mhp_wait_first_edge
|
|
|
|
|
|
; Start measurement at this edge.
|
|
|
|
ld d,a
|
|
|
|
ld c,#0
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Measure until next edge.
|
|
; ---------------------------------------------------------------------
|
|
|
|
mhp_measure_loop:
|
|
|
|
inc c
|
|
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
cp d
|
|
|
|
jr nz,mhp_done
|
|
|
|
|
|
ld a,c
|
|
|
|
cp #0xFF
|
|
|
|
jr nz,mhp_measure_loop
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Saturated.
|
|
; ---------------------------------------------------------------------
|
|
|
|
mhp_saturated:
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
cp d
|
|
|
|
jr z,mhp_saturated
|
|
|
|
|
|
ld a,#0xFF
|
|
|
|
ret
|
|
|
|
|
|
mhp_done:
|
|
|
|
ld a,c
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; classify_sample
|
|
;
|
|
; Input:
|
|
;
|
|
; A = measured carrier period
|
|
;
|
|
; Output:
|
|
;
|
|
; A = 1 HIGH-frequency carrier
|
|
; A = 0 LOW-frequency carrier
|
|
;
|
|
; sample <= TONE_THRESHOLD : HIGH
|
|
; sample > TONE_THRESHOLD : LOW
|
|
;
|
|
; Preserves BC, DE, HL.
|
|
; =====================================================================
|
|
|
|
classify_sample:
|
|
|
|
push bc
|
|
|
|
|
|
ld c,a
|
|
|
|
|
|
ld a,(TONE_THRESHOLD)
|
|
|
|
cp c
|
|
|
|
|
|
; threshold < sample -> LOW
|
|
|
|
jr c,classify_low
|
|
|
|
|
|
ld a,#1
|
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
classify_low:
|
|
|
|
xor a
|
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; scan_run
|
|
;
|
|
; Input:
|
|
;
|
|
; A = carrier currently present
|
|
;
|
|
; Output:
|
|
;
|
|
; RUN_COUNT = measured run length
|
|
; A = newly detected carrier
|
|
;
|
|
;
|
|
; IMPORTANT:
|
|
;
|
|
; The previous scan_run consumed the first measurement belonging to
|
|
; this run while detecting the previous transition.
|
|
;
|
|
; Therefore the run starts at count 1, not 0.
|
|
;
|
|
; This was the important fix which changed training from:
|
|
;
|
|
; H=2 L=1
|
|
;
|
|
; to:
|
|
;
|
|
; H=3 L=2
|
|
; =====================================================================
|
|
|
|
scan_run:
|
|
|
|
ld e,a ; expected tone
|
|
|
|
|
|
; First sample of this run was already consumed by the
|
|
; previous transition detection.
|
|
|
|
ld b,#1
|
|
|
|
|
|
scan_run_loop:
|
|
|
|
call measure_halfperiod
|
|
|
|
call classify_sample
|
|
|
|
|
|
cp e
|
|
|
|
jr nz,scan_run_changed
|
|
|
|
|
|
inc b
|
|
|
|
|
|
; Protect against wraparound.
|
|
|
|
jp z,timing_error
|
|
|
|
|
|
jr scan_run_loop
|
|
|
|
|
|
scan_run_changed:
|
|
|
|
; A contains new tone.
|
|
|
|
ld (TEMP_TONE),a
|
|
|
|
|
|
ld a,b
|
|
|
|
ld (RUN_COUNT),a
|
|
|
|
|
|
ld a,(TEMP_TONE)
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; divide_hl_by_8
|
|
; =====================================================================
|
|
|
|
divide_hl_by_8:
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
srl h
|
|
rr l
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; make_run_threshold
|
|
;
|
|
; Input:
|
|
;
|
|
; A = learned FULL run length
|
|
;
|
|
; Output:
|
|
;
|
|
; A = ceil(3/4 * FULL)
|
|
;
|
|
; Formula:
|
|
;
|
|
; (3*FULL + 3) / 4
|
|
;
|
|
; Examples:
|
|
;
|
|
; full = 2 -> threshold = 2
|
|
; full = 3 -> threshold = 3
|
|
; full = 4 -> threshold = 3
|
|
; full = 5 -> threshold = 4
|
|
; full = 6 -> threshold = 5
|
|
; =====================================================================
|
|
|
|
make_run_threshold:
|
|
|
|
ld b,a
|
|
|
|
|
|
add a,a ; 2 * full
|
|
|
|
add a,b ; 3 * full
|
|
|
|
add a,#3
|
|
|
|
|
|
srl a
|
|
srl a
|
|
|
|
|
|
or a
|
|
ret nz
|
|
|
|
|
|
ld a,#1
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; run_is_full
|
|
;
|
|
; Input:
|
|
;
|
|
; A = carrier of run just measured
|
|
;
|
|
; Uses RUN_COUNT.
|
|
;
|
|
; Output:
|
|
;
|
|
; A = 0 HALF
|
|
; A = 1 FULL
|
|
; =====================================================================
|
|
|
|
run_is_full:
|
|
|
|
or a
|
|
|
|
jr z,run_check_low
|
|
|
|
|
|
ld a,(HIGH_RUN_THRESH)
|
|
|
|
jr run_do_compare
|
|
|
|
|
|
run_check_low:
|
|
|
|
ld a,(LOW_RUN_THRESH)
|
|
|
|
|
|
run_do_compare:
|
|
|
|
ld b,a
|
|
|
|
|
|
ld a,(RUN_COUNT)
|
|
|
|
cp b
|
|
|
|
|
|
; run_count < threshold -> HALF
|
|
|
|
jr c,run_is_half
|
|
|
|
|
|
ld a,#1
|
|
|
|
ret
|
|
|
|
|
|
run_is_half:
|
|
|
|
xor a
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; get_bit
|
|
;
|
|
; Entry:
|
|
;
|
|
; positioned immediately after a known Manchester midpoint
|
|
;
|
|
; CURRENT_TONE = carrier following that midpoint
|
|
;
|
|
;
|
|
; If the next carrier run is FULL:
|
|
;
|
|
; its ending transition is the next midpoint.
|
|
;
|
|
;
|
|
; If the next carrier run is HALF:
|
|
;
|
|
; its ending transition is a bit boundary.
|
|
;
|
|
; Consume another HALF run to reach the midpoint.
|
|
;
|
|
;
|
|
; At a midpoint:
|
|
;
|
|
; LOW -> HIGH = bit 0
|
|
; HIGH -> LOW = bit 1
|
|
;
|
|
; Since TEMP_TONE is the tone AFTER the midpoint:
|
|
;
|
|
; new HIGH = bit 0
|
|
; new LOW = bit 1
|
|
;
|
|
; Returns:
|
|
;
|
|
; A = 0 or 1
|
|
;
|
|
; Preserves BC, DE, HL.
|
|
; =====================================================================
|
|
|
|
get_bit:
|
|
|
|
push bc
|
|
push de
|
|
push hl
|
|
|
|
|
|
ld a,(CURRENT_TONE)
|
|
|
|
ld (OLD_TONE),a
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Measure first run after previous midpoint.
|
|
; ---------------------------------------------------------------------
|
|
|
|
call scan_run
|
|
|
|
|
|
; A = new tone.
|
|
|
|
ld (TEMP_TONE),a
|
|
|
|
|
|
; Was old run FULL or HALF?
|
|
|
|
ld a,(OLD_TONE)
|
|
|
|
call run_is_full
|
|
|
|
|
|
or a
|
|
|
|
jr nz,get_bit_at_midpoint
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; HALF:
|
|
;
|
|
; First transition was a bit boundary.
|
|
;
|
|
; Consume the second HALF run.
|
|
; ---------------------------------------------------------------------
|
|
|
|
ld a,(TEMP_TONE)
|
|
|
|
ld (CURRENT_TONE),a
|
|
|
|
|
|
call scan_run
|
|
|
|
|
|
ld (TEMP_TONE),a
|
|
|
|
|
|
; This second run should also be HALF.
|
|
|
|
ld a,(CURRENT_TONE)
|
|
|
|
call run_is_full
|
|
|
|
|
|
or a
|
|
|
|
jp nz,timing_error
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; We are now at a Manchester midpoint.
|
|
; ---------------------------------------------------------------------
|
|
|
|
get_bit_at_midpoint:
|
|
|
|
ld a,(TEMP_TONE)
|
|
|
|
ld (CURRENT_TONE),a
|
|
|
|
|
|
; New HIGH means LOW -> HIGH = bit 0.
|
|
|
|
or a
|
|
|
|
jr z,get_bit_one
|
|
|
|
|
|
xor a
|
|
|
|
jr get_bit_done
|
|
|
|
|
|
get_bit_one:
|
|
|
|
ld a,#1
|
|
|
|
|
|
get_bit_done:
|
|
|
|
pop hl
|
|
pop de
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; get_byte
|
|
;
|
|
; Decode eight bits, MSB first.
|
|
; =====================================================================
|
|
|
|
get_byte:
|
|
|
|
push bc
|
|
push de
|
|
|
|
|
|
ld b,#8
|
|
ld e,#0
|
|
|
|
|
|
get_byte_loop:
|
|
|
|
call get_bit
|
|
|
|
|
|
ld d,a
|
|
|
|
|
|
sla e
|
|
|
|
|
|
ld a,d
|
|
|
|
or e
|
|
|
|
ld e,a
|
|
|
|
|
|
djnz get_byte_loop
|
|
|
|
|
|
ld a,e
|
|
|
|
|
|
pop de
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; Diagnostic printing
|
|
; =====================================================================
|
|
|
|
print_training_values:
|
|
|
|
ld hl,#msg_leader
|
|
call print_string
|
|
|
|
ld a,(LEADER_AVG)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_leader_max
|
|
call print_string
|
|
|
|
ld a,(LEADER_MAX)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_low_trigger
|
|
call print_string
|
|
|
|
ld a,(LOW_TRIGGER)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_tone_threshold
|
|
call print_string
|
|
|
|
ld a,(TONE_THRESHOLD)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_high_run
|
|
call print_string
|
|
|
|
ld a,(HIGH_RUN_AVG)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_low_run
|
|
call print_string
|
|
|
|
ld a,(LOW_RUN_AVG)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_high_threshold
|
|
call print_string
|
|
|
|
ld a,(HIGH_RUN_THRESH)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_low_threshold
|
|
call print_string
|
|
|
|
ld a,(LOW_RUN_THRESH)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
call print_crlf
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; Printing helpers
|
|
; =====================================================================
|
|
|
|
print_with_irqs:
|
|
|
|
ei
|
|
|
|
call print_string
|
|
|
|
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_hex8
|
|
;
|
|
; Print A as two hexadecimal characters.
|
|
; =====================================================================
|
|
|
|
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
|
|
|
|
|
|
; =====================================================================
|
|
; Strings
|
|
; =====================================================================
|
|
|
|
msg_start:
|
|
|
|
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_sync_ok:
|
|
|
|
.ascii "SYNC D3 91 FOUND"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_training_error:
|
|
|
|
.ascii "TAPE TRAINING ERROR"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_sync_error:
|
|
|
|
.ascii "TAPE SYNC ERROR"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_timing_error:
|
|
|
|
.ascii "TAPE MANCHESTER TIMING ERROR"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_leader:
|
|
|
|
.ascii "LEADER AVG = "
|
|
.db 0
|
|
|
|
|
|
msg_leader_max:
|
|
|
|
.ascii "LEADER MAX = "
|
|
.db 0
|
|
|
|
|
|
msg_low_trigger:
|
|
|
|
.ascii "LOW TRIGGER = "
|
|
.db 0
|
|
|
|
|
|
msg_tone_threshold:
|
|
|
|
.ascii "CARRIER THRESHOLD = "
|
|
.db 0
|
|
|
|
|
|
msg_high_run:
|
|
|
|
.ascii "HIGH FULL RUN = "
|
|
.db 0
|
|
|
|
|
|
msg_low_run:
|
|
|
|
.ascii "LOW FULL RUN = "
|
|
.db 0
|
|
|
|
|
|
msg_high_threshold:
|
|
|
|
.ascii "HIGH RUN THRESH = "
|
|
.db 0
|
|
|
|
|
|
msg_low_threshold:
|
|
|
|
.ascii "LOW RUN THRESH = "
|
|
.db 0
|
|
|
|
|
|
msg_bytes:
|
|
|
|
.ascii "DECODED BYTES:"
|
|
.db 0 |