CP/M 2.2 Selbstbaurechner (Z80) mit AVRs für externe Peripherie

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Einleitung

von Mario Latzig

Bild Homebrew CP/M: Selbstbaurechner mit Z80 und AVRs für Peripherie

CP/M ist seit etwa 1974 neben UNIX eines der ersten und ältesten plattformunabhängigen Betriebssystemen. Das ursprüngliche CP/M-80 basiert auf den Intel 8080 und dem dazu kompatiblen Zilog Z80 Prozessor und ist als Einzelplatzsystem konzipiert, wurde aber auch für den Intel 8086/8088 (CP/M-86) und den Motorola 68000 (CP/M-68k) portiert.

Auch wenn mittlerweile im Internet unzählige Anleitungen und Schaltpläne zu einem Selbstbaurechner mit dem Z80 Prozessor und CP/M zu finden sind, ebenso in den 70er und 80er Jahren viele CP/M-System kommerziell hergestellt wurden, hatte ich den Ehrgeiz, auch mal ein selbstgebautes System zu entwickeln und zu bauen, um besser zu verstehen, wie ein Betriebssystem, auch wenn es veraltet ist, funktioniert und mit der Hardware kommuniziert. Als Einstieg bietet sich hier CP/M 2.2 an, das sehr einfach aufgebaut ist. Der Code ist mittlerweile offen gelegt und lässt sich somit leicht an eigene Bedürfnisse anpassen. Es gibt auch noch das neuere CP/M 3.0 (CP/M Plus), welches Zusätze wie eine Bankverwaltung für mehr als 64KB Speicher bietet, aber dementsprechend auch komplizierter für ein eigenes System anzupassen ist.

Hardware

Folgende Bauteile waren bei mir bereits teilweise vorhanden und wurden in diesem Projekt integriert, um ein vollständiges System zu haben, mit dem man „arbeiten“ kann:

  • Hauptplatine:
    • Z80-Prozessor (6MHz), hier: Zilog Z0840006PSC
    • 64KB RAM, hier: KM681000BLP-7L (sind eigentlich 128KB)
    • Peripherie-Controller Atmega32
      • UART (9600 Baud) zur Kommunikation mit dem PC
      • System-Bus mittels HW SPI
  • Festplatte:
    • IDE, hier: DiskOnModule PQI, 128 MB (Flash)
  • Bildschirm:
    • LCD Modul (LCM-5430-E)
    • als VT52-kompatibles Terminal mit 80x24 Zeichen
    • Controller mittels ATmega328
  • Tastatur:
    • FDC-3402
    • Controller mittels ATmega168

Der Bus vom Z80 arbeitet parallel. Man sieht, dass nun mehrere ATmegas als Controller verwendet wurden. Zur Kommunikation zwischen den einzelnen Peripherie-Komponenten und der Hauptplatine, sozusagen als System-Bus, wird SPI verwendet, damit ist der Bus nun seriell, was den Verdrahtungsaufwand verringert, aber evtl. auch den Bus etwas langsamer macht als der Z80 an Durchsatz schaffen könnte.

Im Folgenden werden nun die einzelnen Komponenten des Systems besprochen, in der Reihenfolge, wie sie auch entwickelt wurden.

Hauptplatine

Auf der Hauptplatine befinden sich die wichtigsten Bausteine, um erste Versuche mit dem Z80 Prozessor durchführen zu können:

  • Oszillator 6 MHz
  • Z80 Prozessor
  • ATmega32 (18.432 MHz)
  • MAX232 für die serielle Schnittstelle via USART
  • 128KB SRAM
  • MCP23S17 (SPI Port Expander für den Adressbus)
  • 7474 D-FlipFlop (Wait-States für E/A-Zugriff)

Cpm-z80.png

Der Z80 ist ein 8Bit-Prozessor mit einem 16Bit breiten Adressbus, der für Speicherzugriffe, aber ebenso auch für E/A-Zugriffe auf externe Peripherie, verwendet werden kann. Für den Speicherzugriff sind Daten- und Adressbus vom SRAM und Z80 direkt miteinander verbunden. Es können somit 64KB vom SRAM adressiert werden. Pin A16 vom SRAM wird derzeit nicht benutzt (liegt auf Masse).

