New -> currentt
This commit is contained in:
@@ -1,35 +0,0 @@
|
||||
# ROM applications
|
||||
|
||||
Applications in this directory execute directly from ROM; the reset bootstrap
|
||||
does not copy them. Each application must have its own non-overlapping `ORG`
|
||||
below `8000` and should include `bios.inc` from the project's `include`
|
||||
directory instead of accessing the UART hardware directly.
|
||||
|
||||
The current allocation is:
|
||||
|
||||
- `1400-1C0C`: Tiny BASIC
|
||||
- `1C0D-2FFF`: available
|
||||
- `3000-32A6`: tape loader
|
||||
- `32A7-7FFF`: available for more applications (`3300` is the next convenient
|
||||
aligned origin)
|
||||
|
||||
The resident BIOS and monitor own high RAM at `C000-FFFF`. Tiny BASIC owns
|
||||
`8000-BFFF` for its workspace and stack. Applications may reuse this lower-RAM
|
||||
allocation because they run one at a time, but they must leave `C000-FFFF`
|
||||
untouched.
|
||||
|
||||
Applications using the interrupt-driven console must leave interrupt mode 2,
|
||||
the `I` register, and the BIOS UART/CTC configuration intact. They may return
|
||||
normally if they preserve the caller's stack, or jump to `BIOS_MONITOR` for a
|
||||
non-returning monitor warm start after replacing the stack pointer.
|
||||
|
||||
Use `BIOS_BANK`, `BIOS_ROM_DISABLE`, and `BIOS_ROM_ENABLE` for the write-only
|
||||
banking latch rather than writing port `00` directly. The BIOS keeps the
|
||||
ROM-disable state when the RAM bank selection changes. A ROM application must
|
||||
not expect a normal `RET` after it maps out the ROM containing its return
|
||||
address.
|
||||
|
||||
Application origins and the resident-memory layout are defined centrally in
|
||||
`rom_layout.mk`. The build turns that file into C and assembly includes. To add
|
||||
an application, add its address there, add its build and overlay rules to the
|
||||
Makefile, and add a monitor command or use `G address` to launch it.
|
||||
@@ -0,0 +1,40 @@
|
||||
.SILENT:
|
||||
ASM := zasm
|
||||
ASMFLAGS := -w -b
|
||||
BUILDDIR := ../../build
|
||||
|
||||
FILES := basic
|
||||
SUBDIRS :=
|
||||
|
||||
SOURCES := $(addprefix ./, $(addsuffix .asm, $(FILES)))
|
||||
TARGETS := $(addprefix $(BUILDDIR)/, $(addsuffix .bin, $(FILES)))
|
||||
|
||||
TOPTARGETS := all clean size
|
||||
$(TOPTARGETS): $(SUBDIRS)
|
||||
|
||||
all: $(BUILDDIR) $(TARGETS) size
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)
|
||||
|
||||
$(BUILDDIR):
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
||||
$(BUILDDIR)/%.bin: ./%.asm
|
||||
echo '>$<'
|
||||
$(ASM) $(ASMFLAGS) $< $@
|
||||
|
||||
size:
|
||||
@for file in $(TARGETS); do \
|
||||
if [ -f "$$file" ]; then \
|
||||
echo "$$file: $$(wc -c < "$$file") bytes"; \
|
||||
else \
|
||||
echo "$$file: Not built yet (run 'make')"; \
|
||||
fi \
|
||||
done
|
||||
|
||||
$(SUBDIRS):
|
||||
$(MAKE) -C $@ $(or $(MAKECMDGOALS),all) --no-print-directory
|
||||
|
||||
.PHONY: $(TOPTARGETS) $(SUBDIRS)
|
||||
|
||||
@@ -35,15 +35,18 @@
|
||||
; Adapted for the freeware Zilog Macro Assembler 2.10 to produce
|
||||
; the original ROM code (checksum A934H). PA
|
||||
|
||||
#include "../../build/memory.inc"
|
||||
#include "../../bios.inc"
|
||||
|
||||
; MACHINE CONFIG
|
||||
MC_BIOS_PUTC .EQU 0C050H
|
||||
MC_BIOS_TRY_GETC .EQU 0C053H
|
||||
MC_BIOS_GETC .EQU 0C056H
|
||||
MC_BIOS_FLUSH .EQU 0C062H
|
||||
MC_BIOS_MONITOR .EQU 0C06EH
|
||||
MC_CODE_START .EQU 04000H
|
||||
MC_RAM_START .EQU 08000H
|
||||
MC_RAM_END .EQU 0BFFFH
|
||||
MC_BIOS_PUTC .EQU BIOS_PUTC
|
||||
MC_BIOS_TRY_GETC .EQU BIOS_TRY_GETC
|
||||
MC_BIOS_GETC .EQU BIOS_GETC
|
||||
MC_BIOS_FLUSH .EQU 00000H
|
||||
MC_BIOS_MONITOR .EQU 00000H
|
||||
MC_CODE_START .EQU BASIC_RAM_START
|
||||
MC_RAM_START .EQU BASIC_RAM_START + BASIC_ROM_SIZE
|
||||
MC_RAM_END .EQU 0FFFFH
|
||||
|
||||
; GENERAL EQUATES
|
||||
|
||||
@@ -182,7 +185,7 @@ COPY: LD A,(DE) ; Get source
|
||||
CALL PRNTCRLF ; Output CRLF
|
||||
LD (BUFFER+72+1),A ; Mark end of buffer
|
||||
LD (PROGST),A ; Initialise program area
|
||||
MSIZE: LD HL,MC_RAM_END+1 ; Directly set top of ram instead of probing
|
||||
MSIZE: LD HL,MC_RAM_END ; Directly set top of ram instead of probing
|
||||
JP SETTOP
|
||||
;LD HL,MEMMSG ; Point to message
|
||||
;CALL PRS ; Output "Memory size"
|
||||
@@ -216,7 +219,7 @@ MSIZE: LD HL,MC_RAM_END+1 ; Directly set top of ram instead of probing
|
||||
;LD (HL),B ; Restore old contents
|
||||
;JP NZ,MSIZE ; Ask again if no RAM
|
||||
|
||||
SETTOP: DEC HL ; Back one byte
|
||||
SETTOP: ;DEC HL ; Back one byte
|
||||
LD DE,STLOOK-1 ; See if enough RAM
|
||||
CALL CPDEHL ; Compare DE with HL
|
||||
JP C,MSIZE ; Ask again if not enough RAM
|
||||
@@ -4344,7 +4347,7 @@ JJUMP1: LD IX,-1 ; Flag cold start
|
||||
MONOUT: JP MC_BIOS_PUTC ; output a char
|
||||
|
||||
|
||||
MONITR: CALL MC_BIOS_FLUSH
|
||||
MONITR: ;CALL MC_BIOS_FLUSH
|
||||
JP MC_BIOS_MONITOR ; Restart (Normally Monitor Start)
|
||||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
Name casette;
|
||||
PartNo 00;
|
||||
Date 2026-10-03;
|
||||
Revision 01;
|
||||
Designer joppe;
|
||||
Company unknown;
|
||||
Assembly None;
|
||||
Location unknown;
|
||||
Device g22v10;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Constants
|
||||
------------------------------------------------------------ */
|
||||
|
||||
ar = 'b'0;
|
||||
sp = 'b'0;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Pin assignments
|
||||
|
||||
Physical signals IOSEL, RD and WR are active-low.
|
||||
Their polarity is expressed in the equations with !.
|
||||
------------------------------------------------------------ */
|
||||
|
||||
/* inputs */
|
||||
|
||||
PIN 1 = CLK;
|
||||
|
||||
PIN 2 = A1;
|
||||
PIN 3 = A2;
|
||||
|
||||
PIN 4 = IOSEL;
|
||||
PIN 5 = RD;
|
||||
PIN 6 = A0;
|
||||
|
||||
PIN 7 = COMP;
|
||||
PIN 8 = WR;
|
||||
|
||||
|
||||
/* data bus */
|
||||
|
||||
PIN 14 = D0;
|
||||
PIN 15 = D1;
|
||||
PIN 16 = D2;
|
||||
PIN 17 = D3;
|
||||
PIN 18 = D4;
|
||||
PIN 19 = D5;
|
||||
PIN 20 = D6;
|
||||
PIN 21 = D7;
|
||||
|
||||
|
||||
/* registered output */
|
||||
|
||||
PIN 22 = LED;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Read register at offset 0
|
||||
|
||||
Conditions:
|
||||
|
||||
/IOSEL = 0
|
||||
/RD = 0
|
||||
/WR = 1
|
||||
A2:A0 = 000
|
||||
|
||||
Result:
|
||||
|
||||
D0 = COMP
|
||||
D7..D1 = 0
|
||||
------------------------------------------------------------ */
|
||||
|
||||
D0 = COMP;
|
||||
|
||||
D1 = 'b'0;
|
||||
D2 = 'b'0;
|
||||
D3 = 'b'0;
|
||||
D4 = 'b'0;
|
||||
D5 = 'b'0;
|
||||
D6 = 'b'0;
|
||||
D7 = 'b'0;
|
||||
|
||||
|
||||
D0.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D1.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D2.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D3.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D4.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D5.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D6.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
D7.OE =
|
||||
!IOSEL
|
||||
& !RD
|
||||
& WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
Writable LED register
|
||||
|
||||
Write conditions:
|
||||
|
||||
/IOSEL = 0
|
||||
/RD = 1
|
||||
/WR = 0
|
||||
A2:A0 = 000
|
||||
|
||||
During such a write:
|
||||
|
||||
LED.D = D0
|
||||
|
||||
Otherwise:
|
||||
|
||||
LED.D = LED
|
||||
|
||||
Pin 1 (CLK) clocks the registered macrocell.
|
||||
------------------------------------------------------------ */
|
||||
|
||||
LED.D =
|
||||
(
|
||||
!IOSEL
|
||||
& RD
|
||||
& !WR
|
||||
& !A2
|
||||
& !A1
|
||||
& !A0
|
||||
& D0
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
(
|
||||
(
|
||||
IOSEL
|
||||
# !RD
|
||||
# WR
|
||||
# A2
|
||||
# A1
|
||||
# A0
|
||||
)
|
||||
& LED
|
||||
);
|
||||
|
||||
|
||||
LED.ar = ar;
|
||||
LED.sp = sp;
|
||||
@@ -1,54 +0,0 @@
|
||||
|
||||
CUPL(WM) 5.0a Serial# MW-10400000
|
||||
Device g22v10 Library DLIB-h-40-1
|
||||
Created Sat Oct 03 16:22:02 2026
|
||||
Name casette
|
||||
Partno 00
|
||||
Revision 01
|
||||
Date 2026-10-03
|
||||
Designer joppe
|
||||
Company unknown
|
||||
Assembly None
|
||||
Location unknown
|
||||
*QP24
|
||||
*QF5892
|
||||
*G1
|
||||
*F0
|
||||
*L00416 00000000000000000000000011111111
|
||||
*L00448 11111111111111111111111111111111
|
||||
*L00480 11111111101110111011011110111111
|
||||
*L00512 10111111110111111111111011110111
|
||||
*L00544 11111111111111111111111111111111
|
||||
*L00576 11101111111110111111111111111111
|
||||
*L00608 11111111111111101111111111111111
|
||||
*L00640 11110111111111111111111111100111
|
||||
*L00672 11111111111111111111111111111111
|
||||
*L00704 11110110111111111111111111111111
|
||||
*L00736 11111111111111111110111111111111
|
||||
*L00768 01111111111111111111111100000000
|
||||
*L00896 00000000000000000000000000001111
|
||||
*L00928 10111011101110111011111101111111
|
||||
*L00960 11111111000000000000000000000000
|
||||
*L01472 00000000000000000000000011111011
|
||||
*L01504 10111011101110111111011111111111
|
||||
*L01536 11110000000000000000000000000000
|
||||
*L02144 00000000000011111011101110111011
|
||||
*L02176 10111111011111111111111100000000
|
||||
*L02880 00000000000000000000000011111011
|
||||
*L02912 10111011101110111111011111111111
|
||||
*L02944 11110000000000000000000000000000
|
||||
*L03648 00001111101110111011101110111111
|
||||
*L03680 01111111111111110000000000000000
|
||||
*L04288 00000000000000000000000011111011
|
||||
*L04320 10111011101110111111011111111111
|
||||
*L04352 11110000000000000000000000000000
|
||||
*L04864 00000000000000000000111110111011
|
||||
*L04896 10111011101111110111111111111111
|
||||
*L05344 00000000000000000000000011111011
|
||||
*L05376 10111011101110111111011111111111
|
||||
*L05408 11111111111111111111111111110111
|
||||
*L05440 11111111111111110000000000000000
|
||||
*L05792 00000000000000000010111111111111
|
||||
*L05824 11110011000000110000000000000000
|
||||
*C5BE4
|
||||
*6A87
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
import wave
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
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 main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate the pulse-width encoded tape leader"
|
||||
)
|
||||
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))
|
||||
|
||||
pulses.leader()
|
||||
pulses.bytes(b'Hello world!\0')
|
||||
|
||||
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()
|
||||
@@ -1,569 +0,0 @@
|
||||
.include "bios.inc"
|
||||
|
||||
.area TAPELOAD (ABS)
|
||||
.org TAPE_LOADER_ROM_ADDRESS
|
||||
|
||||
.macro print_var arg1
|
||||
ld a, (arg1)
|
||||
ei
|
||||
call print_hex8
|
||||
call print_crlf
|
||||
di
|
||||
.endm
|
||||
|
||||
.macro error_ret arg1,arg2
|
||||
arg1:
|
||||
ld a,#arg2
|
||||
scf
|
||||
ret
|
||||
.endm
|
||||
|
||||
TAPE_PORT = 0x18
|
||||
MAX_BLOCK_SIZE = 0x100
|
||||
|
||||
STACK_TOP = 0xBFFF
|
||||
WORK_BASE = 0xB000
|
||||
|
||||
OLD_SP = WORK_BASE + 0x00 ; word
|
||||
OLD_IFF = WORK_BASE + 0x02 ; byte
|
||||
SHORT_AVG = WORK_BASE + 0x03 ; byte
|
||||
SHORT_MAX = WORK_BASE + 0x04 ; byte
|
||||
SHORT_MIN = WORK_BASE + 0x05 ; byte
|
||||
LONG_AVG = WORK_BASE + 0x06 ; byte
|
||||
LONG_MAX = WORK_BASE + 0x07 ; byte
|
||||
LONG_MIN = WORK_BASE + 0x08 ; byte
|
||||
|
||||
BLOCK_BUFFER = WORK_BASE + 0x100 ; MAX_BLOCK_SIZE
|
||||
|
||||
; #################################################3
|
||||
; Entry
|
||||
; #################################################3
|
||||
tape_loader:
|
||||
ld (OLD_SP),sp
|
||||
; Save interrupt state.
|
||||
ld a,i
|
||||
jp po,iff_was_off
|
||||
ld a,#1
|
||||
jr save_iff
|
||||
iff_was_off:
|
||||
xor a
|
||||
save_iff:
|
||||
ld (OLD_IFF),a
|
||||
di
|
||||
ld sp,#STACK_TOP
|
||||
|
||||
main:
|
||||
; Initial message.
|
||||
ld hl,#msg_start
|
||||
call print_string_ei
|
||||
|
||||
; Calibrate reader
|
||||
call calibrate
|
||||
jr nc,1$
|
||||
ei
|
||||
call print_hex8
|
||||
call print_crlf
|
||||
ld hl,#msg_cal_fail
|
||||
call print_string
|
||||
di
|
||||
jp exit
|
||||
1$:
|
||||
; try to read the sync bytes
|
||||
call read_sync
|
||||
jr nc,2$
|
||||
ld hl,#msg_sync_error
|
||||
call print_string_ei
|
||||
jp exit
|
||||
2$:
|
||||
|
||||
; Test, read Hello World!\0 (13 bytes)
|
||||
exx
|
||||
ld e,#0
|
||||
ld hl,#BLOCK_BUFFER
|
||||
exx
|
||||
4$:
|
||||
call read_byte
|
||||
jr nc,5$
|
||||
ld hl,#msg_read_error
|
||||
call print_string_ei
|
||||
exx
|
||||
ei
|
||||
call print_hex16
|
||||
call print_crlf
|
||||
di
|
||||
exx
|
||||
jp exit
|
||||
5$:
|
||||
exx
|
||||
ld (hl),a
|
||||
inc hl
|
||||
inc e
|
||||
ld a,e
|
||||
cp a,#13
|
||||
exx
|
||||
jr nz,4$
|
||||
|
||||
; #################################################3
|
||||
; Exit
|
||||
; #################################################3
|
||||
exit:
|
||||
ei
|
||||
call BIOS_FLUSH
|
||||
di
|
||||
ld sp,(OLD_SP)
|
||||
; Return code 0
|
||||
ld a,#0
|
||||
ld c,a
|
||||
; Restore caller's interrupt state.
|
||||
ld a,(OLD_IFF)
|
||||
or a
|
||||
ld a,c
|
||||
jr z,return_di
|
||||
ei
|
||||
ret
|
||||
return_di:
|
||||
ret
|
||||
|
||||
; #################################################3
|
||||
; Measure edge-to-edge pulse
|
||||
; needs d (comparator state)
|
||||
; returns b (loop count)
|
||||
; carry is timeout
|
||||
; uses a
|
||||
; loop is 36T, extra 22T (0.6 loops)
|
||||
; #################################################3
|
||||
measure_pulse:
|
||||
ld b,#0 ; 4T
|
||||
1$:
|
||||
; Use b as timeout counter (256 loops)
|
||||
inc b ; 4T
|
||||
jr z,9$ ; 12/7T
|
||||
in a,(TAPE_PORT) ; 11T
|
||||
cp a,d ; 4T
|
||||
jp z,1$ ; 10T
|
||||
; loop has 36T
|
||||
; Edge found
|
||||
ld d,a ; 4T
|
||||
or a,a ; 4T
|
||||
ret ; 10T
|
||||
9$:
|
||||
scf
|
||||
ret
|
||||
|
||||
; #################################################3
|
||||
; Ignore one pulse then measure next pulse
|
||||
; needs d (comparator state)
|
||||
; returns b (loop count)
|
||||
; carry is timeout
|
||||
; uses a
|
||||
; loop is 36T, extra 26T (0.7 loops)
|
||||
; #################################################3
|
||||
sample_pulse:
|
||||
; wait for first edge
|
||||
ld b,#0 ; 4T
|
||||
1$:
|
||||
; Use b as timeout counter (256 loops)
|
||||
inc b ; 4T
|
||||
jr z,9$ ; 12/7T
|
||||
in a,(TAPE_PORT) ; 11T
|
||||
cp a,d ; 4T
|
||||
jp z,1$ ; 10T
|
||||
; loop has 36T
|
||||
; Edge found
|
||||
ld d,a ; 4T
|
||||
; start timing pulse length
|
||||
ld b,#0 ; 4T
|
||||
2$:
|
||||
; Use b as timeout counter (256 loops)
|
||||
inc b ; 4T
|
||||
jr z,9$ ; 12/7T
|
||||
in a,(TAPE_PORT) ; 11T
|
||||
cp a,d ; 4T
|
||||
jp z,2$ ; 10T
|
||||
; loop has 36T
|
||||
; Edge found
|
||||
ld d,a ; 4T
|
||||
or a,a ; 4T
|
||||
ret ; 10T
|
||||
9$:
|
||||
scf
|
||||
ret
|
||||
|
||||
; #################################################3
|
||||
; Average 8 pulse measurements
|
||||
; needs d (comparator state)
|
||||
; returns a (average)
|
||||
; carry is timeout
|
||||
; uses b,e,h
|
||||
; #################################################3
|
||||
average8:
|
||||
ld e,#0
|
||||
ld h,#8
|
||||
1$:
|
||||
; synchronize to next pulse
|
||||
; ignore first measurement
|
||||
; contains part of the calculations
|
||||
call sample_pulse
|
||||
call sample_pulse
|
||||
; when timeout just return with carry
|
||||
ret c
|
||||
; Add loop count to e
|
||||
ld a,e
|
||||
add a,b
|
||||
ld e,a
|
||||
; repeat 8 times
|
||||
dec h
|
||||
jr nz,1$
|
||||
; divide by 8
|
||||
ld a,e
|
||||
srl a
|
||||
srl a
|
||||
srl a
|
||||
; clear carry
|
||||
or a,a
|
||||
ret
|
||||
|
||||
; #################################################3
|
||||
; Make +/-25% window around a
|
||||
; needs a (average)
|
||||
; returns b (minimum) c (maximum)
|
||||
; uses e,h
|
||||
; NOTE: USES 8 BIT COUNTER
|
||||
; @1MHZ t_long<1.14ms
|
||||
; @2MHZ t_long<571us
|
||||
; @4MHZ t_long<285us
|
||||
; TODO: Check feasibility of using 16bit accumulator
|
||||
; #################################################3
|
||||
make_window:
|
||||
ld e,a
|
||||
; get 25%
|
||||
srl a
|
||||
srl a
|
||||
ld h,a
|
||||
; a-25%
|
||||
ld a,e
|
||||
sub h
|
||||
ld b,a
|
||||
; a+25%
|
||||
ld a,e
|
||||
add a,h
|
||||
ld c,a
|
||||
ret
|
||||
|
||||
; #################################################3
|
||||
; Calibration
|
||||
; #################################################3
|
||||
calibrate:
|
||||
; Get current comparator state
|
||||
in a,(TAPE_PORT)
|
||||
ld d,a
|
||||
|
||||
; make sure we start in silence
|
||||
call sample_pulse
|
||||
; should timeout
|
||||
jp nc,cal_fail_no_silence
|
||||
|
||||
; Wait for first edge
|
||||
ld l,#0
|
||||
1$:
|
||||
call sample_pulse
|
||||
jr nc,2$
|
||||
; Use b as timeout counter (256 loops)
|
||||
; inc l
|
||||
; jr nz,1$
|
||||
jr 1$
|
||||
; timeout
|
||||
;jp cal_fail_no_short_leader ; TODO IGNORED TIMEOUT FOR NOW HERE
|
||||
2$:
|
||||
; edge found
|
||||
|
||||
; get short leader timing
|
||||
call average8
|
||||
jr c,cal_fail_short
|
||||
ld (SHORT_AVG),a
|
||||
|
||||
call make_window
|
||||
ld a,b
|
||||
ld (SHORT_MIN),a
|
||||
ld a,c
|
||||
ld (SHORT_MAX),a
|
||||
|
||||
; find long leader
|
||||
; need 4 consecutive pulses > short*1.5
|
||||
ld a,(SHORT_AVG)
|
||||
ld c,a
|
||||
srl a
|
||||
add a,c
|
||||
ld c,a
|
||||
; c = short*1.5
|
||||
|
||||
; TODO no overal timeout here. a continuous
|
||||
; short pulse leader will keep on waiting
|
||||
; forever
|
||||
3$:
|
||||
ld e,#4
|
||||
4$:
|
||||
; ignore first to get accurate measurement
|
||||
call sample_pulse
|
||||
call sample_pulse
|
||||
jr c,cal_fail_find_long
|
||||
ld a,b
|
||||
; if it is no long pulse try again
|
||||
cp a,c
|
||||
jr c,3$
|
||||
; long pulse found
|
||||
dec e
|
||||
jr nz,4$
|
||||
; 4 (or well... 8) long pulses found
|
||||
|
||||
; get long leader timning
|
||||
call average8
|
||||
jr c,cal_fail_long
|
||||
ld (LONG_AVG),a
|
||||
|
||||
call make_window
|
||||
ld a,b
|
||||
ld (LONG_MIN),a
|
||||
ld a,c
|
||||
ld (LONG_MAX),a
|
||||
|
||||
; A quick sanity check
|
||||
; long > 1.5*short
|
||||
ld a,(SHORT_AVG)
|
||||
ld c,a
|
||||
srl a
|
||||
add a,c
|
||||
ld c,a
|
||||
; c = 1.5*short
|
||||
ld a,(LONG_AVG)
|
||||
cp a,c
|
||||
jr c,cal_fail_sanity
|
||||
|
||||
; Find the marker
|
||||
; marker is 2*long high 2*long low
|
||||
; threshold is 1.5*long
|
||||
ld a,(LONG_AVG)
|
||||
ld c,a
|
||||
srl a
|
||||
add a,c
|
||||
ld c,a
|
||||
; c = 1.5*long
|
||||
|
||||
; Find two consecutive marker-width intervals.
|
||||
5$:
|
||||
ld e,#0
|
||||
6$:
|
||||
call measure_pulse
|
||||
jr c,cal_fail
|
||||
ld a,b
|
||||
cp a,c
|
||||
jr c,5$
|
||||
inc e
|
||||
ld a,e
|
||||
cp a,#2
|
||||
jr c,6$
|
||||
; Both marker halves consumed. We are now at the
|
||||
; beginning of the first data pulse.
|
||||
|
||||
; next pulse is first pulse of training byte
|
||||
; must decode as 0x55
|
||||
call read_byte
|
||||
jr c,cal_fail_read_error
|
||||
cp a,#0x55
|
||||
jr nz,cal_fail_training_byte
|
||||
|
||||
ld a,#0
|
||||
or a,a
|
||||
ret
|
||||
|
||||
error_ret cal_fail 0
|
||||
error_ret cal_fail_short 1
|
||||
error_ret cal_fail_find_long 2
|
||||
error_ret cal_fail_long 3
|
||||
error_ret cal_fail_sanity 4
|
||||
error_ret cal_fail_training_byte 5
|
||||
error_ret cal_fail_no_silence 6
|
||||
error_ret cal_fail_no_short_leader 7
|
||||
error_ret cal_fail_read_error 8
|
||||
|
||||
; #################################################3
|
||||
; Read single bit
|
||||
; needs d (comparator state)
|
||||
; valid: z=1 c=bit
|
||||
; error: z=0
|
||||
; uses a,b,c
|
||||
; #################################################3
|
||||
read_bit:
|
||||
; entry may be late into first pulse
|
||||
; wait for first edge to measure
|
||||
ld b,#0
|
||||
1$:
|
||||
inc b
|
||||
jr z,bit_bad
|
||||
in a,(TAPE_PORT)
|
||||
cp a,d
|
||||
jp z,1$
|
||||
ld d,a
|
||||
; ignored first edge
|
||||
; measure second pulse
|
||||
ld b,#0
|
||||
2$:
|
||||
inc b
|
||||
jr z,bit_bad
|
||||
in a,(TAPE_PORT)
|
||||
cp a,d
|
||||
jp z,2$
|
||||
ld d,a
|
||||
; if in short window (second half is short pulse)
|
||||
; 1
|
||||
ld a,(SHORT_MIN)
|
||||
ld c,a
|
||||
ld a,b
|
||||
cp a,c
|
||||
jr c,bit_bad ; pulse<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
|
||||
; IO Helper functions
|
||||
; #################################################3
|
||||
print_string_ei:
|
||||
ei
|
||||
call print_string
|
||||
call BIOS_FLUSH
|
||||
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_hex16:
|
||||
push hl
|
||||
ld a,h
|
||||
call print_hex8
|
||||
pop hl
|
||||
ld a,l
|
||||
call print_hex8
|
||||
ret
|
||||
|
||||
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
|
||||
|
||||
; #################################################3
|
||||
; Strings
|
||||
; #################################################3
|
||||
msg_start:
|
||||
.ascii "TAPE DECODE TEST - PLAY TAPE"
|
||||
.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.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
*.bin
|
||||
*.lst
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user