Added old tape routines and started with cload
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
*__pycache__**
|
||||||
@@ -5,11 +5,6 @@ ASM := zasm
|
|||||||
ASMFLAGS := -w -b
|
ASMFLAGS := -w -b
|
||||||
BUILDDIR := build
|
BUILDDIR := build
|
||||||
|
|
||||||
FILES := bios
|
|
||||||
|
|
||||||
SOURCES := $(addprefix ./, $(addsuffix .asm, $(FILES)))
|
|
||||||
TARGETS := $(addprefix $(BUILDDIR)/, $(addsuffix .bin, $(FILES)))
|
|
||||||
|
|
||||||
TOPTARGETS := all clean
|
TOPTARGETS := all clean
|
||||||
$(TOPTARGETS): $(SUBDIRS)
|
$(TOPTARGETS): $(SUBDIRS)
|
||||||
|
|
||||||
@@ -21,9 +16,9 @@ clean:
|
|||||||
$(BUILDDIR):
|
$(BUILDDIR):
|
||||||
mkdir -p $(BUILDDIR)
|
mkdir -p $(BUILDDIR)
|
||||||
|
|
||||||
$(BUILDDIR)/%.bin: ./%.asm $(BUILDDIR)/memory.inc
|
$(BUILDDIR)/bios.bin: bios.asm tape.asm $(BUILDDIR)/memory.inc
|
||||||
echo '>$<'
|
echo '>$<'
|
||||||
$(ASM) $(ASMFLAGS) $< $@
|
$(ASM) $(ASMFLAGS) bios.bin $(BUILDDIR)/bios.bin
|
||||||
|
|
||||||
basic:
|
basic:
|
||||||
$(MAKE) -C apps/basic $(or $(MAKECMDGOALS),all) --no-print-directory
|
$(MAKE) -C apps/basic $(or $(MAKECMDGOALS),all) --no-print-directory
|
||||||
|
|||||||
+132
-2
@@ -15,6 +15,7 @@
|
|||||||
; - Unsupported functions cause FCERR, not a reset or REM
|
; - Unsupported functions cause FCERR, not a reset or REM
|
||||||
; - modern backspace handling
|
; - modern backspace handling
|
||||||
; - Added DROM/EROM/BANK0/BANK1 commands
|
; - Added DROM/EROM/BANK0/BANK1 commands
|
||||||
|
; - CLOAD
|
||||||
;
|
;
|
||||||
;==================================================================================
|
;==================================================================================
|
||||||
; The updates to the original BASIC within this file are copyright Grant Searle
|
; The updates to the original BASIC within this file are copyright Grant Searle
|
||||||
@@ -423,8 +424,8 @@ WORDTB: .WORD PEND
|
|||||||
.WORD CONT
|
.WORD CONT
|
||||||
.WORD LIST
|
.WORD LIST
|
||||||
.WORD CLEAR
|
.WORD CLEAR
|
||||||
.WORD FCERR
|
.WORD CLOAD
|
||||||
.WORD FCERR
|
.WORD FCERR ; csave
|
||||||
.WORD NEW
|
.WORD NEW
|
||||||
|
|
||||||
; RESERVED WORD TOKEN VALUES
|
; RESERVED WORD TOKEN VALUES
|
||||||
@@ -4543,4 +4544,133 @@ FULLSETTOP:
|
|||||||
FULLMSG:
|
FULLMSG:
|
||||||
.BYTE "[FULL MEMORY]",CR,LF,0,0
|
.BYTE "[FULL MEMORY]",CR,LF,0,0
|
||||||
|
|
||||||
|
CLOAD: ld a,(hl) ; get byte after "CLOAD"
|
||||||
|
cp ZTIMES ; "*" token? ("CLOAD*")
|
||||||
|
jp z,FCERR ; yes - array load (ARRLD1)
|
||||||
|
cp ZPRINT ; "?" ("PRINT token") verify
|
||||||
|
jp z,FCERR ; yes - flag "verify"
|
||||||
|
cp '#' ; 'cload#'?
|
||||||
|
jp z,CLOADB ; yes - load binary
|
||||||
|
; just cload - load basic
|
||||||
|
jp FCERR
|
||||||
|
|
||||||
|
CLOADB: inc hl ; skip #
|
||||||
|
call GETNUM ; evaluate numeric expression
|
||||||
|
call DEINT ; convert FPREG to de
|
||||||
|
; de contains load address
|
||||||
|
; TODO do safety check on address
|
||||||
|
push hl
|
||||||
|
push bc
|
||||||
|
push de
|
||||||
|
|
||||||
|
ld hl,CLSTARTMSG
|
||||||
|
call PRS
|
||||||
|
|
||||||
|
; Load block
|
||||||
|
call CLREADBLOCK
|
||||||
|
; hl contains block, check block type
|
||||||
|
ld a,(hl)
|
||||||
|
cp 1 ; first block needs to be a header
|
||||||
|
jr nz,CLNOHDR ; First block is not a header
|
||||||
|
; first block is a header, get nr of blocks
|
||||||
|
inc hl
|
||||||
|
ld de,(hl)
|
||||||
|
; print number of blocks to load
|
||||||
|
push de
|
||||||
|
push hl
|
||||||
|
ex de,hl
|
||||||
|
call PRNTHL
|
||||||
|
ld hl,CLBLOCKSMSG
|
||||||
|
call PRS
|
||||||
|
pop hl
|
||||||
|
inc hl
|
||||||
|
inc hl ; get filename
|
||||||
|
call PRS ; print filename
|
||||||
|
; de contains the nr of blocks to load
|
||||||
|
; TODO check de>0
|
||||||
|
|
||||||
|
pop de
|
||||||
|
1$: push de ; save repeat counter
|
||||||
|
call CLREADBLOCK ; hl = new data
|
||||||
|
; TODO check block type
|
||||||
|
inc hl ; goto start of data
|
||||||
|
pop de ; get repeat counter
|
||||||
|
push hl ; save stored data
|
||||||
|
|
||||||
|
exx
|
||||||
|
pop hl ; get stored data back
|
||||||
|
pop de ; get destination
|
||||||
|
ld bc,64 ; block size
|
||||||
|
ldir ; copy data to memory
|
||||||
|
push de ; store destination address
|
||||||
|
exx
|
||||||
|
|
||||||
|
dec de
|
||||||
|
ld a,e
|
||||||
|
cp 0
|
||||||
|
jr nz,1$ ; repeat if not done yet
|
||||||
|
ld a,d
|
||||||
|
cp 0
|
||||||
|
jr nz,1$ ; repeat if not done yet
|
||||||
|
|
||||||
|
pop de
|
||||||
|
pop bc
|
||||||
|
pop hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
CLREADBLOCK:
|
||||||
|
call BIOS_CAS_READ_BLOCK
|
||||||
|
jr nc,1$
|
||||||
|
call BIOS_CAS_WAIT_SILENCE
|
||||||
|
call BIOS_CAS_READ_BLOCK
|
||||||
|
push hl
|
||||||
|
jr nc,2$
|
||||||
|
; error, make sure it is silent again
|
||||||
|
push af
|
||||||
|
call BIOS_CAS_WAIT_SILENCE
|
||||||
|
pop af
|
||||||
|
jr CLERR
|
||||||
|
1$: push hl ; save hl
|
||||||
|
call BIOS_CAS_READ_BLOCK_DRY
|
||||||
|
; Ignore errors on this block
|
||||||
|
2$: ; but make sure it is silent again
|
||||||
|
call BIOS_CAS_WAIT_SILENCE
|
||||||
|
pop hl
|
||||||
|
ret
|
||||||
|
; Second block failed as well, error
|
||||||
|
CLERR: cp 1
|
||||||
|
jr z,CLECAL
|
||||||
|
cp 2
|
||||||
|
jr z,CLESYN
|
||||||
|
cp 3
|
||||||
|
jr z,CLERED
|
||||||
|
cp 4
|
||||||
|
jr z,CLECHS
|
||||||
|
jp FCERR ; Unknown error
|
||||||
|
CLECAL: ld hl, CLECALMSG
|
||||||
|
jr CLEPRINT
|
||||||
|
CLESYN: ld hl, CLESYNMSG
|
||||||
|
jr CLEPRINT
|
||||||
|
CLERED: ld hl, CLEREDMSG
|
||||||
|
jr CLEPRINT
|
||||||
|
CLECHS: ld hl, CLECHSMSG
|
||||||
|
jr CLEPRINT
|
||||||
|
CLNOHDR:
|
||||||
|
ld hl,CLNOHDRMSG
|
||||||
|
CLEPRINT:
|
||||||
|
call PRS
|
||||||
|
pop de
|
||||||
|
pop bc
|
||||||
|
pop hl
|
||||||
|
jp FCERR
|
||||||
|
|
||||||
|
CLSTARTMSG: .db "Play tape...",0ah,0dh,00h
|
||||||
|
CLECALMSG: .db "Calibration failed",0ah,0dh,00h
|
||||||
|
CLESYNMSG: .db "Synchronization failed",0ah,0dh,00h
|
||||||
|
CLEREDMSG: .db "Read failed",0ah,0dh,00h
|
||||||
|
CLECHSMSG: .db "Checksum error",0ah,0dh,00h
|
||||||
|
CLNOHDRMSG: .db "No header found", 0ah,0dh,00h
|
||||||
|
CLBLOCKSMSG: .db " blocks to load", 0ah,0dh,00h
|
||||||
|
CLCLRF: .db 0ah,0dh,00h
|
||||||
|
|
||||||
.end
|
.end
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ TX_BUSY .equ WORKSPACE+006h
|
|||||||
TX_BUF .equ WORKSPACE+007h
|
TX_BUF .equ WORKSPACE+007h
|
||||||
RX_BUF .equ TX_BUF+BUF_SIZE
|
RX_BUF .equ TX_BUF+BUF_SIZE
|
||||||
SYSREG_VALUE .equ RX_BUF+BUF_SIZE
|
SYSREG_VALUE .equ RX_BUF+BUF_SIZE
|
||||||
|
END_OF_BIOS_WS .equ SYSREG_VALUE+001h
|
||||||
|
|
||||||
; -------------------------------------------------------
|
; -------------------------------------------------------
|
||||||
; entry
|
; entry
|
||||||
@@ -99,6 +100,12 @@ boot_copy:
|
|||||||
jp setrom
|
jp setrom
|
||||||
.ORG BIOS_BANK
|
.ORG BIOS_BANK
|
||||||
jp bank
|
jp bank
|
||||||
|
.ORG BIOS_CAS_READ_BLOCK
|
||||||
|
jp read_block
|
||||||
|
.ORG BIOS_CAS_WAIT_SILENCE
|
||||||
|
jp wait_silence
|
||||||
|
.ORG BIOS_CAS_READ_BLOCK_DRY
|
||||||
|
jp read_block_dry
|
||||||
|
|
||||||
; -------------------------------------------------------
|
; -------------------------------------------------------
|
||||||
; BIOS ENTRY
|
; BIOS ENTRY
|
||||||
@@ -137,9 +144,30 @@ init:
|
|||||||
|
|
||||||
; Enter bios
|
; Enter bios
|
||||||
jp BASIC_RAM_START
|
jp BASIC_RAM_START
|
||||||
|
; 100$
|
||||||
|
; ld hl,msg_start
|
||||||
|
; call BIOS_PUTS
|
||||||
|
; call BIOS_FLUSH
|
||||||
|
; call read_block
|
||||||
|
; jr c,101$ ; error
|
||||||
|
; ; block received correctly
|
||||||
|
; ld hl,BLOCK_BUFFER
|
||||||
|
; call BIOS_PUTS
|
||||||
|
; call BIOS_FLUSH
|
||||||
|
; jp 100$
|
||||||
|
; 101$:
|
||||||
|
; add '0' ; get error number
|
||||||
|
; call BIOS_PUTC
|
||||||
|
; ld a, 0ah
|
||||||
|
; call BIOS_PUTC
|
||||||
|
; ld a, 0dh
|
||||||
|
; call BIOS_PUTC
|
||||||
|
; call BIOS_FLUSH
|
||||||
|
; jp 100$
|
||||||
|
|
||||||
; Shouldnt come here
|
; Shouldnt come here
|
||||||
; just to be safe an endless loop
|
; just to be safe an endless loop
|
||||||
|
ei
|
||||||
0$:
|
0$:
|
||||||
call getc
|
call getc
|
||||||
call putc
|
call putc
|
||||||
@@ -524,3 +552,8 @@ bank:
|
|||||||
; Strings
|
; Strings
|
||||||
; -------------------------------------------------------
|
; -------------------------------------------------------
|
||||||
msg_header: .db "BIOS v1.0 - Joppe Blondel",00dh,00ah,000h
|
msg_header: .db "BIOS v1.0 - Joppe Blondel",00dh,00ah,000h
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Other files
|
||||||
|
; -------------------------------------------------------
|
||||||
|
#include "tape.asm"
|
||||||
@@ -5,3 +5,6 @@ BIOS_PUTS .equ 0c059h
|
|||||||
BIOS_FLUSH .equ 0c05ch
|
BIOS_FLUSH .equ 0c05ch
|
||||||
BIOS_ROM .equ 0c05fh
|
BIOS_ROM .equ 0c05fh
|
||||||
BIOS_BANK .equ 0c062h
|
BIOS_BANK .equ 0c062h
|
||||||
|
BIOS_CAS_READ_BLOCK .equ 0c065h
|
||||||
|
BIOS_CAS_WAIT_SILENCE .equ 0c068h
|
||||||
|
BIOS_CAS_READ_BLOCK_DRY .equ 0c06bh
|
||||||
@@ -4,3 +4,5 @@ ROM_DISABLE_BIT .equ 001h
|
|||||||
BANK_SELECT_BIT .equ 002h
|
BANK_SELECT_BIT .equ 002h
|
||||||
BANK_ENABLE_BIT .equ 004h
|
BANK_ENABLE_BIT .equ 004h
|
||||||
BANK_MODE_CLEAR_MASK .equ 0f9h
|
BANK_MODE_CLEAR_MASK .equ 0f9h
|
||||||
|
|
||||||
|
TAPE_PORT .equ 018h
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ SHARED_MEM_END := 0xffff
|
|||||||
|
|
||||||
BIOS_STACK_TOP := 0xffff
|
BIOS_STACK_TOP := 0xffff
|
||||||
BIOS_ROM_START := 0x0080
|
BIOS_ROM_START := 0x0080
|
||||||
BIOS_ROM_SIZE := 0x0580
|
BIOS_ROM_SIZE := 0x0780
|
||||||
|
|
||||||
BASIC_ROM_START := 0x0600
|
BASIC_ROM_START := 0x0800
|
||||||
BASIC_ROM_SIZE := 0x2000
|
BASIC_ROM_SIZE := 0x2000
|
||||||
|
|
||||||
BIOS_RAM_START := 0xc000
|
BIOS_RAM_START := 0xc000
|
||||||
BIOS_WP_RAM_START := 0xc600
|
BIOS_WP_RAM_START := 0xc800
|
||||||
BASIC_RAM_START := 0xca00
|
BASIC_RAM_START := 0xcc00
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
import wave
|
||||||
|
import os
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
BLOCK_SIZE = 64
|
||||||
|
TYPE_RAW = 0
|
||||||
|
TYPE_HEADER = 1
|
||||||
|
|
||||||
|
def crc16(data: bytes) -> int:
|
||||||
|
"""Calculate CRC-16/CCITT-FALSE."""
|
||||||
|
crc = 0xffff
|
||||||
|
for value in data:
|
||||||
|
crc ^= value << 8
|
||||||
|
for _ in range(8):
|
||||||
|
if crc & 0x8000:
|
||||||
|
crc = (crc << 1) ^ 0x1021
|
||||||
|
else:
|
||||||
|
crc <<= 1
|
||||||
|
crc &= 0xffff
|
||||||
|
return crc
|
||||||
|
|
||||||
|
|
||||||
|
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.short_samples = self._duration_samples(t_short_us)
|
||||||
|
self.long_samples = self._duration_samples(t_long_us)
|
||||||
|
self.high = round(32767 * amplitude)
|
||||||
|
self.low = -self.high
|
||||||
|
self.samples = bytearray()
|
||||||
|
|
||||||
|
self.space(0.1)
|
||||||
|
|
||||||
|
def space(self, time: float) -> None:
|
||||||
|
self._level(0, int(time*self.sample_rate))
|
||||||
|
|
||||||
|
def _duration_samples(self, duration_us: float) -> int:
|
||||||
|
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 _level(self, value: int, sample_count: int) -> None:
|
||||||
|
self.samples.extend(struct.pack("<h", value) * sample_count)
|
||||||
|
|
||||||
|
def short_pulse(self) -> None:
|
||||||
|
self._level(self.high, self.short_samples)
|
||||||
|
self._level(self.low, self.long_samples)
|
||||||
|
|
||||||
|
def long_pulse(self) -> None:
|
||||||
|
self._level(self.high, self.long_samples)
|
||||||
|
self._level(self.low, self.short_samples)
|
||||||
|
|
||||||
|
def marker(self) -> None:
|
||||||
|
self._level(self.high, 2*self.long_samples)
|
||||||
|
self._level(self.low, 2*self.long_samples)
|
||||||
|
|
||||||
|
def bit(self, value: int) -> None:
|
||||||
|
if value:
|
||||||
|
self.long_pulse()
|
||||||
|
else:
|
||||||
|
self.short_pulse()
|
||||||
|
|
||||||
|
def byte(self, value: int) -> None:
|
||||||
|
# Transmit bytes most-significant bit first.
|
||||||
|
for bit_number in range(7, -1, -1):
|
||||||
|
self.bit((value >> bit_number) & 1)
|
||||||
|
|
||||||
|
def bytes(self, value: bytes) -> None:
|
||||||
|
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)
|
||||||
|
|
||||||
|
def block(self, type:int, data:bytes) -> None:
|
||||||
|
self.leader()
|
||||||
|
block_data = struct.pack("<B",type) + data[:BLOCK_SIZE].ljust(BLOCK_SIZE, b'\0')
|
||||||
|
self.bytes(block_data)
|
||||||
|
self.bytes(struct.pack("<H", crc16(block_data)))
|
||||||
|
|
||||||
|
def header(pulses: PulseWriter, path:Path) -> None:
|
||||||
|
fname = path.name
|
||||||
|
fsize = os.path.getsize(path)
|
||||||
|
data = struct.pack("<H", math.ceil(fsize/BLOCK_SIZE)) + fname.encode()[:32].ljust(32, b' ')
|
||||||
|
pulses.block(TYPE_HEADER, data)
|
||||||
|
pulses.block(TYPE_HEADER, data)
|
||||||
|
|
||||||
|
def raw(pulses: PulseWriter, data:bytes) -> None:
|
||||||
|
pulses.block(TYPE_RAW, data)
|
||||||
|
pulses.block(TYPE_RAW, data)
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate the pulse-width encoded tape leader"
|
||||||
|
)
|
||||||
|
parser.add_argument("input", type=Path, help="input file to encode")
|
||||||
|
parser.add_argument("output", type=Path, help="output WAV file")
|
||||||
|
parser.add_argument("--sample-rate", type=int, default=48_000)
|
||||||
|
parser.add_argument(
|
||||||
|
"--t-short",
|
||||||
|
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)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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
|
||||||
|
header(pulses, args.input)
|
||||||
|
pulses.space(1.0)
|
||||||
|
with open(args.input, "rb") as f:
|
||||||
|
while True:
|
||||||
|
data = f.read(BLOCK_SIZE)
|
||||||
|
if not len(data):
|
||||||
|
break
|
||||||
|
raw(pulses, data)
|
||||||
|
|
||||||
|
pulses.space(1.0)
|
||||||
|
with wave.open(str(args.output), "wb") as wav:
|
||||||
|
wav.setnchannels(1)
|
||||||
|
wav.setsampwidth(2)
|
||||||
|
wav.setframerate(args.sample_rate)
|
||||||
|
wav.writeframes(pulses.samples)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,530 @@
|
|||||||
|
BLOCK_SIZE .equ 1+64+2 ; type + 64 bytes + crc-16
|
||||||
|
BLOCK_ERROR_CALIBRATION .equ 1
|
||||||
|
BLOCK_ERROR_SYNC .equ 2
|
||||||
|
BLOCK_ERROR_READ .equ 3
|
||||||
|
BLOCK_ERROR_CHSUM .equ 4
|
||||||
|
|
||||||
|
TAPE_WORKSPACE .equ END_OF_BIOS_WS
|
||||||
|
SHORT_AVG .equ TAPE_WORKSPACE + 000h ; byte
|
||||||
|
SHORT_MIN .equ TAPE_WORKSPACE + 001h ; byte
|
||||||
|
SHORT_MAX .equ TAPE_WORKSPACE + 002h ; byte
|
||||||
|
LONG_AVG .equ TAPE_WORKSPACE + 003h ; byte
|
||||||
|
LONG_MIN .equ TAPE_WORKSPACE + 004h ; byte
|
||||||
|
LONG_MAX .equ TAPE_WORKSPACE + 005h ; byte
|
||||||
|
BLOCK_BUFFER .equ TAPE_WORKSPACE + 006h ; BLOCK_SIZE
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; 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)
|
||||||
|
; -------------------------------------------------------
|
||||||
|
measure_pulse:
|
||||||
|
ld b,0 ; 4T
|
||||||
|
1$:
|
||||||
|
; use b as timeout counter (246 loops)
|
||||||
|
inc b ; 4T
|
||||||
|
jr z,9$ ; 12/7T
|
||||||
|
in a,(TAPE_PORT) ; 11T
|
||||||
|
cp d ; 4T
|
||||||
|
jp z,1$ ; 10T
|
||||||
|
; loop has 36T
|
||||||
|
; edge found
|
||||||
|
ld d,a ; 4T
|
||||||
|
or a ; 4T, clear carry
|
||||||
|
ret ; 10T
|
||||||
|
9$:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Ignore one pulse and then measure the next
|
||||||
|
; needs d (comparator state)
|
||||||
|
; returns b (loop count)
|
||||||
|
; carry is timeout
|
||||||
|
; uses a
|
||||||
|
; loop is 36T extra 26T (0.7 loops)
|
||||||
|
; -------------------------------------------------------
|
||||||
|
sample_pulse:
|
||||||
|
ld b,0 ; 4T
|
||||||
|
1$:
|
||||||
|
; use b as timeout counter (246 loops)
|
||||||
|
inc b ; 4T
|
||||||
|
jr z,9$ ; 12/7T
|
||||||
|
in a,(TAPE_PORT) ; 11T
|
||||||
|
cp d ; 4T
|
||||||
|
jp z,1$ ; 10T
|
||||||
|
; loop has 36T
|
||||||
|
; edge found
|
||||||
|
ld d,a ; 4T
|
||||||
|
; start timing from now
|
||||||
|
ld b,0 ; 4T
|
||||||
|
2$:
|
||||||
|
; use b as timeout counter (246 loops)
|
||||||
|
inc b ; 4T
|
||||||
|
jr z,9$ ; 12/7T
|
||||||
|
in a,(TAPE_PORT) ; 11T
|
||||||
|
cp d ; 4T
|
||||||
|
jp z,2$ ; 10T
|
||||||
|
; loop has 36T
|
||||||
|
; edge found
|
||||||
|
ld d,a ; 4T
|
||||||
|
or a ; 4T, clear carry
|
||||||
|
ret
|
||||||
|
9$:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Average 8 pulse measurements
|
||||||
|
; needs d (comparator state)
|
||||||
|
; returns a (average)
|
||||||
|
; cary is timeout
|
||||||
|
; uses b,e,h
|
||||||
|
; -------------------------------------------------------
|
||||||
|
average8:
|
||||||
|
ld e,0
|
||||||
|
ld h,8
|
||||||
|
1$:
|
||||||
|
; synchronize to next pulse and ignore measurement
|
||||||
|
call sample_pulse
|
||||||
|
ret c ; return when timeout
|
||||||
|
call sample_pulse
|
||||||
|
ret c ; return when timeout
|
||||||
|
ld a,e ; add loop count to e
|
||||||
|
add b
|
||||||
|
ld e,a
|
||||||
|
dec h
|
||||||
|
jr nz,1$ ; repeat this 8 times
|
||||||
|
ld a,e
|
||||||
|
srl a
|
||||||
|
srl a
|
||||||
|
srl a ; divide by 8
|
||||||
|
or a ; cear carry
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Make a +/-25% window around a
|
||||||
|
; needs a
|
||||||
|
; 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
|
||||||
|
; -------------------------------------------------------
|
||||||
|
make_window:
|
||||||
|
ld e,a
|
||||||
|
srl a
|
||||||
|
srl a
|
||||||
|
ld h,a ; h = a/4
|
||||||
|
ld a,e
|
||||||
|
sub h
|
||||||
|
ld b,a ; b = a-25%
|
||||||
|
ld a,e
|
||||||
|
add h
|
||||||
|
ld c,a ; c = a+25%
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Calibration
|
||||||
|
; d needs to be kept from now on
|
||||||
|
; uses a,b,c,e,h,l
|
||||||
|
; carry is error
|
||||||
|
; -------------------------------------------------------
|
||||||
|
calibrate:
|
||||||
|
ld a,(TAPE_PORT) ; get current state
|
||||||
|
ld d,a
|
||||||
|
call sample_pulse ; make sure we start in silence
|
||||||
|
jp nc,cal_err ; should timeout
|
||||||
|
; wait for the first edge
|
||||||
|
ld l,0
|
||||||
|
1$:
|
||||||
|
call sample_pulse
|
||||||
|
jr nc,2$
|
||||||
|
; use l as timout counter
|
||||||
|
; inc l
|
||||||
|
; jr nx,1$
|
||||||
|
jr 1$
|
||||||
|
; timeout
|
||||||
|
jp cal_err ; TODO ignored timeout for now
|
||||||
|
2$:
|
||||||
|
; edge found
|
||||||
|
; get short leader timing
|
||||||
|
call average8
|
||||||
|
jr c,cal_err ; timeout
|
||||||
|
ld (SHORT_AVG),a
|
||||||
|
call make_window
|
||||||
|
ld a,b
|
||||||
|
ld (SHORT_MIN),a
|
||||||
|
ld a,c
|
||||||
|
ld (SHORT_MAX),a
|
||||||
|
; find the long leader
|
||||||
|
; need 8 consecutive pulses > short*1.5
|
||||||
|
ld a,(SHORT_AVG)
|
||||||
|
ld c,a
|
||||||
|
srl a
|
||||||
|
add c ; a = 1.5*short
|
||||||
|
ld c,a
|
||||||
|
; TODO no overall timeout here
|
||||||
|
3$:
|
||||||
|
ld e,4 ; repeat 4 times, each first pulse
|
||||||
|
; is ignored
|
||||||
|
4$:
|
||||||
|
call sample_pulse
|
||||||
|
jr c,cal_err
|
||||||
|
call sample_pulse
|
||||||
|
jr c,cal_err
|
||||||
|
ld a,b
|
||||||
|
; try again if its a short pulse
|
||||||
|
cp c
|
||||||
|
jr c,3$
|
||||||
|
; we found a long pulse
|
||||||
|
dec e
|
||||||
|
jr nz,4$ ; repeat this 4 times
|
||||||
|
; long leader found
|
||||||
|
call average8
|
||||||
|
jr c,cal_err ; timeout
|
||||||
|
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 c
|
||||||
|
ld c,a ; c = 1.5*short
|
||||||
|
ld a,(LONG_AVG)
|
||||||
|
cp c
|
||||||
|
jr c,cal_err
|
||||||
|
; Find the marker. marker is 2*long
|
||||||
|
; threshold is 1.5*long
|
||||||
|
ld a,(LONG_AVG)
|
||||||
|
ld c,a
|
||||||
|
srl a
|
||||||
|
add 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_err ; timeout
|
||||||
|
ld a,b
|
||||||
|
cp c
|
||||||
|
jr c,5$ ; not a marker
|
||||||
|
inc e ; marker interval found
|
||||||
|
ld a,e
|
||||||
|
cp 2 ; we need two of them
|
||||||
|
jr c,6$ ; not 2 yet, measure next
|
||||||
|
; both marker halves found
|
||||||
|
; we are at the beginning of the first data pulse
|
||||||
|
; next 8 bytes is the training byte 0x55
|
||||||
|
call read_byte
|
||||||
|
jr c,cal_err
|
||||||
|
cp a,055h
|
||||||
|
jr nz,cal_err ; training byte not 0x55
|
||||||
|
; calibration complete
|
||||||
|
or a ; clear carry
|
||||||
|
ret
|
||||||
|
cal_err:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Read a single bit
|
||||||
|
; needs d (comparator state)
|
||||||
|
; valid: z=1 c=bit
|
||||||
|
; error: z=0
|
||||||
|
; uses a,b,c
|
||||||
|
; -------------------------------------------------------
|
||||||
|
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 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 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 c
|
||||||
|
jr c,bit_bad ; pulse<SHORT_MIN
|
||||||
|
ld a,(SHORT_MAX)
|
||||||
|
ld c,a
|
||||||
|
ld a,b
|
||||||
|
cp 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 c
|
||||||
|
jr c,bit_bad ; pulse<LONG_MAX
|
||||||
|
ld a,(LONG_MAX)
|
||||||
|
ld c,a
|
||||||
|
ld a,b
|
||||||
|
cp c
|
||||||
|
jr c,bit_zero
|
||||||
|
jr z,bit_zero ; pulse<=LONG_MAX
|
||||||
|
bit_bad:
|
||||||
|
ld a,1
|
||||||
|
or a
|
||||||
|
ret
|
||||||
|
bit_zero:
|
||||||
|
xor a
|
||||||
|
ret
|
||||||
|
bit_one:
|
||||||
|
xor a
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Read byte, MSB first
|
||||||
|
; returns a (byte)
|
||||||
|
; carry means error
|
||||||
|
; needs d (comparator state)
|
||||||
|
; uses e,h,b,c
|
||||||
|
; -------------------------------------------------------
|
||||||
|
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
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Read and check sync bytes
|
||||||
|
; carry is error
|
||||||
|
; -------------------------------------------------------
|
||||||
|
read_sync:
|
||||||
|
call read_byte
|
||||||
|
ret c
|
||||||
|
cp 05ah
|
||||||
|
jr nz,9$
|
||||||
|
call read_byte
|
||||||
|
ret c
|
||||||
|
cp 0c3h
|
||||||
|
jr nz,9$
|
||||||
|
or a
|
||||||
|
ret
|
||||||
|
9$:
|
||||||
|
scf
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Read a block into BLOCK_BUFFER
|
||||||
|
; needs d (comparator state)
|
||||||
|
; uses b,c,d,e,h,l
|
||||||
|
; returns error code in a
|
||||||
|
; returns start of block in hl
|
||||||
|
; carry is fail
|
||||||
|
; -------------------------------------------------------
|
||||||
|
read_block:
|
||||||
|
; Block reading uses de' and hl' so preserve them
|
||||||
|
exx
|
||||||
|
push de
|
||||||
|
push hl
|
||||||
|
exx
|
||||||
|
; calibrate reader
|
||||||
|
call calibrate
|
||||||
|
jr nc,1$ ; if no error jump
|
||||||
|
ld a,BLOCK_ERROR_CALIBRATION
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
1$:
|
||||||
|
; calibrated, read sync bytes
|
||||||
|
call read_sync
|
||||||
|
jr nc,2$ ; if no error jump
|
||||||
|
ld a,BLOCK_ERROR_SYNC
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
2$:
|
||||||
|
; reader initialized, start reading bytes
|
||||||
|
exx
|
||||||
|
ld e,0
|
||||||
|
ld hl,BLOCK_BUFFER
|
||||||
|
exx
|
||||||
|
4$:
|
||||||
|
call read_byte
|
||||||
|
jr nc,5$
|
||||||
|
; read error
|
||||||
|
exx
|
||||||
|
ld a,BLOCK_ERROR_READ
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
5$:
|
||||||
|
exx
|
||||||
|
ld (hl),a
|
||||||
|
inc hl
|
||||||
|
inc e
|
||||||
|
ld a,e
|
||||||
|
cp BLOCK_SIZE
|
||||||
|
exx
|
||||||
|
jr nz,4$
|
||||||
|
|
||||||
|
; Block is in BLOCK_BUFFER
|
||||||
|
; calculate crc16
|
||||||
|
ld hl, BLOCK_BUFFER
|
||||||
|
ld a, BLOCK_SIZE-2 ; dont use received crc value
|
||||||
|
call crc16
|
||||||
|
; todo compare received crc with hl
|
||||||
|
ld bc, (BLOCK_BUFFER+BLOCK_SIZE-2)
|
||||||
|
or a ; clear carry
|
||||||
|
sbc hl,bc ; do hl-bc-c
|
||||||
|
jr z,6$ ; if the same jump
|
||||||
|
; crc error
|
||||||
|
ld a,BLOCK_ERROR_CHSUM
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
6$:
|
||||||
|
; Block received correctly
|
||||||
|
ld hl,BLOCK_BUFFER
|
||||||
|
or a ; clear carry
|
||||||
|
101$:
|
||||||
|
exx
|
||||||
|
pop hl
|
||||||
|
pop de
|
||||||
|
exx
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Read a block but do not store
|
||||||
|
; needs d (comparator state)
|
||||||
|
; uses b,c,d,e,h,l
|
||||||
|
; returns error code in a
|
||||||
|
; carry is fail
|
||||||
|
; -------------------------------------------------------
|
||||||
|
read_block_dry:
|
||||||
|
; Block reading uses de' and hl' so preserve them
|
||||||
|
exx
|
||||||
|
push de
|
||||||
|
push hl
|
||||||
|
exx
|
||||||
|
; calibrate reader
|
||||||
|
call calibrate
|
||||||
|
jr nc,1$ ; if no error jump
|
||||||
|
ld a,BLOCK_ERROR_CALIBRATION
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
1$:
|
||||||
|
; calibrated, read sync bytes
|
||||||
|
call read_sync
|
||||||
|
jr nc,2$ ; if no error jump
|
||||||
|
ld a,BLOCK_ERROR_SYNC
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
2$:
|
||||||
|
; reader initialized, start reading bytes
|
||||||
|
exx
|
||||||
|
ld e,0
|
||||||
|
exx
|
||||||
|
4$:
|
||||||
|
call read_byte
|
||||||
|
jr nc,5$
|
||||||
|
; read error
|
||||||
|
exx
|
||||||
|
ld a,BLOCK_ERROR_READ
|
||||||
|
scf
|
||||||
|
jr 101$
|
||||||
|
5$:
|
||||||
|
exx
|
||||||
|
inc hl
|
||||||
|
inc e
|
||||||
|
ld a,e
|
||||||
|
cp BLOCK_SIZE
|
||||||
|
exx
|
||||||
|
jr nz,4$
|
||||||
|
; Block fully received
|
||||||
|
or a ; clear carry
|
||||||
|
101$:
|
||||||
|
exx
|
||||||
|
pop hl
|
||||||
|
pop de
|
||||||
|
exx
|
||||||
|
ret
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; CRC16
|
||||||
|
; CRC-16/CCITT-FALSE (polynomial 1021h, initial value 0ffffh)
|
||||||
|
; needs hl (pointer to memory buffer)
|
||||||
|
; needs a (length of buffer)
|
||||||
|
; returns hl (crc16)
|
||||||
|
; uses a,b,c,d,e
|
||||||
|
; -------------------------------------------------------
|
||||||
|
crc16:
|
||||||
|
ld b,a
|
||||||
|
ld de,0ffffh
|
||||||
|
or a
|
||||||
|
jr z,4$
|
||||||
|
1$:
|
||||||
|
ld a,(hl)
|
||||||
|
inc hl
|
||||||
|
xor d
|
||||||
|
ld d,a
|
||||||
|
ld c,8
|
||||||
|
2$:
|
||||||
|
sla e
|
||||||
|
rl d
|
||||||
|
jr nc,3$
|
||||||
|
ld a,e
|
||||||
|
xor 021h
|
||||||
|
ld e,a
|
||||||
|
ld a,d
|
||||||
|
xor 010h
|
||||||
|
ld d,a
|
||||||
|
3$:
|
||||||
|
dec c
|
||||||
|
jr nz,2$
|
||||||
|
djnz 1$
|
||||||
|
4$:
|
||||||
|
ex de,hl
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Wait for silence
|
||||||
|
; uses a,b,d
|
||||||
|
; -------------------------------------------------------
|
||||||
|
wait_silence:
|
||||||
|
call sample_pulse
|
||||||
|
jr nc,wait_silence ; no timeout -> no silence
|
||||||
|
ret
|
||||||
|
|
||||||
|
; -------------------------------------------------------
|
||||||
|
; Strings
|
||||||
|
; -------------------------------------------------------
|
||||||
|
msg_start: .db "PLAY TAPE", 0ah,0dh,00h
|
||||||
Binary file not shown.
Reference in New Issue
Block a user