Beim E/A-Zugriff nutzt der Z80 nur die ersten 8 Bits des Adressbusses. Darüber wird die Peripherie adressiert. Man müsste nun eine Adresslogik entwerfen, die zwischen der unterschiedlichen Peripherie unterscheiden kann. Zum Beispiel bei Adresse $00 soll der USART aktiviert werden, bei Adresse $08 werden Schreib- und Lesezugriffe auf den IDE-Controller gestartet etc. Diese Adresslogik sollte, anstatt sie mit 74er Logikbausteinen diskret zu lösen, über einen ATmega-Mikrocontroller geregelt werden. Zudem hat man gleich eine serielle Schnittstelle (USART), um mit der Außenwelt zu kommunizieren bzw. in den ersten Versuchen Debug-Informationen an den PC zu senden.

Übrigens, alle Control-Pins vom Z80 sind low-aktiv!

Der Z80 signalisiert mit zwei unterschiedlichen Pins MREQ und IOREQ, ob ein Speicher- oder E/A-Zugriff stattfindet. Der ATmega32 hat ebenfalls Zugriff auf den Daten- und Adressbus. Auf den Adressbus wird hier aber mittels hardwareseitigen SPI über den Port-Expander MC23S17 zugegriffen. Damit es keine Konflikte beim Zugriff auf den Bus zwischen Z80 und ATmega32 gibt, ist vorgesehen, dass der ATmega32 nur aktiv (also schreibend) auf den Bus zugreifen darf, wenn der Z80 sich im Reset-Modus befindet, daher steuert der ATmega32 den RESET Pin vom Z80. Wenn sich der Z80 im Reset-Modus befindet, sind nämlich alle Ausgänge inaktiv. Andersherum muss der ATmega32 alle Pins, die mit dem Daten- und Adressbus verbunden sind, inaktiv (also als Eingang) schalten, sobald der Z80 seine Arbeit aufnimmt. Der ATmega32 hat aber weiterhin die Möglichkeit, den Bus auszulesen. Diese Möglichkeit ist wichtig, sobald ein E/A-Zugriff mittels IOREQ Pin vom Z80 stattfindet.

Da ein E/A-Zugriff mittels IOREQ Pin vom Z80 evtl. mehrere Takte länger dauern kann, als der Z80 bis zur Aufruf des nächsten Befehls braucht, z.B. beim Zugriff über den IDE-Controller oder dem VT52-Terminal, muss man die Möglichkeit vom Z80 nutzen, so genannte Wartezyklen von außen einzufügen, indem man den WAIT Pin auf Low setzt. Der Z80 verharrt dann in seinem jetzigen Zustand, führt also den Befehl für den Zugriff solange weiter aus, bis dieser Pin wieder auf High gesetzt wird.

Ursprünglich war geplant, dass der IOREQ vom Z80 ein Interrupt auf dem ATmega32 auslösen sollte. Daraufhin sollte der WAIT Pin des Z80 vom ATmega32 auf Low gesetzt werden. Leider hat sich heraus gestellt, dass der ATmega32, trotz dreifach höherem Takteingang noch zu viele Takte braucht, um den WAIT Pin auf Low zu setzen, der nächste Befehl auf dem Z80 wurde bereits ausgeführt. Nun wird mit Hilfe eines FlipFlops der WAIT Pin vom Z80 gesteuert. Ein Low-Impulse des IOREQ vom Z80 setzt sofort das FlipFlop vom IC 7474 zurück (Pin 1). Der WAIT Pin vom Z80 ist Low und es werden Wartezyklen ausgeführt. Erst wenn der ATmega32 Pin 4 vom IC 7474 aktiv-low schaltet, wird das FlipFlop wieder gesetzt. Der WAIT Pin geht wieder auf High und der Z80 setzt seine Befehlsverarbeitung fort.

Während der Wartezyklen des E/A-Zugriffes kann der ATmega32 über den MCP23S17 die ersten 8Bit des Adressbusses auslesen und entsprechende Peripherie ausführen.

Festplatte

Cpm-spi-ide.png

CP/M 2.2 kann maximal 16 Laufwerke mit bis zu 8MB ansteuern, daher machte es von Anfang an nicht viel Sinn, eine echte IDE Festplatte anzusteuern. Die heutigen Festplattengrößen sind völlig überdimensioniert. Pollin bietet für wenige Euro einen „DiskOnModul“ IDE Flashspeicher (NAND) mit einer Kapazität von 128MB an. Auch durch die geringe Bauteilgröße eignen sich die Speicher ideal als Festplatte für das CP/M-System.

