Cassette

R Tape loading error, Teil 2

Im ersten Teil habe ich gezeigt, wie der Sinclair ZX Spectrum Daten auf Kassette gespeichert hat. Dieser zweite Teil erklärt, was gespeichert wird und was einen Bandladefehler (Tape loading error) verursacht.

Das ZX Spectrum BASIC bietet einen SAVE-Befehl zum Speichern aller Arten von Daten. Er kann verwendet werden, um ein BASIC-Programm, Variablen-Arrays, aber auch beliebige Teile des Speichers zu sichern. Diese Dateien werden immer in zwei separaten Blöcken gespeichert. Der erste Block wird Header (Kopfdaten) genannt. Er enthält den Dateinamen, den Datentyp und andere Meta-Informationen. Der zweite Block folgt etwa eine Sekunde später und enthält die eigentlichen Daten.

Die interne Struktur jedes Blocks ist identisch. Das erste Byte unterscheidet zwischen Header ($00) und Datenblöcken ($FF). Das letzte Byte ist eine Paritäts-Prüfsumme. Alles zwischen diesen beiden Bytes sind die Nutzdaten (Payload).

Ein Header-Block enthält immer eine Nutzdatenmenge von 17 Bytes. Das erste Byte identifiziert den Dateityp, gefolgt vom Dateinamen (10 Zeichen), gefolgt von der Länge des Datenblocks, und abgeschlossen durch zwei optionale Parameter, die je nach Dateityp unterschiedliche Bedeutungen haben. Die Länge und die beiden Parameter belegen jeweils zwei Bytes, wobei das niederwertige Byte zuerst kommt, da die Z80-CPU Little Endian ist.

Dies ist ein beispielhafter Header-Block eines Screenshots:

00$00 = Header
0003$03 = Binärdatei (Code oder SCREEN$)
0153S
0268h
0372r
0465e
0564d
062E.
077Az
086Fo
096En
1065e
11001BLänge: 6912 Bytes ($1B00)
130040Parameter 1, hier: Startadresse ($4000)
150000Parameter 2, hier: ungenutzt
20Parität

Ein Screenshot ist eigentlich nur ein Speicherabzug, der an der Adresse $4000 beginnt (was die Startadresse des Bildschirmspeichers ist) und exakt 6912 Bytes lang ist (der ZX Spectrum hat eine Auflösung von 256×192 monochromen Pixeln plus 32×24 Bytes Farbattribute, was eine Bildschirmspeichergröße von 6912 Bytes ergibt).

Bei anderen Dateitypen haben die beiden optionalen Parameter andere Bedeutungen. Zum Beispiel speichert eine BASIC-Programmdatei die Zeilennummer, bei der nach dem Laden gestartet werden soll.

Das letzte Byte ist die Parität. Sie wird zur Fehlererkennung verwendet und einfach berechnet, indem alle gelesenen Bytes per XOR verknüpft werden. Das Ergebnis muss $00 sein, andernfalls wird ein “R Tape loading error” gemeldet.

Diese Art der Fehlererkennung ist ziemlich schwach. Aufgrund der Natur der XOR-Operation ergeben zwei Fehler wieder etwas Richtiges. Das bedeutet, dass eine gerade Anzahl an fehlerhaften Bits an der gleichen Position im Block unentdeckt bleibt. Es ist auch nicht möglich, Lesefehler zu korrigieren, da die XOR-Operation nur erlaubt, die Position des fehlerhaften Bits zu identifizieren, aber nicht das tatsächliche Byte, das den Fehler enthielt. Ausgefeiltere Fehlerkorrekturalgorithmen hätten den Ladevorgang jedoch verlangsamt.

Die Parität wird als letzter Schritt überprüft, nachdem alle Bytes aus dem Block auf dem Band gelesen wurden. Aus diesem Grund kann der Lader erst am Ende der Aufnahme entscheiden, ob der Ladevorgang erfolgreich war oder nicht.

Aber warum taucht der Tape Loading Error dann manchmal auf, während der Block noch geladen wird? Nun, im ersten Teil habe ich dir erklärt, dass die Laderoutine einfach eine unbekannte Anzahl von Bytes liest. Sie endet, wenn das Warten auf eine Impulsänderung zu lange gedauert hat. Wenn es nun eine Audiolücke auf dem Band gibt, scheint das Signal einfach mitten im Block zu enden. Es ist dann sehr wahrscheinlich, dass die Paritäts-Prüfsumme falsch ist, weil noch Bytes fehlen.

Einige einfache Kopierschutzmechanismen machten sich die Art und Weise zunutze, wie der Spectrum Daten vom Band lädt. Eine sehr übliche Methode waren “headerlose” Dateien, bei denen der Header-Block weggelassen und nur der Datenblock auf Band aufgenommen wurde. Der BASIC-LOAD-Befehl war aufgrund des fehlenden Headers nicht in der Lage, diese Dateien zu lesen.

R Tape loading error

In der Anfangszeit der Heimcomputer, zu Beginn der 1980er Jahre, waren Festplatten und sogar Disketten für den Heimgebrauch zu teuer. Der günstigste Weg, große Datenmengen zu speichern, war die Kassette. Kassetten und Kassettenrekorder waren erschwinglich und in fast jedem Haushalt verfügbar.

In diesem Blogartikel werde ich dir erklären, wie der Sinclair ZX Spectrum Programme auf Kassetten speicherte. Andere Heimcomputer jener Zeit, wie der Commodore 64 oder der Amstrad CPC, funktionierten ähnlich.

