Pulse width encoding
Working hello world transfer in sim and irl
This commit is contained in:
+95
-93
@@ -1,133 +1,135 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import argparse
|
import argparse
|
||||||
import math
|
|
||||||
import struct
|
import struct
|
||||||
import wave
|
import wave
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
class FSKWriter:
|
|
||||||
def __init__(self, sample_rate: int, c_low: int, c_high: int, f_low: float, f_high: float, amplitude: float, block_spacing: float):
|
class PulseWriter:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
sample_rate: int,
|
||||||
|
t_short_us: float,
|
||||||
|
t_long_us: float,
|
||||||
|
amplitude: float,
|
||||||
|
) -> None:
|
||||||
self.sample_rate = sample_rate
|
self.sample_rate = sample_rate
|
||||||
self.c_low = c_low
|
self.short_samples = self._duration_samples(t_short_us)
|
||||||
self.c_high = c_high
|
self.long_samples = self._duration_samples(t_long_us)
|
||||||
self.f_low = f_low
|
self.high = round(32767 * amplitude)
|
||||||
self.f_high = f_high
|
self.low = -self.high
|
||||||
self.amplitude = amplitude
|
|
||||||
self.samples_per_low = (sample_rate * c_low) // f_low
|
|
||||||
self.samples_per_high = (sample_rate * c_high) // f_high
|
|
||||||
self.phase = 0.0
|
|
||||||
self.block_spacing = block_spacing
|
|
||||||
self.samples = bytearray()
|
self.samples = bytearray()
|
||||||
|
|
||||||
self.space(1.0)
|
self.space(0.1)
|
||||||
|
|
||||||
def finalize(self):
|
def space(self, time: float) -> None:
|
||||||
self.space(1.0)
|
self._level(0, int(time*self.sample_rate))
|
||||||
|
|
||||||
def space(self, time:float = 0.03):
|
def _duration_samples(self, duration_us: float) -> int:
|
||||||
self.samples.extend(b'\0\0'*int(time*self.sample_rate))
|
sample_count = round(duration_us * self.sample_rate / 1_000_000)
|
||||||
|
if sample_count < 1:
|
||||||
|
raise ValueError(
|
||||||
|
f"pulse duration {duration_us:g} us is shorter than one sample"
|
||||||
|
)
|
||||||
|
return sample_count
|
||||||
|
|
||||||
def _tone(self, frequency: float, sample_count: int) -> None:
|
def _level(self, value: int, sample_count: int) -> None:
|
||||||
phase_step = 2.0 * math.pi * frequency / self.sample_rate
|
self.samples.extend(struct.pack("<h", value) * sample_count)
|
||||||
scale = int(32767 * self.amplitude)
|
|
||||||
phase = self.phase
|
|
||||||
out = self.samples
|
|
||||||
|
|
||||||
for _ in range(int(sample_count)):
|
def short_pulse(self) -> None:
|
||||||
sample = int(round(scale * math.sin(phase)))
|
self._level(self.high, self.short_samples)
|
||||||
out.extend(struct.pack("<h", sample))
|
self._level(self.low, self.long_samples)
|
||||||
phase += phase_step
|
|
||||||
if phase >= 2.0 * math.pi:
|
|
||||||
phase -= 2.0 * math.pi
|
|
||||||
|
|
||||||
self.phase = phase
|
def long_pulse(self) -> None:
|
||||||
|
self._level(self.high, self.long_samples)
|
||||||
|
self._level(self.low, self.short_samples)
|
||||||
|
|
||||||
def leader(self) -> None:
|
def marker(self) -> None:
|
||||||
self._tone(self.f_high, (200*self.sample_rate)//self.f_high)
|
self._level(self.high, 2*self.long_samples)
|
||||||
self._tone(self.f_low, (200*self.sample_rate)//self.f_low)
|
self._level(self.low, 2*self.long_samples)
|
||||||
for _ in range(8):
|
|
||||||
self.bit(1)
|
|
||||||
self.bit(0)
|
|
||||||
self.byte(0xf0)
|
|
||||||
|
|
||||||
def bit(self, value: int) -> None:
|
def bit(self, value: int) -> None:
|
||||||
if value:
|
if value:
|
||||||
self._tone(self.f_high, self.samples_per_high)
|
self.long_pulse()
|
||||||
else:
|
else:
|
||||||
self._tone(self.f_low, self.samples_per_low)
|
self.short_pulse()
|
||||||
|
|
||||||
def byte(self, value: int) -> None:
|
def byte(self, value: int) -> None:
|
||||||
# MSB first
|
# Transmit bytes most-significant bit first.
|
||||||
for bit_no in range(7, -1, -1):
|
for bit_number in range(7, -1, -1):
|
||||||
self.bit((value >> bit_no) & 1)
|
self.bit((value >> bit_number) & 1)
|
||||||
|
|
||||||
def bytes(self, data: bytes) -> None:
|
def bytes(self, value: bytes) -> None:
|
||||||
for value in data:
|
for byte in value:
|
||||||
|
self.byte(byte)
|
||||||
|
|
||||||
|
def leader(self) -> None:
|
||||||
|
self.space(0.2)
|
||||||
|
for _ in range(50):
|
||||||
|
self._level(self.high, self.short_samples)
|
||||||
|
self._level(self.low, self.short_samples)
|
||||||
|
for _ in range(50):
|
||||||
|
self._level(self.high, self.long_samples)
|
||||||
|
self._level(self.low, self.long_samples)
|
||||||
|
self.marker()
|
||||||
|
for value in (0x55, 0x5A, 0xC3):
|
||||||
self.byte(value)
|
self.byte(value)
|
||||||
|
|
||||||
def block(self, nr: int, data: bytes) -> None:
|
|
||||||
print(f'> {data}')
|
|
||||||
self.leader()
|
|
||||||
self.byte(nr)
|
|
||||||
l = len(data)
|
|
||||||
self.byte(l>>8)
|
|
||||||
self.byte(l&0xff)
|
|
||||||
#self.samples.extend(b'\0'*int(self.samples_per_low*8))
|
|
||||||
self.bytes(data)
|
|
||||||
self.byte(self.chsum(data))
|
|
||||||
self.space(self.block_spacing)
|
|
||||||
|
|
||||||
def chsum(self, data: bytes) -> int:
|
|
||||||
cs = 0
|
|
||||||
for d in data:
|
|
||||||
cs += int(d)
|
|
||||||
return cs
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
parser = argparse.ArgumentParser(description="Encode a binary file as two tone FSK WAV")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("input", type=Path, help="Binary input file")
|
description="Generate the pulse-width encoded tape leader"
|
||||||
parser.add_argument("output", type=Path, help="output WAV; default <input>.wav")
|
)
|
||||||
parser.add_argument("--sample-rate", type=int, default=48000)
|
parser.add_argument("output", type=Path, help="output WAV file")
|
||||||
parser.add_argument("--low", type=float, default=2000.0, help="low tone in Hz")
|
parser.add_argument("--sample-rate", type=int, default=48_000)
|
||||||
parser.add_argument("--high", type=float, default=4000.0, help="high tone in Hz")
|
parser.add_argument(
|
||||||
parser.add_argument("--low-cycles", type=int, default=2, help="low tone cycle count for a 0")
|
"--t-short",
|
||||||
parser.add_argument("--high-cycles", type=int, default=4, help="high tone cycle count for a 1")
|
type=float,
|
||||||
|
default=250.0,
|
||||||
|
metavar="MICROSECONDS",
|
||||||
|
help="short pulse interval in microseconds (default: 250)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--t-long",
|
||||||
|
type=float,
|
||||||
|
default=500.0,
|
||||||
|
metavar="MICROSECONDS",
|
||||||
|
help="long pulse interval in microseconds (default: 500)",
|
||||||
|
)
|
||||||
parser.add_argument("--amplitude", type=float, default=0.75)
|
parser.add_argument("--amplitude", type=float, default=0.75)
|
||||||
parser.add_argument("--block-size", type=int, default=128)
|
|
||||||
parser.add_argument("--block-spacing", type=float, default=1.0)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
output_path = args.output or args.input.with_name(args.input.stem + ".wav")
|
if args.sample_rate <= 0:
|
||||||
|
parser.error("--sample-rate must be positive")
|
||||||
|
if args.t_short <= 0 or args.t_long <= 0:
|
||||||
|
parser.error("pulse intervals must be positive")
|
||||||
|
if args.t_short >= args.t_long:
|
||||||
|
parser.error("--t-short must be less than --t-long")
|
||||||
|
if not 0.0 <= args.amplitude <= 1.0:
|
||||||
|
parser.error("--amplitude must be between 0 and 1")
|
||||||
|
|
||||||
tones = FSKWriter(args.sample_rate, args.low_cycles, args.high_cycles, args.low, args.high, args.amplitude, args.block_spacing)
|
try:
|
||||||
|
pulses = PulseWriter(
|
||||||
|
args.sample_rate,
|
||||||
|
args.t_short,
|
||||||
|
args.t_long,
|
||||||
|
args.amplitude,
|
||||||
|
)
|
||||||
|
except ValueError as error:
|
||||||
|
parser.error(str(error))
|
||||||
|
|
||||||
# Create header block
|
pulses.leader()
|
||||||
nrblocks = math.ceil(os.path.getsize(args.input)/args.block_size)
|
pulses.bytes(b'Hello world!\0')
|
||||||
print(f"Number of blocks {nrblocks}*2")
|
|
||||||
|
|
||||||
tones.block(0, struct.pack('<H', nrblocks*2) + args.input.name.encode() + b'\0')
|
pulses.space(1.0)
|
||||||
tones.space(1.0)
|
with wave.open(str(args.output), "wb") as wav:
|
||||||
|
|
||||||
block = 1
|
|
||||||
with open(args.input, 'rb') as f:
|
|
||||||
while True:
|
|
||||||
data = f.read(args.block_size)
|
|
||||||
if not len(data):
|
|
||||||
break
|
|
||||||
tones.block(block, data)
|
|
||||||
tones.block(block, data)
|
|
||||||
block += 1
|
|
||||||
|
|
||||||
tones.finalize()
|
|
||||||
with wave.open(str(output_path), "wb") as wav:
|
|
||||||
wav.setnchannels(1)
|
wav.setnchannels(1)
|
||||||
wav.setsampwidth(2)
|
wav.setsampwidth(2)
|
||||||
wav.setframerate(args.sample_rate)
|
wav.setframerate(args.sample_rate)
|
||||||
wav.writeframes(tones.samples)
|
wav.writeframes(pulses.samples)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -3,6 +3,21 @@
|
|||||||
.area TAPELOAD (ABS)
|
.area TAPELOAD (ABS)
|
||||||
.org TAPE_LOADER_ROM_ADDRESS
|
.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
|
TAPE_PORT = 0x18
|
||||||
MAX_BLOCK_SIZE = 0x100
|
MAX_BLOCK_SIZE = 0x100
|
||||||
|
|
||||||
@@ -11,6 +26,12 @@ WORK_BASE = 0xB000
|
|||||||
|
|
||||||
OLD_SP = WORK_BASE + 0x00 ; word
|
OLD_SP = WORK_BASE + 0x00 ; word
|
||||||
OLD_IFF = WORK_BASE + 0x02 ; byte
|
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
|
BLOCK_BUFFER = WORK_BASE + 0x100 ; MAX_BLOCK_SIZE
|
||||||
|
|
||||||
@@ -36,6 +57,52 @@ main:
|
|||||||
ld hl,#msg_start
|
ld hl,#msg_start
|
||||||
call print_string_ei
|
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
|
; #################################################3
|
||||||
; Exit
|
; Exit
|
||||||
; #################################################3
|
; #################################################3
|
||||||
@@ -57,6 +124,379 @@ exit:
|
|||||||
return_di:
|
return_di:
|
||||||
ret
|
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<SHORT_MIN
|
||||||
|
ld a,(SHORT_MAX)
|
||||||
|
ld c,a
|
||||||
|
ld a,b
|
||||||
|
cp a,c
|
||||||
|
jr c,bit_one
|
||||||
|
jr z,bit_one ; pulse<=SHORT_MAX
|
||||||
|
; test long window (second half is long pulse)
|
||||||
|
ld a,(LONG_MIN)
|
||||||
|
ld c,a
|
||||||
|
ld a,b
|
||||||
|
cp a,c
|
||||||
|
jr c,bit_bad ; pulse<LONG_MAX
|
||||||
|
ld a,(LONG_MAX)
|
||||||
|
ld c,a
|
||||||
|
ld a,b
|
||||||
|
cp a,c
|
||||||
|
jr c,bit_zero
|
||||||
|
jr z,bit_zero ; pulse<=LONG_MAX
|
||||||
|
bit_bad:
|
||||||
|
ld a,#1
|
||||||
|
or a,a
|
||||||
|
ret
|
||||||
|
bit_zero:
|
||||||
|
xor a,a
|
||||||
|
ret
|
||||||
|
bit_one:
|
||||||
|
xor a,a
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; #################################################3
|
||||||
|
; Read byte, MSB first
|
||||||
|
; returns a (byte)
|
||||||
|
; carry means error
|
||||||
|
; needs d (comparator state)
|
||||||
|
; uses e,h,b,c
|
||||||
|
; #################################################3
|
||||||
|
read_byte:
|
||||||
|
ld e,#0
|
||||||
|
ld h,#8
|
||||||
|
1$:
|
||||||
|
call read_bit
|
||||||
|
jr nz,9$
|
||||||
|
; shift in the carry bit
|
||||||
|
rl e
|
||||||
|
; repeat 8 times
|
||||||
|
dec h
|
||||||
|
jr nz,1$
|
||||||
|
; byte done,
|
||||||
|
ld a,e
|
||||||
|
or a
|
||||||
|
ret
|
||||||
|
|
||||||
|
9$:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; #################################################3
|
||||||
|
; Read and check sync bytes
|
||||||
|
; carry is error
|
||||||
|
; #################################################3
|
||||||
|
read_sync:
|
||||||
|
call read_byte
|
||||||
|
ret c
|
||||||
|
cp a,#0x5a
|
||||||
|
jr nz,9$
|
||||||
|
call read_byte
|
||||||
|
ret c
|
||||||
|
cp a,#0xc3
|
||||||
|
jr nz,9$
|
||||||
|
or a,a
|
||||||
|
ret
|
||||||
|
9$:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
; #################################################3
|
; #################################################3
|
||||||
; IO Helper functions
|
; IO Helper functions
|
||||||
; #################################################3
|
; #################################################3
|
||||||
@@ -118,3 +558,12 @@ print_hex_digit:
|
|||||||
msg_start:
|
msg_start:
|
||||||
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
||||||
.db 13,10,0
|
.db 13,10,0
|
||||||
|
msg_cal_fail:
|
||||||
|
.ascii "Calibration failed"
|
||||||
|
.db 13,10,0
|
||||||
|
msg_sync_error:
|
||||||
|
.ascii "Sync bytes not found"
|
||||||
|
.db 13,10,0
|
||||||
|
msg_read_error:
|
||||||
|
.ascii "read error"
|
||||||
|
.db 13,10,0
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user