Ein IDE Port arbeitet parallel und würde daher viele Pins eines Controllers benötigen, um Schreib- und Lesezugriffe auf den Flashspeicher auszuführen. Um genau zu sein: Es müssen mind. 23 der 40 Pins angesteuert werden. Deswegen steuert der ATmega32 über hardwareseitigen SPI zwei weitere MCP23S17 Port-Expander an. Da jeder MCP23S17 einen eigenen Adressdekoder hat, können die beiden Port-Expander vom IDE-Port und der eine für den Adressbus der Hauptplatine über den gleichen Chip-Select angesprochen werden.

Ein Sektor in CP/M2.2 hat eine feste Größe von 128 Bytes. IDE nutzt eine Größe von 512Bytes pro LBA Block. 4 Sektoren in CP/M teilen somit einen LBA Block. Für den Flashspeicher stehen 256000 Blöcke zur Verfügung. Eine Spur in CP/M2.2 enthält 32 Sektoren, das entspricht 4KB. Jede CP/M Disk hat 2000 Spuren, kann also 8000KB fassen. Da die LBA Adressierung für IDE verwendet wird, ist die Anzahl der CP/M Sektoren pro Spur frei definiert, könnte also theoretisch auch anders definiert sein.

Alternativ besteht auch natürlich die Möglichkeit, hier eine SD Karte anzusprechen. In meinem Fall hatte ich den Flashspeicher schon parat.

Bildschirm

Cpm-vt52-lcd.png

Zu Zeiten CP/M war es üblich, Daten über eine Konsole, einen Terminal anzuzeigen oder einzugeben. Das BIOS vom CP/M kennt noch keine Grafik. Daten werden über eine Konsole zeichenweise (ASCII) ausgegeben oder eingelesen. Häufig war ein Terminal dazu nötig, das die Daten seriell (RS232) übertragen hat. In welchem Format z.B. die Steuercodes zum Löschen des Bildschirms oder das Positionieren des Cursors übertragen werden, ist nicht vom CP/M definiert. Jedes CP/M Programm muss daher für einen bestimmten Terminal-Standard konfiguriert oder „gepatched“ werden. In diesem Projekt wollte ich zumindest einen älteren Terminal-Standard unterstützen, der bei den CP/M-Programmen, wie Wordstar oder Multiplan, konfiguriert werden kann. Der VT52 Standard definiert u.a. einen Textbereich mit 80x24 Zeichen und einfache Escape-Sequenzen zum Setzen des Cursors und Manipulieren von Teilen des Textbereiches.

Auch wenn zum Anfang des Projektes noch die Darstellung und die Eingabe der Konsole mittels serieller Schnittstelle über den PC verwendet wurde, sollte eigentlich ein eigener Bildschirm mit eigenem Controller für die Konsole entwickelt werden.

Bereits vorhanden waren ein monochromes und 10,4 Zoll großes LCD Modul (LCM-5430-E) und ein passender DC/DC-Wandler (RECOM1.5-0512DR/X1), der die nötige Kontrastspannung (VEE) für das LCD von -22V erzeugen kann. Das LCD hat eine Auflösung von 640 x 200 Punkten, es besitzt aber keinen eigenen Controller oder Bildschirmspeicher. Wenn jedes Zeichen 8x8 Pixel groß ist, können die gewünschten 80x24 Zeichen dargestellt werden. Hier wollte ich den ATmega328 verwenden, da dieser mit 2KB genug RAM für den Zeichenpuffer hat. Ein ASCII Zeichen benötigt nur 7 Bit, somit kann im höchstwertigen Bit auch noch die Information für die inverse Darstellung des Zeichens gespeichert werden. VT52 erlaubt in einer Erweiterung diese Darstellung mittels Escape-Sequenzen zu nutzen. Da die ersten 32 ASCII-Codes nur „unsichtbare“ Steuerzeichen sind, habe ich mir 32 zusätzliche Grafik-Zeichen (Blockgrafik) definiert, die ebenfalls mit Escape-Sequenzen aktiviert werden können. Der ATmega328 wird mit 24MHz schon kräftig übertaktet, da sich im Laufe des Projektes herausgestellt hat, das trotz Assembler und einigen Optimierungen der Durchsatz für ein flimmerfreie Textdarstellung nicht ganz reicht, da auch ein blinkender Cursor noch implementiert werden musste.