Kassetten waren dafür gedacht, Audiosignale wie Sprache oder Musik zu speichern, also mussten die Erfinder der Heimcomputer einen Weg finden, Daten in Audiosignale umzuwandeln. Der einfachste Weg ist, die Daten in einen Bitstrom aus 1en und 0en zu serialisieren und einen langen Rechteckwellenzyklus für “1” und einen kurzen Rechteckwellenzyklus für “0” zu erzeugen. Genau das macht der ZX Spectrum tatsächlich!

Ein kurzer Wellenzyklus wird erzeugt, indem der Audioausgang für 855 sogenannte T-States mit Strom versorgt wird und der Strom dann für weitere 855 T-States abgeschaltet wird. Ein “T-State” ist die Zeit eines einzelnen Taktimpulses der Z80-A CPU. Da die CPU eines klassischen ZX Spectrum mit 3,5 MHz getaktet ist, hat ein T-State eine Dauer von 286 ns. Die Dauer eines kurzen Wellenzyklus beträgt somit 489 µs, was eine Audiofrequenz von etwa 2.045 Hz ergibt. Der lange Wellenzyklus ist einfach doppelt so lang.

Aufgrund allerlei Filter im analogen Audiopfad wird das rechteckige Signal bei der Wiedergabe zu einem sinusförmigen Signal geglättet. Ein Schmitt trigger in der Hardware des ZX Spectrum wandelt das Audiosignal wieder in eine rechteckige Form um. Da das Audiosignal unterschiedliche Amplituden haben oder sogar invertiert sein kann, achtet die Hardware nur auf Signalflanken, nicht auf Pegel. Alles, was die Laderoutine jetzt noch tun muss, ist, die Dauer der Impulse zu messen, den Bitstrom zu regenerieren und die Bytes wieder zusammenzusetzen.

Wenn du denkst, dass die Dinge nicht so einfach sein können, hast du recht. 😄 Der schwierigste Teil für den Lader ist es, den Anfang des Bitstroms zu finden. Wenn er auch nur um einen Zyklus (oder sogar nur um einen Impuls) abweicht, verschieben sich alle Bytes um ein Bit und das Ergebnis ist unbrauchbar. Jegliches Rauschen auf dem Band macht es jedoch unmöglich, einfach auf den Beginn des Signals zu warten.

Aus diesem Grund beginnt die Aufnahme mit einem Vorlaufsignal (Leader), gefolgt von einem Synchronisationswellenzyklus (Sync), gefolgt vom eigentlichen Bitstrom. Das Vorlaufsignal ist lediglich eine kontinuierliche Welle mit einer Impulslänge von 2.168 T-States, was einen 806-Hz-Ton ergibt, der durch rote und cyanfarbene Ränder auf dem Fernseher angezeigt wird. Der Sync-Wellenzyklus ist ein Impuls von 667 T-States “an”, gefolgt von 735 T-States “aus”. Danach beginnt der eigentliche Datenstrom, der in blauen und gelben Randfarben angezeigt wird. Wenn das letzte Bit übertragen wurde, endet der Datenstrom einfach.

Wenn der ZX Spectrum also eine Datei von Kassette lädt, wartet er zuerst auf das 806-Hz-Vorlaufsignal. Wenn es für mindestens 317 ms erkannt wurde, wartet er auf die Sync-Impulse und beginnt dann, die Bitsequenz zu lesen, bis es beim Warten auf den nächsten Impuls zu einem Timeout kommt.

Es ist eine sehr einfache Methode, um Daten auf Kassette zu speichern. Und dennoch ist sie erstaunlich zuverlässig. Nach 30 Jahren konnte ich recover almost all files von meinen alten Kassetten wiederherstellen. Einige davon waren von den billigsten Marken, die ich 1987 in die Finger bekommen konnte.

Der einzige Nachteil ist, dass diese Methode sehr langsam ist. Mit 489 µs für eine “0” und 978 µs für eine “1” kann das Speichern von nur 48 KBytes an Daten bis zu 6 Minuten dauern, was eine durchschnittliche Bitrate von 1.363 bps (ja, Bits pro Sekunde) ergibt. Wenn wir eine einzige 3 MBytes große mp3-Datei auf diese Weise speichern würden, würde das fast 5 Stunden dauern (und 5 Kassetten mit jeweils 60 Minuten Aufnahmezeit erfordern).

Einige kommerzielle Spiele verwendeten Speedloader und Kopierschutzmechanismen. Speedloader reduzierten einfach die Anzahl der T-States für die Impulse, was die Bitrate erhöhte. Einiger Kopierschutz verwendete einen “klickenden” Vorlaufton, bei dem das Vorlaufsignal unterbrochen wurde, bevor die minimale Erkennungszeit von 317 ms erreicht war. Die originale Laderoutine konnte sich nicht auf diese Art von Signalen synchronisieren, weshalb es unmöglich war, diese Dateien in Kopierprogramme einzulesen. Diese Schutzmaßnahmen konnten zwar umgangen werden, indem man direkt von Kassette zu Kassette kopierte, aber das funktionierte aufgrund des zunehmenden Audiorauschens nur wenige Male.

Im nächsten Artikel werde ich mir den Inhalt des Bitstroms genauer ansehen, und ich werde dir auch erklären, woher der gefürchtete “R Tape loading error” kommt.