1055 lines
18 KiB
NASM
1055 lines
18 KiB
NASM
.include "bios.inc"
|
|
|
|
.area TAPELOAD (ABS)
|
|
.org TAPE_LOADER_ROM_ADDRESS
|
|
|
|
|
|
; =====================================================================
|
|
; Cassette training diagnostic
|
|
;
|
|
; CPU: Z80 @ 1 MHz
|
|
; Comparator: port 0x18
|
|
;
|
|
; Comparator register:
|
|
; D0 = comparator output
|
|
; D7..D1 = 0
|
|
;
|
|
; This program DOES NOT decode data yet.
|
|
;
|
|
; It:
|
|
; 1. Finds a stable leader
|
|
; 2. Measures leader timing
|
|
; 3. Waits for 3 consecutive slower-carrier periods
|
|
; 4. Captures 128 raw comparator half-periods
|
|
; 5. Prints the raw values
|
|
; 6. Classifies them as H/L
|
|
; 7. Prints carrier run lengths
|
|
;
|
|
; All sampling happens with interrupts disabled.
|
|
; Printing happens only before or after sampling.
|
|
; =====================================================================
|
|
|
|
|
|
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
|
|
|
|
RETURN_CODE = WORK_BASE + 0x08 ; byte
|
|
|
|
CAPTURE_BUF = 0xB100
|
|
CAPTURE_COUNT = 128
|
|
|
|
|
|
; =====================================================================
|
|
; Entry
|
|
; =====================================================================
|
|
|
|
tape_loader:
|
|
|
|
; -------------------------------------------------------------
|
|
; Save caller stack.
|
|
; -------------------------------------------------------------
|
|
|
|
ld (OLD_SP),sp
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Save interrupt state.
|
|
;
|
|
; LD A,I copies IFF2 into P/V.
|
|
; -------------------------------------------------------------
|
|
|
|
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
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Use our own stack.
|
|
; -------------------------------------------------------------
|
|
|
|
di
|
|
|
|
ld sp,#STACK_TOP
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Startup message.
|
|
;
|
|
; BIOS printing requires interrupts.
|
|
; print_with_irqs returns with interrupts disabled.
|
|
; -------------------------------------------------------------
|
|
|
|
ld hl,#msg_start
|
|
call print_with_irqs
|
|
|
|
|
|
; =====================================================================
|
|
; Find stable leader
|
|
;
|
|
; The first measurement becomes our reference.
|
|
;
|
|
; Require another 31 measurements within +/-2 counts of that reference.
|
|
;
|
|
; At 1 MHz we only get a few loop counts per carrier half-period, so
|
|
; allowing +/-2 is intentionally generous.
|
|
;
|
|
; We also keep track of the maximum observed leader count.
|
|
; =====================================================================
|
|
|
|
find_leader:
|
|
|
|
call measure_halfperiod
|
|
|
|
|
|
ld (LEADER_REF),a
|
|
ld (LEADER_MAX),a
|
|
|
|
|
|
; HL = running sum.
|
|
|
|
ld l,a
|
|
ld h,#0
|
|
|
|
|
|
; We already have one sample.
|
|
; Need 31 more for 32 total.
|
|
|
|
ld b,#31
|
|
|
|
|
|
leader_stability_loop:
|
|
|
|
call measure_halfperiod
|
|
|
|
|
|
; E = measurement
|
|
|
|
ld e,a
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Calculate:
|
|
;
|
|
; abs(sample - reference)
|
|
;
|
|
; without destroying E.
|
|
; -------------------------------------------------------------
|
|
|
|
ld a,(LEADER_REF)
|
|
|
|
cp e
|
|
jr nc,leader_ref_ge_sample
|
|
|
|
|
|
; sample > 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:
|
|
|
|
; A still contains reference.
|
|
|
|
sub e
|
|
|
|
|
|
leader_diff_ready:
|
|
|
|
; Difference 0, 1, or 2 accepted.
|
|
;
|
|
; >= 3 means this isn't a stable leader.
|
|
; Start over.
|
|
|
|
cp #3
|
|
jr nc,find_leader
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Update maximum leader period.
|
|
; -------------------------------------------------------------
|
|
|
|
ld a,(LEADER_MAX)
|
|
|
|
cp e
|
|
jr nc,leader_max_ok
|
|
|
|
|
|
ld a,e
|
|
ld (LEADER_MAX),a
|
|
|
|
|
|
leader_max_ok:
|
|
|
|
; -------------------------------------------------------------
|
|
; Add sample to 16-bit sum.
|
|
; -------------------------------------------------------------
|
|
|
|
ld d,#0
|
|
|
|
add hl,de
|
|
|
|
|
|
djnz leader_stability_loop
|
|
|
|
|
|
; =====================================================================
|
|
; Calculate leader average.
|
|
;
|
|
; 32 samples -> divide by 32.
|
|
; =====================================================================
|
|
|
|
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
|
|
jr z,find_leader
|
|
|
|
|
|
ld (LEADER_AVG),a
|
|
|
|
|
|
; =====================================================================
|
|
; Initial carrier threshold.
|
|
;
|
|
; Everything up to LEADER_MAX is treated as the HIGH-frequency carrier.
|
|
;
|
|
; To decide that the LOW-frequency carrier has appeared, require:
|
|
;
|
|
; measurement >= LEADER_MAX + 1
|
|
;
|
|
; AND require this to happen three times consecutively.
|
|
;
|
|
; Example from your previous capture:
|
|
;
|
|
; leader average ~= 02
|
|
; leader max ~= 03 or 04
|
|
;
|
|
; A single 04 can therefore no longer falsely trigger acquisition.
|
|
; =====================================================================
|
|
|
|
ld a,(LEADER_MAX)
|
|
|
|
ld (TONE_THRESHOLD),a
|
|
|
|
|
|
inc a
|
|
|
|
jr nz,low_trigger_ok
|
|
|
|
|
|
; Protect against overflow, although this should never happen.
|
|
|
|
ld a,#0xFF
|
|
|
|
|
|
low_trigger_ok:
|
|
|
|
ld (LOW_TRIGGER),a
|
|
|
|
|
|
; =====================================================================
|
|
; Wait for the beginning of the slower carrier.
|
|
;
|
|
; We require THREE consecutive periods >= LOW_TRIGGER.
|
|
;
|
|
; This is the important change from the previous diagnostic.
|
|
; =====================================================================
|
|
|
|
wait_for_low_restart:
|
|
|
|
ld b,#3
|
|
|
|
|
|
wait_for_low:
|
|
|
|
call measure_halfperiod
|
|
|
|
ld e,a
|
|
|
|
|
|
ld a,(LOW_TRIGGER)
|
|
|
|
|
|
; We want:
|
|
;
|
|
; E >= LOW_TRIGGER
|
|
;
|
|
; CP does A - E.
|
|
;
|
|
; A < E -> carry
|
|
; A = E -> zero
|
|
|
|
cp e
|
|
|
|
jr z,low_candidate
|
|
jr c,low_candidate
|
|
|
|
|
|
; Too short: still leader/noise.
|
|
; Restart the consecutive counter.
|
|
|
|
jr wait_for_low_restart
|
|
|
|
|
|
low_candidate:
|
|
|
|
djnz wait_for_low
|
|
|
|
|
|
; =====================================================================
|
|
; We have now observed three consecutive slow periods.
|
|
;
|
|
; Capture 128 FRESH measurements.
|
|
;
|
|
; We deliberately do not print anything here because the tape stream
|
|
; has no gaps.
|
|
; =====================================================================
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
ld b,#CAPTURE_COUNT
|
|
|
|
|
|
capture_loop:
|
|
|
|
call measure_halfperiod
|
|
|
|
ld (hl),a
|
|
|
|
inc hl
|
|
|
|
djnz capture_loop
|
|
|
|
|
|
; =====================================================================
|
|
; Capture complete.
|
|
;
|
|
; Timing no longer matters, so enable interrupts for BIOS output.
|
|
; =====================================================================
|
|
|
|
ei
|
|
|
|
|
|
; =====================================================================
|
|
; Print calibration summary.
|
|
; =====================================================================
|
|
|
|
ld hl,#msg_leader_avg
|
|
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_threshold
|
|
call print_string
|
|
|
|
ld a,(TONE_THRESHOLD)
|
|
call print_hex8
|
|
|
|
call print_crlf
|
|
|
|
call print_crlf
|
|
|
|
|
|
; =====================================================================
|
|
; Print raw half-period measurements.
|
|
;
|
|
; 16 per line.
|
|
; =====================================================================
|
|
|
|
ld hl,#msg_raw
|
|
call print_string
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
ld b,#CAPTURE_COUNT
|
|
ld c,#0
|
|
|
|
|
|
print_raw_loop:
|
|
|
|
ld a,(hl)
|
|
call print_hex8
|
|
|
|
|
|
ld a,#' '
|
|
call BIOS_PUTC
|
|
|
|
|
|
inc hl
|
|
inc c
|
|
|
|
|
|
; New line every 16 samples.
|
|
|
|
ld a,c
|
|
and #0x0F
|
|
|
|
jr nz,raw_no_newline
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
raw_no_newline:
|
|
|
|
djnz print_raw_loop
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
; =====================================================================
|
|
; Print H/L classification.
|
|
;
|
|
; H:
|
|
; sample <= TONE_THRESHOLD
|
|
;
|
|
; L:
|
|
; sample > TONE_THRESHOLD
|
|
;
|
|
; 64 characters per line.
|
|
; =====================================================================
|
|
|
|
ld hl,#msg_class
|
|
call print_string
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
ld b,#CAPTURE_COUNT
|
|
ld c,#0
|
|
|
|
|
|
print_class_loop:
|
|
|
|
ld a,(hl)
|
|
|
|
call classify_sample
|
|
|
|
|
|
or a
|
|
jr z,class_print_low
|
|
|
|
|
|
ld a,#'H'
|
|
jr class_print_char
|
|
|
|
|
|
class_print_low:
|
|
|
|
ld a,#'L'
|
|
|
|
|
|
class_print_char:
|
|
|
|
call BIOS_PUTC
|
|
|
|
|
|
inc hl
|
|
inc c
|
|
|
|
|
|
; New line every 64 classifications.
|
|
|
|
ld a,c
|
|
and #0x3F
|
|
|
|
jr nz,class_no_newline
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
class_no_newline:
|
|
|
|
djnz print_class_loop
|
|
|
|
|
|
call print_crlf
|
|
|
|
|
|
; =====================================================================
|
|
; Print carrier run lengths.
|
|
;
|
|
; Example:
|
|
;
|
|
; L06 H0D L07 H0C ...
|
|
;
|
|
; This is the most interesting output for the 0x55 preamble.
|
|
;
|
|
; For nominal 2 kHz / 4 kHz at 600 baud we might expect something
|
|
; roughly around:
|
|
;
|
|
; LOW run: 6-7 comparator half-periods
|
|
; HIGH run: 12-14 comparator half-periods
|
|
;
|
|
; Exact values don't matter.
|
|
; Consistency is what matters.
|
|
; =====================================================================
|
|
|
|
ld hl,#msg_runs
|
|
call print_string
|
|
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#CAPTURE_BUF
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Establish first carrier.
|
|
; -------------------------------------------------------------
|
|
|
|
ld a,(hl)
|
|
call classify_sample
|
|
|
|
|
|
; D = current carrier
|
|
;
|
|
; 0 = LOW
|
|
; 1 = HIGH
|
|
|
|
ld d,a
|
|
|
|
|
|
; C = current run length
|
|
|
|
ld c,#1
|
|
|
|
|
|
inc hl
|
|
|
|
|
|
; 127 measurements remain.
|
|
|
|
ld b,#127
|
|
|
|
|
|
run_scan_loop:
|
|
|
|
ld a,(hl)
|
|
|
|
; classify_sample preserves BC and DE.
|
|
|
|
call classify_sample
|
|
|
|
|
|
cp d
|
|
jr nz,run_changed
|
|
|
|
|
|
; Same carrier.
|
|
|
|
inc c
|
|
|
|
jr run_scan_next
|
|
|
|
|
|
run_changed:
|
|
|
|
; -------------------------------------------------------------
|
|
; A = new carrier.
|
|
;
|
|
; Save it while printing the old run.
|
|
; -------------------------------------------------------------
|
|
|
|
ld e,a
|
|
|
|
|
|
ld a,d
|
|
call print_tone_letter
|
|
|
|
|
|
ld a,c
|
|
call print_hex8
|
|
|
|
|
|
ld a,#' '
|
|
call BIOS_PUTC
|
|
|
|
|
|
; Start new run.
|
|
|
|
ld d,e
|
|
ld c,#1
|
|
|
|
|
|
run_scan_next:
|
|
|
|
inc hl
|
|
|
|
djnz run_scan_loop
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Print final run.
|
|
; -------------------------------------------------------------
|
|
|
|
ld a,d
|
|
call print_tone_letter
|
|
|
|
|
|
ld a,c
|
|
call print_hex8
|
|
|
|
|
|
call print_crlf
|
|
call print_crlf
|
|
|
|
|
|
ld hl,#msg_done
|
|
call print_string
|
|
|
|
|
|
xor a
|
|
ld (RETURN_CODE),a
|
|
|
|
|
|
; =====================================================================
|
|
; Return to monitor / BIOS caller.
|
|
; =====================================================================
|
|
|
|
training_exit:
|
|
|
|
di
|
|
|
|
|
|
ld sp,(OLD_SP)
|
|
|
|
|
|
ld a,(RETURN_CODE)
|
|
ld c,a
|
|
|
|
|
|
; -------------------------------------------------------------
|
|
; Restore caller interrupt state.
|
|
; -------------------------------------------------------------
|
|
|
|
ld a,(OLD_IFF)
|
|
|
|
or a
|
|
|
|
|
|
ld a,c
|
|
|
|
jr z,training_return_di
|
|
|
|
|
|
; EI becomes active after the following instruction.
|
|
; RET serves as that instruction.
|
|
|
|
ei
|
|
ret
|
|
|
|
|
|
training_return_di:
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; measure_halfperiod
|
|
;
|
|
; 1. Wait for a comparator edge.
|
|
; 2. Starting exactly from that edge, count polling iterations until
|
|
; the next comparator edge.
|
|
;
|
|
; Returns:
|
|
;
|
|
; A = count, 1..255
|
|
;
|
|
; If the interval is very long the counter saturates at FF instead of
|
|
; wrapping.
|
|
;
|
|
;
|
|
; At 1 MHz your observed leader currently gives roughly:
|
|
;
|
|
; 02 / 03 counts
|
|
;
|
|
; which is perfectly usable as long as the two carrier clusters remain
|
|
; distinct.
|
|
; =====================================================================
|
|
|
|
measure_halfperiod:
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
ld d,a
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Wait for 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 to following edge.
|
|
; ---------------------------------------------------------------------
|
|
|
|
mhp_measure_loop:
|
|
|
|
inc c
|
|
|
|
|
|
in a,(#COMP_PORT)
|
|
|
|
cp d
|
|
|
|
jr nz,mhp_done
|
|
|
|
|
|
; Saturate at FF.
|
|
|
|
ld a,c
|
|
|
|
cp #0xFF
|
|
|
|
jr nz,mhp_measure_loop
|
|
|
|
|
|
; ---------------------------------------------------------------------
|
|
; Saturated:
|
|
; just wait for the edge without incrementing further.
|
|
; ---------------------------------------------------------------------
|
|
|
|
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 half-period
|
|
;
|
|
; Output:
|
|
;
|
|
; A = 1 HIGH-frequency / short-period carrier
|
|
; A = 0 LOW-frequency / long-period carrier
|
|
;
|
|
; Preserves:
|
|
;
|
|
; BC
|
|
; DE
|
|
; HL
|
|
; =====================================================================
|
|
|
|
classify_sample:
|
|
|
|
push bc
|
|
|
|
|
|
ld c,a
|
|
|
|
|
|
ld a,(TONE_THRESHOLD)
|
|
|
|
cp c
|
|
|
|
|
|
; threshold < sample -> LOW-frequency carrier
|
|
|
|
jr c,class_sample_low
|
|
|
|
|
|
ld a,#1
|
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
class_sample_low:
|
|
|
|
xor a
|
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
|
; =====================================================================
|
|
; print_tone_letter
|
|
;
|
|
; Input:
|
|
;
|
|
; A = 0 -> L
|
|
; A = 1 -> H
|
|
; =====================================================================
|
|
|
|
print_tone_letter:
|
|
|
|
or a
|
|
|
|
jr z,print_tone_low
|
|
|
|
|
|
ld a,#'H'
|
|
|
|
jp BIOS_PUTC
|
|
|
|
|
|
print_tone_low:
|
|
|
|
ld a,#'L'
|
|
|
|
jp BIOS_PUTC
|
|
|
|
|
|
; =====================================================================
|
|
; 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
|
|
;
|
|
; A -> 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
|
|
|
|
|
|
; =====================================================================
|
|
; ROM strings
|
|
; =====================================================================
|
|
|
|
msg_start:
|
|
|
|
.ascii "TAPE TRAINING - PLAY TAPE"
|
|
.db 13,10,0
|
|
|
|
|
|
msg_leader_avg:
|
|
|
|
.ascii "LEADER AVG = "
|
|
.db 0
|
|
|
|
|
|
msg_leader_max:
|
|
|
|
.ascii "LEADER MAX = "
|
|
.db 0
|
|
|
|
|
|
msg_low_trigger:
|
|
|
|
.ascii "LOW TRIGGER = "
|
|
.db 0
|
|
|
|
|
|
msg_threshold:
|
|
|
|
.ascii "H/L THRESHOLD = "
|
|
.db 0
|
|
|
|
|
|
msg_raw:
|
|
|
|
.ascii "RAW HALF-PERIOD COUNTS:"
|
|
.db 0
|
|
|
|
|
|
msg_class:
|
|
|
|
.ascii "CLASSIFICATION:"
|
|
.db 0
|
|
|
|
|
|
msg_runs:
|
|
|
|
.ascii "RUN LENGTHS:"
|
|
.db 0
|
|
|
|
|
|
msg_done:
|
|
|
|
.ascii "TRAINING CAPTURE DONE"
|
|
.db 13,10,0 |