Zum Übertragen der dazustellenden Zeichen und der Steuercodes verwende ich SPI zur Kommunikation zwischen Hauptplatine und dem ATmega328, wobei nun ein eigener Chip-Select benutzt wird (s. Pin CCS auf der Hauptplatine).

Auch wenn nicht so glücklich gelöst, die ISR für SPI behandelt auch die Ausführung der etwas zeitaufwändigeren Escape-Sequenzen, wie Bildschrim löschen oder das Scrollen von Textbereichen. In dieser Zeit wird der LCD deaktiviert, was ein leichtes Flackern des Bildschirms bei zeitintensiven Routinen bewirkt. In einer früheren Implementierung wurden die zeitintensiven Routinen mit dem HSYNC-Signal synchronisiert, was das Flackern verhinderte, aber eine langsamere Darstellung (zum Beispiel beim Scrollen von Texten) zur Folge hatte, daher wurde diese Synchronisierung wieder entfernt. Ob der ATmega328 noch mit der Ausführung der letzten SPI Daten beschäftigt ist, wird mit dem BUSY Pin mitgeteilt.

Tastatur

Cpm-keyboard.png

Die Konsoleneingabe soll in diesem Projekt mit einer eigenen Tastatur erfolgen. Erst war die Überlegung, eine alte PS/2-Tastatur zu benutzen und mittels diesem Protokoll die Scan-Codes der Tastatur auszuwerten, doch dann entdeckte ich bei Pollin die sehr günstige Infrarot-Tastatur (FDC-3402). Diese Tastatur nutzt das IRMP-Protokoll. Die Daten müssen hier aber nicht erst über einen Infrarotempfänger eingelesen werden, man kann diese auch direkt von einem Pin auf Tastaturplatine abgreifen, was weniger anfällig ist für Empfangsfehler.

Cpm-tastatur-pins.jpg

Das Analysieren des IRMP Datenstroms der Tastatur löst ein ATmega168. Der Controller wandelt Tastendrücke in entsprechende ASCII Codes um. Ebenfalls werden zusätzliche Tasten, wie die Pfeiltasten oder die oberen Funktionstasten, in den Codebereich $80-$9F gemappt.

Software

Die Software musste auf verschiedenen Ebenen implementiert werden.

Erste Schritte

Im ersten Schritt war die Hauptplatine vorhanden. Der Z80 wurde erst einmal nur im Reset-Modus gehalten, während die ersten Versuche daraufhin abzielten, das Lesen und Schreiben des SRAMs mittels ATmega32 zu testen (Adressbus via MCP23S17). Daraufhin entstand die Initialisierungsroutine beim Boot-Prozess, die den kompletten 64KB mit einem CRC-Code beschreibt, validert und danach auf 0 zurücksetzt (s. Z80V1.asm/main_ram_init).

Danach wurde das erste Mal manuell ein kleines Assemblerprogramm in das SRAM ab Adresse $0000 geladen, eine Art „Hello World“:

.org $0000

start:
	ld a,$41
	out ($00),a
	jp start
	
.end

Mit diesem kleinen Stück Code wird nach dem Beenden des Reset-Modus vom Z80 fortlaufend der Wert $41 (ASCII für ‚A’) an E/A Adresse $00 geschrieben. Die E/A Adresse steht für die Konsole (am Anfang ist das der serielle Port). Nun ging es daran, eine ISR auf dem ATmega32 für den INT0 (IOREQ Pin) zu implementieren. Beim Auftreten eines INT0 wird die E/A Adresse durch Auslesen des MCP23S17 erkannt. Ebenfalls wird anhand der Pins RD und WR ermittelt, ob beim E/A Zugriff gelesen oder geschrieben wird. Mittels einer Sprungtabelle wird dann in die entsprechende Funktion für diese E/A Adresse aufgerufen, und bei einem Lesezugriff die Daten am Datenbus ausgewertet oder beim Schreibzugriff entsprechend die Daten am Datenbus gesetzt (s. Z80V1.asm/ bus_isr_int0). Für E/A Adresse $00 bedeutet es das Empfangen oder Schreiben eines Zeichens über den seriellen Port. Nachdem die entsprechende Funktion ausgeführt wurde, muss der WAIT Pin vom Z80 über das FlipFlop (IC 7474) wieder auf High gesetzt werden, damit die Wartezyklen für den E/A Zugriff beendet werden.

Monitor (Hilfsprogramm)

Bevor es an das eigentliche CP/M Betriebssystem geht, sollte erst einmal ein davon unabhängiges Monitor-Programm existieren, mit dem es möglich ist, Speicherbereiche des SRAMS zu schreiben, zu lesen und zu füllen und einfache IDE Operationen zu auszuführen, wie „Sektoren ins RAM laden“ bzw. „vom RAM auf Sektoren schreiben“. Das Monitor-Programm soll das typische Henne-Ei-Problem lösen. CP/M bootet von der Festplatte. Um Daten auf die Festplatte zu bekommen, muss ein Werkzeug dazu existieren. CP/M kann aber nicht das Werkzeug sein, da es noch nicht auf der Festplatte existiert. Ebenfalls sollte jeder, der plant, ein eigenes CP/M-System zu bauen, mit einem Monitor-Programm beginnen, um die Assemblerprogrammierung auf dem Z80 zu trainieren, falls man da nicht schon Experte ist, da später auch das BIOS vom CP/M in Assembler implementiert werden muss. Daher ist so ein Monitor-Programm auch eine gute Übung. Das Monitor-Programm hier ist etwa 2600 Bytes groß. In binärer Form ist es im Flash vom ATmega32 gespeichert. Im Boot-Prozess (Initialisierung) vom ATmega32 kann es durch einen Druck auf die ESC Taste ins SRAM ab Adresse $D000 kopiert und nach dem Reset des Z80 gestartet werden. Ebenfalls wird beim Auftreten eines HALT vom Z80 (siehe INT2 vom ATmega32) das Monitor-Programm ins SRAM kopiert und gestartet.

CP/M 2.2 (BDOS + CCP)

CP/M 2.2 ist als Schichtenmodell aufgebaut:

  • BIOS (Basic Input Output System) ist die unterste Ebene. Diese Schicht ist die Hardwareabstraktionsebene, d.h. in dieser Schicht werden Hardwareaufrufe soweit abstrahiert. Alle Zugriffe auf Hardware in den Schichten darüber erfolgen über diese abstrahierten Funktionen. Bei einem selbst entwickelten System müssen hier die abstrahierten Funktionen für die eigene Hardware implementiert werden.
  • BDOS (Basic Disk Operating System) ist der Systemkern und stellt teilweise höhere Funktionen für die Diskettenoperationen bereit. Es ist sozusagen die API, mit der auch alle CP/M Programme arbeiten.
  • CCP (Console Command Processor) beinhaltet den Kommandozeileninterpreter zum Ausführen von Kommandozeilenbefehlen. Einige Befehle sind hier direkt im Speicher verankert und können sofort ausgeführt werden (z.B. der Befehl „dir“ (Zeige Verzeichnis) oder „era“ (Lösche Datei), andere Befehle werden von der Diskette/Festplatte geladen.

Der Code vom CP/M 2.2 ist offen gelegt und kann herunter geladen werden. Für dieses Projekt wurde die CPM22.Z80 benutzt. Zum Kompilieren der Datei verwende ich den TASM (Version 3.2). Der TASM kennt nicht die Notation "M" für den indirekten Speichzugriff mit HL, also "(HL)". Man kann aber einfach im Quellcode das Auftreten aller Zeichenketten >,M< durch die Zeichenkette >,(HL)< austauschen, damit der TASM fehlerfrei baut. Für dieses Projekt wurde der Wert MEM auf 64 gesetzt (Der ursprüngliche Code definiert ein 62KB System), da man ja den vollen Speicherbereich nutzen kann. Später sind noch einige Anpassungen mehr hinzugekommen. Der Vorteil (wie in Linux) ist, dass man das Betriebssystem leicht an eigene Wünsche anpassen kann. Man muss nur wissen, was man tut ;-) Durch o.g. minimale Anpassungen sollte aber das BDOS und CCP kompiliert werden können. Die Quelldatei enthält aber noch kein BIOS (nur eine vordefinierte Sprungtabelle am Ende der Datei).

CCP liegt bei einem 64KB System im Speicher ab Adresse $E400. BDOS beginnt ab Adresse $EC06.

CP/M 2.2 (BIOS)

Das BIOS ist die unterste Ebene, die uns auch hauptsächlich interessiert, da diese Schicht die Schnittstelle zur eigenen Hardware definiert. Eine Vorlage für ein BIOS kann herunter geladen werden (Achtung, Code ist in 8080 Assembler).

Bei einem 64KB System ist das BIOS üblicherweise im Speicherbereich $FA00-$FFFF zu finden. Am Anfang des Speicherbereichs sind 17 Funktionen in einer Sprungtabelle definiert:

  • BOOT: Dies ist die Kaltstartfunktion, diese wird nur beim ersten Start des Systems aufgerufen. In diesem Projekt wird diese Funktion aufgerufen, sobald der ATmega32 CP/M von den ersten 64 Sektoren des ersten Laufwerks gelesen und in den Speicherbereich $E000-$FFFF geladen hat. Mit dem Ende des Resets vom Z80 wird dann in diese Funktion gesprungen. Hier werden nur zwei Parameter IOBYTE (Logisches Mapping der Geräte) und DISK (Laufwerk A + User 0) vorbelegt.
  • WBOOT: Die Warmstartfunktion wird aufgerufen, nachdem ein transientes Programm in CP/M ausgeführt wurde. Es initialisiert den CCP neu. Bei einem Warmstart sollte das ganze CP/M System neu vom Laufwerk gelesen und in den Speicher kopiert werden (auch wenn es Zeit kostet!). Es gibt CP/M Programme, die den Speicherbereich überschreiben. Nach dem das Programm dann beendet wird, funktioniert evtl. CP/M nicht mehr korrekt, wenn es nicht neu geladen wurde.
  • CONST: Liefert den Status der Konsoleneingabe. Wenn A = $FF zurückgegeben wird, wurde ein neues Zeichen in der Konsole eingegeben, andernfalls wird A = $00 zurückgeliefert.
  • CONIN: Liefert das ASCII Zeichen der Konsoleneingabe in A zurück. Falls kein Zeichen eingegeben wurde, wird solange gewartet. Bit 7 sollte immer 0 sein, d.h. Werte von $00-$7F sind nur gültig. Je nach Wert des IOBYTE kann in diesem Projekt die Konsoleneingabe anstatt von der Tastatur auch auf die serielle Schnittstelle umgeleitet sein.
  • CONOUT: ASCII Zeichen in C wird auf die Konsole ausgegeben. Bit 7 sollte immer 0 sein, d.h. Werte von $00-$7F sind nur gültig. Je nach Wert des IOBYTE kann in diesem Projekt die Konsolenausgabe anstatt auf dem Bildschirm auch auf die serielle Schnittstelle umgeleitet sein.
  • LIST: Gibt ein ASCII Zeichen in C zu einem Drucker aus. Dieses Projekt implementiert diese Funktion nicht.
  • PUNCH: (ähnlich CONOUT). Gibt ein ASCII Zeichen in C an ein Bandgerät oder Terminal aus. In diesem Projekt erfolgt die Ausgabe hier über die serielle Schnittstelle.
  • READER: (ähnlich CONIN). Liefert das ASCII Zeichen eines Bandgerätes oder Terminal in A. Falls kein Zeichen eingegeben wurde, wird solange gewartet.
  • HOME: Setzt die Spur des Laufwerks auf 0. (Historisch musste z.B. früher bei Diskettenlaufwerken der Lesekopf auf Spur 0 zurückgefahren werden, damit sich dieser neu kalibrieren konnte.)
  • SELDSK: Setzt das Laufwerk (0-15) in C. In HL muss die Adresse auf den entsprechenden Disk Parameter Header zurückgeliefert werden, der die Parameter für das Laufwerk beschreibt. Ein ungültiges Laufwerk muss 0 zurückliefern.
  • SETTRK: Setzt die Spur in BC (hier 0-1999).
  • SETSEC: Setzt den Sektor in BC (hier 0-31).
  • SETDMA: Setzt die Startadresse anhand BC für den Sektorbuffer (128 Bytes) des Laufwerks. Folgende Lese- und Schreibzugriffe benutzen dann diesen Speicherbereich für die Sektordaten.
  • READ: Liest einen Sektor vom aktuellem Laufwerk und schreibt die Daten (128 Bytes) in dem zuletzt festgelegten Speicherbereich. Bei Erfolg, wird 0 in A zurückgeliefert, andernfalls 1 bei einem Fehler.
  • WRITE: Schreibt einen Sektor auf das aktuelle Laufwerk mit den Daten (128 Bytes) aus dem zuletzt festgelegten Speicherbereich. Bei Erfolg, wird 0 in A zurückgeliefert, andernfalls 1 bei einem Fehler.
  • LISTST: (ähnlich CONST). Liefert den Status des Druckers. Wenn A = $FF zurückgegeben wird, kann ein neues Zeichen an den Drucker geschickt werden, andernfalls muss A = $00 zurückgeliefert werden. In diesem Projekt wird immer $00 zurückgeliefert, da keine Druckfunktionalität implementiert ist.
  • SECTRN: Übersetzt einen Sektor in BC nach HL durch eine Übersetzungstabelle in DE. (Historische musste früher z.B. bei Diskettenlaufwerken zwischen logischen und physikalischen Sektoren übersetzt werden). In diesem Projekt benutzen wir keine Übersetzungstabelle. Wir übergeben direkt den Wert von BC nach HL.

Benutzung

Formatieren der CP/M Laufwerke

Mit dem Monitor-Programm ist es möglich, die Laufwerke zu formatieren, so dass CP/M diese bei der ersten Benutzung ordnungsgemäß behandelt und als leer erkennt. Ein Laufwerk ist im Prinzip formatiert, wenn alle Verzeichniseinträge („Directory Entries“, auch „Records“ genannt) mit $E5 gefüllt sind. In diesem Projekt sind jeweils für ein Laufwerk 1024 Einträge reserviert. Ein Eintrag ist generell in CP/M 32 Bytes groß, macht also 8 Spuren (256 Sektoren) auf der Festplatte. Die ersten beiden Spuren (64 Sektoren) eines Laufwerks in diesem Projekt sind immer Bootsektoren. Spur 2 bis 9 enthalten also die Verzeichniseinträge.

Mit folgenden Befehlen lässt sich also ein Laufwerk formatieren, als Beispiel Laufwerk B:

>D B
>F 1000 4000 E5	        (Speicherbereich $1000-$4FFF mit $E5 füllen)
>S 1000 0002 00 80	(Speichere $1000-$4FFF nach Spur 2 - 5)
>S 1000 0006 00 80	(Speichere $1000-$4FFF nach Spur 6 – 9)

Transfer des CP/M und erste Programme vom PC zum CP/M System

Da es zu mühsam wäre, mit dem Monitor-Programm mehrere KB per Hand einzugeben, wurde noch ein Tool von mir in Java implementiert, mit dem es möglich ist, über die serielle Schnittstelle und dem aktivierten Monitor-Programm das kompilierte CP/M in die ersten 64 Sektoren des ersten Laufwerks zu schreiben (Boot Sektoren).

Um eine kompiliertes CP/M (z.B. „cpm22.obj“) vom PC (Windows) zum Selbstbausystem zu übertragen und dort in den Boot-Sektoren zu speichern, muss man nur folgenden Kommandozeilenbefehl aufrufen:

Z80Transfer.bat [SerialPort] CPM cpm22.obj

also z.B.

Z80Transfer.bat COM3 CPM d:\_data\z80\src\cpm22.obj

Nach dem Beenden des Monitor-Programms, müsste dann CP/M ordnungsgemäß booten und die Eingabeaufforderung mit „A>“ erscheinen.

Ebenfalls besteht die Möglichkeit, vor dem Boot vom CP/M ein CP/M Programm in den Programmbereich ab Adresse $0100 (genannt TPA) zu laden:

Z80Transfer.bat [SerialPort] UPLOAD [Program].COM

also z.B.

Z80Transfer.bat COM3 UPLOAD d:\cpmtools\STAT.COM

Lädt das System-Tool STAT.COM in den TPA. Wurde STAT.COM ordnungsgemäß in den Speicher geladen, wird die Anzahl an Bytes, und hier sehr wichtig, auch die Anzahl an Pages (256 Bytes per Page) für das Programm zurückgeliefert. Wenn man nun das Monitor-Programm mit X beendet und CP/M bootet, kann man in CP/M mit folgender Kommandozeile das Programm im Speicher (TPA) auf dem Laufwerk zurückspeichern:

A>SAVE [NumPages] [Program].COM

Dateien

Links

Hardware: